Android Development
Fragment Communication
Data Passing
Container Activity
Mobile App Development

Passing data between a fragment and its container activity

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Fragments are reusable UI components that live inside an Activity. Because they are designed to be modular, they should not reference their host Activity directly. Instead, Android provides several well-defined patterns for sending data back and forth between a Fragment and its container Activity. Choosing the right pattern depends on the complexity of the data and the architecture of your app.

Bundle Arguments

The simplest way to pass data into a Fragment is through a Bundle attached as arguments. This is the standard pattern for initial configuration data.

kotlin
1// In the Activity — create the Fragment with arguments
2val fragment = DetailFragment().apply {
3    arguments = Bundle().apply {
4        putString("ITEM_ID", "abc-123")
5        putInt("ITEM_COUNT", 5)
6    }
7}
8
9supportFragmentManager.beginTransaction()
10    .replace(R.id.container, fragment)
11    .commit()
kotlin
1// In the Fragment — read the arguments
2class DetailFragment : Fragment() {
3    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
4        super.onViewCreated(view, savedInstanceState)
5        val itemId = arguments?.getString("ITEM_ID")
6        val count = arguments?.getInt("ITEM_COUNT", 0)
7    }
8}

Bundle arguments survive configuration changes (such as rotation) because the system saves and restores them automatically. Use this approach for simple, serializable values that the Fragment needs at creation time.

Shared ViewModel

A ViewModel scoped to the Activity can be observed by both the Activity and any of its Fragments. This is the recommended approach for sharing dynamic, observable data.

kotlin
1// Shared ViewModel
2class SharedViewModel : ViewModel() {
3    private val _selectedItem = MutableLiveData<String>()
4    val selectedItem: LiveData<String> = _selectedItem
5
6    fun selectItem(itemId: String) {
7        _selectedItem.value = itemId
8    }
9}
kotlin
1// In the Fragment
2class ListFragment : Fragment() {
3    private val viewModel: SharedViewModel by activityViewModels()
4
5    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
6        super.onViewCreated(view, savedInstanceState)
7        // Send data to the Activity (or any other observer)
8        viewModel.selectItem("abc-123")
9    }
10}
kotlin
1// In the Activity
2class MainActivity : AppCompatActivity() {
3    private val viewModel: SharedViewModel by viewModels()
4
5    override fun onCreate(savedInstanceState: Bundle?) {
6        super.onCreate(savedInstanceState)
7        viewModel.selectedItem.observe(this) { itemId ->
8            // React to the data the Fragment sent
9            loadDetails(itemId)
10        }
11    }
12}

Because the ViewModel is scoped to the Activity, any Fragment that calls activityViewModels() gets the same instance. The data survives configuration changes and the Fragment never holds a direct reference to the Activity.

Interface Callbacks

Before ViewModels existed, the standard pattern was to define an interface in the Fragment and have the Activity implement it. You may still encounter this in older codebases.

kotlin
1class ListFragment : Fragment() {
2    interface OnItemSelectedListener {
3        fun onItemSelected(itemId: String)
4    }
5
6    private var listener: OnItemSelectedListener? = null
7
8    override fun onAttach(context: Context) {
9        super.onAttach(context)
10        listener = context as? OnItemSelectedListener
11            ?: throw ClassCastException("$context must implement OnItemSelectedListener")
12    }
13
14    private fun handleClick(itemId: String) {
15        listener?.onItemSelected(itemId)
16    }
17
18    override fun onDetach() {
19        super.onDetach()
20        listener = null
21    }
22}
kotlin
1class MainActivity : AppCompatActivity(), ListFragment.OnItemSelectedListener {
2    override fun onItemSelected(itemId: String) {
3        // Handle the data from the Fragment
4    }
5}

This pattern works, but it tightly couples the Fragment to any Activity that hosts it and requires manual null-clearing in onDetach to avoid memory leaks.

Fragment Result API

The Fragment Result API was introduced in AndroidX Fragment 1.3.0 as a type-safe, lifecycle-aware replacement for interface callbacks. The Fragment sends a result through the FragmentManager, and the listener receives it only when the host is in the STARTED state or higher.

kotlin
1// In the Fragment — send a result
2parentFragmentManager.setFragmentResult(
3    "requestKey",
4    bundleOf("selectedId" to "abc-123")
5)
kotlin
1// In the Activity — listen for the result
2supportFragmentManager.setFragmentResultListener(
3    "requestKey",
4    this  // LifecycleOwner
5) { _, bundle ->
6    val selectedId = bundle.getString("selectedId")
7    // use the data
8}

The key advantage is that neither side needs to know about the other. The Fragment only needs the request key string, and the Activity registers a listener for that same key.

If you use the Jetpack Navigation component, Safe Args generates type-safe classes for passing data between destinations. This eliminates raw string keys and catches type mismatches at compile time.

First, add the Safe Args plugin:

groovy
// build.gradle (project)
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:2.7.7"

Define arguments in your navigation graph XML:

xml
1<fragment
2    android:id="@+id/detailFragment"
3    android:name="com.example.DetailFragment">
4    <argument
5        android:name="itemId"
6        app:argType="string" />
7</fragment>

Navigate with type-safe arguments:

kotlin
// In the sending Fragment or Activity
val action = ListFragmentDirections.actionListToDetail(itemId = "abc-123")
findNavController().navigate(action)
kotlin
1// In the receiving Fragment
2class DetailFragment : Fragment() {
3    private val args: DetailFragmentArgs by navArgs()
4
5    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
6        super.onViewCreated(view, savedInstanceState)
7        val itemId = args.itemId
8    }
9}

Safe Args is the best option when your navigation is already managed by the Navigation component because it provides compile-time safety and eliminates boilerplate Bundle handling.

Common Pitfalls

  • Calling getActivity() and casting it directly without a null check, which causes a crash if the Fragment is detached.
  • Forgetting to set the Fragment's arguments before the transaction commits, so getArguments() returns null inside onViewCreated.
  • Using a Fragment-scoped ViewModel (by viewModels()) instead of an Activity-scoped one (by activityViewModels()), which creates a separate ViewModel instance that the Activity cannot observe.
  • Not clearing the interface callback reference in onDetach, leading to a memory leak that keeps the old Activity alive after a configuration change.
  • Using raw string keys for Bundle extras across multiple files without constants, which leads to silent key mismatches that are hard to debug.

Summary

  • Use Bundle arguments for simple, one-time data that a Fragment needs at creation.
  • Use a shared ViewModel scoped to the Activity for dynamic, observable data that survives configuration changes.
  • Interface callbacks work but create tight coupling; prefer the Fragment Result API for decoupled communication.
  • The Fragment Result API is lifecycle-aware and does not require the Fragment to know anything about its host.
  • Navigation Safe Args provide compile-time type safety and are the best choice when you already use the Jetpack Navigation component.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.