All articles

Technical

Handling Page Lifecycle and State Preservation in Android TWAs

September 22, 2026 · 7 min read

When a web application is converted into an Android application using a Trusted Web Activity (TWA), it gains access to native app-like distribution. However, it also inherits the strict memory management lifecycle of the Android operating system. Unlike a standard desktop web browser tab, which may stay open indefinitely in the background, Android reserves the right to freeze, suspend, or completely terminate background applications to reclaim system memory for active foreground tasks. Developers must handle these transitions correctly to prevent users from losing their session state, half-filled forms, or application progress.

Understanding the Android App Lifecycle and TWAs

When a user minimises your TWA, locks their screen, or switches to another application, the operating system pauses the app. From a technical perspective, the under-the-hood web browser rendering engine (typically Chrome Custom Tabs) pauses running JavaScript execution. If memory becomes scarce, the entire OS process running the TWA can be terminated. When the user returns to the application from the Android task switcher, the app launches fresh. If you have not implemented state preservation, the user will experience a full page reload, dropping them back to your default landing page or start URL.

To solve this, developers must leverage the standardised Page Lifecycle API. This browser API exposes hooks that correspond directly to Android OS lifecycle transitions, allowing you to save the state of your application before termination occurs.

The Core States of the Page Lifecycle API

The Page Lifecycle API categorises the application into several logical states. In a TWA context, understanding three of these states is vital for preserving data:

  • Hidden: The application is no longer visible to the user. This happens when the user minimises the app or switches to another task. The document's visibilityState changes to hidden.
  • Frozen: The browser has suspended JavaScript execution to conserve CPU and battery. Active network connections may be paused, and timers will stop firing.
  • Terminated: The browser has unloaded the application and cleared the process from memory. This transition happens silently when the OS needs to free up system memory.
Lifecycle StateTriggering ActionJavaScript EventAction Required by Developer
HiddenUser presses home button or locks device.visibilitychangeSave unsaved user draft inputs and application state.
FrozenOS pauses execution after being backgrounded.freezeStop background UI animations and interval timers.
TerminatedSystem terminates background process.None (fired via system unload)No reliable real-time event. State must be pre-saved during Hidden state.

Why the Unload Event Cannot Be Trusted

Historically, web developers relied on the unload and beforeunload events to clean up sessions or warn users about unsaved changes. In modern mobile browsers and especially within Android TWA environments, these events are highly unreliable. If Android decides to kill a background application process, it does so instantly and without executing the unload callbacks. Relying on unload to save database states or sync local variables will inevitably lead to data loss. All state preservation logic must instead be triggered during the transition to the Hidden state, which is guaranteed to fire before an application is frozen or killed.

Implementing State Preservation with JS

To implement state preservation, your application should monitor changes in visibility. When the page enters the hidden state, you should write the current application state to a durable web storage solution, such as local storage or IndexedDB.

The following example shows how to register a listener to detect when the TWA transitions to the background, allowing you to save input values from a form:

document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'hidden') {
const formData = {
userTitle: document.getElementById('post-title').value,
userContent: document.getElementById('post-body').value,
lastSaved: Date.now()
};
localStorage.setItem('twa_draft_state', JSON.stringify(formData));
}
});

Additionally, you should listen to the freeze event to pause intensive background processes, such as WebRTC streams, canvas animations, or WebSockets, preventing resource leaks while the app is frozen by the OS:

window.addEventListener('freeze', () => {
if (webSocketConnection) {
webSocketConnection.close();
}
});

Restoring Application State on Restart

When the user returns to the TWA after it has been terminated, your initialization script must check for saved states. If a valid, non-expired state exists, your application should bypass the default loading view and pre-fill the layout with the cached data. This creates the illusion that the application remained running in memory the entire time.

window.addEventListener('DOMContentLoaded', () => {
const savedStateStr = localStorage.getItem('twa_draft_state');
if (savedStateStr) {
const state = JSON.parse(savedStateStr);
const isRecent = (Date.now() - state.lastSaved) < (1000 * 60 * 60 * 24);
if (isRecent) {
document.getElementById('post-title').value = state.userTitle;
document.getElementById('post-body').value = state.userContent;
localStorage.removeItem('twa_draft_state');
}
}
});

Managing Single Page App Routing

In addition to form data, single-page applications (SPAs) must preserve the user's active route. If a user is deep inside a multi-step checkout funnel and the app is backgrounded, they should return exactly to that step upon re-opening. Store the current router path in your state object on visibility change. When initializing the application, read the saved path from storage and programmatically route the user to that URL instead of the default manifest start URL. This mimics the standard navigation preservation behavior expected of high-quality native Android applications.

Ready to ship your Android app?

Paste your PWA URL, get a signed APK and a Google Play ready AAB in minutes.

Build my app