# How gsygithubappcompose Persists User Session Across App Restarts Using Jetpack DataStore

> Learn how gsygithubappcompose Android app persists user sessions across restarts using Jetpack DataStore to store authentication tokens. Auto-restored on app launch.

- Repository: [Shuyu Guo/gsygithubappcompose](https://github.com/carguo/gsygithubappcompose)
- Tags: internals
- Published: 2026-02-26

---

**The gsygithubappcompose Android app persists user sessions across restarts using Jetpack DataStore to store authentication tokens, which are automatically restored on app launch by a custom OkHttp interceptor.**

The open-source GitHub client application `carguo/gsygithubappcompose` implements robust user session persistence to maintain authentication state across process terminations and device reboots. By leveraging **Jetpack DataStore**—Google's modern replacement for SharedPreferences—the app securely stores OAuth tokens and user credentials in persistent storage. This implementation ensures users remain logged in after restarting the app without requiring manual re-authentication.

## Storing Credentials in DataStore

The session persistence layer centers on `UserPreferencesDataStore`, which defines the schema for storing authentication data and handles read/write operations.

### Preferences Keys and Storage File

Located in [`core/common/src/main/java/com/shuyu/gsygithubappcompose/core/common/datastore/UserPreferencesDataStore.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/core/common/src/main/java/com/shuyu/gsygithubappcompose/core/common/datastore/UserPreferencesDataStore.kt) (lines 15-38), the DataStore manages three distinct `PreferencesKeys`: `auth_token`, `username`, and `user_id`. These keys correspond to values stored in a file named *user_preferences* on the device. The class exposes these values as Kotlin `Flow` properties (`authToken`, `username`, `userId`), enabling reactive observation of session state changes without blocking the main thread.

### Login Flow Implementation

The `UserRepository` class handles credential persistence through its `login` and `loginWithOAuth` methods in [`data/src/main/java/com/shuyu/gsygithubappcompose/data/repository/UserRepository.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/data/src/main/java/com/shuyu/gsygithubappcompose/data/repository/UserRepository.kt) (lines 34-40). Upon successful authentication, these methods immediately write to the DataStore:

```kotlin
// Login – stores the token in DataStore
suspend fun login(token: String): Result<User> = try {
    val user = apiService.getAuthenticatedUser("token $token")
    preferencesDataStore.saveAuthToken(token)   // ← persisted
    preferencesDataStore.saveUsername(user.login)
    preferencesDataStore.saveUserId(user.id.toString())
    Result.success(user)
} catch (e: Exception) { Result.failure(e) }

```

This synchronous write operation ensures the token is committed to disk before the login flow completes, guaranteeing persistence across immediate app termination.

## Restoring the Session on App Start

The critical mechanism for maintaining session continuity lies in how the app retrieves stored credentials when the process restarts.

### TokenInterceptor Initialization

The `TokenInterceptor` class in [`core/network/src/main/java/com/shuyu/gsygithubappcompose/core/network/interceptor/TokenInterceptor.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/core/network/src/main/java/com/shuyu/gsygithubappcompose/core/network/interceptor/TokenInterceptor.kt) serves as the gatekeeper for all authenticated network requests. During its `init` block (lines 25-31), it subscribes to the `authToken` flow from `UserPreferencesDataStore`, populating an in-memory `MutableStateFlow` named `_token`:

```kotlin
// Initialization block subscribing to DataStore changes
init {
    scope.launch {
        userPreferencesDataStore.authToken.collect { token ->
            _token.value = token
        }
    }
}

```

### Synchronous Fallback for First Request

To handle race conditions where a network request occurs before the asynchronous flow emits a value, the interceptor implements a synchronous fallback mechanism. If `_token` is null when intercepting a request, it blocking-reads the first value from DataStore using `authToken.first()` (lines 39-43) and caches it immediately:

```kotlin
override fun intercept(chain: Interceptor.Chain): Response {
    var request = chain.request()
    var currentToken = _token.value
    if (currentToken == null) {
        // read synchronously from DataStore on first use
        currentToken = runBlocking { userPreferencesDataStore.authToken.first() }
        _token.value = currentToken
    }
    // ... add header
}

```

This ensures that even the very first network request after app startup includes valid authentication credentials.

## Authenticating Network Requests

Once restored, the token is applied to every outgoing HTTP call. The interceptor checks the cached `_token` value and injects the `Authorization: token <token>` header into the request builder (lines 45-50):

```kotlin
currentToken?.let {
    request = request.newBuilder()
        .header("Authorization", "token $it")
        .build()
}
return chain.proceed(request)

```

Because the token is maintained in both persistent storage (DataStore) and memory (`_token`), the app seamlessly maintains authentication state across configuration changes and process restarts.

## Clearing the Session on Logout

Session termination requires wiping both persistent storage and memory caches to prevent credential leakage.

The `UserRepository.logout()` method orchestrates this cleanup by calling `preferencesDataStore.clearAll()` to remove all three keys from the DataStore, followed by wiping the local Room database (lines 73-76). Additionally, `TokenInterceptor` provides a `clearAuthorization()` method (lines 55-60) that explicitly nulls the in-memory `_token` state:

```kotlin
// Logout – clears persisted session
suspend fun logout() {
    preferencesDataStore.clearAll()   // removes token, username, user_id
    appDatabase.clearAllData()
}

```

## Summary

- **Jetpack DataStore** serves as the persistent storage mechanism for authentication tokens, replacing legacy SharedPreferences in the `gsygithubappcompose` architecture.
- **UserPreferencesDataStore** defines three preference keys (`auth_token`, `username`, `user_id`) backed by the *user_preferences* file, exposing them as reactive Kotlin Flows for asynchronous access.
- **TokenInterceptor** automatically restores the session on app startup by subscribing to the DataStore flow and implementing a synchronous fallback using `first()` for immediate request handling.
- **UserRepository** handles both persistence during login (`saveAuthToken`) and complete cleanup during logout (`clearAll`), ensuring no credential remnants survive session termination.

## Frequently Asked Questions

### What storage mechanism does gsygithubappcompose use for session persistence?

The application uses **Jetpack DataStore**, specifically `Preferences DataStore`, to persist the `auth_token`, `username`, and `user_id` values. This modern storage solution replaces SharedPreferences and provides type safety, asynchronous operations via Kotlin coroutines, and transactional consistency.

### How does the app handle the first network request before DataStore loads?

The `TokenInterceptor` implements a synchronous fallback mechanism using `runBlocking` to call `userPreferencesDataStore.authToken.first()`. This blocking read ensures that even if the asynchronous flow hasn't emitted a value yet, the first request can still retrieve the stored token from disk immediately without waiting for the flow collection.

### Where is the authentication token physically stored on the device?

The token is stored in a file named *user_preferences* managed by the DataStore API, located in the app's private data directory. The `UserPreferencesDataStore` class in [`core/common/src/main/java/com/shuyu/gsygithubappcompose/core/common/datastore/UserPreferencesDataStore.kt`](https://github.com/carguo/gsygithubappcompose/blob/main/core/common/src/main/java/com/shuyu/gsygithubappcompose/core/common/datastore/UserPreferencesDataStore.kt) abstracts access to this file using the `auth_token` preference key (lines 15-20).

### How is the session cleared when a user logs out?

Logging out triggers `UserRepository.logout()`, which invokes `preferencesDataStore.clearAll()` (lines 73-76) to delete all authentication keys from DataStore, followed by `appDatabase.clearAllData()` to wipe local caches. The `TokenInterceptor` also provides `clearAuthorization()` (lines 55-60) to reset the in-memory `_token` state, ensuring complete session termination.