Revision control

Copy as Markdown

---
layout: post
title: "💾 Saving and restoring browser session state"
date: 2019-02-18 14:35:00 +0200
author: sebastian
---
Losing open tabs in a browser can be a painful experience for the user. By itself [SessionManager](https://mozac.org/api/mozilla.components.browser.session/-session-manager/) keeps its state only in memory. This means that something as simple as switching to a different app can cause Android to [kill the browser app's process and reclaim its resources](https://developer.android.com/guide/components/activities/process-lifecycle). The result of that: The next time the user switches back to the browser app they start with a fresh browser without any tabs open.
[Mozilla's Android Components](https://mozac.org/) come with two implementations of session storage and helpers to write your own easily.
## SessionStorage
The [SessionStorage](https://mozac.org/api/mozilla.components.browser.session.storage/-session-storage/) class that comes with the [browser-session](https://github.com/mozilla-mobile/android-components/tree/main/components/browser/session) component saves the state as a file on disk (using [AtomicFile](https://developer.android.com/reference/android/util/AtomicFile) under the hood). It can be used for a browser that wants to have a single state that gets saved and restored (like Fennec or Chrome).
```kotlin
val sessionStorage SessionStorage(applicationContext, engine)
val sessionManager = sessionManager(engine).apply {
sessionStorage.restore()?.let { snapshot -> restore(snapshot) }
}
```
ℹ️ Since restoring the last state requires a disk read, it is recommended to perform it off the main thread. This requires the app to gracefully handle the situation where the app starts without any sessions at first. [SessionManager](https://mozac.org/api/mozilla.components.browser.session/-session-manager/) will invoke [onSessionsRestored()](https://mozac.org/api/mozilla.components.browser.session/-session-manager/-observer/on-sessions-restored.html) on a registered [SessionManager.Observer](https://mozac.org/api/mozilla.components.browser.session/-session-manager/-observer/) after restoring has completed.
## SessionBundleStorage
Other than [SessionStorage](https://mozac.org/api/mozilla.components.browser.session.storage/-session-storage/) the [SessionBundleStorage](https://mozac.org/api/mozilla.components.feature.session.bundling/-session-bundle-storage/) implementation can save and restore from multiple states. State is saved as a Bundle in a database.
The storage is set up with a bundle lifetime. [SessionBundleStorage](https://mozac.org/api/mozilla.components.feature.session.bundling/-session-bundle-storage/) will only restore the last bundle if its lifetime has not expired. If there's no active bundle then a new empty bundle will be created to save the state.
```kotlin
val engine: Engine = ...
val sessionStorage = SessionBundleStorage(
applicationContext,
bundleLifetime = Pair(1, TimeUnit.HOURS)
val sessionManager = sessionManager(engine).apply {
// We launch a coroutine on the main thread. Once a snapshot has been restored
// we want to continue with it on the main thread.
GlobalScope.launch(Dispatchers.Main) {
// We restore on the IO dispatcher to not block the main thread:
val snapshot = async(Dispatchers.IO) {
val bundle = sessionStorage.restore()
// If we got a bundle then restore the snapshot from it
bundle.restoreSnapshot(engine)
}
// If we got a snapshot then restore it now:
snapshot.await()?.let { sessionManager.restore(it) }
}
}
```
The storage comes with an [API](https://mozac.org/api/mozilla.components.feature.session.bundling/-session-bundle-storage/#functions) that allows apps to build UIs to list, restore, save and remove bundles.
![](/assets/images/blog/session-bundles.png)
## AutoSave
Knowing when to save state, by calling [SessionStorage.save()](https://mozac.org/api/mozilla.components.browser.session.storage/-session-storage/save.html) or [SessionBundleStorage.save()](https://mozac.org/api/mozilla.components.feature.session.bundling/-session-bundle-storage/save.html), can be complicated. Restoring an outdated state can be an as bad a user experience as restoring no state at all.
The [AutoSave](https://mozac.org/api/mozilla.components.browser.session.storage/-auto-save/) class is a helper for configuring automatic saving of the browser state - and you can use it with [SessionStorage](https://mozac.org/api/mozilla.components.browser.session.storage/-session-storage/) as well as [SessionBundleStorage](https://mozac.org/api/mozilla.components.feature.session.bundling/-session-bundle-storage/).
```kotlin
sessionStorage.autoSave(sessionManager)
// Automatically save the state every 30 seconds:
.periodicallyInForeground(interval = 30, unit = TimeUnit.SECONDS)
// Save the state when the app goes to the background:
.whenGoingToBackground()
// Save the state whenever sessions change (e.g. a new tab got added or a website
// has completed loading).
.whenSessionsChange()
```
## Implementing your own SessionStorage
If neither [SessionStorage](https://mozac.org/api/mozilla.components.browser.session.storage/-session-storage/) nor [SessionBundleStorage](https://mozac.org/api/mozilla.components.feature.session.bundling/-session-bundle-storage/) satisfy the requirements of your app (e.g. you want to save the state in your favorite database or in a cloud-enabled storage) then it is possible to implement a custom storage.
The [AutoSave.Storage](https://mozac.org/api/mozilla.components.browser.session.storage/-auto-save/-storage/) interface from the [browser-session](https://github.com/mozilla-mobile/android-components/tree/main/components/browser/session) component defines the methods that are expected from a session storage. Technically it is not required to implement the interface if your app code is the only one interacting with the session store; but implementing the interface makes your implementation compatible with other components code. Specifically you can use [AutoSave](https://mozac.org/api/mozilla.components.browser.session.storage/-auto-save/) with any class implementing SessionStorage without any additional code.
The [SnapshotSerializer](https://mozac.org/api/mozilla.components.browser.session.storage/-snapshot-serializer/) class is helpful when translating snapshots from and to JSON.
```kotlin
class MyCustomStorage(
private val engine: Engine
) : AutoSave.Storage {
private val serializer = SnapshotSerializer()
override fun save(snapshot: SessionManager.Snapshot): Boolean {
val json = serializer.toJSON(snapshot)
// TODO: Save JSON custom storage.
// Signal that save operation was successful:
return true
}
fun restore(): SessionManager.Snapshot {
// TODO: Get JSON from custom storage.
val json = ...
return serializer.fromJSON(engine, json)
}
}
```
ℹ️ For simplicity the implementation above does not handle [JSONException](ad https://developer.android.com/reference/org/json/JSONException.html) which can be thrown by [SnapshotSerializer](https://mozac.org/api/mozilla.components.browser.session.storage/-snapshot-serializer/).