Refactoring an Android App: Structuring a Project for Success
I’ve been creating the whole project for quite a long time as part of learning the Android ecosystem, the Kotlin language, and learning mobile app development. It is also a mobile app that I plan to showcase at the end of my studies.
The project structure I used was quite simple yet effective. I began by setting up the project structure in Android Studio. I then created a package for the application, where I put all the necessary files for the project. This included an AndroidManifest.xml, a build.gradle file, a class for the activity, and a resource directory for the wallpaper.
Next, I created a layout file for the activity, which contained all the necessary components for the application. This included a TextView, ImageView, and a Button. I also added the necessary styles and colors to the layout file.
Initially, the structure of the project looked like this, and this speaks for itself as to why there has been some restructuring here. I had neglected the subject of aesthetics a little in favor of learning the Android ecosystem and the Kotlin language faster, but it was time to make up for the shortcomings.
└── com
└── example
└── exchangeabletoken
├── AddTransactionActivity.kt
├── ChooseSignUpActivity.kt
├── DataBaseService.kt
├── DataProduct.kt
├── ExchangeableTokenActivity.kt
├── FirebaseDatabase.kt
├── FirebaseDatabaseService.kt
├── FirebaseStorageService.kt
├── First2Fragment.kt
├── FirstFragment.kt
├── LoginActivity.kt
├── MainActivity.kt
├── MarketActivity.kt
├── ProductActivity.kt
├── ProductAdapter.kt
├── ReceiverChecker.kt
├── SearchProductActivity.kt
├── Second2Fragment.kt
├── SecondActivity.kt
├── SecondFragment.kt
├── SettingsActivity.kt
├── SettingsFragment.kt
├── SignUpActivity.kt
├── SuccessfulSignUpActivity.kt
├── TransactionActivity.kt
├── TransactionBuilder.kt
├── Transaction.kt
├── ui
│ └── main
│ ├── PageViewModel.kt
│ ├── PlaceholderFragment.kt
│ └── SectionsPagerAdapter.kt
└── WalletStatusActivity.kt
After reading several articles and reviewing a couple of examples repositories that already contained created mobile app projects written for Android in Kotlin, I chose a structure that suited the needs of my project.
└── com
└── example
└── exchangeabletoken
├── data
│ ├── database
│ │ └── FirebaseDatabaseService.kt
│ ├── model
│ │ ├── Product.kt
│ │ └── Transaction.kt
│ └── repository
│ └── DatabaseService.kt
├── ui
│ ├── base
│ │ └── BaseActivity.kt
│ ├── main
│ │ ├── MainActivity.kt
│ │ └── MainViewModel.kt
│ ├── market
│ │ ├── MarketActivity.kt
│ │ └── MarketViewModel.kt
│ ├── product
│ │ ├── ProductActivity.kt
│ │ └── ProductViewModel.kt
│ ├── search
│ │ ├── SearchProductActivity.kt
│ │ └── SearchProductViewModel.kt
│ ├── settings
│ │ ├── SettingsActivity.kt
│ │ └── SettingsViewModel.kt
│ └── wallet
│ ├── WalletStatusActivity.kt
│ └── WalletStatusViewModel.kt
└── utils
├── FirebaseStorageService.kt
└── ReceiverChecker.kt
In this restructuring, the main packages are data
, ui
, and utils
.
The data
package contains all the data-related classes and services, including database services, models, and repositories.
The ui
package contains all the UI-related classes, including activities and view models. Each sub-package represents a different feature or screen in the app, such as main
, market
, product
, search
, settings
, and wallet
.
The utils
package contains utility classes, such as the FirebaseStorageService
and ReceiverChecker
.
However, after a while, I remembered that I had strange code fragments responsible for transaction views…. In the current restructure, the PageViewModel.kt, PlaceholderFragment.kt and SectionsPagerAdapter.kt files would better fit into a ui sub-package dedicated to handling fragments and navigation in the application, such as the navigation sub-package within the ui package:
.
└── com
└── example
└── exchangeabletoken
├── data
│ ├── database
│ │ └── FirebaseDatabaseService.kt
│ ├── model
│ │ ├── Product.kt
│ │ └── Transaction.kt
│ └── repository
│ └── DatabaseService.kt
├── ui
│ ├── base
│ │ └── BaseActivity.kt
│ ├── main
│ │ ├── MainActivity.kt
│ │ └── MainViewModel.kt
│ ├── market
│ │ ├── MarketActivity.kt
│ │ └── MarketViewModel.kt
│ ├── navigation
│ │ ├── PageViewModel.kt
│ │ ├── PlaceholderFragment.kt
│ │ └── SectionsPagerAdapter.kt
│ ├── product
│ │ ├── ProductActivity.kt
│ │ └── ProductViewModel.kt
│ ├── search
│ │ ├── SearchProductActivity.kt
│ │ └── SearchProductViewModel.kt
│ ├── settings
│ │ ├── SettingsActivity.kt
│ │ └── SettingsViewModel.kt
│ └── wallet
│ ├── WalletStatusActivity.kt
│ └── WalletStatusViewModel.kt
└── utils
├── FirebaseStorageService.kt
└── ReceiverChecker.kt
This keeps all the classes related to navigation and fragments in one place, making it easier to maintain and modify the navigation structure of the app as needed.
The most obvious things have already been separated, but there are still quite a few files left to allocate…
SettingsFragment.kt
would belong in the same navigation
sub-package as the other fragments, since it's likely a fragment that will be used for navigation within the app.
├── AddTransactionActivity.kt
├── ChooseSignUpActivity.kt
├── data
│ ├── database
│ │ └── FirebaseDatabaseService.kt
│ ├── model
│ │ ├── Product.kt
│ │ └── Transaction.kt
│ └── repository
│ └── DatabaseService.kt
├── ExchangeableTokenActivity.kt
├── FirebaseDatabase.kt
├── First2Fragment.kt
├── FirstFragment.kt
├── LoginActivity.kt
├── ProductAdapter.kt
├── Second2Fragment.kt
├── SecondActivity.kt
├── SecondFragment.kt
├── SignUpActivity.kt
├── SuccessfulSignUpActivity.kt
├── TransactionActivity.kt
├── TransactionBuilder.kt
├── ui
│ ├── base
│ │ └── BaseActivity.java
│ ├── main
│ │ ├── MainActivity.kt
│ │ └── MainViewModel.kt
│ ├── market
│ │ ├── MarketActivity.kt
│ │ └── MarketViewModel.kt
│ ├── navigation
│ │ ├── PageViewModel.kt
│ │ ├── PlaceholderFragment.kt
│ │ ├── SectionsPagerAdapter.kt
│ │ └── SettingsFragment.kt
│ ├── product
│ │ ├── ProductActivity.kt
│ │ └── ProductViewModel.kt
│ ├── search
│ │ ├── SearchProductActivity.kt
│ │ └── SearchProductViewModel.kt
│ ├── settings
│ │ ├── SettingsActivity.kt
│ │ └── SettingsViewModel.kt
│ └── wallet
│ ├── WalletStatusActivity.kt
│ └── WalletStatusViewModel.kt
└── utils
├── FirebaseStorageService.kt
└── ReceiverChecker.kt
In any case, the structure had already started to look much better than at the beginning, but it was still weak…
I was still thinking about how to separate the files into different packages based on their functionality. This will make the code easier to understand and maintain in the future. I only realized this when I recently tried to show the code to a friend of mine who was seeing it for the first time. It was only then that I realized what a mess I had made of my code.
However, I had quite a few files that I didn’t know what to do with…
-rw-r--r-- 1 kacper kacper 7055 Feb 10 08:24 AddTransactionActivity.kt
-rw-r--r-- 1 kacper kacper 833 Feb 7 22:05 ChooseSignUpActivity.kt
-rw-r--r-- 1 kacper kacper 337 Feb 7 22:05 ExchangeableTokenActivity.kt
-rw-r--r-- 1 kacper kacper 6596 Feb 10 08:16 FirebaseDatabase.kt
-rw-r--r-- 1 kacper kacper 1255 Feb 7 22:05 First2Fragment.kt
-rw-r--r-- 1 kacper kacper 1250 Feb 7 22:05 FirstFragment.kt
-rw-r--r-- 1 kacper kacper 2518 Feb 10 08:21 LoginActivity.kt
-rw-r--r-- 1 kacper kacper 1199 Feb 10 08:16 ProductAdapter.kt
-rw-r--r-- 1 kacper kacper 1259 Feb 7 22:05 Second2Fragment.kt
-rw-r--r-- 1 kacper kacper 646 Feb 7 22:05 SecondActivity.kt
-rw-r--r-- 1 kacper kacper 1254 Feb 7 22:05 SecondFragment.kt
-rw-r--r-- 1 kacper kacper 5285 Feb 10 08:21 SignUpActivity.kt
-rw-r--r-- 1 kacper kacper 593 Feb 7 22:05 SuccessfulSignUpActivity.kt
-rw-r--r-- 1 kacper kacper 1311 Feb 7 22:05 TransactionActivity.kt
-rw-r--r-- 1 kacper kacper 1066 Feb 10 08:13 TransactionBuilder.kt
It dawned on me after a long conclusion that it would be a good idea to create more packages so that I could arrange the code nicely.
So I wrote out all these files for myself, thought about them for a while and hence my conclusions on where to arrange them.
- AddTransactionActivity.kt: This class is likely to be an activity that is responsible for allowing users to add transactions to the database. This can be placed in the
ui.transaction
package. - ChooseSignUpActivity.kt: This class is likely to be an activity that presents a choice to the user for either signing up or logging in. This can be placed in the
ui.authentication
package. - ExchangeableTokenActivity.kt: This class is likely to be an activity that is responsible for handling the exchange of tokens between users. This can be placed in the
ui.exchange
package. - FirebaseDatabase.kt: This class is likely to be a class that provides an interface to the Firebase database. This can be placed in the
data.firebase
package. - First2Fragment.kt and FirstFragment.kt: These classes are likely to be fragments that are used to build a UI for the first and second steps in the app’s flow. These can be placed in the
ui.main
package. - LoginActivity.kt: This class is likely to be an activity that is responsible for handling the user’s login process. This can be placed in the
ui.authentication
package. - ProductAdapter.kt: This class is likely to be an adapter that is used to display products in a list. This can be placed in the
ui.market
package. - Second2Fragment.kt, SecondActivity.kt, and SecondFragment.kt: These classes are likely to be related to a secondary flow in the app. They can be placed in a package named
ui.secondary
or similar. - SignUpActivity.kt: This class is likely to be an activity that is responsible for handling the user’s sign-up process. This can be placed in the
ui.authentication
package. - SuccessfulSignUpActivity.kt: This class is likely to be an activity that is displayed after a successful sign-up. This can be placed in the
ui.authentication
package. - TransactionActivity.kt: This class is likely to be an activity that displays the details of a transaction. This can be placed in the
ui.transaction
package. - TransactionBuilder.kt: This class is likely to be a class that builds transactions. This can be placed in the
data.model
package.
Now the hardest 3 files are left for last. I created them a long time ago and don’t even remember quite what they were for or what they refer to, and their names are not obvious either.
├── FirstFragment.kt
├── SecondActivity.kt
├── SecondFragment.kt
FirstFragment.kt and SecondFragment.kt are the fragments used in the UI, it would be best to put them in a sub-package under the ui package. For example, create a sub-package named fragments in the ui package and place the fragments there.
As for SecondActivity.kt, it would also be best to place it in a sub-package in the ui package, but in a sub-package specifically activities
.
Of course, this is not without changes to the code; after all, all the files lay in one directory, which has consequences.
Moving the code into packages can affect the code’s references, imports, and visibility of certain classes and functions.
I had to start updating the imports and access modifiers in my code because although everything compiled correctly, the logged-in user activity stopped working.
It’s a good idea to consider the architecture and design patterns you want to use in your code, and structure your packages accordingly. This can help improve the maintainability and readability of your code.
The biggest problem was that the Login stopped working, and also did not display the logged-in user’s details.
The big problem with Kotlin and object-oriented languages in general, as I have often encountered in Java, is that after restructuring the code, it’s likely due to some changes in the references of classes, resources, or methods.
This happened because of moving and renaming files and can result in broken references in the code.
I had to check the code that is responsible for the Login functionality, identify any broken references, and update them as necessary.
However, the credentials had all been thoroughly tested beforehand and it was coming out that there were other issues that were causing Login to stop working, such as changes to the database structure or authentication methods.
I thoroughly tested the Login functionality and isolated any problems.
I reviewed the code responsible for authentication in general, and for providing activity for authentication.
Generally, this was not displaying the details of the logged-in user, meaning that the problem probably lay in the code that fetched and displayed the user information, although the problem could also lie in the database logic, as the application was tightly coupled with solutions provided by Firebase.
To this end, I checked that the code retrieving and displaying the user information was working correctly, that the data retrieved from the database was correct, and that the classes were accessing each other.
I needed to create a User class and inside it some isLoggedIn function. The User class should be created in an equivalent package, personally, I would put this in the data package that has other classes that represent data models, such as Product or Transaction. As I have already created a “data.model”, I figured that there it would be good to manage the data model in the application.
Here’s an example of a isLoggedIn
function in Kotlin that can be used to check if a user is logged in to a given project:
fun isLoggedIn(): Boolean {
val user = User.getInstance()
return user.isLoggedIn
}
Here, User
is a class that contains information about the user, such as their username and password. The isLoggedIn
function uses the getInstance
method to retrieve the current instance of the User
class, and then checks its isLoggedIn
property to determine if the user is logged in.
Note that this is just one way of implementing a login system, and your specific requirements may vary.
You can import the getInstance()
method from the FirebaseAuth
class in the following way:
import com.google.firebase.auth.FirebaseAuth
// ...
fun isLoggedIn(): Boolean {
val firebaseAuth = FirebaseAuth.getInstance()
val currentUser = firebaseAuth.currentUser
return currentUser != null
}
This will allow you to use the getInstance()
method to retrieve the instance of the FirebaseAuth
class, which you can then use to check if the user is logged in.
class User(val name: String, val email: String, val password: String) {
companion object {
fun isLoggedIn(): Boolean {
val firebaseAuth = FirebaseAuth.getInstance()
val currentUser = firebaseAuth.currentUser
return currentUser != null
}
}
}
However, the User needed to have the right structure, such as I have in my console, to better manage the data now as well as in the future to avoid difficulties in maintaining the code in the future.
class User(
val address : String,
val balance : Double,
val email : String,
val name : String,
val phone : String,
val uid : String,
) {
companion object {
fun isLoggedIn(): Boolean {
val firebaseAuth = FirebaseAuth.getInstance()
val currentUser = firebaseAuth.currentUser
return currentUser != null
}
}
}
Adding such logic and attaching it to the button responsible for entering the login screen said goodbye to the problem.
class ChooseSignUpActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_choose_sign_up)
// set listener for login button
findViewById<android.widget.Button>(R.id.log_in_button).setOnClickListener {
// if user is already logged in, go to market activity
if (com.example.exchangeabletoken.data.model.User.isLoggedIn()) {
val intent = Intent(this, com.example.exchangeabletoken.ui.market.MarketActivity::class.java)
startActivity(intent)
} else {
val intent = Intent(this, LoginActivity::class.java)
startActivity(intent)
}
}
// set listener for sign up button
findViewById<android.widget.Button>(R.id.sign_up_button).setOnClickListener {
val intent = Intent(this, SignUpActivity::class.java)
startActivity(intent)
}
}
}
The problem more specifically lay in the fact that previously when everything was together, logging was handled differently. Now the logic that checked whether the user was logged in was separated, thus redirecting them to the wrong screens every time they clicked on the button responsible for logging in to their account.
The next problem, which only came out after a while of testing, was the settings screen. Below I have pasted the code that redirects to the settings activity.
val settingsButton = findViewById<FloatingActionButton>(R.id.settings_button)
settingsButton.setOnClickListener {
val intent = Intent(this, SettingsActivity::class.java)
startActivity(intent)
}
During the course of investing in the problem, I noticed that the existing database connections also stopped working, which meant that data on, for example, a user’s balance or their UID did not display. That is, they were displayed, but as null.
What was strange was that even the debugger did not want to go to the SettingsActivity class.
package com.example.exchangeabletoken.ui.settings
import android.content.Intent
import android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import com.example.exchangeabletoken.R
import com.example.exchangeabletoken.ui.main.MainActivity
import com.example.exchangeabletoken.ui.navigation.SettingsFragment
import com.google.firebase.auth.ktx.auth
import com.google.firebase.ktx.Firebase
private const val TITLE_TAG = "settingsActivityTitle"
class SettingsActivity : AppCompatActivity(),
PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.settings_activity)
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, SettingsFragment())
.commit()
} else {
title = savedInstanceState.getCharSequence(TITLE_TAG)
}
if (savedInstanceState == null) {
supportFragmentManager
.beginTransaction()
.replace(R.id.settings, HeaderFragment())
.commit()
} else {
title = savedInstanceState.getCharSequence(TITLE_TAG)
}
supportFragmentManager.addOnBackStackChangedListener {
if (supportFragmentManager.backStackEntryCount == 0) {
setTitle(R.string.title_activity_settings)
}
}
supportActionBar?.setDisplayHomeAsUpEnabled(true)
val logOutButton = findViewById<Button>(R.id.logout_button)
logOutButton.setOnClickListener {
Firebase.auth.signOut()
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
// Save current activity title so we can set it again after a configuration change
outState.putCharSequence(TITLE_TAG, title)
}
override fun onSupportNavigateUp(): Boolean {
if (supportFragmentManager.popBackStackImmediate()) {
return true
}
return super.onSupportNavigateUp()
}
override fun onPreferenceStartFragment(
caller: PreferenceFragmentCompat,
pref: Preference
): Boolean {
// Instantiate the new Fragment
val args = pref.extras
val fragment = pref.fragment?.let {
supportFragmentManager.fragmentFactory.instantiate(
classLoader,
it
).apply {
arguments = args
}
}
// Replace the existing Fragment with the new Fragment
if (fragment != null) {
supportFragmentManager.beginTransaction()
.replace(R.id.settings, fragment)
.addToBackStack(null)
.commit()
}
title = pref.title
return true
}
class HeaderFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.header_preferences, rootKey)
}
}
class MessagesFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.messages_preferences, rootKey)
}
}
class SyncFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.sync_preferences, rootKey)
}
}
class AccountFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.account_preferences, rootKey)
}
}
class NotificationsFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.notifications_preferences, rootKey)
}
}
class PrivacyFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.privacy_preferences, rootKey)
}
}
class HelpFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.help_preferences, rootKey)
}
}
class AboutFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.about_preferences, rootKey)
}
}
class ExchangeableTokenFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.exchangeable_token_preferences, rootKey)
}
}
class LogOutFragment : PreferenceFragmentCompat() {
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
setPreferencesFromResource(R.xml.log_out_preferences, rootKey)
}
}
}
After analyzing the code relating to the Settings activity, I came to the conclusion that I would refactor it so that it could be reused because:
- There are two identical
if (savedInstanceState == null)
blocks, with the same content, that replace theSettingsFragment
in theFragmentManager
. - The
logOutButton
is not referenced in the XML layout, so it is likely to cause aNullPointerException
when trying to set the click listener. - The
SettingsFragment
is not being used at all, so it can be removed.
Also, I found a bug in the app that made the buttons not work, it was quite simple, I forgot to change a few paths in the Android Manifest.
This is just one example of how the files could be organized, but the exact structure may vary depending on the specific needs of your app. The key is to be consistent and have a clear structure that makes sense for your app and makes it easy to navigate and maintain.
In any case, I succeeded in such a structure:
└── com
└── example
└── exchangeabletoken
├── AddTransactionActivity.kt
├── ChooseSignUpActivity.kt
├── DataBaseService.kt
├── DataProduct.kt
├── ExchangeableTokenActivity.kt
├── FirebaseDatabase.kt
├── FirebaseDatabaseService.kt
├── FirebaseStorageService.kt
├── First2Fragment.kt
├── FirstFragment.kt
├── LoginActivity.kt
├── MainActivity.kt
├── MarketActivity.kt
├── ProductActivity.kt
├── ProductAdapter.kt
├── ReceiverChecker.kt
├── SearchProductActivity.kt
├── Second2Fragment.kt
├── SecondActivity.kt
├── SecondFragment.kt
├── SettingsActivity.kt
├── SettingsFragment.kt
├── SignUpActivity.kt
├── SuccessfulSignUpActivity.kt
├── TransactionActivity.kt
├── TransactionBuilder.kt
├── Transaction.kt
├── ui
│ └── main
│ ├── PageViewModel.kt
│ ├── PlaceholderFragment.kt
│ └── SectionsPagerAdapter.kt
└── WalletStatusActivity.kt
To achieve such a structure:
├── data
│ ├── database
│ │ └── FirebaseDatabaseService.kt
│ ├── firebase
│ │ └── FirebaseDatabase.kt
│ ├── model
│ │ ├── Product.kt
│ │ ├── TransactionBuilder.kt
│ │ ├── Transaction.kt
│ │ └── User.kt
│ └── repository
│ └── DatabaseService.kt
├── ui
│ ├── activities
│ │ └── SecondActivity.kt
│ ├── authentication
│ │ ├── ChooseSignUpActivity.kt
│ │ ├── LoginActivity.kt
│ │ ├── SignUpActivity.kt
│ │ └── SuccessfulSignUpActivity.kt
│ ├── base
│ │ └── BaseActivity.java
│ ├── exchange
│ │ └── ExchangeableTokenActivity.kt
│ ├── fragments
│ │ ├── FirstFragment.kt
│ │ └── SecondFragment.kt
│ ├── main
│ │ ├── First2Fragment.kt
│ │ ├── MainActivity.kt
│ │ └── MainViewModel.kt
│ ├── market
│ │ ├── MarketActivity.kt
│ │ ├── MarketViewModel.kt
│ │ └── ProductAdapter.kt
│ ├── navigation
│ │ ├── PageViewModel.kt
│ │ ├── PlaceholderFragment.kt
│ │ ├── SectionsPagerAdapter.kt
│ │ └── SettingsFragment.kt
│ ├── product
│ │ ├── ProductActivity.kt
│ │ └── ProductViewModel.kt
│ ├── search
│ │ ├── SearchProductActivity.kt
│ │ └── SearchProductViewModel.kt
│ ├── secondary
│ │ └── Second2Fragment.kt
│ ├── settings
│ │ ├── SettingsActivity.kt
│ │ └── SettingsViewModel.kt
│ ├── transaction
│ │ ├── AddTransactionActivity.kt
│ │ └── TransactionActivity.kt
│ └── wallet
│ ├── WalletStatusActivity.kt
│ └── WalletStatusViewModel.kt
└── utils
├── FirebaseStorageService.kt
└── ReceiverChecker.kt
For me, this is a success.
Source code: https://github.com/53jk1/ExchangeableToken