This repository contains a detailed sample app that implements MVVM architecture in Kotlin using Dagger Hilt, Room, Coroutines...
The main players in the MVVM pattern are:
-
The View — that informs the ViewModel about the user’s actions
-
The ViewModel — exposes streams of data relevant to the View
-
The DataModel — abstracts the data source. The ViewModel works with the DataModel to get and save the data
The MVVM pattern supports two-way data binding between the View and ViewModel and there is a many-to-one relationship between View and ViewModel.
- Kotlin
- Room
- Android Architecture Components
- Android Support Libraries
- Paging
- Coroutines
- Dagger hilt
- Picasso
- Retrofit
- OkHttp
- Gson
- Timber
In order to reduce boilerplate and the steps required to create new Activities and Fragments, Dagger provides some base classes called DaggerApplication, DaggerAppCompatActivity and DaggerFragment.
These contain applicationInjector, AndroidInjection and AndroidSupportInjection respectively which were supposed to be included in all classes and fragments.
From the Project Pane, we have the following classes:
A class which extends the DaggerApplication class.
This is a base class in android that contains all android components such as activities, services, broadcast receivers etc.
class MyApplication : DaggerApplication() {
override fun applicationInjector(): AndroidInjector<out DaggerApplication> =
DaggerAppComponent.builder().create(this)
companion object {
private lateinit var instance: MyApplication
fun getInstance() = instance
}
override fun onCreate() {
super.onCreate()
instance = this
}
}
This class combines all the module classes used in the app for compilation by the Dagger 2 library.
@Singleton
@Component(
modules = [AndroidSupportInjectionModule::class,
AndroidInjectionModule::class,
AppModule::class,
NetworkModule::class,
DataModule::class,
BuildersModule::class]
)
interface AppComponent : AndroidInjector<MyApplication> {
@Component.Builder
abstract class Builder : AndroidInjector.Builder<MyApplication>()
}
This class houses all Activities and Fragment Modules.
@Module
abstract class BuildersModule {
@ContributesAndroidInjector(modules = [MainActivityModule::class])
abstract fun mainActivity(): MainActivity
@ContributesAndroidInjector(modules = [TestActivityModule::class])
abstract fun testActivity(): TestActivity
@ContributesAndroidInjector(modules = [TestFragmentModule::class])
abstract fun testFragment(): TestFragment
}
...
An abstract class created which extends the DaggerAppCompatActivity class. Per adventure you have some other methods that would be used in all activities, you could put them in this class while the activities extend it.
abstract class BaseActivity : DaggerAppCompatActivity()
An abstract class which extends the DaggerFragment class. Including methods as explained in BaseActivity also applies here.
abstract class BaseFragment<B : ViewDataBinding> : DaggerFragment()
An abstract class which extends the ViewModel class
abstract class BaseViewModel : ViewModel()
Why the Repository Pattern ?
-
Decouples the application from the data sources
-
Provides data from multiple sources (DB, API) without clients being concerned about this
-
Isolates the data layer
-
Single place, centralized, consistent access to data
-
Testable business logic via Unit Tests
-
Easily add new sources
So our repository now talks to the API data source and with the cache data source. We would now want to add another source for our data, a database source.
On Android, we have several options here :
-
using pure SQLite (too much boilerplate)
-
Realm ( too complex for our use case, we don’t need most of it’s features)
-
GreenDao ( a good ORM, but I think they will focus the development on objectbox in the future)
-
Room ( the newly introduced ORM from Google, good support for RXJava 2 )
I will be using for my example Room, the new library introduced by Google.
Thread-safe live data to resolve this issue: when perform not in main Thread. (almost case is in testing)
class SafeMutableLiveData<T> : MutableLiveData<T>() {
override fun setValue(value: T) {
try {
super.setValue(value)
} catch (e: Exception) {
// if we can't set value due to not in main thread, must call post value instead
super.postValue(value)
}
}
}
databaseManager.withTransaction {
try {
val usersFromDb = dbHelper.getUsers()
// here you have your usersFromDb
} catch (e: Exception) {
// handler error
}
}
All pull requests are welcome, make sure to follow the contribution guidelines when you submit pull request.
Copyright 2018 LyHoangVinh.
Licensed under the the GPL-3.0 license.
See the LICENSE file for the whole license text.