-
Notifications
You must be signed in to change notification settings - Fork 1.2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
rememberSaveable
does not work - restore/save logic is never called
#1932
Comments
On which OS do you use it? |
I'm on Windows 10. |
Initially, from what I gathered reading docs, it seemed like But the impression I get now is that perhaps And worse - the impression I get from talking to people in a few places now is that both So instead of doing this: fun main() = singleWindowApplication {
MaterialTheme {
val state = remember { AppState() }
MainUi(state)
}
} We should actually be doing this: fun main() {
val state = AppState()
singleWindowApplication {
MaterialTheme {
MainUi(state)
}
}
} Which is fine, but now I'm back to having no convenient way to store my app state at all, and I'm back to reinventing the wheel. |
I think saving state of App is not a functionality of UI framework... You just need to put your state to some object and serialize it to some persistant storage. |
I partially agree, except:
So yeah, Compose may be a UI framework and not an app framework, but if that's the case, things like |
Did you end up writing your own version of remember for things like windowState/appState which actually persists the state? |
No... I didn't have the slightest idea where to start. |
I did some investigation and looks like To make it work you need to create instance of Here is snippet of what I did in my project:
|
Btw, don't misunderstand, |
I disagree. If you don't need it to persist across application invocations, The docs for The actual wording in the docs is:
Please re-read this carefully, particularly this bit:
So now that you have read the docs, please explain the following:
|
This description refers to android specific components so it's not completely relevant for multiplatform. And even in android case it doesn't persist data across application sessions, the only exception is the case when single session is temporary ̶k̶i̶l̶l̶e̶d̶ hibernated by system and then system restores it later, I don't think that any desktop OS has similar behavior |
Correct. Now you're up to date with what I said on Mar 16, 2022 above. Although I never had an Android device handy to really test that behaviour. It always seemed to me that the user killing the process should be treated the same way. So as far as I can tell, saying that With regard to the question (paraphrased): "Do any desktop OS have the ability to suspend and recreate apps which were previously running?" The answer is yes, it happens on macOS. When you reboot, things like TextEdit have their state serialised and come back with the same state next session, including all unsaved data. So of course, I consider that sort of behaviour essential for a well-behaved desktop app, which is why I wanted this sort of state saving to work. As far as I can tell, all you have to do to appear to be doing the same thing as TextEdit is, (1) save your state, and (2) terminate immediately when the OS asks you to terminate, don't pop up prompts to save documents, etc. Note that you don't have to save your state at the shutdown request - you could save state continuously, so that it's always safe to just terminate. |
Oh right, I remember that feature, mac os applications certainly might persist data between reboots, although I'm not sure whether their state is restored, I only remember it could reopen all applications automatically. This might be a good feature, but idk if any other desktop os doing something similar. Also I don't think Also here a better description from a Allows to save the state defined with rememberSaveable for the subtree before disposing it to make it possible to compose it back next time with the restored state. It allows different navigation patterns to keep the ui state like scroll position for the currently not composed screens from the backstack. This is the only feature from |
Worth noting that the current version of Notepad on Windows now behaves like this as well. You can kill the application and the next time it will come back with the window in the same place and your text still there. |
Let me try to summarize this thread. Android Jetpack Compose behavior
So, it's NOT stored on disk, it does NOT survive when the user closes the application. Compose Multiplatform behaviorCurrently save/restore aren't invoked out of the box because For now you can provide a custom implementation of Providing custom savable logicIf you want to provide custom storing logic, you can do it via providing CompositionLocalProvider(
LocalSaveableStateRegistry provides saveableStateRegistry,
) {
// your compose content
} Example of simple class GlobalSaveableStateRegistry(
val saveableId: String,
) : SaveableStateRegistry by SaveableStateRegistry(
restoredValues = map[saveableId],
canBeSaved = { true }
) {
fun save() { map[saveableId] = performSave() } // Should be called manually before Compose's dispose
companion object {
private val map = mutableMapOf<String, SaveableStateData>()
}
} Based on this you can implement saving the state on the disk if you want to. |
I assume the missing piece here is something along these lines... @Composable
fun rememberGlobalSaveableStateRegistry(saveableId: String): SaveableStateRegistry {
val saveableStateRegistry = remember {
GlobalSaveableStateRegistry(saveableId)
}
DisposableEffect(Unit) {
onDispose {
saveableStateRegistry.save()
}
}
return saveableStateRegistry
} But yeah, it really just sounds like this was a misunderstanding on my part, caused by the wording in the docs. The docs say, "and also across activity or process recreation", which leads one to be optimistic about the situations in which it should work. The docs do follow that with, "using the saved instance state mechanism", but I didn't come from Android dev, so I read that as buzz, rather than a description of a limitation. There's no hyperlink from that paragraph out to anywhere describing what that mechanism means, which reinforces my interpretation of it as buzz. I maintain that being able to remember state across process recreation, even if it was an OS crash which caused it, is useful behaviour, though. It just sounds like implementing that is being pushed back on the app developer. Maybe some day there will be a framework where the batteries are included, because I for one am sick of building batteries. |
I intentionally avoided |
Buuuuut, how do you know when you should call it then? When I wrote that code, I think I was assuming that disposals are called in the opposite order from the order they are initialised. Which is usually a fairly sane assumption to make, but I don't know how Compose has actually implemented it. (Maybe what I should have done was had the What I wish Compose had was something like a |
I thought about a general case with
It's true, the order is opposite and consistent. But you need to save it BEFORE disposing the content that you will initialize later You just need to make sure you call setContent {
val saveableStateRegistry = remember { GlobalSaveableStateRegistry("KEY") }
CompositionLocalProvider(LocalSaveableStateRegistry provides saveableStateRegistry) {
ComposeContent()
}
DisposableEffect(Unit) {
onDispose { saveableStateRegistry.save() }
}
} Previously, since I had full control over Compose lifecycle, I thought it would be just easier not to rely on that order. But if it done correctly, it will work with |
It has to be in the root Composable right Ivan? |
No, it's regular compose local and might be applied/replaced only to part of your application. I didn't test it personally, but I don't see any reason why it wouldn't work
I'm currently working on porting lifecycle, things a bit more complicated there. On Android lifecycle library is independent from Compose, but vice versa - Compose has it in the dependencies to find "lifecycle owner" from View tree and provide as composition local. However, there is no such "lifecycle owner" like activity in multiplatform, and Compose does window management related things itself. So, as the first step Compose will be "lifecycle owner". With ViewModels and "state registry owner" the situation is similar - there is no API at the moment to hold it outside of Compose. I wrote about it above:
But it's another topic.
Usually current visual state is not stored in database. I mean for example collapsed spoiler or selected text range.
What happen? The last example is about custom registry with custom logic. It's up to you when you're going to save the current state. Maybe you want to save it every minute? The saving just before disposing just allows you to save the state for next run - it's what @hakanai wanted. My example shows how to achieve that. |
I understand, what happens is sometimes |
Compose Navigation provides BTW |
@MatkovIvan I would think that Is there any plan for that, or would I need to roll my own with |
While I agree that |
I addressed some of your comments in the description there. |
Please check the following ticket on YouTrack for follow-ups to this issue. GitHub issues will be closed in the coming weeks. |
I currently have:
I thought I would try switching to
rememberSaveable
so that app settings would persist across app invocations.So I changed it to:
AppState.Saver
is currently just:If I put a breakpoint on those
TODO
lines, I can see that they are never called. I'd expect a call to save during application exit, and a call to restore some time during startup.I assume there's a way to call the right things programmatically to get this to happen as a workaround but haven't figured it out yet.
The text was updated successfully, but these errors were encountered: