Android EncryptedSharedPreferences With Jetpack Compose (Alt to Secure Jetpack Datastore)

Edit module build.gradle

dependencies {
    implementation("androidx.security:security-crypto:1.1.0-alpha04")
}

Edit AppSingleton.kt

import android.content.Contextimport android.content.SharedPreferencesimport androidx.security.crypto.EncryptedSharedPreferencesimport androidx.security.crypto.MasterKeyclass AppSingleton {    companion object {        private var secureSharedPreferences: SharedPreferences? = null        fun getSecurePreferences(context: Context): SharedPreferences {            return secureSharedPreferences ?: let {                val masterKey: MasterKey = MasterKey.Builder(context)                    .setKeyScheme(MasterKey.KeyScheme.AES256_GCM)                    .build()                secureSharedPreferences = EncryptedSharedPreferences.create(                    context,                    "secureSharedPrefs",                    masterKey,                    EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,                    EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM                )                secureSharedPreferences!!            }        }    }}

Remove secureSharedPrefs from backup rules.

Edit AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.luasoftware.sampleApp">

    <application
        android:name=".sampleApp"
        android:allowBackup="true"
        android:dataExtractionRules="@xml/data_extraction_rules"
        android:fullBackupContent="@xml/backup_rules"
        >
    </application>

</manifest>

NOTE: If allowBackup=false, setup for dataExtractionRules and fullBackupContent is not required.

Edit @xml/backup_rules

<?xml version="1.0" encoding="utf-8"?><!--
  Sample backup rules file; uncomment and customize as necessary.
  See https://developer.android.com/guide/topics/data/autobackup
  for details.
  Note: This file is ignored for devices older that API 31
  See https://developer.android.com/about/versions/12/backup-restore
-->
<full-backup-content>
  <!--
  <include domain="sharedpref" path="."/>
  <exclude domain="sharedpref" path="device.xml"/>
  -->
  <exclude domain="sharedpref" path="secureSharedPrefs"/>
</full-backup-content>

Edit @xml/data_extraction_rules

<?xml version="1.0" encoding="utf-8"?><!--
   Sample data extraction rules file; uncomment and customize as necessary.
   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
   for details.
-->
<data-extraction-rules>
    <cloud-backup>
        <!-- TODO: Use <include> and <exclude> to control what is backed up.
        <include .../>
        <exclude .../>
        -->
        <exclude domain="sharedpref" path="secureSharedPrefs"/>
    </cloud-backup>
    <!--
    <device-transfer>
        <include .../>
        <exclude .../>
    </device-transfer>
    -->
</data-extraction-rules>

ViewModel

class HomeViewModel() : ViewModel() {    private const val PREFERENCES_PASSCODE = "passcode"    fun isPasscodeAvailable(context: Context): Boolean {        val sharedPreferences = AppSingleton.getSecurePreferences(context)        return sharedPreferences.getString(PREFERENCES_PASSCODE, null) != null    }    fun setPasscode(context: Context, passcode: String?) {        val sharedPreferences = AppSingleton.getSecurePreferences(context)        with (sharedPreferences.edit()) {            if (passcode == null)                remove(PREFERENCES_PASSCODE)            else                putString(PREFERENCES_PASSCODE, passcode)            apply()        }    }}

Usage in compose

@Composablefun HomeScreen(    viewModel: HomeViewModel = viewModel()) {    val context = LocalContext.current    var isPasscodeAvailable by remember { mutableStateOf(viewModel.isPasscodeAvailable(context)) }    fun submit(passcode: String) {      viewModel.setPasscode(context, "1234")    }    Button(onClick = { submit("1234") }) {        Text("Set Passcode")    }    if (isPasscodeAvailable) {        Button(onClick = { submit(null) }) {            Text("Remove Passcode")        }    }}

References:

❤️ Is this article helpful?

Buy me a coffee ☕ or support my work via PayPal to keep this space 🖖 and ad-free.

Do send some 💖 to @d_luaz or share this article.

✨ By Desmond Lua

A dream boy who enjoys making apps, travelling and making youtube videos. Follow me on @d_luaz

👶 Apps I built

Travelopy - discover travel places in Malaysia, Singapore, Taiwan, Japan.