Fragment to Fragment Communication in Android using Shared ViewModel
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
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: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.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: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.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:
Contact Us