Android & Kotlin

Fragment Communication using ViewModel

Pinterest LinkedIn Tumblr

In this android tutorial, We’ll learn Fragment communication using ViewModel. Using shared ViewModel is recommended way by Google for communicating between two fragments. In this technique, We’ll create a shared ViewModel instance and owner of this ViewModel will be Activity. Both fragments will access this ViewModel.

What is ViewModel?

Android Architecture Components provide a ViewModel helper class. That is used to store and manage UI-related changes in a lifecycle conscious way. We usually create one view model for one activity. One activity can have many fragments, means two or more fragment can share one ViewModel.

Data sharing between fragments

Data sharing between fragments is a very common task. While you developing an android application, So many scenarios come where you need to communicate between two fragments. There are several ways to do that, Read our article – Four ways to communicate between fragments.

Fragment communication using ViewModel (Demo App)

Fragment Communication Sample Application

In this tutorial, we’ll create a sample application, that contains one shared ViewModel, ViewPager with TabLayout. Here we’ll have two fragments in ViewPager and we will try to send data from first fragment to second fragment. Both fragments will be used common ViewModel.  So, let’s get started.

1. Project Setup

Let’s move to Android Studio and create a new project with BasicActivity template. After creating the project verify below dependencies.

dependencies {
  
  implementation 'androidx.appcompat:appcompat:1.0.2'
  implementation 'com.google.android.material:material:1.0.0'
  implementation 'androidx.constraintlayout:constraintlayout:1.1.3'

  implementation 'androidx.lifecycle:lifecycle-extensions:2.0.0'
  implementation 'androidx.legacy:legacy-support-v4:1.0.0'
}

2. Prepare MainActivity UI

Before getting into logic part, create some UI for this demo application. Open the activity_main.xml layout file and add below code

<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
    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"
    >

  <com.google.android.material.appbar.AppBarLayout
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      android:theme="@style/AppTheme.AppBarOverlay"
      >

    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:minHeight="?actionBarSize"
        android:padding="@dimen/appbar_padding"
        android:text="@string/app_name"
        android:textAppearance="@style/TextAppearance.Widget.AppCompat.Toolbar.Title"
        />

    <com.google.android.material.tabs.TabLayout
        android:id="@+id/tabs"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="?attr/colorPrimary"
        />
  </com.google.android.material.appbar.AppBarLayout>

  <androidx.viewpager.widget.ViewPager
      android:id="@+id/view_pager"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      app:layout_behavior="@string/appbar_scrolling_view_behavior"
      />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

3. Create a subclass of ViewModel

In main package create subclass of ViewModel named is PageViewModel. In this class, we are taking one MutableLiveData<String> instance. that collect the input from FirstFragment and show the data other fragments. Final code looks like below

package com.fragmentcommunicationexample.ui.main;

import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel;

public class PageViewModel extends ViewModel {

  /**
   * Live Data Instance
   */
  private MutableLiveData<String> mName = new MutableLiveData<>();

  public void setName(String name) {
    mName.setValue(name);
  }

  public LiveData<String> getName() {
    return mName;
  }
}

5. Create a Fragment named is FirstFragment

5.1 Prepare Fragment UI

Once you have prepared a layout for MainActivity then create a new fragment is named FirstFragment. I ‘m adding in TextInputLayout in this layout file, code is below

<?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"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >
  <ImageView
      android:id="@+id/imageView"
      android:layout_width="72dp"
      android:layout_height="72dp"
      android:layout_marginEnd="8dp"
      android:layout_marginLeft="8dp"
      android:layout_marginRight="8dp"
      android:layout_marginStart="8dp"
      android:layout_marginTop="24dp"
      android:src="@drawable/user_avatar"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />
  <com.google.android.material.textfield.TextInputLayout
      android:id="@+id/textInputLayout"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_marginEnd="16dp"
      android:layout_marginLeft="16dp"
      android:layout_marginRight="16dp"
      android:layout_marginStart="16dp"
      android:layout_marginTop="32dp"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/imageView"
      style="@style/Widget.MaterialComponents.TextInputLayout.OutlinedBox"
      >
    <com.google.android.material.textfield.TextInputEditText
        android:id="@+id/textInputTextName"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Enter Name"
        />
  </com.google.android.material.textfield.TextInputLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
5.2 Bind the UI with Fragment and init ViewModel

So, we are done with the UI part. Let’s move on to the coding part of this fragment class. As you saw in code I have inflated the view and add addTextChangedListener() listener on TextInputEditText. So onTextChanged() we will send the data to second fragment.

Now let’s move on to ViewModel part. In fragment onCreate() method we initializing ViewModel this owner of it is Activity.

// init ViewModel
pageViewModel = ViewModelProviders.of(requireActivity()).get(PageViewModel.class);

After initializing ViewModel we’ll set the data like this

  pageViewModel.setName(charSequence.toString());
Finally, FirstFragment code looks like below
package com.fragmentcommunicationexample.ui.main;

import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProviders;
import com.fragmentcommunicationexample.R;
import com.google.android.material.textfield.TextInputEditText;

public class FirstFragment extends Fragment {

  private PageViewModel pageViewModel;

  public FirstFragment() {
    // Required empty public constructor
  }

  /**
   * Create a new instance of this fragment
   * @return A new instance of fragment FirstFragment.
   */
  public static FirstFragment newInstance() {
    return new FirstFragment();
  }

  @Override public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // init ViewModel
    pageViewModel = ViewModelProviders.of(requireActivity()).get(PageViewModel.class);
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_first, container, false);
  }

  @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    TextInputEditText nameEditText = view.findViewById(R.id.textInputTextName);

    // Add Text Watcher on name input text
    nameEditText.addTextChangedListener(new TextWatcher() {
      @Override public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {

      }

      @Override public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {
        pageViewModel.setName(charSequence.toString());
      }

      @Override public void afterTextChanged(Editable editable) {

      }
    });
  }
}

6. Create another Fragment named is SecondFragment

Same as previous fragment, I’m adding a TextView in layout file for showing input that taken from FirstFragment.

<?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=".ui.main.SecondFragment"
    >

  <ImageView
      android:id="@+id/imageView2"
      android:layout_width="72dp"
      android:layout_height="72dp"
      android:layout_marginEnd="8dp"
      android:layout_marginLeft="8dp"
      android:layout_marginRight="8dp"
      android:layout_marginStart="8dp"
      android:layout_marginTop="24dp"
      android:src="@drawable/user_avatar"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent"
      />
  <TextView
      android:id="@+id/textViewName"
      android:layout_width="0dp"
      android:layout_height="wrap_content"
      android:layout_marginEnd="8dp"
      android:layout_marginLeft="8dp"
      android:layout_marginRight="8dp"
      android:layout_marginStart="8dp"
      android:layout_marginTop="32dp"
      android:gravity="center"
      android:hint="User Display Name"
      android:textColor="@color/colorPrimaryDark"
      android:textSize="22sp"
      android:textStyle="bold"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="@+id/imageView2"
      tools:text="Morris"
      />

</androidx.constraintlayout.widget.ConstraintLayout>
Inflate SecondFragment Layout

Nothings to do special here just bind Inflate the layout. Now our final task is to get the from ViewModel and display it on the TextView present in the MessageReceiver fragment:

Now, our final task is to get the message from the ViewModel and display it on the TextView present in the SecondFragment. Code is below

    pageViewModel.getName().observe(requireActivity(), new Observer<String>() {
      @Override
      public void onChanged(@Nullable String s) {
        txtName.setText(s);
      }
    });
Final code of SecondFragment is below
package com.fragmentcommunicationexample.ui.main;

import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProviders;
import com.fragmentcommunicationexample.R;

public class SecondFragment extends Fragment {

  private PageViewModel pageViewModel;
  private TextView txtName;

  public ≈() {
    // Required empty public constructor
  }

  /**
   * Use this factory method to create a new instance of this fragment.
   *
   * @return A new instance of fragment SecondFragment.
   */
  public static SecondFragment newInstance() {
    return new SecondFragment();
  }

  @Override
  public void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // initialise ViewModel here
    pageViewModel = ViewModelProviders.of(requireActivity()).get(PageViewModel.class);
  }

  @Override
  public View onCreateView(LayoutInflater inflater, ViewGroup container,
      Bundle savedInstanceState) {
    // Inflate the layout for this fragment
    return inflater.inflate(R.layout.fragment_second, container, false);
  }

  @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    txtName = view.findViewById(R.id.textViewName);

    pageViewModel.getName().observe(requireActivity(), new Observer<String>() {
      @Override
      public void onChanged(@Nullable String s) {
        txtName.setText(s);
      }
    });
  }
}

4. Create a Fragment Pager Adapter

Let’s full fill this demo needs. You know we are using ViewPager for showing two fragments. Let’s create a ViewPager adapter named is ViewPagerAdapter. So, the code for ViewPagerAdapter will be:

package com.fragmentcommunicationexample.ui.main;

import android.content.Context;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentPagerAdapter;
import com.fragmentcommunicationexample.R;

/**
 * A [FragmentPagerAdapter] that returns a fragment corresponding to
 * one of the sections/tabs/pages.
 */
public class ViewPagerAdapter extends FragmentPagerAdapter {

  @StringRes
  private static final int[] TAB_TITLES = new int[] { R.string.tab_text_1, R.string.tab_text_2 };
  private final Context mContext;

  public ViewPagerAdapter(Context context, FragmentManager fm) {
    super(fm);
    mContext = context;
  }

  @Override
  public Fragment getItem(int position) {
    // getItem is called to instantiate the fragment for the given page.
    if (position == 0) {
      return FirstFragment.newInstance();
    } else {
      return SecondFragment.newInstance();
    }
  }

  @Nullable
  @Override
  public CharSequence getPageTitle(int position) {
    return mContext.getResources().getString(TAB_TITLES[position]);
  }

  @Override
  public int getCount() {
    // Show 2 total pages.
    return 2;
  }
}

Open the MainActivity and paste below

This is not related to this topic but it part of this sample application. Just open the MainActivity file and add final code.

package com.fragmentcommunicationexample;

import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
import androidx.viewpager.widget.ViewPager;
import com.fragmentcommunicationexample.ui.main.ViewPagerAdapter;
import com.google.android.material.tabs.TabLayout;

public class MainActivity extends AppCompatActivity {

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    ViewPagerAdapter viewPagerAdapter =
        new ViewPagerAdapter(this, getSupportFragmentManager());
    ViewPager viewPager = findViewById(R.id.view_pager);
    viewPager.setAdapter(viewPagerAdapter);
    TabLayout tabs = findViewById(R.id.tabs);
    tabs.setupWithViewPager(viewPager);
  }
}

Now run the application and put the input in TextInputLayout. Now swipe view you see all changes appear in the second fragment.

Conclusion

In this post, we learned how to communicate between Fragments using shared ViewModel in our application. I hope you enjoyed this article. Help me by sharing this post with all your friends who learning android app development. Suggestion – checkout our LiveData and for background job using coroutines

Deep drive on fragment commucation Four ways to communicate between fragments in android

5 Comments

  1. Ariel Barreiros Reply

    a very good post, I think fragment communication using viewmodels is a good way to pass data without implementing interfaces, the example itself is general enough to use in several other applications, it worked for me, thanks a lot

  2. Hey
    pageViewModel.getName().observe(requireActivity(), new Observer()
    i think requireActivity() should be replaced on viewLifecycleOwner
    otherwise i am getting errors when manipulating with fragments

Write A Comment