Skip to content

Saved state

Maxim Kostenko edited this page Jun 21, 2022 · 2 revisions

The Kompot framework supports saving and restoring the state of your application in full. When the Android system kills an app process, and a user returns to the app, the latest screen and the back stack will be restored from scratch (only in debug mode atm). As you already know, screens have a domain state which serves as a source of truth of the data shown on the screen. When the framework restores a screen, it shows an initial domain state on it.

Let's look at the chat screen that you can find in the sample app

Its initial state declared like this:

override val initialState = DomainState(
    contact = inputData.contact,
    messages = emptyList(),
    messageInputText = ""
)

So after the process kills the app, the framework restores a screen to this state, and the latest user's input will be lost.

To restore the screen exactly how the user left it, we'll need to provide some instructions. Let's see what we can do with the chat screen to achieve that:

First of all, we declare what state should we keep for the screen. For our case, it's message input only, so in the ChatScreenContract we declare a parcelable RetainedState that has only a messageInputText property.

@Parcelize
data class RetainedState(
    val messageInputText: String
): ScreenStates.RetainedDomain

Chat domain state also has a messages list. We can fetch it from the room database, so there is no need to keep messages in the saved state and make our app bundle grow for no reason.

The next step is to declare a ChatSaveStateDelegate, which has two methods: getRetainedState(currentState: DomainState) Tells how to get a state to retain from the latest domain state. The framework calls this one when it needs to prepare a bundle with the saved state.

restoreDomainState(initialState: DomainState, retainedState: RetainedState) Given a retained state and initial domain state, you can build a domain state that will be shown on the screen after restoration. You can use the initial state to get a default state for properties that are optional for restoration.

Here is an example of a chat screen:

internal class ChatSaveStateDelegate : SaveStateDelegate<DomainState, RetainedState>() {

    override fun getRetainedState(currentState: DomainState) = RetainedState(currentState.messageInputText)

    override fun restoreDomainState(
        initialState: DomainState,
        retainedState: RetainedState
    ) = initialState.copy(
        messageInputText = retainedState.messageInputText
    )

}

The final step is to override a factory method of a corresponding screen model and provide it with our saved state delegate:

class ChatScreenModel @Inject constructor(
    ...
) : BaseScreenModel<DomainState, UIState, IOData.EmptyOutput>(stateMapper), ScreenModelApi {

    override val initialState = ...

    override val saveStateDelegate = ChatSaveStateDelegate()

    ...
}

After that, we can see the latest message input after the app process is restored: