I'd like to serialize my app state such that upon ...
# compose-desktop
m
I'd like to serialize my app state such that upon close/reopen it restores the state as it was left. How would I realistically accomplish this? I tried making a variety of things @Serializable, but a bunch of things like MutableState are not really compatible with this, not to mention I'd have to run a save on a loop rather than after every change to the program's state.
z
On Android, there are OS hooks for "you're about to potentially go away, please save your state". That is ultimately wired up to asking the
SaveableStateRegistry
to save its providers and serializing those values (also using some Android-specific features). I would think most desktop environments would have a hook for that too, although idk how or even if that's exposed through Java.
m
On Desktop it's unreliable at best (maybe using atexit or Cleaner?) but on Web there's no API for it. I wouldn't mind saving every few seconds, but handling any delegated properties from/to MutableState/SnapshotStateList/etc I'd have no clue how to handle reliably, especially since I can't put delegations in data class constructors
z
You don’t need to handle those those directly, just create a SaveableStateRegistry and install it at the composition root. All your save/restore code should just deal with the registry and individual states can worry about their own Savers.
m
Do you have an example for this? e.g. lets say I have
Copy code
class SudokuState(
    val solvedBoard: IntArray,
    val revealed: BooleanArray
) {
    val userAnswers = SnapshotStateList(solvedBoard.size) { if (revealed[it]) solvedBoard[it] else -1 }  // -1 = empty
    var solved by mutableStateOf(false)

    fun set(i: Int, newValue: Int) {
        userAnswers[i] = newValue
        solved = solvedBoard.toList().zip(userAnswers).all { (a, b) -> a == b } 
    }
}

@Composable 
fun Sudoku(state: SudokuState) {
    // ...
    SudokuSlot(state.userAnswers[i]) { newValue ->
        state.set(i, newValue)
    }
}
How/where would I implement saving/loading to a SavableStateRegistry? especially if multiple SudokuStates may exist at any given time
z
Wherever you create your `SudokuState`:
Copy code
val state = rememberSaveable(saver = SudokuState.Saver(solvedBoard, revealed)) { SudokuState(solvedBoard, revealed) }
Then in `SudokuState`:
Copy code
class SudokuState(…) {
  class Saver(
    val solvedBoard: IntArray,
    val revealed: BooleanArray
  ) : androidx.compose.runtime.Saver<SudokuState, Something> {
    // implement save/restore
  }
}
m
I see, I was hoping there'd be a way to just hook into kotlinx.serialization and have it work with all properties in a fairly straightforward manner. Also, since my SudokuState is a property on a parent state, would I need to update the parent state somehow during my save to make the parent object save? Or does altering properties on the SudokuState also trigger a save on the parent object? In my case, something along the lines of
Copy code
class FakeDesktopState {
    val windows = snapshotStateListOf<FakeWindow>()
}

class FakeWindow {
    val handle: AppInstance<*>
}

class AppInstance<T> {
    val app: App<T>
    val state: T
}

class SudokuApp : App<SudokuState>

// Editing the SudokuState here should also make sure the FakeDesktopState saves downwards, because the SudokuState cares about the window it's in, and windows may switch positions in the list (but the states switch along with them, and I believe rememberSavable cares about the current index in the composable tree?)
z
You might want to look at the SavedState library, it supports kotlinx.serialization and I think it can be wired up to Compose. I haven’t played with it myself but it seems like that’s the intention.
m
Write pluggable components that save the UI state when a process dies
That doesn't sound like it'd work on web 😕
z
That’s just one common use case
If it’s a KMP library that supports js, it should work on web
m
It looks like it requires a ViewModel setup, so I don't think it'll fit my use-case