Skip to content

anbazhan/mvvmcore

Repository files navigation

mvvmcore

Lib was created for faster start in MVVM architecture.

There are introduced 3 base mvvm abstraction Activity, Fragment and FragmentDialog.

You can also find a specific RVAdapter for faster programming logic connected with RecycleView and binding.

Let's do some practice.

Add dependencies

Add into project build.gradle or settings.gradle

allprojects {
	repositories {
		...
		maven { url 'https://jitpack.io' }
	}
}

Add into app build.gradle

dependencies {
	implementation 'com.github.anbazhan.mvvmcore:final:VERSION'
}

Add databinding into your project

buildFeatures {
   dataBinding = true
}

Create a simple ViewModel

class MyViewModel : BaseActivityViewModel() {

   val isChecked: MutableLiveData<Boolean> = MutableLiveData(false)

   val text = Transformations.map(isChecked) {
       if (it) {
           "Goodbye World"
       } else {
           "Hello World"
       }
   }
}

Examples connected with activity, but other components work the same way.

Create a simple xml

<?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>

       <variable
           name="viewModel"
           type="ru.morizo.testmvvm.MyViewModel" />

   </data>

   <androidx.constraintlayout.widget.ConstraintLayout
       android:layout_width="match_parent"
       android:layout_height="match_parent">

       <Switch
           android:id="@+id/my_switch"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_marginStart="16dp"
           android:layout_marginTop="16dp"
           android:checked="@={viewModel.isChecked()}"
           app:layout_constraintStart_toStartOf="parent"
           app:layout_constraintTop_toTopOf="parent" />

       <TextView
           android:id="@+id/my_text"
           android:layout_width="wrap_content"
           android:layout_height="wrap_content"
           android:layout_marginStart="16dp"
           android:layout_marginTop="16dp"
           android:text="@{viewModel.text}"
           android:textColor="@color/black"
           android:textSize="20sp"
           app:layout_constraintLeft_toLeftOf="parent"
           app:layout_constraintTop_toBottomOf="@id/my_switch"
           tools:text="Hello World!" />

   </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

And last step create Activity

class MainActivity : BaseActivity<MyViewModel, ActivityMainBinding>() {

   override val layoutIdRes: Int = R.layout.activity_main
   override val viewModelBRVarId: Int = BR.viewModel

   // You can also provide it with DI or use any way you need
   override val viewModel: MyViewModel by lazy {
       MyViewModel()
   }
}

You can use livedata for observing your component using method observeViewModel

override fun observeViewModel() {
   super.observeViewModel()
  
   viewModel.isChecked.observe(this) {
       // SomeLogic
   }
}

And you can use the binding field for communicating with view.

binding.myText.text

Library also contains a realization for RecyclerView Adapter which works with binding using mvvm architecture.

For better understanding, let’s create our own expanded adapter

But first let’s create a generic interface. It will be used to provide header and item the same way.

interface MyItem

Create simple data classes.

data class Item(val text: String)
data class Header(val text: String, val itemList: List<Item>)

Create wrapper for this classes which we will use in our logic

class ItemWrapper(val item: Item) : MyItem

For header it will be more complex class

class HeaderWrapper(
   val header: Header,
   val onClick: (headerWrapper: HeaderWrapper, List<BindingItem<ItemWrapper>>) -> Unit
) : MyItem {

   private val bindingItems = header.itemList.map {
       BindingItem(
           BR.item,
           R.layout.item_second,
           ItemWrapper(it)
       )
   }

   fun click() {
       onClick(this, bindingItems)
   }
}

There is a specific class BindingItem. It is used for binding items with layout. Let’s create xml files for our items

first.xml

<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>

       <variable
           name="item"
           type="ru.morizo.testmvvm.items.HeaderWrapper" />

   </data>

   <androidx.constraintlayout.widget.ConstraintLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content"
       android:onClick="@{() -> item.click()}">

       <TextView
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:gravity="center"
           android:paddingVertical="8dp"
           android:text="@{item.header.text}"
           android:textColor="@color/black"
           android:textSize="24sp"
           app:layout_constraintTop_toTopOf="parent"
           tools:text="text" />

   </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

second.xml

<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>

       <variable
           name="item"
           type="ru.morizo.testmvvm.items.ItemWrapper" />

   </data>

   <androidx.constraintlayout.widget.ConstraintLayout
       android:layout_width="match_parent"
       android:layout_height="wrap_content">

       <TextView
           android:layout_width="match_parent"
           android:layout_height="wrap_content"
           android:paddingVertical="8dp"
           android:text="@{item.item.text}"
           android:textColor="@color/black"
           android:textSize="18sp"
           app:layout_constraintTop_toTopOf="parent"
           tools:text="text" />

   </androidx.constraintlayout.widget.ConstraintLayout>

</layout>

Now we should modify our ViewModel

val items: MutableLiveData<MutableList<BindingItem<MyItem>>> = MutableLiveData(mutableListOf())
…
override fun onCreate(owner: LifecycleOwner) {
   super.onCreate(owner)
   items.value = items.value?.apply {
       addAll(
           mutableListOf(
               Header("FirstGroup", listOf(Item("first"), Item("second"))),
               Header("SecondGroup", listOf(Item("first"), Item("second"), Item("third")))
           ).map {
               BindingItem(
                   BR.item,
                   R.layout.item_first,
                   HeaderWrapper(
                       it,
                   ) { header, items ->
                       onHeaderClick(header, items as List<BindingItem<MyItem>>)
                   }
               )
           }
       )
   }
}

fun onHeaderClick(header: HeaderWrapper, list: List<BindingItem<MyItem>>) {
   items.value?.indexOfFirst { it.item == header }?.let { position ->
       items.value = items.value?.apply {
           if (this.containsAll(list)) {
               removeAll(list)
           } else {
               addAll(position + 1, list)
           }
       }
   }
}

You can provide your own logic for expanding items. It is just an example.

Add RecyclerView in our activity layout.

<androidx.recyclerview.widget.RecyclerView
   android:id="@+id/rv"
   android:layout_width="match_parent"
   android:layout_height="0dp"
   android:items="@{viewModel.items}"
   app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
   app:layout_constraintBottom_toBottomOf="parent"
   app:layout_constraintTop_toBottomOf="@id/my_text" />

You can find that android:items is a simple extension function for RecyclerView. You can provide your own logic for notifying your adapter.

And last step modify our activity

override fun onCreate(savedInstanceState: Bundle?) {
   super.onCreate(savedInstanceState)
   binding.rv.adapter = BaseRVAdapter<MyItem, BindingItem<MyItem>>()
}