How to Pass Data Between Fragments in Android: Safe Args, ViewModel, Bundles

How to Pass Data Between Fragments in Android: Safe Args, ViewModel, Bundles


Learning how to pass data between fragments in Android is a core skill for app development learners. In modern Android apps, you’ll almost always use the Navigation Component, the Fragment Result API, or a shared ViewModel to send data from one fragment to another. In this beginner-friendly guide, we’ll explain what each option is, when to use it, and how to implement it step by step in Kotlin (with brief Java examples where helpful). By the end, you’ll know the easiest and safest ways to pass strings, IDs, and even small custom objects between fragments—without falling into common pitfalls.

Quick answer: Which method should you use?

how to pass data between fragments in android with Bundle arguments and the Navigation graph
Passing arguments from one fragment to another with Bundle and the Navigation component.
  • Safe Args (Navigation Component): Best for forward navigation (e.g., ListFragment → DetailFragment). Type-safe, compile-time checked arguments and easy access with navArgs().
  • Shared ViewModel: Best for ongoing shared UI state across multiple fragments (e.g., a selection that multiple screens display). Scope it to the activity or navigation graph.
  • Fragment Result API: Best for one-time return values (e.g., EditFragment returns a result to the previous fragment). Decoupled and lifecycle-aware.

Method Primary use case Direction How to send How to receive Watch-outs
Safe Args (Navigation) Forward navigation between destinations Source → Destination findNavController().navigate(Directions) by navArgs() or SavedStateHandle in ViewModel Requires nav graph & plugin; keep args small; prefer Parcelable
Shared ViewModel Ongoing shared UI state across fragments Bidirectional (read/write in both) Update state (e.g., sharedVM.select(id)) Observe LiveData/Flow in each fragment Scope correctly (activityViewModels/navGraphViewModels); not for one-off returns
Fragment Result API One-time result back to previous/parent Child → Parent/Previous setFragmentResult(key, bundle) setFragmentResultListener(key) Keys must match; payloads small; not for long-lived state
Tip: For beginners asking “how to pass data between fragments in Android,” start with Safe Args for forward navigation and add a shared ViewModel or Fragment Result API as needed.

These are the recommended approaches in 2026. Avoid deprecated patterns like setTargetFragment and don’t pass large objects through Bundles.

Prerequisites and setup

how to pass data between fragments in android using a shared ViewModel and LiveData
Sync fragment state via a shared ViewModel observed with LiveData.

Before you start, add the Navigation and Lifecycle dependencies and enable Safe Args and Parcelize in your project. Use the latest stable versions in your Gradle files.

Module-level build.gradle.kts

Kotlin
plugins {
id("com.android.application")
kotlin("android")
id("androidx.navigation.safeargs.kotlin") // Safe Args
kotlin("plugin.parcelize") // For @Parcelize
}

dependencies {
implementation("androidx.fragment:fragment-ktx:latest.release")
implementation("androidx.navigation:navigation-fragment-ktx:latest.release")
implementation("androidx.navigation:navigation-ui-ktx:latest.release")
implementation("androidx.lifecycle:lifecycle-viewmodel-ktx:latest.release")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:latest.release")
}

Create a navigation graph resource (e.g., res/navigation/nav_graph.xml) and host it in your activity with a NavHostFragment.

Practical checklist: how to pass data between fragments in Android
  • Use Safe Args for forward navigation; define <argument> in nav_graph.xml.
  • Prefer passing IDs/URIs; for small objects use @Parcelize (Parcelable).
  • Keep Bundles small to avoid TransactionTooLargeException.
  • Scope a shared ViewModel with activityViewModels() or navGraphViewModels(graphId).
  • Collect flows with repeatOnLifecycle in fragments to be lifecycle-safe.
  • Read nav args inside a ViewModel via SavedStateHandle for process-death resilience.
  • For returning a value, use the Fragment Result API (setFragmentResult + setFragmentResultListener).
  • Test process death: enable “Don’t keep activities” in Developer options and verify state restoration.
  • Keep Navigation/Fragment libraries updated; align KTX versions.

Method 1: Pass data with Navigation Component Safe Args

Safe Args is the most beginner-friendly and robust way to send data from one fragment to another. It generates type-safe classes for your arguments, catching mistakes at compile time and making your code easier to read.

Step 1: Define your fragments and arguments in nav_graph.xml

XML
Code
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/app_nav"
    app:startDestination="@id/listFragment">

    <fragment
        android:id="@+id/listFragment"
        android:name="com.example.ListFragment"
        android:label="List">
        <action
            android:id="@+id/action_listFragment_to_detailFragment"
            app:destination="@id/detailFragment" />
    </fragment>

    <fragment
        android:id="@+id/detailFragment"
        android:name="com.example.DetailFragment"
        android:label="Detail">

        <argument
            android:name="userId"
            app:argType="integer"
            android:defaultValue="0" />

        <argument
            android:name="userName"
            app:argType="string"
            android:nullable="true" />
    </fragment>

</navigation>

Step 2: Send data from one fragment to another

Inside ListFragment, navigate to DetailFragment with the generated directions class. This is the “send data from one fragment to another” part.

Kotlin
Code
class ListFragment : Fragment(R.layout.fragment_list) {

    class="cd-keyword cd-access">private fun onUserClicked(id: Int, name: String?) {
        val action = ListFragmentDirections
            .actionListFragmentToDetailFragment(userId = id, userName = name)
        findNavController().navigate(action)
    }
}

Step 3: Receive arguments with navArgs()

In DetailFragment, use by navArgs() to access your arguments. This is typed and null-safe.

Kotlin
Code
class DetailFragment : Fragment(R.layout.fragment_detail) {
    class="cd-keyword cd-access">private val args: DetailFragmentArgs by navArgs()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val id = args.userId
        val name = args.userName
        // Use id and name to load/display data
    }
}

Bonus: Read args from a ViewModel with SavedStateHandle

To survive process death and keep logic testable, read navigation args inside a ViewModel using SavedStateHandle. Safe Args generates helpers like DetailFragmentArgs.fromSavedStateHandle(handle).

Kotlin
Code
class DetailViewModel(
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    class="cd-keyword cd-access">private val navArgs = DetailFragmentArgs.fromSavedStateHandle(savedStateHandle)
    val userId: Int = navArgs.userId
    val userName: String? = navArgs.userName

    // Expose LiveData/Flow for UI...
}

Scope the ViewModel to the fragment or navigation graph:

Kotlin
Code
class DetailFragment : Fragment(R.layout.fragment_detail) {

    // If you want state shared across a nav graph, use navGraphViewModels
    class="cd-keyword cd-access">private val viewModel: DetailViewModel by navGraphViewModels(R.id.app_nav)

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        // Observe viewModel and update UI
    }
}

Java snippet: pass data between fragments (Safe Args)

If you’re learning Java, here’s the equivalent “pass data between fragments android java example.”

Java
Code
class="cd-keyword cd-access">public class ListFragment extends Fragment {

    void onUserClicked(int id, class="cd-annotation">@Nullable String name) {
        ListFragmentDirections.ActionListFragmentToDetailFragment action =
            ListFragmentDirections.actionListFragmentToDetailFragment(id, name);
        NavHostFragment.findNavController(this).navigate(action);
    }
}

class="cd-keyword cd-access">public class DetailFragment extends Fragment {

    class="cd-annotation">@Override class="cd-keyword cd-access">public void onViewCreated(class="cd-annotation">@NonNull View view, class="cd-annotation">@Nullable Bundle savedInstanceState) {
        DetailFragmentArgs args = DetailFragmentArgs.fromBundle(getArguments());
        int id = args.getUserId();
        String name = args.getUserName();
    }
}

Method 2: Share data between fragments with a shared ViewModel

Use a shared ViewModel when you want multiple fragments to observe and update the same piece of UI state (e.g., a selected item that various screens react to). This avoids bundling data repeatedly and integrates well with flows and lifecycle.

Create a SharedViewModel

Kotlin
Code
class SharedViewModel(
    class="cd-keyword cd-access">private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    // Backed by SavedStateHandle for process death resilience
    class="cd-keyword cd-access">private val _selectedId = MutableStateFlow(savedStateHandle.get<Int>("selectedId") ?: -1)
    val selectedId: StateFlow<Int> = _selectedId.asStateFlow()

    fun select(id: Int) {
        _selectedId.value = id
        savedStateHandle["selectedId"] = id
    }
}

Get the shared ViewModel in both fragments

Scope it to the Activity (or to a navigation graph) so both fragments receive the same instance.

Kotlin
Code
// Option A: Activity-scoped
class ListFragment : Fragment(R.layout.fragment_list) {
    class="cd-keyword cd-access">private val sharedVM: SharedViewModel by activityViewModels()

    class="cd-keyword cd-access">private fun onUserClicked(id: Int) {
        sharedVM.select(id)
        findNavController().navigate(R.id.action_listFragment_to_detailFragment)
    }
}

class DetailFragment : Fragment(R.layout.fragment_detail) {
    class="cd-keyword cd-access">private val sharedVM: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                sharedVM.selectedId.collect { id ->
                    // Load user by id and update UI
                }
            }
        }
    }
}
Kotlin
Code
// Option B: Nav-graph-scoped (only fragments inside this graph share it)
class ListFragment : Fragment(R.layout.fragment_list) {
    class="cd-keyword cd-access">private val sharedVM: SharedViewModel by navGraphViewModels(R.id.app_nav)
    // ...
}

Compose tip

If your fragments host Jetpack Compose UIs, collect ViewModel state in Compose:

Kotlin
@Composable
fun DetailScreen(sharedVM: SharedViewModel) {
val id by sharedVM.selectedId.collectAsState()
// Draw UI based on id
}

This approach is ideal to “share data between fragments using a shared ViewModel” without passing bundles repeatedly.

Method 3: Return a one-time result with the Fragment Result API

Use the Fragment Result API when a child fragment needs to return a result to a parent or previous fragment—similar to an activity result, but entirely within a FragmentManager. Results are queued until a listener reaches STARTED state, so you won’t miss them on configuration changes.

Listen for a result

Kotlin
Code
class ParentFragment : Fragment(R.layout.fragment_parent) {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        parentFragmentManager.setFragmentResultListener(
            "edit_result_key",
            this
        ) { _, bundle ->
            val updatedName = bundle.getString("updated_name")
            // Handle one-time result
        }
    }

    class="cd-keyword cd-access">private fun openEditor() {
        findNavController().navigate(R.id.action_parent_to_editor)
    }
}

Send the result from the child fragment

Kotlin
Code
class EditorFragment : Fragment(R.layout.fragment_editor) {

    class="cd-keyword cd-access">private fun onSaveClicked(updatedName: String) {
        parentFragmentManager.setFragmentResult(
            "edit_result_key",
            bundleOf("updated_name" to updatedName)
        )
        findNavController().popBackStack()
    }
}

This is the recommended alternative to the old, deprecated setTargetFragment pattern.

Passing custom objects: @Parcelize and best practices

You can pass small custom objects by marking them Parcelable. In Kotlin, use @Parcelize to avoid boilerplate. However, keep arguments small—pass IDs or URIs when possible and load data in the target fragment via a repository or ViewModel.

Kotlin
Code
class="cd-annotation">@Parcelize
data class UserPreview(
    val id: Int,
    val displayName: String
) : Parcelable

Declare the argument in your nav_graph.xml with the fully qualified type:

XML
<argument
android:name="userPreview"
app:argType="com.example.model.UserPreview"
android:nullable="false" />

Then navigate:

Kotlin
val preview = UserPreview(id = 42, displayName = "Alex")
val action = ListFragmentDirections.actionListFragmentToDetailFragment2(userPreview = preview)
findNavController().navigate(action)

And receive it:

Kotlin
val args: DetailFragment2Args by navArgs()
val preview = args.userPreview

Important notes:

  • Avoid Serializable for performance; prefer Parcelable/@Parcelize.
  • Don’t pass large objects or bitmaps in Bundles; you risk TransactionTooLargeException.
  • Prefer IDs/URIs, then load the full data in the destination fragment or ViewModel.

Visual flow: Safe Args → Shared ViewModel → Fragment Result API
ListFragment
onUserClicked(id, name)
Navigate with Safe Args
navigate(Directions)
DetailFragment
args via navArgs() + ViewModel
Open Editor
navigate(editor)
EditorFragment
setFragmentResult(key, bundle)
Pop back
popBackStack()
DetailFragment
listener receives result

Blue boxes are fragments; gray boxes are actions. This end-to-end path shows how to pass data between fragments in Android using modern APIs.

Putting it all together: A beginner flow

Here’s a simple beginner tutorial to pass data from one fragment to another using Safe Args, share selection with a ViewModel, and return an edit result:

  1. ListFragment displays a list. On click, it both:
    • Updates SharedViewModel.select(id) so other screens know which user is selected.
    • Navigates to DetailFragment and passes userId (and optionally userName) via Safe Args.
  2. DetailFragment reads args with navArgs(), and ViewModel reads the same values via SavedStateHandle.
  3. EditorFragment lets the user change the name, then returns the result to DetailFragment using the Fragment Result API.

ListFragment (send args + update shared state)

Kotlin
Code
class ListFragment : Fragment(R.layout.fragment_list) {
    class="cd-keyword cd-access">private val sharedVM: SharedViewModel by activityViewModels()

    class="cd-keyword cd-access">private fun onUserClicked(id: Int, name: String?) {
        sharedVM.select(id)
        val action = ListFragmentDirections.actionListFragmentToDetailFragment(id, name)
        findNavController().navigate(action)
    }
}

DetailFragment (receive args, observe shared state, open editor)

Kotlin
Code
class DetailFragment : Fragment(R.layout.fragment_detail) {
    class="cd-keyword cd-access">private val args: DetailFragmentArgs by navArgs()
    class="cd-keyword cd-access">private val sharedVM: SharedViewModel by activityViewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        parentFragmentManager.setFragmentResultListener(
            "edit_result_key",
            this
        ) { _, bundle ->
            val updatedName = bundle.getString("updated_name")
            // Update UI or ViewModel with the new name
        }
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        val id = args.userId
        val name = args.userName
        // Load data with id, show name if available

        viewLifecycleOwner.lifecycleScope.launch {
            viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
                sharedVM.selectedId.collect { selectedId ->
                    // React if needed
                }
            }
        }

        view.findViewById<View>(R.id.editButton).setOnClickListener {
            findNavController().navigate(R.id.action_detail_to_editor)
        }
    }
}

EditorFragment (return one-time result)

Kotlin
Code
class EditorFragment : Fragment(R.layout.fragment_editor) {

    class="cd-keyword cd-access">private fun onSave(updatedName: String) {
        parentFragmentManager.setFragmentResult(
            "edit_result_key",
            bundleOf("updated_name" to updatedName)
        )
        findNavController().popBackStack()
    }
}

Common pitfalls and best practices

  • Choose the right channel: Safe Args for forward navigation; Fragment Result API for a one-time return; Shared ViewModel for ongoing shared state.
  • Keep Bundles small: Prefer IDs/URIs; load full data in the destination. Avoid bitmaps and large payloads.
  • Prefer @Parcelize over Serializable for custom objects, and use it sparingly.
  • Use SavedStateHandle in ViewModels for reading nav args and surviving process death.
  • Scope ViewModels correctly: activityViewModels() for app-wide sharing; navGraphViewModels() for graph-local sharing.
  • Avoid deprecated approaches: Don’t use setTargetFragment or activity-style onActivityResult for fragment-to-fragment communication.
  • Stay up to date: Navigation and Fragment libraries continue to improve type-safety and SavedState integration; keep your dependencies current.

Output / Result

After following this guide, you will be able to:

  • Navigate from ListFragment to DetailFragment while passing a userId and optional userName using Safe Args.
  • Share a selected user ID across multiple fragments using a shared ViewModel, surviving configuration and process death via SavedStateHandle.
  • Open an EditorFragment and return a one-time updated name back to DetailFragment using the Fragment Result API.

FAQ

What is the easiest way to pass data between fragments in Android?

For forward navigation, the easiest and safest method is the Navigation Component with Safe Args. It gives compile-time checked, type-safe arguments and simple accessors via navArgs().

How do I use a Bundle to send data from one fragment to another?

You can still use a Bundle with setArguments() and getArguments(), but Safe Args generates Bundle code for you in a type-safe way. Prefer Safe Args to avoid key typos and type mismatches.

How can I pass data between fragments using a shared ViewModel?

Scope a ViewModel to the activity or a navigation graph and expose state via LiveData or StateFlow. Both fragments obtain the same ViewModel instance and read/write shared state without Bundles.

What is Safe Args and how do I use it to pass arguments in the Navigation Component?

Safe Args is a Gradle plugin that generates classes for navigation directions and arguments. Define <argument> tags in your nav_graph.xml, then call the generated Directions action with parameters and read them in the destination using navArgs() or fromSavedStateHandle() in a ViewModel.

Can I pass custom objects between fragments, and what is the best practice?

Yes, but keep them small and Parcelable (use @Parcelize). Best practice is to pass IDs or URIs and load the full object in the destination via a repository/ViewModel to avoid large Bundles.

Sources / Further reading

Wrap-up

To recap, if you’re wondering how to pass data between fragments in Android today, start with Safe Args for navigation, a shared ViewModel for ongoing state, and the Fragment Result API for one-time callbacks. Keep arguments small, prefer @Parcelize for lightweight custom types, and use SavedStateHandle for process-death resilience. With these modern patterns, your code will be safer, clearer, and easier to maintain.

Subscribe
Notify of
guest
0 Comments
Oldest
Newest Most Voted