0

Normally, I use Code A or Code B to read or write SharedPreferences.

At present, I update my project to use "androidx.preference:preference-ktx:1.1.1" with Kotlin.

Is there a better way to read and write SharedPreferences when I use "androidx.preference:preference-ktx:1.1.1" with Kotlin ?

Code A

SharedPreferences prfs = getSharedPreferences("AUTHENTICATION_FILE_NAME", Context.MODE_PRIVATE);
String Astatus = prfs.getString("Authentication_Status", "");

Code B

SharedPreferences preferences = getSharedPreferences("AUTHENTICATION_FILE_NAME", Context.MODE_PRIVATE);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("Authentication_Id",userid.getText().toString());
editor.putString("Authentication_Password",password.getText().toString());
editor.putString("Authentication_Status","true");
editor.apply();
HelloCW
  • 843
  • 22
  • 125
  • 310

4 Answers4

6

If you're not using a dependency injection tool such as hilt, koin, etc, it's better to make a singleton class which manages preferences values to not to obtain a SharedPreferences object each time you want to read or write a value. SingletonHolder helps you make a singleton class with parameters in a thread-safe manner.

Otherwise, if you're using a dependency injection tool in your project, you can skip the singleton part of the below solution and let the DI tool do it.


PrefManager.kt

import android.content.Context

class PrefManager private constructor(context: Context) {

    // ------- Preference Variables

    var authenticationId: String?
        get() = pref.getString("KEY_AUTHENTICATION_ID", null)
        set(value) = pref.edit { putString("KEY_AUTHENTICATION_ID", value) }

    var authenticationStatus: Boolean
        get() = pref.getBoolean("KEY_AUTHENTICATION_STATUS", false)
        set(value) = pref.edit { putBoolean("KEY_AUTHENTICATION_STATUS", value) }

    // ---------------------------------------------------------------------------------------------

    private val pref = context.getSharedPreferences(FILE_NAME, Context.MODE_PRIVATE)

    fun clear() = pref.edit { clear() }

    companion object : SingletonHolder<PrefManager, Context>(::PrefManager) {
        private const val FILE_NAME = "AUTHENTICATION_FILE_NAME"
    }
}

SingletonHolder.kt

open class SingletonHolder<out T, in A>(private val constructor: (A) -> T) {

    @Volatile
    private var instance: T? = null

    fun getInstance(arg: A): T {
        return when {
            instance != null -> instance!!
            else -> synchronized(this) {
                if (instance == null) instance = constructor(arg)
                instance!!
            }
        }
    }
}

Usage:

Now we can read a value like the following:

val authenticationId = PrefManager.getInstance(context).authenticationId

and in order to write:

PrefManager.getInstance(context).authenticationId = "SOME VALUE"
aminography
  • 21,986
  • 13
  • 70
  • 74
1

without writing own extra code, for your code B snippet, you can do this with the ktx library

val preferences = getSharedPreferences("AUTHENTICATION_FILE_NAME", MODE_PRIVATE)
preferences.edit {
   putString("Authentication_Id", userid.getText().toString())
   putString("Authentication_Password", password.getText().toString())
   putString("Authentication_Status", "true")
}
Minki
  • 934
  • 5
  • 14
0

In your Pref.class file give Context to the constructor.

    class Prefs constructor(
            private val context: Context
        ) {
    
       private fun getSharedPreferences(prefsName: String) =
            context.getSharedPreferences(prefsName, Context.MODE_PRIVATE)
    
        private val AUTH_PHONE = "auth_phone"
        private val KEY_AUTH_PHONE = "auth_phone"
        private val authPhone by lazy { getSharedPreferences(AUTH_PHONE) }
        
        var phoneNumber: String
            get() {
                return authPhone.getString(KEY_AUTH_PHONE, null) ?: return ""
            }
            set(value) {
                authPhone.edit()
                    .putString(KEY_AUTH_PHONE, value)
                    .apply()
            } 
}

Then you can use it in your activity.

  Pref(context).phoneNumber = "998998578086"
0

If you want to take it a step further and improve security, you can use the encrypted sharedPreferences in parallel, since it's only for Android M and above. It's really easy if you're using DI:

interface SharedPrefs {
    var accessToken: String?
    fun get(): SharedPreferences
}

class SimplePreferences(context: Context) : SharedPrefs {
    private val preferences: SharedPreferences =
        context.getSharedPreferences("myapp_preferences", Context.MODE_PRIVATE)

    override fun get(): SharedPreferences = preferences

    override var accessToken: String?
        get() = preferences.getString(MyApp.ACCESS_TOKEN_PREF_KEY, null)
        set(value) = preferences.edit().putString(MyApp.ACCESS_TOKEN_PREF_KEY, value)
            .apply()
}

@TargetApi(Build.VERSION_CODES.M)
class EncryptedSharedPrefs(val context: Context) : SharedPrefs {

    private var encryptedPrefs: SharedPreferences = EncryptedSharedPreferences.create(
        context,
        "myapp_encrypted_prefs",
        MasterKey.Builder(context).build(),
        EncryptedSharedPreferences.PrefKeyEncryptionScheme.AES256_SIV,
        EncryptedSharedPreferences.PrefValueEncryptionScheme.AES256_GCM
    )

    override fun get(): SharedPreferences = encryptedPrefs

    override var accessToken: String?
        get() = encryptedPrefs.getString(MyApp.ACCESS_TOKEN_PREF_KEY, null)
        set(value) = encryptedPrefs.edit().putString(
            MyApp.ACCESS_TOKEN_PREF_KEY,
            value
        ).apply()
}

koin module:

single {
   if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
         EncryptedSharedPrefs(applicationContext)
   } else
         SimplePreferences(applicationContext)
   }
}

Usage:

val sharedPrefs by inject<SharedPrefs>()

// read 
println(sharedPrefs.accessToken)

// write
sharedPrefs.accessToken = "qwerty"
Eliza Camber
  • 1,536
  • 1
  • 13
  • 21