Open In App

Fragment to Fragment Communication in Android using Shared ViewModel

Last Updated : 06 Jun, 2021
Improve
Improve
Like Article
Like
Save
Share
Report

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.

Fragment to Fragment Communication in Android using Shared ViewModel

Note: This discussion is implemented in Kotlin programming language.

Prerequisites

Steps to implement the communication between fragments

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 vary
def 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: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.

Fragment to Fragment Communication in Android using Shared ViewModel

To implement the same invoke the following code.

Kotlin




import androidx.lifecycle.MutableLiveData
import 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: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: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.

Fragment to Fragment Communication in Android using Shared ViewModel

To implement the same in both fragments invoke the, following code inside Fragment1.kt.

Kotlin




import android.os.Bundle
import android.text.Editable
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import androidx.lifecycle.Observer
import 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.Bundle
import android.text.Editable
import androidx.fragment.app.Fragment
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.EditText
import androidx.lifecycle.Observer
import 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.Bundle
import androidx.appcompat.app.AppCompatActivity
import 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:



Like Article
Suggest improvement
Previous
Next
Share your thoughts in the comments

Similar Reads