rememberLauncherForActivityResult causes composable to lose state? - android

Let's say I've the following code:
#Composable
fun Widget() {
var text1 by remember { mutableStateOf("DEFAULT") }
val picker = rememberLauncherForActivityResult(
contract = ActivityResultContracts.GetMultipleContents(),
onResult = {
for (uri in it) print(uri)
},
)
Text(
text = text1,
)
Button(
onClick = { text1 = "CHANGED" },
) {
Text(
text = "Change text1",
)
}
Button(
onClick = { picker.launch("image/*") },
) {
Text(
text = "Launch Picker",
)
}
}
When my application is installed and launched for the first time if you change the text1 by pressing on the button labeled 'Change text1' and then press the button to launch the picker the composable state is lost and text1 reverts to "DEFAULT".
What is interesting is this happens only for the first time after installing and launching the app or after restarting the phone and using the app for the first time.
I also like to point out that this happens in both debug and release versions of the app.
So, I would like to know what could be the cause of this? is this a known compose bug? or is it how I instantiate/use the picker?

I think there might be a configuration change happening just the first time, which remember does not handle.
Try replacing remember with rememberSaveable. From docs:
While remember helps you retain state across recompositions, the state is not retained across configuration changes. For this, you must use rememberSaveable. rememberSaveable automatically saves any value that can be saved in a Bundle. For other values, you can pass in a custom saver object.

As the other answer already explains, remember is not enough to keep the object between configuration changes.
If you need to keep a complex object you can use rememberScoped with this library: https://github.com/sebaslogen/resaca
This is an alternative to rememberSaveable so you don't need to implement Parcelable nor Saver interfaces, but it won't keep the object between process deaths.

Related

Activity Launcher(File picker) is loading multiple times in single event - Jetpack compose

I am using a file picker inside a HorizontalPager in jetpack compose. When the corresponding screen is loaded while tapping the button, the launcher is triggered 2 times.
Code snippet
var openFileManager by remember {
mutableStateOf(false)
}
if (openFileManager) {
launcher.launch("*/*")
}
Button(text = "Upload",
onClick = {
openFileManager = true
})
Edited: First of all Ian's point is valid why not just launch it in the onClick directly? I also assumed that maybe you want to do something more with your true false value. If you want nothing but launch then all these are useless.
The screen can draw multiple times when you click and make openFileManager true so using only condition won't prevent it from calling multiple times.
You can wrap your code with LaunchedEffect with openFileManager as a key. The LaunchedEffect block will run only when your openFileManager change.
if (openFileManager) {
LaunchedEffect(openFileManager) {
launcher.launch("*/*")
}
}
You should NEVER store such important state inside a #Composable. Such important business logic is meant to be stored in a more robust holder like the ViewModel.
ViewModel{
var launch by mutableStateOf (false)
private set
fun updateLaunchValue(newValue: Boolean){
launch = newValue
}
}
Pass these to the Composable from the main activity
MyComposable(
launchValue = viewModel.launch
updateLaunchValue = viewModel::updateLaunchValue
)
Create the parameters in the Composable as necessary
#Comoosable
fun Uploader(launchValue: Boolean, onUpdateLaunchValue: (Boolean) -> Unit){
LaunchedEffect (launchValue){
if (launchValue)
launcher.launch(...)
}
Button { // This is onClick
onUpdateLaunchValue(true) // makes the value true in the vm, updating state
}
}
If you think it is overcomplicated, you're in the wrong paradigm. This is the recommended AND CORRECT way of handling state in Compose, or any declarative paradigm, really afaik. This keeps the code clean, while completely separating UI and data layers, allowing controlled interaction between UI and state to achieve just the perfect behaviour for the app.

Android Language change using JetPack Compose

I am trying to change locale of the application using jetpack compose function like below
#Composable
fun SetLanguage(position: Int) {
val locale = Locale(
when (position) {
0 -> "ar"
1 -> "en"
2 -> "fr"
else -> {
"ar"
}
}
)
Locale.setDefault(locale)
val configuration = LocalConfiguration.current
configuration.setLocale(locale)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1)
configuration.setLocale(locale)
else
configuration.locale = locale
var resources = LocalContext.current.resources
resources.updateConfiguration(configuration, resources.displayMetrics)
}
you can check the working example (without buttons or textfield ) here
https://github.com/MakeItEasyDev/Jetpack-Compose-Multi-Language-Support
but the problem that is not working with OutlinedTextField or Buttons as they dont change when this function is called even rightToLeft support is not working and i dont find a good alternative to this solution for my problem as i cant recreate the activity in my project
The problem many developers make when starting out with Compose is believing that when a recomposition occurs, everything within the composable will get recomposed. This isn't true. Compose looks at the composable signature and tries to determine if anything changes from the last time it was called. Only when the parameter values change will the function be called. In the source code you posted on Github, it didn't include a button or outline text field to demonstrate the problem, so I added one. When you add a button like this:
Button(onClick = {}) {
Text("Do Something")
}
the Text composable inside of the Button will only be called when the initial composition occurs. But when the Button is recomposed, the Text will not be recomposed because the last parameter in the Button function hasn't changed. Lambda functions don't change. In regard to your case, changing the language does initiate a recomposition of the button, but because the last parameter does not change, the content inside of the lambda (in this example, the Text composable) will never be called. To get around this, one solution is to make the string resource that is used by the Text composable mutable. Anything that is mutable will automatically cause any composable that uses it to recompose.
The following code is what I took from your Github project and added a button. Notice how the string resource id is made mutable and this mutable state is used inside the Text:
#Composable
fun LanguageContentPortrait(
selectedPosition: Int,
onLanguageSelected: (Int) -> Unit
) {
val buttonTextResId by remember { mutableStateOf(R.string.hello) }
CompositionLocalProvider(
LocalLayoutDirection provides
if (LocalConfiguration.current.layoutDirection == LayoutDirection.Rtl.ordinal)
LayoutDirection.Rtl
else LayoutDirection.Ltr
) {
Column(
modifier = Modifier
.fillMaxSize()
.padding(16.dp),
horizontalAlignment = Alignment.CenterHorizontally
) {
Spacer(modifier = Modifier.height(100.dp))
ToggleGroup(selectedPosition = selectedPosition, onClick = onLanguageSelected)
Spacer(modifier = Modifier.height(60.dp))
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Text(
text = stringResource(id = R.string.content),
modifier = Modifier.fillMaxSize(),
textAlign = TextAlign.Center
)
Button(onClick = {}) {
Text(stringResource(buttonTextResId))
}
}
}
}
}
So anywhere you use trailing lambda expressions including click event handlers and you need language-dependent changes to occur, you will need to add mutable states to those resources inside those lambdas as shown above.
Even though the solution above works, I can't recommend using it. Most apps will have a lot of language dependent components and having to create a mutable state for every resource string would be a pain. A better solution is to force your entire app to recompose whenever the language changes. Since Compose-only apps are generally only a single activity, it will cause the entire app to recompose. This will ensure that all screens recompose and force all the Text composables to recompose without the need to have a mutable state for each one. There are different ways you can force your app to recompose the entire UI tree. Unfortunately, Compose does not contain an API that lets you recompose the entire tree from scratch, so the only real solution is to restart the app.
Since your app is designed to work with device configuration changes such as language changes, you might want to check out a Compose framework I developed that was specifically designed to handle device configuration changes. It's called Jetmagic. It not only handles language changes but all the other changes like screen orientation, screen size, screen density and all the other configuration qualifiers that are used with the older view-based system. Jetmagic allows you to treat your composables like resources instead of just a bunch of functions and it handles them in the exact same way xml resources are handled under the view-based system using the same algorithm. The sample app included also shows how changing the device's system language (under Android's settings) or by changing the language programmatically, causes your composable UIs to recompose rendering the content in the correct language:
https://github.com/JohannBlake/Jetmagic

Android Jetpack Compose Localisation issue

Trying to slowly integrate Compose into our app.
The app is released in a few countries, so we support some extra languages other than English.
During our first steps with Compose, we are trying to migrate a "Change Password" screen.
Here is a small code snippet:
#Composable
fun ChangePasswordScreen() {
Scaffold(
topBar = {
CustomTopBarWithBackArrow(title = stringResource(id = R.string.a_change_password))
},
modifier = Modifier
.background(Color.White)
.fillMaxSize(),
content = {
ChangePasswordScreenContent()
}
)
}
#Composable
fun ChangePasswordScreenContent() {
Column(
modifier = Modifier
.padding(16.dp)
.background(colorResource(id = R.color.white))
.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
PasswordInput(
title = stringResource(id = R.string.d_current_password),
placeholder = stringResource(id = R.string.e_password_length)
)
}
}
#Composable
fun PasswordInput(title: String, placeholder: String) {
TextFieldTitle(title = title)
PasswordTextField(placeholder = placeholder)
}
I am using the stringResource method.
The user is able to change the selected country and based on that country, we update the Locale.
For some reason, the stringResource method is not updated with the latest Locale, unless we restart the app.
The LocalConfiguration.current.locales[0].country is returning always the correct country code. But the resources are not updated.
Has anybody found a possible solution to this?
Or maybe, am I missing something?
Changing the language/region programmatically does not recompose your UI. You either have to update the state of the various screens/composable or restart your app. Updating your screens/composables can be a pain. You are better off restarting the activity:
startActivity(Intent.makeRestartActivityTask(this.intent?.component))
A better solution is to use Jetmagic. It's a framework designed to handle device configuration changes without the need to restart the app. A demo app shows how the locale is updated:
https://github.com/JohannBlake/Jetmagic
Building upon Johann's answer, you can try to manually trigger a recomposition.
Try this,
setContent{
var trigger by mutableStateOf(false)
}
Then, at the place you are changing the Locale, just trigger = !trigger. Now, since the setContent method is reading it, the entire screen will recompose.
Anyway, if your Locale Changer is located somewhere outside setContent in a non-Compose scope, you can just declare this variable on top of the activity, then just type it just like that in the setContent
setContent{
trigger
...
}
This is just to give Compose a message that this trigger is being read by setContent and it should recompose upon its value change. Since we do not really care what the value of trigger is, you can write it without using remember at all.
Seems clean to me,

What is the difference between "remember" and "mutableState" in android jetpack compose?

I'm new in jetpack compose and trying to understand the difference between remember and mutableStateOf
In other words the deference between this line
val text = remember{ mutableStateOf("") }
and this
val text = remember{ "" }
and this also
val text = mutableStateOf("")
remember is a composable function that can be used to cache expensive operations. You can think of it as a cache which is local to your composable.
val state: Int = remember { 1 }
The state in the above code is immutable. If you want to change that state and also update the UI, you can use a MutableState. Compose will observe any reads/writes the MutableState object and trigger a recomposition to update the UI.
val state: MutableState<Int> = remember { mutableStateOf(1) }
Text(
modifier = Modifier.clickable { state.value += 1 },
text = "${state.value}",
)
Another variant (added in alpha12) called rememberSaveable which is similar to remember, but the stored value can survive process death or configuration changes.
val state: MutableState<Int> = rememberSaveable { mutableStateOf(1) }
Note: You can also use property delegates as a syntactic sugar to unwrap the MutableState.
var state: Int by remember { mutableStateOf(1) }
Regarding the last part of your question:
val text = mutableStateOf("")
MutableState is an alternative to using LiveData or Flow. Compose does not observe any changes to this object by default and therefore no recomposition will happen. If you want the changes to be observed and the state to be cached use remember. If you don't need the caching but only want to observe, you can use derivedStateOf. Here is a sample of how to use it.
As pointed out by Ahmad Hamwi in the comments and quoting them:
Compose does observe the value of the state, in fact there's a #Stable annotation on top of it, and that's its sole responsibility, but since we're not remembering the state, a mutableStateOf(1) will always be created AGAIN, so there will be a new instance of a state, but will still have the same value of 1. So the state seems to not change, but there IS a recomposition happening.
As i understand.
remember just cache result of computation to keep result instance between compositions. Any object. And MutableState instance. And this is why it is useful.
val text = remember{ "" }
just cache empty string.
val text = mutableStateOf("")
create MutableState and compose observe it value, but not cache MutableState instance, so it will be re-created on next recomposition (of course if recomposition will happen in this place)
for example:
val state: MutableState<Int> = mutableStateOf(1)
println(state.toString())
Text(
modifier = Modifier.clickable { state.value += 1 },
text = "${state.value}",
)
the text will always be 1, because every recomposition re-creates state and the output will be:
MutableState(value=1)#227069120
MutableState(value=1)#104526071
MutableState(value=1)#915621104
MutableState(value=1)#580489706
remember caches MutableState object and keep same instance on every recomposition
val state: MutableState<Int> = remember { mutableStateOf(1) }
println(state.toString())
Text(
modifier = Modifier.clickable { state.value += 1 },
text = "${state.value}",
)
work as expected.
MutableState(value=2)#1121832406
MutableState(value=3)#1121832406
MutableState(value=4)#1121832406
MutableState(value=5)#1121832406
remember(key)
val key = remember { 0 }
var state by remember(key) { mutableStateOf(1) }
println(state.toString())
Text(
modifier = Modifier.clickable { state += 1 },
text = "${state}",
)
Works like the example above, even though the key doesn't change. It's because in case of a MutableState, not the value is cached, but the instance of MutableState itself with the value field, which changes.
changing key value will recreate MutableState instance
If remember is used with a field it's value will persist across recompositions.
If mutableState is used with a field, all the composables which are using that field will be recomposed whenever the field values changes.
Basically, in the first example you are storing a mutable value and in the second you are storing an immutable value.
According to the doc: "You can store immutable values when caching expensive UI operations, such as computing text formatting. The remembered value is stored in the Composition with the composable that called remember." Source
For more info on mutableStateOf, here is the doc link. You use this when you want your UI the be recomposed when there is a change in your values.
The remember keyword can store a mutable or an immutable object. If you pass mutableStateOf to remember, any time the value of that object changes, it will force recomposition of the composables that are reading that value.
mutableStateOf(): it's an observable that observes the values when underlaying values gets changes and updates the UI. like we use liveData and stateFlows but LiveData also has lifecycle mechanism and almost same goes with StateFlows.
remember{}: It persists the data across the recomposition.
what is recomposition? When the state get changes, the composable that holds that value recomposes itself to give us updated value.
for example: we have a textView(composable) that observes the value, right now which is 1. We press the button and increment the value with 2. What happens here when the value goes from 1 to 2 the textView(composable) recreates(recomposes) itself and shows us the updated value which is 2, this is known as recomposition.
when we use this: val text = remember{ mutableStateOf("") } that's means we are not only observing the data but also persisting the data across the recomposition.

What does Jetpack Compose remember actually do, how does it work under the hood?

Checking out codelab's basic tutorial there is a snippet to increase counter on button when clicked
#Composable
fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
val counterState = remember { mutableStateOf(0) }
Column(modifier = Modifier.fillMaxHeight()) {
Column(modifier = Modifier.weight(1f)) {
for (name in names) {
Greeting(name = name)
Divider(color = Color.Black)
}
}
Counter(
count = counterState.value,
updateCount = { newCount ->
counterState.value = newCount
}
)
}
}
#Composable
fun Counter(count: Int, updateCount: (Int) -> Unit) {
Button(
onClick = { updateCount(count + 1) },
colors = ButtonConstants.defaultButtonColors(
backgroundColor = if (count > 5) Color.Green else Color.White
)
) {
Text("I've been clicked $count times")
}
}
It is clear that remember { mutableStateOf(0) } stores the state/value. My question is what remember does under the hood. Using var count = remember { 0 } or mutableStateOf(0) without remember does not increase the value.
fun MyScreenContent(names: List<String> = listOf("Android", "there")) {
var count = remember { 0 }
Column(modifier = Modifier.fillMaxHeight()) {
Column(modifier = Modifier.weight(1f)) {
for (name in names) {
Greeting(name = name)
Divider(color = Color.Black)
}
}
Counter(
count = count,
updateCount = { newCount ->
count = newCount
}
)
}
}
Snippet above does not update the value printed on Text, does remember only work with MutableState?
remember - allows you to remember state from previous recompose invocation and just this.
So if you for instance randomize color at initial run. The randomized color will going to be calculated once and reused whenever re-compose is necessary.
so ...
remember = store value just in case recompose will be called.
Now the second thing is knowing when re-compose should be actually triggered.
and there mutable states comes to help.
mutablestate = store the value AND in case i update value trigger recompose for all elements using this data.
To learn how composition and recomposition works you can check out Under the hood of Jetpack Compose article by Leland Richardson, which describes inner works very well, also youtube video here. And most of this answer uses article as reference and quoted most from it.
The implementation of the Composer contains a data structure that is closely related to a Gap Buffer. This data structure is commonly used in text editors.
A gap buffer represents a collection with a current index or cursor. It is implemented in memory with a flat array. That flat array is larger than the collection of data that it represents, with the unused space referred to as the gap.
Basically adding space near your Composable function slot table to be able to update UI dynamically with high costs since get, move, insert, and delete — are constant time operations, except for moving the gap. Moving the gap is O(n) but this does not happen often which you need to change all UI structure, on average, UIs don’t change structure very much.
#Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(
text="Count: $count",
onPress={ count += 1 }
)
}
When the compiler sees the Composable annotation, it inserts additional parameters and calls into the body of the function.
First, the compiler adds a call to the composer.start method and passes it a compile time generated key integer.
fun Counter($composer: Composer) {
$composer.start(123)
var count by remember($composer) { mutableStateOf(0) }
Button(
$composer,
text="Count: $count",
onPress={ count += 1 },
)
$composer.end()
}
When this composer executes it does the following:
Composer.start gets called and stores a group object
remember inserts a group object
the value that mutableStateOf returns, the state instance, is stored.
Button stores a group, followed by each of its parameters.
And then finally we arrive at composer.end.
The data structure now holds all of the objects from the composition, the entire tree in execution order, effectively a depth first traversal of the tree.
So remember needed to store a mutableState() to get value from previous composition and mutableState() is required to trigger one.
And MutableState interface uses #Stable annotation
#Stable
interface MutableState<T> : State<T> {
override var value: T
operator fun component1(): T
operator fun component2(): (T) -> Unit
}
Stable is used to communicate some guarantees to the compose compiler about how a certain type or function will behave.
When applied to a class or an interface, Stable indicates that the following must be true:
The result of equals will always return the same result for the same two instances.
When a public property of the type changes, composition will be notified.
All public property types are stable.
When applied to a function or a property, the Stable]annotation indicates that the function will return the same result if the same
parameters are passed in. This is only meaningful if the parameters
and results are themselves Stable, Immutable, or primitive.
The invariants that this annotation implies are used for optimizations by the compose compiler, and have undefined behavior if
the above assumptions are not met. As a result, one should not use this annotation unless they are certain that these conditions are satisfied.
Another source with a Video that describes how Compose works.
Codelab example mentions about remember and mutableState as
Reacting to state changes is at the very heart of Compose. Compose
apps transform data into UI by calling Composable functions. If your
data changes, you recall these functions with the new data, creating
an updated UI. Compose offers tools for observing changes in your
app's data, which will automatically recall your functions—this is
called recomposing. Compose also looks at what data is needed by an
individual composable so that it only needs to recompose components
whose data has changed and can skip composing those that are not
affected.
Under the hood, Compose uses a custom Kotlin compiler plugin so when
the underlying data changes, the composable functions can be
re-invoked to update the UI hierarchy.
To add internal state to a composable, use the mutableStateOf
function, which gives a composable mutable memory. To not have a
different state for every recomposition, remember the mutable state
using remember. And, if there are multiple instances of the composable
at different places on the screen, each copy will get its own version
of the state. You can think of internal state as a private variable in
a class.
remember{X} and remember{mutableStateOf(X)} are useful in different scenarios.
First one is required when your object doesn't need to be instantiated at each recomposition, and there is another trigger that triggers composition.
An example for this is remember{Paint()}, any object that doesn't need to be instantiated more than once or memory heavy to instantiate. If a Composable that possesses this object is recomposed, properties of your object don't change thanks to remember, if you don't use remember your object is instantiated on each recomposition and all the properties previously set are reset.
If you need a trigger(mutableStateOf) and need to have the latest value(remember) like in question choose remember{mutableStateOf()}
Variables are cleared on every compositon.
Using remember will get the previous value.
I think its equivalent to declare a mutableState in ViewModel.

Categories

Resources