Fragment to Fragment Communication in Android using Shared ViewModel

06 Jun, 2021
If there are two or more fragments in an activity, they need to communicate and share the data between them. The traditional way of sharing the data between the two fragments is implementing the callback using an interface that is cumbersome and may throw exceptions. But the modern way of doing that is using shared ViewModel. So in this article, it’s been demonstrated how the shared ViewModel can be used to communicate between the fragments. Have a look at the following image to get an overview of the discussion.
Note: This discussion is implemented in Kotlin programming language.
Prerequisites
Fragment Lifecycle in Android
ViewModel in Android Architecture Components
Step 1: Create an empty activity project
Create an empty activity Android Studio project, and select Kotlin as the programming language. Refer to Android | How to Create/Start a New Project in Android Studio?.
Step 2: Adding required dependencies
The dependencies include the ViewModel and the LiveData. So, in the app level Gradle file add the following dependencies and sync the project.
// lifecycle_version and architecture versions may varydef lifecycle_version = “2.3.1”
def arch_version = “2.1.0”
// ViewModel
implementation “androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version”
// LiveData
implementation “androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version”
// Lifecycles only (without ViewModel or LiveData)
implementation “androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version”
// architecture Lifecycle extensions
implementation “androidx.lifecycle:lifecycle-extensions:$arch_version”
Step 3: Working with activity_main.xml file
The main layout of the application contains the two FrameLayouts which hold the two fragments. To implement the same invoke the following code inside the activity_main.xml file.
XML
<?xml version="1.0" encoding="utf-8"?><androidx.constraintlayout.widget.ConstraintLayout     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"    android:layout_width="match_parent"    android:layout_height="match_parent"    tools:context=".MainActivity"    tools:ignore="HardcodedText">      <TextView        android:id="@+id/textView"        style="@style/TextAppearance.MaterialComponents.Headline6"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginTop="16dp"        android:text="Fragment 1:"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toTopOf="parent" />      <FrameLayout        android:id="@+id/fragment_1_holder"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginTop="8dp"        android:layout_marginEnd="16dp"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintHorizontal_bias="0.0"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/textView" />      <View        android:id="@+id/view"        android:layout_width="match_parent"        android:layout_height="1dp"        android:layout_marginTop="16dp"        android:background="@color/green_700"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/fragment_1_holder" />      <TextView        android:id="@+id/textView2"        style="@style/TextAppearance.MaterialComponents.Headline6"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginTop="16dp"        android:text="Fragment 2: "        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/view" />      <FrameLayout        android:id="@+id/fragment_2_holder"        android:layout_width="match_parent"        android:layout_height="wrap_content"        android:layout_marginStart="16dp"        android:layout_marginTop="8dp"        android:layout_marginEnd="16dp"        app:layout_constraintEnd_toEndOf="parent"        app:layout_constraintHorizontal_bias="0.0"        app:layout_constraintStart_toStartOf="parent"        app:layout_constraintTop_toBottomOf="@+id/textView2" />  </androidx.constraintlayout.widget.ConstraintLayout>
Step 4: Implementing Shared ViewModel
In the SharedViewModel.kt file there are there is one MutableLiveData of CharSequence for setting the data for EditTexts. Two functions setData and getData for updating that mutable live data as soon as the data inside the EditTexts changes.
To implement the same invoke the following code.
Kotlin
import androidx.lifecycle.MutableLiveDataimport androidx.lifecycle.ViewModel  class SharedViewModel : ViewModel() {      // Mutable LiveData which observed by LiveData       // and updated to EditTexts when it is changed.    private val mutableLiveData: MutableLiveData<CharSequence> = MutableLiveData()      // function to set the changed      // data from the EditTexts    fun setData(input: CharSequence) {        mutableLiveData.value = input    }      // function to get the changed data from the EditTexts    fun getData(): MutableLiveData<CharSequence> = mutableLiveData}
Step 5: Creating 2 Fragments
Create two Fragments with their own layouts naming Fragment1.kt and Fragment2.kt.
For each of the fragment’s layouts, it contains one EditText to get the data to send for fragment 2 and one button, when clicked it shares the data to another fragment.
To implement the layout for Fragment 1, invoke the following code inside the fragment_1.xml file
XML
<?xml version="1.0" encoding="utf-8"?><RelativeLayout     xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:clipToPadding="false"    android:padding="4dp"    tools:context=".Fragment1"    tools:ignore="HardcodedText">      <com.google.android.material.textfield.TextInputLayout        android:id="@+id/edit_text_layout_1"        android:layout_width="match_parent"        android:layout_height="wrap_content">          <com.google.android.material.textfield.TextInputEditText            android:id="@+id/edit_text_from_fragment_1"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:hint="Enter the data" />      </com.google.android.material.textfield.TextInputLayout>      <Button        android:id="@+id/send_button_fragment_1"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_below="@id/edit_text_layout_1"        android:layout_alignParentEnd="true"        android:layout_marginTop="8dp"        android:layout_marginEnd="16dp"        android:text="Send Data to Fragment 2" />  </RelativeLayout>
To implement the layout for Fragment 2, invoke the following code inside the fragment_2.xml file.
XML
<?xml version="1.0" encoding="utf-8"?><RelativeLayout     xmlns:android="http://schemas.android.com/apk/res/android"    xmlns:tools="http://schemas.android.com/tools"    android:layout_width="match_parent"    android:layout_height="match_parent"    android:clipToPadding="false"    android:padding="4dp"    tools:context=".Fragment2"    tools:ignore="HardcodedText">      <com.google.android.material.textfield.TextInputLayout        android:id="@+id/edit_text_layout_2"        android:layout_width="match_parent"        android:layout_height="wrap_content">          <com.google.android.material.textfield.TextInputEditText            android:id="@+id/edit_text_from_fragment_2"            android:layout_width="match_parent"            android:layout_height="wrap_content"            android:hint="Enter the data" />      </com.google.android.material.textfield.TextInputLayout>      <Button        android:id="@+id/send_button_fragment_2"        android:layout_width="wrap_content"        android:layout_height="wrap_content"        android:layout_below="@id/edit_text_layout_2"        android:layout_alignParentEnd="true"        android:layout_marginTop="8dp"        android:layout_marginEnd="16dp"        android:text="Send Data to Fragment 1" />  </RelativeLayout>
Step 6: Creating instances of shared view model inside the Fragment.kt files
There is a need to create the instance of the ViewModel of the type ShareViewModel when the Activity is created.
So one needs to override the onActivityCreated() method inside each of the Fragments.
This is so because when we see the lifecycle of the fragments the views are created and updated after the onCreateView() is called and onAcrivityCreated() callback is called after the onCreateView() is executed. So the LiveData can keep track of UI elements for which elements they have updated. Have a look at the following chart for the activity lifecycle of the fragment.
To implement the same in both fragments invoke the, following code inside Fragment1.kt.
Kotlin
import android.os.Bundleimport android.text.Editableimport androidx.fragment.app.Fragmentimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.Buttonimport android.widget.EditTextimport androidx.lifecycle.Observerimport androidx.lifecycle.ViewModelProviders  class Fragment1 : Fragment() {      private var sharedViewModelInstance: SharedViewModel? = null      private var editTextFromFragment1: EditText? = null      override fun onCreateView(            inflater: LayoutInflater, container: ViewGroup?,            savedInstanceState: Bundle?    ): View? {        val view: View = inflater.inflate(R.layout.fragment_1, container, false)          val sendDataButton: Button = view.findViewById(R.id.send_button_fragment_1)        editTextFromFragment1 = view.findViewById(R.id.edit_text_from_fragment_1)          // as soon as the button is clicked           // send the data to ViewModel        // and the Live data will take care of          // updating the data inside another Fragment        sendDataButton.setOnClickListener {            sharedViewModelInstance?.setData(editTextFromFragment1!!.text)        }          return view    }      override fun onActivityCreated(savedInstanceState: Bundle?) {        super.onActivityCreated(savedInstanceState)          // create instances of the shared view model           // when the activity is created        sharedViewModelInstance = ViewModelProviders.of(activity!!).get(SharedViewModel::class.java)          // observe the data inside the view model that           // is mutable live of type CharSequence and           // set the data for edit text        sharedViewModelInstance!!.getData().observe(viewLifecycleOwner, Observer {            editTextFromFragment1!!.text = it as Editable?        })    }}
And same goes with the Fragment2.kt file, invoke the following code.
Kotlin
import android.os.Bundleimport android.text.Editableimport androidx.fragment.app.Fragmentimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.Buttonimport android.widget.EditTextimport androidx.lifecycle.Observerimport androidx.lifecycle.ViewModelProviders  class Fragment2 : Fragment() {      private var sharedViewModelInstance: SharedViewModel? = null      private var editTextFromFragment2: EditText? = null      override fun onCreateView(            inflater: LayoutInflater, container: ViewGroup?,            savedInstanceState: Bundle?    ): View? {        val view: View = inflater.inflate(R.layout.fragment_2, container, false)          val sendDataButton: Button = view.findViewById(R.id.send_button_fragment_2)        editTextFromFragment2 = view.findViewById(R.id.edit_text_from_fragment_2)          // as soon as the button is clicked           // send the data to ViewModel        // and the Live data will take care of           // updating the data inside another Fragment        sendDataButton.setOnClickListener {            sharedViewModelInstance?.setData(editTextFromFragment2!!.text)        }          return view    }      override fun onActivityCreated(savedInstanceState: Bundle?) {        super.onActivityCreated(savedInstanceState)          // create instances of the shared view model          // when the activity is created        sharedViewModelInstance = ViewModelProviders.of(activity!!).get(SharedViewModel::class.java)          // observe the data inside the view model that is mutable           // live of type CharSequence and set the data for edit text        sharedViewModelInstance!!.getData().observe(viewLifecycleOwner, Observer {            editTextFromFragment2!!.text = it as Editable?        })    }}
Step 7: Populate the fragment holders using MainActivity.kt file
Inside the MainActivity.kt file, one needs to populate the fragment holders from the activity_main.xml, with both fragments. To implement the same invoke the following code inside the MainActivity.kt file.
Kotlin
import android.os.Bundleimport androidx.appcompat.app.AppCompatActivityimport androidx.fragment.app.FragmentTransaction  class MainActivity : AppCompatActivity() {    override fun onCreate(savedInstanceState: Bundle?) {        super.onCreate(savedInstanceState)        setContentView(R.layout.activity_main)          val fragmentTransaction: FragmentTransaction = supportFragmentManager.beginTransaction()        fragmentTransaction.apply {            add(R.id.fragment_1_holder, Fragment1())            add(R.id.fragment_2_holder, Fragment2())            commit()        }    }}
Output:
Kotlin Android
Android
Kotlin
Android
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.