Android MVVM Library + Comprehensive Demo Application
Android library designed to greatly simplify the implementation process of an MVVM-based application by providing all the means necessary to solve the common problems and avoid the annoying boilerplate code.
The library is accompanied by a comprehensive Demo Application that is built around MVVM and Clean Architecture concepts. The Demo Application utilizes such popular libraries as: RxJava, Dagger2, Android Navigation Architecture Component, OkHttp, Retrofit, Room, Glide.
Android MVVM Library will make the implementation of your MVVM-based application a trivial task thus allowing you to spend more time focusing on other important things.
Contents
- Demo
- Getting Started
- Modules
- Basic Implementation
- Dagger Based Implementation
- Navigation Component Based Implementation
- Navigation Component and Dagger Based Implementation
- Compilation
- Contribution
- Hall of Fame
- License
Demo
Marvel Universe Application is an application built around MVVM and Clean Architecture concepts, as well as the data provided by the Marvel API. The application allows you to browse through the Marvel comics, events and characters; each of the aforementioned entities is accompanied by a corresponding detailed overview screen, which gives you even more insight into the Marvel Universe.
Screenshots
Application Architecture
Getting Started
Prerequisites
1. Make sure that you've added the jcenter()
repository to your top-level build.gradle
file.
buildscript {
//...
repositories {
//...
jcenter()
}
//...
}
2. Enable the jetifier and androidX support in the top-level gradle.properties
file.
//...
android.enableJetifier=true
android.useAndroidX=true
//....
3. Update your compileSdkVersion
in the module-level build.gradle
file to 29+.
//...
android {
//...
compileSdkVersion 29
//...
}
//...
4. Enable the Data Binding in the module-level build.gradle
file.
//...
android {
//...
dataBinding {
enabled true
}
//...
}
//...
5. Replace your com.android.support.appcompat.*
dependency with the new androidx.appcompat.*
alternative.
//...
dependencies {
//...
implementation "androidx.appcompat:appcompat:1.0.2"
//...
}
//...
6. Add the Android Lifecycle (ViewModel), RxJava and RxBus dependencies to the module-level build.gradle
file.
//...
dependencies {
//...
implementation "androidx.lifecycle:lifecycle-viewmodel:2.0.0"
implementation "io.reactivex.rxjava2:rxjava:2.2.12"
implementation "io.reactivex.rxjava2:rxandroid:2.1.1"
implementation "com.arthurivanets.rxbus:rxbus:1.1.0"
//...
}
//...
Android MVVM Dependencies
The basic implementation must include the core module
implementation "com.arthurivanets.mvvm:mvvm-core:X.Y.Z"
Which should be added to your module-level build.gradle
file.
ext {
//...
androidMvvmLibraryVersion = "1.3.1"
}
dependencies {
//...
implementation "com.arthurivanets.mvvm:mvvm-core:$androidMvvmLibraryVersion"
}
After that you can proceed with further implementation.
See: Basic Implementation, Dagger Based Implementation, Navigation Component Based Implementation, Navigation Component and Dagger Based Implementation
Modules
The library is comprised of several modules, namely:
mvvm-core
- core implementation (Required)mvvm-dagger
- Dagger DI based implementation (Optional)mvvm-navigation
- Android Navigation Component based implementation (Optional)mvvm-navigation-dagger
- Android Navigation Component + Dagger DI based implementation (Optional)
The mvvm-core
module is a core module the other modules depend on. It provides all the means necessary to create the MVVM-based Fragments and Activities, as well the corresponding ViewModels. (See: MvvmFragment
, MvvmActivity
, BaseViewModel
, AbstractViewModel
, Command
, ViewState
, Route
)
The mvvm-dagger
module is a module that provides the MvvmActivity
and MvvmFragment
implementations that automatically handle the injection of the Dagger DI
dependencies.
The mvvm-navigation
module is a module that provides the MvvmActivity
and MvvmFragment
implementations with built-in support for the Android Navigation Component
based navigation.
mvvm-navigation-dagger
module is a module that provides the MvvmActivity
and MvvmFragment
implementations that have both the built-in support for the Android Navigation Component
based navigation and automatic handling of the injection of the Dagger DI
dependencies.
Basic Implementation
The basic implementation consists of 5 simple steps, namely:
- Creation of the ViewModel
- Creation of the ViewModel-specific View States & Commands
- Creation of the application screen routes
- Creation of the
layout.xml
for the Activity/Fragment - Implementation of the Activity/Fragment
So, let's start with the creation of the ViewModel for our Activity and/or Fragment.
SimpleViewModel.kt [contract] (click to expand)
interface SimpleViewModel : BaseViewModel {
// The rest of your observable fields and event propagation methods should be defined here
}
The ViewModel contract should implement the
BaseViewModel
interface.
SimpleViewModelImpl.kt [concrete implementation] (click to expand)
class SimpleViewModelImpl : AbstractViewModel(), SimpleViewModel {
// Your concrete implementation...
}
The concrete implementation of the ViewModel should extend the
AbstractViewModel
class and implement the corresponding contract interface.
Then, create the ViewModel-specific View States & Commands.
GeneralViewStates.kt (click to expand)
sealed class GeneralViewStates<T>(payload : T? = null) : ViewState<T>(payload) {
class Initial : GeneralViewStates<Unit>()
class Loading<T>(payload : T? = null) : GeneralViewStates<T>(payload)
class Success<T>(payload : T? = null) : GeneralViewStates<T>(payload)
class Error<T>(payload : T? = null) : GeneralViewStates<T>(payload)
// The rest of your View State go here...
}
The implementation of the ViewModel-specific View States should be based upon the
ViewState
class.
GeneralViewModelCommands.kt (click to expand)
sealed class GeneralViewModelCommands<T>(payload : T? = null) : Command<T>(payload) {
class ShowToast(text : String) : GeneralViewModelCommands<String>(text)
class RestartApplication : GeneralViewModelCommands<Unit>()
// The rest of your ViewModel Commands go here...
}
The implementation of the ViewModel-specific Commands should be based upon the
Command
class.
Then, create the application screen routes.
MarvelRoutes.kt (click to expand)
sealed class MarvelRoutes<T>(payload : T? = null) : Route<T>(payload) {
class CharacterInfoScreen(character : Character) : MarvelRoutes<Character>(character)
class ComicsInfoScreen(comics : Comics) : MarvelRoutes<Comics>(comics)
class EventInfoScreen(event : Event) : MarvelRoutes<Event>(event)
// The rest of your Application Routes go here...
}
The implementation of the Application Screen Routes should be based upon the
Route
class.
After that, let's create the layout.xml
files for both our Activity and Fragment.
activity_simple_mvvm.xml + fragment_simple_mvvm.xml (click to expand)
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<!-- Data-binding-related -->
<data>
<variable
name="viewModel"
type="com.yourapplication.sample.SimpleViewModel"/>
</data>
<!-- The Content Layout -->
<!-- Your content layout goes here... -->
</layout>
Finally, let's implement the MVVM-based Activity and Fragment.
SimpleMvvmActivity.kt (click to expand)
import com.arthurivanets.mvvm.MvvmActivity
class SimpleMvvmActivity : MvvmActivity<ActivitySimpleMvvmBinding, SimpleViewModel>() {
private lateinit var localViewModel : SimpleViewModel
override fun injectDependencies() {
// Initialize your View Model here...
localViewModel = SimpleViewModelImpl()
}
// The rest of the Activity Initialization goes here...
override fun onRegisterObservables() {
// Register your ViewModel's observable fields here...
}
override fun onHandleCommand(command : Command<*>) {
// handle the ViewModel-specific command here... (e.g. Restart the Application, Show Toast, etc.)
}
override fun onViewStateChanged(state : ViewState<*>) {
// handle the View State Change here... (adjust your UI correspondingly)
}
override fun onRoute(route : Route<*>) {
// handle the Application Route here... (navigate to the corresponding screen)
}
override fun getLayoutId() : Int {
return R.layout.activity_simple_mvvm
}
override fun getBindingVariable() : Int {
return BR.viewModel
}
override fun getViewModel() : SimpleViewModel {
return localViewModel
}
}
The implementation of the MVVM Activity should be based upon the Core
MvvmActivity
class.
SimpleMvvmFragment.kt (click to expand)
import com.arthurivanets.mvvm.MvvmFragment
class SimpleMvvmFragment : MvvmFragment<FragmentSimpleMvvmBinding, SimpleViewModel>() {
private lateinit var localViewModel : SimpleViewModel
override fun injectDependencies() {
// Initialize your View Model here...
localViewModel = SimpleViewModelImpl()
}
// The rest of the Fragment Initialization goes here...
override fun onRegisterObservables() {
// Register your ViewModel's observable fields here...
}
override fun onHandleCommand(command : Command<*>) {
// handle the ViewModel-specific command here... (e.g. Restart the Application, Show Toast, etc.)
}
override fun onViewStateChanged(state : ViewState<*>) {
// handle the View State Change here... (adjust your UI correspondingly)
}
override fun onRoute(route : Route<*>) {
// handle the Application Route here... (navigate to the corresponding screen)
}
override fun getLayoutId() : Int {
return R.layout.fragment_simple_mvvm
}
override fun getBindingVariable() : Int {
return BR.viewModel
}
override fun getViewModel() : SimpleViewModel {
return localViewModel
}
}
The implementation of the MVVM Fragment should be based upon the Core
MvvmFragment
class.
The MvvmActivity
, MvvmFragment
and AbstractViewModel
classes provide many convenience methods for dealing with the lifecycle of the ObservableField subscriptions and Rx disposbles, so it's definitely a good idea to look through the implementations in order to familiarize yourself with the available APIs.
See:
MvvmActivity
,MvvmFragment
,BaseViewModel
,AbstractViewModel
,Command
,ViewState
,Route
,GeneralViewStates
,MarvelRoutes
Dagger Based Implementation
The Dagger-based implementation process is almost identical to the one of the Basic Implementation, the only thing that's different here is the fact that you need to use the MvvmActivity
and MvvmFragment
provided by the mvvm-dagger
module instead of the ones coming from the mvvm-core
module.
SimpleMvvmActivity.kt (click to expand)
import com.arthurivanets.mvvm.dagger.MvvmActivity
class SimpleMvvmActivity : MvvmActivity<ActivitySimpleMvvmBinding, SimpleViewModel>() {
@Inject
private lateinit var localViewModel : SimpleViewModel
// The rest of the Activity Initialization goes here...
override fun onRegisterObservables() {
// Register your ViewModel's observable fields here...
}
override fun onHandleCommand(command : Command<*>) {
// handle the ViewModel-specific command here... (e.g. Restart the Application, Show Toast, etc.)
}
override fun onViewStateChanged(state : ViewState<*>) {
// handle the View State Change here... (adjust your UI correspondingly)
}
override fun onRoute(route : Route<*>) {
// handle the Application Route here... (navigate to the corresponding screen)
}
override fun getLayoutId() : Int {
return R.layout.activity_simple_mvvm
}
override fun getBindingVariable() : Int {
return BR.viewModel
}
override fun getViewModel() : SimpleViewModel {
return localViewModel
}
}
The implementation of the MVVM Activity should be based upon the Dagger
MvvmActivity
class.
SimpleMvvmFragment.kt (click to expand)
import com.arthurivanets.mvvm.dagger.MvvmFragment
class SimpleMvvmFragment : MvvmFragment<FragmentSimpleMvvmBinding, SimpleViewModel>() {
@Inject
private lateinit var localViewModel : SimpleViewModel
// The rest of the Fragment Initialization goes here...
override fun onRegisterObservables() {
// Register your ViewModel's observable fields here...
}
override fun onHandleCommand(command : Command<*>) {
// handle the ViewModel-specific command here... (e.g. Restart the Application, Show Toast, etc.)
}
override fun onViewStateChanged(state : ViewState<*>) {
// handle the View State Change here... (adjust your UI correspondingly)
}
override fun onRoute(route : Route<*>) {
// handle the Application Route here... (navigate to the corresponding screen)
}
override fun getLayoutId() : Int {
return R.layout.fragment_simple_mvvm
}
override fun getBindingVariable() : Int {
return BR.viewModel
}
override fun getViewModel() : SimpleViewModel {
return localViewModel
}
}
The implementation of the MVVM Fragment should be based upon the Dagger
MvvmFragment
class.
See:
MvvmActivity
,MvvmFragment
Navigation Component Based Implementation
The Navigation Component based implementation process has many things in common with the Basic Implementation; the differences are shown in the code snippets below.
activity_host.xml (click to expand)
<?xml version="1.0" encoding="utf-8"?>
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<!-- Data-binding-related -->
<data>
<variable
name="viewModel"
type="com.yourapplication.sample.StubViewModel"/>
</data>
<!-- The Actual Layout -->
<com.google.android.material.internal.ScrimInsetsFrameLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<fragment
android:id="@+id/nav_host_fragment"
android:name="com.arthurivanets.mvvm.navigation.MvvmNavHostFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:defaultNavHost="true"
app:navGraph="@navigation/your_navigation_graph"/>
</com.google.android.material.internal.ScrimInsetsFrameLayout>
</layout>
The
MvvmNavHostFragment
should be used as a Navigation Host Fragment of the Host Activity.
HostActivity.kt (click to expand)
import com.arthurivanets.mvvm.navigation.MvvmActivity
class HostActivity : MvvmActivity<ActivityHostBinding, StubViewModel>() {
private var localViewModel : StubViewModel
override fun injectDependencies() {
localViewModel = StubViewModelImpl()
}
override fun getLayoutId() : Int {
return R.layout.activity_host
}
override fun getBindingVariable() : Int {
return BR.viewModel
}
override fun getViewModel() : HostActivityViewModel {
return localViewModel
}
override fun getNavigationGraphId() : Int {
return R.navigation.your_navigation_graph
}
}
The implementation of the Host Activity should be based upon the Navigation
MvvmActivity
class.
SimpleMvvmFragment.kt (click to expand)
import com.arthurivanets.mvvm.navigation.MvvmFragment
class SimpleMvvmFragment : MvvmFragment<FragmentSimpleMvvmBinding, SimpleViewModel>() {
private lateinit var localViewModel : SimpleViewModel
override fun injectDependencies() {
// Initialize your View Model here...
localViewModel = SimpleViewModelImpl()
}
// The rest of the Fragment Initialization goes here...
override fun onRegisterObservables() {
// Register your ViewModel's observable fields here...
}
override fun onHandleCommand(command : Command<*>) {
// handle the ViewModel-specific command here... (e.g. Restart the Application, Show Toast, etc.)
}
override fun onViewStateChanged(state : ViewState<*>) {
// handle the View State Change here... (adjust your UI correspondingly)
}
override fun onRoute(route : Route<*>) {
// handle the Application Route here... (navigate to the corresponding screen)
}
override fun getLayoutId() : Int {
return R.layout.fragment_simple_mvvm
}
override fun getBindingVariable() : Int {
return BR.viewModel
}
override fun getViewModel() : SimpleViewModel {
return localViewModel
}
}
The implementation of the MVVM Fragment should be based upon the Navigation
MvvmFragment
class.
Navigation Component and Dagger Based Implementation
Shares many implementation-specific aspects with the previously described implementation types and is used in the Demo Application
.
See:
MvvmActivity
,MvvmFragment
Compilation
In order to compile the app
module you need to obtain the PUBLIC_API_KEY
and PRIVATE_API_KEY
from the Marvel API
portal, which should be saved in either global gradle.properties
file or the project-specific one thereafter.
gradle.properties
marvelApiPublicKey=PUBLIC_API_KEY
marvelApiPrivateKey=PRIVATE_API_KEY
Contribution
See the CONTRIBUTING.md file.
Hall of Fame
Using Android MVVM Library in your app and want it to get listed here? Email me at [email protected]!
License
Android MVVM Library is licensed under the Apache 2.0 License.