Shared ViewModel in Android: Shared between fragments

Communication between Activities or Fragments in Android is a very common task. Almost every application has some communication between various activities or fragments. One common example of this can be the Registration activity, where you take the information of the user in two Activities i.e. some information in the first activity and other in the second activity. After taking information, the data from the first activity along with the second activity can be sent to the database to store the information. Will the application behaves normally in all cases? In most the cases you will store and fetch the right data from the database but in certain situation like UI change, your activity will be refreshed or you can say re-created.

Let’s take an example of a random function. Suppose, you are having one random function in your activity and you are displaying some random number on the app screen. Let’s say the number is 4. Now, change the orientation of the screen, the number will be changed to something else. This happens because when the orientation is changed then the Activity will be recreated and random function will be called again.

So, in order to have some consistent data in the UI of the application we use onSaveInstanceState(), which can be used to restore data. But y using this you can restore only a small amount of data. For big data, Google introduced the concept of ViewModel in Android that is used to store and manage UI-related data. In this blog, we will learn how we can use this ViewModel in our application to communicate between various fragments in our application.

What is ViewModel?

ViewModel is a class that is used to store and manage UI-related data. It is a part of Android Jetpack. So, by using ViewModel your application will some consistent data even if there is a change in UI of the application.

Wherever you are using a ViewModel, the objects of the ViewModel are used to store the data and this data is used to set the value of the UI items in the Activity or Fragment. So, whenever there is a change in the UI of the application, the ViewModel objects are automatically retained and are used to set the value of the UI in the changed orientation.

So, whenever you want to show some data in your app, you can use ViewModel to store that data first and after that use that data in your UI. For example, in order to store the list of users from the database, you can create a ViewModel class and store the data as follows:

class MyViewModel : ViewModel() {
    private val users: MutableLiveData<List<User>> by lazy {
        MutableLiveData().also {
            loadUsers()
        }
    }

    fun getUsers(): LiveData<List<User>> {
        return users
    }

    private fun loadUsers() {
        // Do an asynchronous operation to fetch users.
    }
}

After that, if you want to access the ViewModel data in your Activity then you can use the below code and update the UI:

class MyActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        // Create a ViewModel the first time the system calls an activity's onCreate() method.
        // Re-created activities receive the same MyViewModel instance created by the first activity.

        val model = ViewModelProviders.of(this).get(MyViewModel::class.java)
        model.getUsers().observe(this, Observer<List<User>>{ users ->
            // update UI
        })
    }
}

Following is an illustration of the lifecycle of the ViewModel in Android:

From the above illustration, we can say that the ViewModel is available throughout the life cycle of an Activity or a Fragment. So, the Android system may call the onCreate() method a number of times, but the ViewModel is there for the application throughout its life cycle.

Data sharing between fragments

Data sharing between Activities and Fragments are very common tasks. One common scenario of this can be one Fragment taking information for the user and the other Fragment displaying the entered information. So, there must be some interface between these fragments. We can use ViewModel Class as a communicator between these Fragments. The first fragment i.e. the fragment taking the information from the user will store data into the ViewModel and the second fragment i.e. the fragment showing the information of the user will collect the data from the ViewModel. Following is an example of ViewModel that is used to collect the data from the list in one fragment and show the data in the other fragment.

class SharedViewModel : ViewModel() {
    val selected = MutableLiveData<Item>()

    fun select(item: Item) {
        selected.value = item
    }
}

class MasterFragment : Fragment() {

    private lateinit var itemSelector: Selector

    private lateinit var model: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        model = activity?.run {
            ViewModelProviders.of(this).get(SharedViewModel::class.java)
        } : throw Exception("Invalid Activity")
        itemSelector.setOnClickListener { item ->
            // Update the UI
        }
    }
}

class DetailFragment : Fragment() {

    private lateinit var model: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        model = activity?.run {
            ViewModelProviders.of(this).get(SharedViewModel::class.java)
        } ?: throw Exception("Invalid Activity")
        model.selected.observe(this, Observer<Item> { item ->
            // Update the UI
        })
    }
}

In the above example, MasterFragment is the fragment having the list of items and the DetailFragment is the Fragment showing the details of the selected item of the MasterFragment.

Example

Let’s do an example to understand the concept of ViewModel that is shared between Fragments. Here, we will be having two Fragments in an Activity and we will try to send the data from one fragment to others using the concept of ViewModel. So, let’s get started.

Create a project in Android Studio by using the Empty Activity template.

After creating a project, add two Fragments in your root directory by right-clicking on the root directory and then New > Fragment > Blank Fragment. I am creating Fragments with names MessageReceiver and MessageSender. After creating the fragments, create a ViewModel class in the root directory. The name of the ViewModel class in my case is MyViewModel.

Before getting into the logic part, let’s write the code for the UI of the application. In our MainActivity, we will be having two fragments. So, the code for the activity_main.xml file will be:

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent">

    <fragment
            android:id="@+id/receiver_fragment"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            android:layout_marginTop="8dp"
            app:layout_constraintBottom_toTopOf="@+id/sender_fragment"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

    </fragment>

    <fragment
            android:id="@+id/sender_fragment"
            android:layout_width="match_parent"
            android:layout_height="0dp"
            android:layout_marginBottom="8dp"
            android:layout_marginEnd="8dp"
            android:layout_marginStart="8dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.5"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/receiver_fragment">

    </fragment>

</android.support.constraint.ConstraintLayout>

After that, we have to add the code for the UI of the fragments. In the MessageReceiver fragment, we are having one TextView to display the message sent by the MessageSender fragment. So, below id the code for the layout file of MessageReceiver:

<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#11209F">

    <TextView
            android:id="@+id/receiver_tv"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:gravity="center"
            android:text="I am a Messenger"
            android:textColor="@color/White"
            android:textSize="18sp"/>

</RelativeLayout>

Now, the MessageSender fragment will have one button that will be used to send the data from the MessageSender fragment to the MessageReceiver fragment. So, the code for the layout file of the MessageSender will be:

<RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="#F5A623">

    <Button
            android:id="@+id/msg_btn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"
            android:layout_gravity="center"
            android:gravity="center"
            android:text="Send Your Message"/>
</RelativeLayout>

So, we are done with the UI part. Let’s move on to the coding part of the ViewModel. Here, we are sharing one message from the MessageSender to the MessageReceiver. So, we need to create a message variable that will be used to store the message and this message will be shared to the receiver i.e. the same message variable will be used to store data from the MessageSender and then retrieve the data from the ViewModel to the MessageReceiver.

So, the code for the MyViewModel class will be:

class MyViewModel : ViewModel() {
    val message = MutableLiveData<String>()

    fun myMessage(msg: String) {
        message.value = msg
    }
}

After writing the code for the ViewModel, our next task is to store or set the value of the message of the ViewModel class. So, in the MessageSender fragment, you have to pass one string to the setMessage() function of the ViewModel. So, the coed for the MessageSender will be:

class MessageSender : Fragment() {
    lateinit var model: MyViewModel

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_sender, container, false)
        model = activity?.let { ViewModelProviders.of(it).get(MyViewModel::class.java) }

        val button = view.findViewById<View>(R.id.msg_btn) as Button
        // on click button
        button.setOnClickListener { model?.myMessage("Hello! I am your message") }
        return view
    }
}

Here, we are sending the message to the ViewModel on the click of the Button.

Now, our final task is to get the message from the ViewModel and display it on the TextView present in the MessageReceiver fragment:

class MessageReceiver : Fragment() {

    internal lateinit var tv_msg: TextView

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val view = inflater.inflate(R.layout.fragment_receiver, container, false)

        val model = activity?.let { ViewModelProviders.of(it).get(MyViewModel::class.java) }
        tv_msg = view.findViewById<View>(R.id.receiver_tv) as TextView

        model?.message?.observe(this, object : Observer<Any> {
            override fun onChanged(o: Any?) {
                tv_msg.text = o?.toString()
            }
        })
        return view
    }
}

Now, run the application in your device and see the output by clicking on the button.

Conclusion

In this blog, we learned about ViewModels in Android. We saw how these ViewModels can be used to have a consistent UI, even if there is a change in the UI. These ViewModels can be used to communicate in between the Fragments and the Activities. In the last, we saw one example on fragment communication using ViewModel. Hope you enjoyed this blog.

Keep Learning :)

Team MindOrks!