How to store API cookies persistently on Android?


I am developing an app that's primary function is to talk to a web server when the user presses a button and then intermittently in the background while the app may be closed. I'm currently trying to figure out how to save the cookies in a way that will persist them across the app closing and opening. To do this I'm attempting to use the preferences data store (following the android basics), but when I'm attempting to collect the flows to get as regular strings to place into the API requests, the function blocks indefinitely.

class CookieRepository (private val dataStore : DataStore<Preferences>) { private companion object { val CSRF = stringPreferencesKey("csrf") val SESSION = stringPreferencesKey("session") } val TAG = "COOKIE_REPOSITORY" suspend fun saveCsrf(csrfToken : String) { dataStore.edit { preferences -> preferences[CSRF] = csrfToken } } suspend fun saveSession(sessionToken : String) { dataStore.edit {preferences -> preferences[SESSION] = sessionToken} } val csrfToken : Flow<String> = dataStore.data .catch { if(it is IOException) { Log.e(TAG, "Error reading preferences.", it) emit(emptyPreferences()) } else { throw it } } .map{ preferences -> preferences[CSRF] ?: "No Cookie" } val sessionToken : Flow<String> = dataStore.data .catch { if(it is IOException) { Log.e(TAG, "Error reading preferences.", it) emit(emptyPreferences()) } else { throw it } } .map{ preferences -> preferences[SESSION] ?: "No Cookie" } suspend fun updateCookiesFromHeaders(headers : Headers) { for (header in headers) { if (header.first == "Set-Cookie") { val name = header.second.split("=")[0] val value = header.second.split("=")[1].split(";")[0] if (name == "csrftoken") { saveCsrf(value) } else if (name == "sessionid") { saveSession(value) } } } } suspend fun getCookiesAsString() : String { var cookieString = "" csrfToken.collect { if (it != "No Cookie") {cookieString = cookieString.plus("csrftoken=$it;")} } sessionToken.collect { if (it != "No Cookie") {cookieString = cookieString.plus("sessionid=$it;")} } return cookieString.dropLast(1) } suspend fun getCsrfToken() : String { var token = "" csrfToken.collect {token = it} return token } }

The CookieRepository is created in my Application.kt injected into my view model, which calls the functions updateCookiesFromHeaders ,getCookiesAsString and getCsrfToken before making the request.

1
Mar 22 at 4:43 PM
User Avataruser32522523
#android#kotlin#cookies#kotlin-flow

Accepted Answer

You need a class that can serialize Cookie objects into strings to save them in SharedPreferences.

class PersistentCookieJar(context: Context) : CookieJar {
    private val sharedPrefs = context.getSharedPreferences("api_cookies", Context.MODE_PRIVATE)
    private val cookies = mutableSetOf<Cookie>()

    override fun saveFromResponse(url: HttpUrl, cookieList: List<Cookie>) {
        cookies.addAll(cookieList)
        // Serialize and save to SharedPreferences here
        sharedPrefs.edit().apply {
            cookieList.forEach { putString(it.name, serialize(it)) }
            apply()
        }
    }

    override fun loadForRequest(url: HttpUrl): List<Cookie> {
        // Filter cookies that haven't expired and match the domain
        return cookies.filter { it.matches(url) && it.expiresAt > System.currentTimeMillis() }
    }
}

Add it into your client builder

val okHttpClient = OkHttpClient.Builder()
    .cookieJar(PersistentCookieJar(applicationContext))
    .build()

// If using Retrofit:
val retrofit = Retrofit.Builder()
    .client(okHttpClient)
    .baseUrl("https://api.yourdomain.com/")
    .build()
User AvatarMax
Mar 22 at 4:48 PM
0