Bug 1824856 - add release-notify-testrail to treeherder. r=gbrown,releng-reviewers...
[gecko.git] / mobile / android / docs / _posts / 2019-02-19-saving-state.md
blob6c7e2eae4a99a13b8d246982b496386a7961c1e8
1 ---
2 layout: post
3 title: "💾 Saving and restoring browser session state"
4 date: 2019-02-18 14:35:00 +0200
5 author: sebastian
6 ---
8 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.
10 [Mozilla's Android Components](https://mozac.org/) come with two implementations of session storage and helpers to write your own easily.
12 ## SessionStorage
14 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).
16 ```kotlin
17 val sessionStorage SessionStorage(applicationContext, engine)
19 val sessionManager = sessionManager(engine).apply {
20     sessionStorage.restore()?.let { snapshot -> restore(snapshot) }
22 ```
24 ℹ️ 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.
26 ## SessionBundleStorage
28 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.
30 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.
32 ```kotlin
33 val engine: Engine = ...
35 val sessionStorage = SessionBundleStorage(
36     applicationContext,
37     bundleLifetime = Pair(1, TimeUnit.HOURS)
39 val sessionManager = sessionManager(engine).apply {
40     // We launch a coroutine on the main thread. Once a snapshot has been restored
41     // we want to continue with it on the main thread.
42     GlobalScope.launch(Dispatchers.Main) {
43         // We restore on the IO dispatcher to not block the main thread:
44         val snapshot = async(Dispatchers.IO) {
45             val bundle = sessionStorage.restore()
46             // If we got a bundle then restore the snapshot from it
47             bundle.restoreSnapshot(engine)
48         }
50         // If we got a snapshot then restore it now:
51         snapshot.await()?.let { sessionManager.restore(it) }
52     }
54 ```
56 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.
58 ![](/assets/images/blog/session-bundles.png)
60 ## AutoSave
62 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.
64 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/).
66 ```kotlin
67 sessionStorage.autoSave(sessionManager)
68     // Automatically save the state every 30 seconds:
69     .periodicallyInForeground(interval = 30, unit = TimeUnit.SECONDS)
70     // Save the state when the app goes to the background:
71     .whenGoingToBackground()
72     // Save the state whenever sessions change (e.g. a new tab got added or a website
73     // has completed loading).
74     .whenSessionsChange()
75 ```
77 ## Implementing your own SessionStorage
79 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.
81 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.
83 The [SnapshotSerializer](https://mozac.org/api/mozilla.components.browser.session.storage/-snapshot-serializer/) class is helpful when translating snapshots from and to JSON.
85 ```kotlin
86 class MyCustomStorage(
87     private val engine: Engine
88 ) : AutoSave.Storage {
89     private val serializer = SnapshotSerializer()
91     override fun save(snapshot: SessionManager.Snapshot): Boolean {
92         val json = serializer.toJSON(snapshot)
94         // TODO: Save JSON custom storage.
96         // Signal that save operation was successful:
97         return true
98     }
100     fun restore(): SessionManager.Snapshot {
101         // TODO: Get JSON from custom storage.
102         val json = ...
104         return serializer.fromJSON(engine, json)
105     }
109 ℹ️ 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/).