Best way to communicate between Fragments

In Android applications, fragments are components used to build flexible and reusable user interfaces. However, when multiple fragments are used in an application, communication between them becomes very important. This article will introduce how to implement communication between fragments and between fragments and host activities in Android applications.

Use ViewModel for communication

ViewModel is an Android architecture component used to share and manage data between activities and fragments. It provides an efficient way to store data in memory so that it remains consistent during configuration changes (such as screen rotation) or switching between fragments.

ViewModels make it easy to share data between multiple fragments, as well as between fragments and their host activities. Here is an example showing how to use ViewModel to share data between fragments and host activities:

// ViewModel
class MainViewModel : ViewModel() {
    private val mutableSelectedItem = MutableLiveData<Item>()
    val selectedItem: LiveData<Item> get() = mutableSelectedItem

    fun selectItem(item: Item) {
        mutableSelectedItem.value = item
    }
}

//Host activity
class MainActivity : AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        viewModel.selectedItem.observe(this, Observer { item ->
            // perform operations
        })
    }
}

// fragment
class MainFragment : Fragment() {
    private val viewModel: MainViewModel by activityViewModels()

    // Called when the item is clicked
    fun onItemClicked(item: Item) {
        // Set up a new project
        viewModel.selectItem(item)
    }
}

In this example, both the host activity and the fragment can share and observe data through the ViewModel. They use by viewModels() and by activityViewModels() to obtain ViewModel instances, ensuring that they use the same ViewModel object.

Share data between fragments

When using multiple fragments in the same activity, communication between the fragments is often required. In order to achieve communication between fragments, you can use the host activity as the scope of the ViewModel so that these fragments share the same ViewModel instance. Here’s an example showing how to communicate between two fragments using a shared ViewModel:

// ViewModel
class MainViewModel : ViewModel() {
    val filters = MutableLiveData<Set<Filter>>()

    // ...other logic...
}

// Fragment A
class ListFragment : Fragment() {
    private val viewModel: MainViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { list ->
            // perform operations
        })
    }
}

// Fragment B
class FilterFragment : Fragment() {
    private val viewModel: MainViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filters.observe(viewLifecycleOwner, Observer { set ->
            //Update selected filter UI
        })
    }

    fun onFilterSelected(filter: Filter) = viewModel.addFilter(filter)
    fun onFilterDeselected(filter: Filter) = viewModel.removeFilter(filter)
}

In this example, both Fragment A and Fragment B use the host activity as the scope of the ViewModel. They achieve data sharing and communication by obtaining the same ViewModel instance.

Share data between parent and child fragments

When using child fragments, the parent fragment and its child fragments may need to share data. In order to share data between parent and child fragments, you can use the parent fragment as the scope of the ViewModel. Here’s an example showing how to share data between parent and child fragments:

// parent fragment
class ListFragment : Fragment() {
    private val viewModel: MainViewModel by viewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleowner, Observer { list ->
            //Update list UI
        })
    }
}

// subfragment
class ChildFragment : Fragment() {
    private val viewModel: MainViewModel by viewModels({ requireParentFragment() })

    // ...other logic...
}

In this example, the child fragment obtains the ViewModel instance of the parent fragment via by viewModels({ requireParentFragment() }). In this way, parent and child fragments can share and observe data in the same ViewModel.

Use navigation graph to scope ViewModel

If you are using the Navigation library, you can also scope the ViewModel within the life cycle of the target NavBackStackEntry. For example, you can scope the ViewModel to the NavBackStackEntry of the ListFragment as follows:

// ListFragment
class ListFragment : Fragment() {
    private val viewModel: ListViewModel by navGraphViewModels(R.id.list_fragment)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewModel.filteredList.observe(viewLifecycleOwner, Observer { item ->
            //Update list UI
        })
    }
}

In this example, the ViewModel is scoped to the ListFragment’s NavBackStackEntry by retrieving the ViewModel from the fragment-ktx library using navGraphViewModels(). This way, the ViewModel’s lifecycle will be consistent with the ListFragment’s navigation lifecycle.

Communicate using Fragment Result API

In Fragment 1.3.0 and later, every FragmentManager implements the FragmentResultOwner interface. This means that the FragmentManager can act as a central store to store fragment results. This change allows components to communicate with each other by setting fragment results and listening for those results, without the components having to reference each other directly.

To pass data back to Fragment A, first set a result listener on Fragment A that receives the result. Just call the setFragmentResultListener() method on the FragmentManager of fragment A.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResultListener("requestKey") { requestKey, bundle ->
        // We use a String here, but any type that can be put in a Bundle is supported.
        val result = bundle.getString("bundleKey")
        // Do something with the result.
    }
}

button.setOnClickListener {
    val result = "result"
    // Use the Kotlin extension in the fragment-ktx artifact.
    setFragmentResult("requestKey", bundleOf("bundleKey" to result))
}

Receive result:

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportFragmentManager
                .setFragmentResultListener("requestKey", this) { requestKey, bundle ->
            // We use a String here, but any type that can be put in a Bundle is supported.
            val result = bundle.getString("bundleKey")
            // Do something with the result.
        }
    }
}

The above is how communication between fragments and between fragments and host activities is implemented in Android applications. Depending on the specific usage scenario, you can choose to use ViewModel for communication or use Fragment Result API for communication. These methods all provide flexible and reliable ways to implement communication between fragments, making the development of Android applications more efficient and maintainable.

Reprinted from: https://mp.weixin.qq.com/s/YBni9BG4JYEnWP0fiBQk7Q