3

I am learning Jetpack compose, and I have been seen so far that lifting the state up to a composable's caller to make a composable stateless is the way to go. I`ve been using this pattern in my Compose apps.

For an app state that I need to modify from many different places of the tree, I will have to pass around a lot of callbacks, This can become difficult to manage.

I have some previous experience with Flutter. The way Flutter deals with providing a state to its descendants in the tree to overcome the above, is to use other mechanisms to manage state, namely Provider + ChangeNotifier.

Basically, with Provider, a Provider Widget is placed in the tree and all the children of the provider will have access to the values exposed by it.

Are there any other mechanisms to manage state in Jetpack Compose apps, besides State hoisting? And, what would you recommend?

lbarqueira
  • 1,101
  • 1
  • 10
  • 28

1 Answers1

3

If you need to share some data between views you can use view models.

@Composable
fun TestScreen() {
    val viewModel = viewModel<SomeViewModel>()
    Column {
        Text("TestScreen text: ${viewModel.state}")
        OtherView()
    }
}

@Composable
fun OtherView() {
    val viewModel = viewModel<SomeViewModel>()
    Text("OtherScreen text: ${viewModel.state}")
}

class SomeViewModel(savedStateHandle: SavedStateHandle): ViewModel() {
    var state by savedStateHandle.saveable { mutableStateOf(UUID.randomUUID().toString()) }
}

The hierarchy topmost viewModel call creates a view model - in my case inside TestScreen. All children that call viewModel of the same class will get the same object. The exception to this is different destinations of Compose Navigation, see how to handle this case in this answer.

You can update a mutable state property of view model, and it will be reflected on all views using that model. Check out more about state in Compose.

The view model lifecycle is bound to the compose navigation route (if there is one) or to Activity / Fragment otherwise, depending on where setContent was called from.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
Phil Dukhov
  • 67,741
  • 15
  • 184
  • 220
  • Taking the TestScreen composable of your example, as soon as you refactor the Column to a composable you have to pass the viewModel object, or its properties/methods as arguments. So as the tree of composables increase, I will have to pass around a lot of callbacks and properties of the viewModel. – lbarqueira Sep 17 '21 at 12:27
  • @lbarqueira as you see I don't pass it into `OtherView`, but `viewModel()` will return the same object, because `OtherView` is child of `TestScreen` – Phil Dukhov Sep 17 '21 at 12:30
  • May I conclude that the reason the Composable State codelab of Google when passing the viewmodel as composable function arguments is to satisfy the unidirectional data flow pattern, that is, state flows down and events flow up? – lbarqueira Sep 21 '21 at 14:53
  • @lbarqueira could you give me a link of the code you're talking about? – Phil Dukhov Sep 21 '21 at 14:55
  • Here it is: https://developer.android.com/codelabs/jetpack-compose-state#3 – lbarqueira Sep 21 '21 at 14:59
  • @lbarqueira I think this is to keep the example simple. If you create a view model inside an activity with `viewModels()`, you can then get the same object in any composable with `viewModel()`(excluding compose navigation). Also, I made a mistake in my answer: `viewModel` will not be destroyed when the view disappears, its lifecycle is related to the `Activity` or `Fragment` calling `setContent` or to the current navigation route. – Phil Dukhov Sep 21 '21 at 15:58