Comments (20)
It is required by the nature of the library. Configurations are unique keys of components. Each component in a particular Router
's stack must have unique configuration within the Router
's stack. This way the Router
can distinguish configurations and create/destroy components accordingly.
I will consider renaming Configuration to Key, perhaps it will make it more explicit. Filed #210.
from decompose.
Hello. The StateKeeper
saves component state (aka business logic), the Children
function is also responsible for saving the UI state. E.g. when you navigate from List to Details and then back to List, the scrolling position is automatically preservaled. This is a general approach, the Google`s compose-navigation should do the same.
It seems that your UI saves a lot of data. Could you describe your case in more detail?
from decompose.
E.g. when you navigate from List to Details and then back to List, the scrolling position is automatically preservaled
If so, maybe a good idea would be to use hashCode of child.configuration instead? If the data is the same, hashCode will be the same too but we won't save the data into bundle.
With hashCode we can get false positive since it can be the same for different data so probably we need another idea.
Could you describe your case in more detail?
I just have a list of objects I got from REST API and cached, pass them to a screen with Pager. After that the screen with the Pager loads more data from REST based on previous objects. So objects are highly coupled and for me it's much easier to pass them into configuration than storing elsewhere.
from decompose.
maybe a good idea would be to use hashCode of child.configuration instead? If the data is the same, hashCode will be the same too but we won't save the data into bundle
Sorry, but it's not clear what you mean. Maybe you could provide a code snippet? The Children
function just preserves states of all Composable functions in the back stack.
Regarding your use case. If you pass objects via configuration then this data should be saved by StateKeeper
, it's not clear how is it related to the UI state (the Children
function). The latter only saves what is saved by the child Composable function.
from decompose.
This is a part of MainComponent:
val router: Router<ChildConf, Content> =
appRouter(
initialConfiguration = { ChildConf.Server },
handleBackButton = true,
childFactory = ::childContent,
configurationClass = ChildConf::class
)
private fun childContent(
conf: ChildConf,
context: AppContext
): Content =
when (conf) {
is ChildConf.Server -> ServerComponent(context, offsetChangedListener).asContent { it.ServerScreen(conf)
is ChildConf.Login -> LoginComponent(context).asContent { it.LoginScreen() }
is ChildConf.Plan -> PlanComponent(context).asContent { it.PlanScreen() }
}
sealed class ChildConf : Parcelable {
@Parcelize
object Server : ChildConf()
@Parcelize
data class Plan(
var plans: List<ApiPlan>,
// maaaany other already cached values, no need to reload from internet
) : ChildConf()
@Parcelize
object Login : ChildConf()
}
This is MainScreen:
@Composable
fun MainScreen(mainComp: MainComponent) {
Column {
Children(routerState = mainComp.routerState, animation = mainComp.routerAnimation) { it: Child.Created<MainComponent.ChildConf, @Composable () -> Unit> ->
it.instance()
}
}
}
The Children function just preserves states of all Composable functions in the back stack.
This line stores the whole configuration (like ChildConf.Plan in my example) in the bundle. If there will be heavy configurations we end up with TransactionTooLargeException.
https://github.com/arkivanov/Decompose/blob/master/extensions-compose-jetbrains/src/commonMain/kotlin/com/arkivanov/decompose/extensions/compose/jetbrains/Children.kt#L27
Let's say I open PlanScreen. I pass some data via ChildConf.Plan.
This is an output of the lib https://github.com/guardian/toolargetool (in order to see the output, I go to an Android launcher to trigger state saving into bundle and returning back to my app):
08-08 21:24:55.880 30512 30512 D TooLargeTool: ServerFragment.onSaveInstanceState wrote: Bundle200945447 contains 3 keys and measures 408.9 KB when serialized as a Parcel
08-08 21:24:55.880 30512 30512 D TooLargeTool: * androidx.lifecycle.BundlableSavedStateRegistry.key = 203.4 KB
08-08 21:24:55.880 30512 30512 D TooLargeTool: * activeFragment = 0.0 KB
08-08 21:24:55.880 30512 30512 D TooLargeTool: * android:view_registry_state = 205.5 KB
Bundle with a key androidx.lifecycle.BundlableSavedStateRegistry.key
will be cleared when I press back from the PlanScreen, so it will store ~0 KB,
Bundle with a key android:view_registry_state
will never be cleared, it contains the keys you pass to rememberSaveable (ChildConf
's).
Am I described the situation better?
For now I made this change to MainScreen:
@Composable
fun MainScreen(mainComp: MainComponent) {
Column(modifier = Modifier) {
val state = mainComp.routerState.subscribeAsState()
val holder = rememberSaveableStateHolder()
holder.retainStates(mainComp.routerState.value.asKeys())
mainComp.routerAnimation(state.value) { child ->
holder.SaveableStateProvider(child.configuration.confAsKey()) {
child.instance()
}
}
}
}
// THIS THING I TALKED ABOUT
private fun Any.confAsKey(): String = this::class.toString() + hashCode()
private fun <C : Any> RouterState<C, *>.asKeys(): Set<String> =
backStack.map { it.configuration.confAsKey() }.toSet() + activeChild.configuration.confAsKey()
from decompose.
Yes, now it's clear. So you are correct, each the configuration is stored twice. I will need to think if can improe the situation.
But the configuration should go away when you close the screen. It should be removed from SaveableStateHolder
, which is done in the same file with Children
function.
from decompose.
@arkivanov it should, yes, but it's not happening. So if visit multiple screens such screens are saved too.
from decompose.
Thanks, I will check it and provide updates here.
from decompose.
I checked the issue with non-removing configurations and could not reproduce. Configurations of popped screens are not saved into the Bundle. Both the StateKeeper
and the Children
function save configurations into androidx.lifecycle.BundlableSavedStateRegistry
. And I don't see android:view_registry_state
at all, which actually comes from FragmentStateManager. I think I will need a reproducer for this issue. Would you be able to provide one?
Regarding duplicated configurations (in StateKeeper
and and in Children
function) - I will try to fix it.
from decompose.
I don't see android:view_registry_state at all, which actually comes from FragmentStateManager
Did you setup your own fragment state listener instead of using the lib I suggested? If so, that's why you didn't see android:view_registry_state. I don't know why but I did the same thing and didn't see it too until I used the lib. Then I just copied the lib files into my project, added more logs into it to see the actual data inside bundle.
androidx.lifecycle.BundlableSavedStateRegistry is cleared ok, but not view_registry_key. Based on keys inside that bundle I found rememberSaveable:)
Anyway, I'll make a reproducer. Probably yesterday.
from decompose.
I checked using the TodoApp example with the following steps:
- Open the details screen and press back button
- Minimize the app
- Simulate app eviction using Logcat tab
- Put breakpoint in
MainActivity.onCreate
beforesuper
call - Open the app with debugger attached
- Check the Bundle using object inspector
These are keys stored in the Bundle:
result = {MapCollections$KeySet@16386} size = 4
0 = "android:viewHierarchyState"
1 = "androidx.lifecycle.BundlableSavedStateRegistry.key"
2 = "android:lastAutofillId"
3 = "android:fragments"
from decompose.
Tried to put everything into a Fragment and the key "android:view_registry_state" was in the Bundle. So it is only added by Fragments. However in my case it contained only one configuration, of the first screen.
from decompose.
In my case I saw the following when I opened one screen, then pressed back, then opened another screen, then pressed back, then went to a launcher:
[androidx.lifecycle.BundlableSavedStateRegistry.key = Bundle[{STATE_KEEPER_STATE=Bundle[{}]}], activ
eFragment = true, android:view_registry_state = Bundle[{androidx.lifecycle.BundlableSavedStateRegist
ry.key=Bundle[{SaveableStateRegistry:-1=Bundle[{wyp51p=[{Plan(someProperty="", ........... ))={}, Ac
count(account="someinfo")={-acf57h=[Closed], 2byr8d=[[0, 0]], dc
4esm=[{DefaultLazyKey(index=0)={}, DefaultLazyKey(index=1)={}, DefaultLazyKey(index=2)={}, DefaultLa
zyKey(index=3)={}}]}, com.myapp.ui.screens.main.MainComponent$ChildConf$Server@e564bfe={5fq
sbv=[Closed], 7wcquv=[113], es3y1y=[[0, 0], [0, 0]], -atccld=[{DefaultLazyKey(index=0)={}, DefaultLa
zyKey(index=1)={}, DefaultLazyKey(index=2)={}, DefaultLazyK.......
Your STATE_KEEPER_STATE is ok
android:view_registry_state has:
- Plan conf for PlanScreen (with all the properties, I just deleted them because they so long). Notice that Plan conf is the key in map with an empty value (I did nothing with this blank value).
- Account conf for AccountScreen. In this case a value for that key contains some other things I don't know about (like
-acf57h=[Closed], 2byr8d=[[0, 0], dc4esm=[{DefaultLazyKey(index=0)={}
).
Since you reproduced the problem do you still need a project-reproducer? Or it's enough info already? I think if you'll figure out a better replacement for the whole object it will be more than enough.
from decompose.
As I mentioned, I couldn't reproduce. The second screen's configuration is removed from all bundles if I do the following steps:
- Close the second screen via back button, so it is removed from stack
- Minimize the app
So a reproducer with exact steps is still would be useful.
from decompose.
Arkadii, I found why we have different results. In my case I modified the instance of Plan conf in a screen so it had new data in it after entering PlanScreen. This way the instance will be preserved in android:view_registry_state
forever.
Example:
// <MainComponent.kt>:
private fun childContent(
conf: ChildConf,
context: AppContext
): Content =
when (conf) {
is ChildConf.Test -> TestComponent(context).asContent { TestScreen(conf) }
}
@Parcelize
data class Test(
var someList: List<String>
) : ChildConf()
// </MainComponent.kt>
// Do it from any screen of the app, for example, after clicking on a button:
val conf = ChildConf.Test(listOf("123"))
// I have one router for all screens inside MainComponent, if it matters. Passing the router via custom component context
router.push(conf)
// <TestComponent.kt>
package com.example.screens.test
// AppContext is just custom context with public router
import com.example.reusable.AppContext
class TestComponent(
private val context: AppContext
) : AppContext by context
// </TestComponent.kt>
// <TestScreen.kt>
package com.example.screens.test
import androidx.compose.runtime.*
import com.example.screens.main.MainComponent
@Composable
fun TestScreen(conf: MainComponent.ChildConf.Test) {
LaunchedEffect(Unit) {
conf.someList = listOf("777")
}
}
// </TestScreen.kt>
Result:
[androidx.lifecycle.BundlableSavedStateRegistry.key = Bundle[{STATE_KEEPER_STATE=Bundle[{STATE_KEEPE
R_STATE=com.arkivanov.essenty.parcelable.AndroidParcelableContainer@127462f}]}], activeFragment = tr
ue, android:view_registry_state = Bundle[{androidx.lifecycle.BundlableSavedStateRegistry.key=Bundle[
{SaveableStateRegistry:-1=Bundle[{ursb73=[{Test(someList=[777])={}, com.example.screens.ma
in.MainComponent$ChildConf$Server@34b84b4={-ugkp4i=[Closed], -r890dq=[0], qcovrk=[[0, 0], [0, 0]], -
y5sdis=[{DefaultLazyKey(index=0)={}, DefaultLazyKey(index=1)={}, DefaultLazyKey(index=2)={}, Def
Are you able to reproduce?
from decompose.
Configurations must be immutable, as they are used as keys in things like Map
, etc. If a configuration is mutated, it can not be longer located. I will add this disclaimer to the documentation.
from decompose.
@arkivanov I didn't expect that configuration will be used as a key somewhere. It's not recommended and a source of bugs as we see. Why not to drop usage as key? Is it required?
from decompose.
For me configuration sounds good but key isn't. Maybe adding a note about usage configuration as key would be enough?
from decompose.
Big thanks for you feedback and use cases @avently. I will think about it.
from decompose.
Thanks for you, not me. I would spend many weeks/months for creating something like Decompose.
from decompose.
Related Issues (20)
- Add Compose previews to all samples
- Describe routing options in the docs HOT 3
- Add `Navigator.navigateSingleTop` extension function HOT 3
- Setup publication checks
- Calling `backPressedDispatcher.onBackPressed()` from a child component does not trigger back action
- Back button handling shouldn't be part of the main API HOT 2
- Consider renaming Configuration to Key HOT 1
- Dead links in the readme HOT 1
- UIKit support HOT 9
- Support new Apple silicon targets
- Dynamic list of active child components HOT 1
- Navigator.bringToFront() not available in 0.3.1 HOT 4
- Enable watchos target for the Counter sample after Kotlin 1.5.31 update HOT 2
- Add simplest code examples to the main Overview docs section
- Nullable Value/MutableValue HOT 3
- Broken link in docs HOT 2
- Failure to render screen after installation HOT 5
- Web Browser routing support HOT 2
- is this support multi window in desktop? HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from decompose.