I'm working with Jetpack Compose in an Android app and had the problem that my uiState (LiveData) was set to its initial value on every recomposition, since I've initialized it like
val authUiState: AuthUIState by authenticationViewModel.uiState.observeAsState(AuthUIState.Loading)
It was set to Loading on every recomposition before it was set to the correct value.
When I tried to Remember the value, I learned that we can't use observeAsState within the remember block and finally changed it to
val authUiState = remember{ mutableStateOf(authenticationViewModel.uiState.value) }.value
This works, but I'm not quite sure, if this is the common and good way to solve this.
What do you think? Should I do it differently? Do you need more information?
See if the uiState inside your viewmodel is something like a LiveData Object, (which is kinda what it seems like from the code), the recommended way is to store it in the viewmodel itself as mutable state.
var uiState by mutableStateOf (initialValue)
private set //Do not allow external modifications to maintain consistency of state
fun onUiStateChange(newValue: Any){
uiState = newValue
}
You just need to initialise it as a MutableState, in the rest of the code, to update, delete or whatever you want to do with it, just treat it as a regular variable. Compose will trigger recomposition every time the value is updated.
The following code snippet below will almost certainly not work, because here, the state is whatever you wrap inside mutableStateOf(), which is just a simple value which will be fetched once from the viewmodel and then remembered throughout recompositions, so no code change will be triggered here
val authUiState by remember{ mutableStateOf(authenticationViewModel.uiState.value) }
Storing state in the viewmodel as mutableState, is as far as my knowledge extends, the best practice in compose. You will see the same in the 'State Codelab' from Android developers
Good luck
Related
I learned that Compose remembers a state in a way such as:
var text by remember { mutableStateOf("") }
So in this case, it remembers a MutableState of a String. My question is why it wants to remember a thing called "MutableState" rather than just the String itself, and why it requires an extra layer?
I know its late, but here's what I understand with remember.
I have a simple Todo app where a list of todos are hoisted in a viewwmodelusing a SnapshotStatelist, this list is rendered by a LazyColumn where each todo model has its own remembered state where I do some pretty basic UI functionality (e.g card elevation, visibility of some icons). Any changes I make to a todo should propagate back up to the mutableStateList (e.g deleting a todo), SnapshotStateList will then notify the LazyColumn to perform a recomposition, however when I edit a Todo (e.g, modifying the title), I also have to update the item composable that holds this todo(some UI changes), then I got stuck up as I can't figure out why the item composable is not recomposing even if I was able to verify that the SnapShotStateList item is modified by using the code below
val todoList = viewModel.todos
val snapshot = Snapshot.takeMutableSnapshot()
snapshot.enter {
for (todo in todoList) {
Log.e("TodoModel", todo.title)
}
}
Code that modifies the list
val index = todos.indexOfFirst { it.id == modifiedModel.id }
todos[index] = todos[index].copy(//..some properties to be copied)
I verified that any modification I make on a todo reflects back to its host list, but the item composable that renders a todo item doesn't trigger a re-composition. I kept reading some posts and carefully thinking object references and trying to understand my code based on this object reference thought, I thought that there must be something that holds the previous state of the item and the changes is not applied to the remember, until I found that you can supply a key to a remember where it will be the thing that will decide if remember needs to re-calculate. Now I found out that remember only remembers a state (by state I dont mean compose state, but state in general) on initial composition, it will keep that initial structure/state for as long as the entire composable it is part of is still running, in this case all the way up to the parent composable (i.e my DashboardScreen), what made my remember re-calculate is I supplied the key with the todo object itself
val itemState: ItemCardState = remember(key1 = todoModel) {
ItemCardState(todoModel = todoModel)
}
This way, when a change happens to the SnapShotStateList, an item's remember will see the same object reference (data class) but with changes applied to it. remember caches the initial state and will hold it forever unless you supply a key that you think might change and will let remember re-calculate a new initial state to be remembered, in my case I supplied the key as the todo object itself and when remember sees the same object reference but with a different value, it will re-calculate.
Having this understanding now, I can't imagine a way when there is no layer that will hold an object (remember) and prevent unnecessary re-composition when the state of an object changes.
Just sharing what I learned, also open for discussion that I may have said in a wrongful way.
remember is used for storing objects to have it when a recomposition happens. Mutable state is used for triggering recomopsition, you can check this answer for more details.
by is delegation that is a feature of Kotlin which translates the code
var text = remember { mutableStateOf("") }
text.value = "newString"
you basically store a trigger and value inside a remember. when you change MutableState.value new recomposition occurs and in this new recomposition you get the latest value of MutableState.
There are also usecases of remember without needing MutableState like a Paint or custom object when something else triggers the recomposition like canvas touch position for instance.
you remember object since you won't instantiate it.
val paint = remember {Paint()}
var offset by remember {mutableStateOf(Offset.Zero)
then when offset changes with user touching screen you trigger recomposition but since and you don't need to instantiate Paint object again.
remember only and remember with MutableState has different usecases.
Mutable state is needed for two reasons:
Saving mutable state between recompositions. remember is gonna save result of lambda calculation, but if you change a variable later - remember cannot save and track it. The solution is to have a state holder - an object that's created by mutableStateOf, saved by remember, will always be the same, but you can mutate it properties, in this case value (which is hidden when you're using delegation with by).
Triggering recomposition. If you just create a class, save it with remember and update a property, Compose won't know that it was changed, and that view update it needed - that's why a special Compose State was created, which notifies a view that it needs to be recomposed.
You can continue deepening your knowledge with state in Compose documentation and Thinking in Compose.
In a ViewModel, remember isn't used, but mutable...is used:
class CustomViewModel : ViewModel() {
// ...
var myDeckList = mutableStateListOf<Deck>()
// ...
}
Does ViewModel have a delegated responsibility similar to what remember provides?
If so, why is mutable... not delegated?
remember is used to preserve state across recompositions. If we are storing state inside ViewModel, it will automatically survive recompositions because it's outside the composition tree.
mutableStateOf serves a different purpose. It creates a MutableState which is:
A mutable value holder where reads to the [value] property during the execution of a [Composable] function, the current [RecomposeScope] will be subscribed to changes of that value. When the [value] property is written to and changed, a recomposition of any subscribed [RecomposeScope]s will be scheduled.
It sets up an observer pattern (like a LiveData, StateFlow, etc.) where writes to the value inform the readers about the value change. So ViewModel has nothing to do with this observer pattern and that's why you still need to use mutable... functions in your ViewModel.
The mutableStateListOf that you have used in you question works along the same lines. It creates a SnapshotStateList which is a type of MutableList that is observable and can be snapshot.
Try using a remember{} function inside a viewModel.
It's just simply not possible since remember{} functions can be only called inside composable contexts
I would like to show a snackbar in the MainActivity (root composable) from any child #Composable.
My first thought was to provide the SnackbarHostState using CompositionLocalProvider but that doesn't seem to work (or I'm doing it incorrectly).
val mainSnackBarHostState = remember { SnackbarHostState() }
val SnackBarHostStateProvider = compositionLocalOf<SnackbarHostState> { mainSnackBarHostState }
CompositionLocalProvider(SnackBarHostStateProvider provides mainSnackBarHostState) {
MainScreenNavigationConfigurations(navController)
}
My child #Composable can't seem to find/access SnackBarHostStateProvider.
Any thoughts?
The best way, I'd say, is to store the state of the snackbar (visible/invisible) in your viewmodel, and let the snackbar read from there. Whenever and wherever from you want to toggle the state, just change the value in the viewmodel, and that should do it
If you are unfamiliar with viewmodel, it is the recommended and standard way to build apps, and remember, in Compose, the recommended way is to store state in the viewmodel not as regular variables, but as stateholders.
For instance, in your use case, you can store the visibility status of your snackbar as mutableStateOf(false), for am initial visibility value of false.
This assumes that you have access to your viewmodel from all over your app, which usually developers do, wherever they need to update state, so, best of luck
Probably you need to declare the variable SnackBarHostStateProvider as a package-level variable, to be able to access it from the provider and the children.
See also this related answer: https://stackoverflow.com/a/69905470/293878
Say that, I'm building a custom compose layout and populating that list as below
val list = remember { dataList.toMutableStateList()}
MyCustomLayout{
list.forEach { item ->
key(item){
listItemCompose( data = item,
onChange = { index1,index2 -> Collections.swap(list, index1,index2)})
}
}
This code is working fine and the screen gets recomposed whenever onChange lambda function is called, but when it comes to any small change in any item's property, it does not recompose, to elaborate that let's change the above lambda functions to do the following
{index1,index2 -> list[index1].propertyName = true}
Having that lambda changing list item's property won't trigger the screen to recompose. I don't know whether this is a bug in jetpack compose or I'm just following the wrong approach to tackle this issue and I would like to know the right way to do it from Android Developers Team. That's what makes me ask if there is a way to force-recomposing the whole screen.
You can't force a composable function to recompose, this is all handled by the compose framework itself, there are optimizations to determine when something has changed that would invalidate the composable and to trigger a recomposition, of only those elements that are affected by the change.
The problem with your approach is that you are not using immutable classes to represent your state. If your state changes, instead of mutating some deep variable in your state class you should create a new instance of your state class (using Kotin's data class), that way (by virtue of using the equals in the class that gets autogenerated) the composable will be notified of a state change and trigger a recomposition.
Compose works best when you use UDF (Unidirectional Data Flow) and immutable classes to represent the state.
This is no different than, say, using a LiveData<List<Foo>> from the view system and mutating the Foos in the list, the observable for this LiveData would not be notified, you would have to assign a new list to the LiveData object. The same principle applies to compose state.
you can recreate an entire composition using this
val text = remember { mutableStateOf("foo") }
key(text.value) {
YourComposableFun(
onClick = {
text.value = "bar"
}
) {
}
}
call this
currentComposer.composition.recompose()
I am referring to the Plaid open source project on github Link to Plaid
It is an excellent source to learn new techniques on Android.
As I was going through the code, I came across a certain style of coding around LiveData which I really didn't understand. If anybody can help me get it.
Here goes:
There's a ViewModel(vm) with this piece of code:
private val _openLink = MutableLiveData<Event<String>>()
val openLink: LiveData<Event<String>>
get() = _openLink
Fairly simple? Note that, there are 2 variables here: openLink and _openLink. The getter for openLink is returning the _openLink LiveData.
In the activity they observe the openLink LiveData as follows:
viewModel.also { vm ->
vm.openLink.observe(this, EventObserver { openLink(it) })
..... // Other stuff
}
Now, the other livedata _openLink is being called by the UI supposedly on a button click and it's defined like this:
fun viewShotRequested() {
_shotUiModel.value?.let { model -> // ignore this part
_openLink.value = Event(model.url) // setValue on _openLink
}
}
So here my understanding is, on setValue() on _openLink, EventObserver{openLink(it)} will get called.
My question is, why have they done it like this?
Questions:
Why not observe directly on _openLink?
Will it not have the same effect? What am I missing here?
_openLink is mutable. You must always expose something which is immutable and cannot be changed by your observer, because that should only be done by your ViewModel, even though exposing _openLink would have no effect.
That's why you need to expose openLink which is immutable.
private val _openLink = MutableLiveData<Event<String>>()
val openLink: LiveData<Event<String>> = _openLink
The MutableLiveData property shouldn't be exposed: it is mutable and could be changed anywhere across your program.
That's why the LiveData is exposed instead: it is responsible for updating your property, and it uses the MutableLiveData as a backing field.
The exception to this would be two-way DataBinding, where direct access to the value would be needed.
My question is, why have they done it like this?
Because Google apparently enjoys writing more code for the sake of writing more code.
The supposed answer if you're inclined to believe them is that LiveData<Event<T>> is preferred over SingleLiveData because they came up with it later than with LiveData<Event<T>> and is therefore supposedly better.
They intend to use an "enqueueable event-bus that forgets items after it is emitted to at least 1 observer", but Jetpack offers no such concept out of the box, personally I had to write one of my own.
Even so, there's a distinction between whether you can write into something, and whether you can only read from something but not write into it yourself. In this case, only the ViewModel wants to be able to emit events, so it is the ViewModel that holds a Mutable__ reference, but exposes a regular LiveData to the outside world (in my case, EventEmitter vs EventSource).
As for the _, it's a Kotlin style convention that will hopefully be changed one day, as you could either do:
private val _openLink = MutableLiveData<Event<String>>()
val openLink: LiveData<Event<String>>
get() = _openLink
But you could also do
private val openLink = MutableLiveData<Event<String>>()
fun openLink(): LiveData<Event<String>> = openLink
And that way we could ditch the _ prefix in our own code, but for some reason the authors of Kotlin hadn't come up with that convention in time.