Pass data to previous composable in Android Compose

Viewed 3247

I will take a simple sample.

I have 2 Screens: Screen A and Screen B. From Screen A, I open Screen B. And when I return Screen B to Screen A, I want to transfer data back to Screen A.

With Android Fragment, I can use Shared ViewModel or Fragment Result API to do this. But with Android Compose, the Fragment Result Api is not in Compose. With using Shard ViewModel, what lifecycle do I have to attach Shared ViewModel so it can keep alive? Activity, ... or something else.

Or is there another way to do this?

3 Answers

If you use jetpack navigation, you can pass back data by adding it to the previous back stack entry's savedStateHandle. (Documentation)

Screen B passes data back:

composable("B") {
  ComposableB(
    popBackStack = { data ->
      // Pass data back to A
      navController.previousBackStackEntry
        ?.savedStateHandle
        ?.set("key", data)

      navController.popBackStack()
    }
  )
}

Screen A Receives data:

composable("A") { backStackEntry ->
  // get data passed back from B
  val data: T by backStackEntry
    .savedStateHandle
    .getLiveData<T>("key")
    .observeAsState()

  ComposableA(
    data = data,
    navToB = {
      // optional: clear data so LiveData emits
      // even if same value is passed again
      backStackEntry.savedStateHandle.remove("key")
      // navigate ...
    }
  )
}

Replace "key" with a unique string, T with the type of your data and data with your data.

All of your compose composition operations happens within a single activity view hierarchy thus your ViewModel lifecycle will inevitably be bound to that root activity. It can actually be accessed from your composition through LocalLifecycleOwner.current.

Keep in mind that Compose is a totally different paradigm than activity/fragment, you can indeed share ViewModel across composables but for the sake of keeping those simple you can also just "share" data simply by passing states using mutable values and triggering recomposition.

class MySharedViewModel(...) : ViewModel() {
    var sharedState by mutableStateOf<Boolean>(...)
}

@Composable
fun MySharedViewModel(viewModel: MySharedViewModel = viewModel()) {
    // guessing you already have your own screen display logic
    // This also works with compose-navigator

    ComposableA(stateResult = viewModel.sharedState)
    ComposableB(onUpdate = { viewModel.sharedState = false })
}

fun ComposableA(stateResult: Boolean) {
   ....
}

fun ComposableB(onUpdate: () -> Unit) {
    Button(onClick = { onUpdate() }) {
        Text("Update ComposableA result")
    }
}

Here you'll find further documentation on managing states with compose

Let's say there are two screens.

1 - FirstScreen it will receive some data and residing on bottom in back stack user will land here from Second screen by press back button.

2 - SecondScreen it will send/attach some data to be received on previous first screen.

Lets start from second screen sending data, for that you can do something like this:

                    navController.previousBackStackEntry
                        ?.savedStateHandle
                        ?.set("key", viewModel.getFilterSelection().toString())

                    navController.popBackStack()

Now lets catch that data on first screen for that you can do some thing like this:

if (navController.currentBackStackEntry!!.savedStateHandle.contains("key")) {
        val keyData =
            navController.currentBackStackEntry!!.savedStateHandle.get<String>(
                "key"
            ) ?: ""
    }

Worked perfectly for me.

Related