16

I have a sharedPreference object, and I want to make it as dependency inject component through the project.

// sharedPreference object
private const val PREF_TAG = "tag"
object MyPreference {
    fun getStoredTag(context: Context): String {
        val prefs = PreferenceManager.getDefaultSharedPreferences(context)
        return prefs.getString(PREF_TAG, "")!!
    }
    fun setStoredTag(context: Context, query: String) {
        PreferenceManager.getDefaultSharedPreferences(context)
            .edit()
            .putString(PREF_TAG, query)
            .apply()
    }
}

// How to correctly inject the sharedPreference?
// create a module?
@Module
@InstallIn(SingletonComponent::class)
object PreferenceModule {
    @Provides
    @Singleton
    fun provideSharedPreference(): SharedPreferences {
        return MyPreference()
    }
}
// or directly inject in viewModel
class LoginViewModel @ViewModelInject constructor(
    application: Application,
    myPreference: MyPreference
) : AndroidViewModel(application) {
    ...
}
// or another way?
ccd
  • 5,788
  • 10
  • 46
  • 96

3 Answers3

22

This is a bit of an opinion-based answer, but at least I will give you a direction to follow.


You'd usually maintain an instance of SharedPreferences within your wrapper class. So...

  1. Use a regular class instead of an object declaration
  2. Since you want to have a Hilt setup, you can directly use hilt annotations for the class, directly injecting Context into the constructor
@Singleton
class MyPreference @Inject constructor(@ApplicationContext context : Context){
    val prefs = PreferenceManager.getDefaultSharedPreferences(context)

    fun getStoredTag(): String {
        return prefs.getString(PREF_TAG, "")!!
    }
    fun setStoredTag(query: String) {
        prefs.edit().putString(PREF_TAG, query).apply()
    }
}
  1. Then you don't need a Module, you can simply use the @ViewModelInject
class LoginViewModel @ViewModelInject constructor(
    application: Application,
    myPreference: MyPreference
) : AndroidViewModel(application) {
    ...
}
Bartek Lipinski
  • 30,698
  • 10
  • 94
  • 132
14

with Hilt 2.38.1:

SharedPreferencesModule.kt

@Module
@InstallIn(SingletonComponent::class)
class SharedPreferencesModule {

    @Singleton
    @Provides
    fun provideSharedPreference(@ApplicationContext context: Context): SharedPreferences {
        return context.getSharedPreferences("preferences_name", Context.MODE_PRIVATE)
    }
}

CustomViewModel.kt

@HiltViewModel
class CustomViewModel @Inject constructor(
    private val sharedPreferences: SharedPreferences
):ViewModel() {

    fun customFunction() {
        sharedPreferences.edit().putString("firstStoredString", "this is the content").apply()

        val firstStoredString = sharedPreferences.getString("firstStoredString", "")
    }
    ...
ivoroto
  • 925
  • 12
  • 12
-1

I believe in simplicity and elegance, and so I created a tiny wrapper to stay away from all the boilerplate Android has been forcing on us when using SharePreferences.

Accessing a shared pref with my Hilt-based wrapper is as easy as this:

@Inject
lateinit var hasDisplayedGettingStartedScreenPref: HasDisplayedGettingStartedScreenPref

...

fun showGettingStartedIfNeeded() {
    // hasDisplayedGettingStartedScreenPref has a default value of false when not set
    if (!hasDisplayedGettingStartedScreenPref.value) {
        showGettingStarted()
        hasDisplayedGettingStartedScreenPref.value = true
    }
}

To define a shared pref:

class HasDisplayedGettingStartedScreenPref 
    @Inject constructor(sharedPreferences: SharedPreferences)
    : NonNullBooleanApplicationPref(sharedPreferences, defaultValue = false)

Helper code:

@Module
@InstallIn(SingletonComponent::class)
class SharedPreferencesModule {

    @Singleton
    @Provides
    fun provideSharedPreference(@ApplicationContext context: Context): SharedPreferences {
        return context.getSharedPreferences(context.packageName + "_preferences", Context.MODE_PRIVATE)
    }
}

@Suppress("unused")
abstract class NullableLongApplicationPref(private val sharedPreferences: SharedPreferences) : ApplicationPref() {
    var value: Long?
        get() = if (sharedPreferences.contains(prefName)) sharedPreferences.getLong(prefName, -1) else null
        set(value) = value?.let { sharedPreferences.edit().putLong(prefName, value).apply() }
            ?: run { sharedPreferences.edit().remove(prefName).apply() }
}

@Suppress("unused")
abstract class NonNullLongApplicationPref(private val sharedPreferences: SharedPreferences, private val defaultValue: Long) : ApplicationPref() {
    var value: Long
        get() = sharedPreferences.getLong(prefName, defaultValue)
        set(value) = sharedPreferences.edit().putLong(prefName, value).apply()
}

@Suppress("unused")
abstract class NullableIntApplicationPref(private val sharedPreferences: SharedPreferences) : ApplicationPref() {
    var value: Int?
        get() = if (sharedPreferences.contains(prefName)) sharedPreferences.getInt(prefName, -1) else null
        set(value) = value?.let { sharedPreferences.edit().putInt(prefName, value).apply() }
            ?: run { sharedPreferences.edit().remove(prefName).apply() }
}

@Suppress("unused")
abstract class NonNullIntApplicationPref(private val sharedPreferences: SharedPreferences, private val defaultValue: Int) : ApplicationPref() {
    var value: Int
        get() = sharedPreferences.getInt(prefName, defaultValue)
        set(value) = sharedPreferences.edit().putInt(prefName, value).apply()
}

@Suppress("unused")
abstract class NullableBooleanApplicationPref(private val sharedPreferences: SharedPreferences) : ApplicationPref() {
    var value: Boolean?
        get() = if (sharedPreferences.contains(prefName)) sharedPreferences.getBoolean(prefName, false) else null
        set(value) = value?.let { sharedPreferences.edit().putBoolean(prefName, value).apply() }
            ?: run { sharedPreferences.edit().remove(prefName).apply() }
}

@Suppress("unused")
abstract class NonNullBooleanApplicationPref(private val sharedPreferences: SharedPreferences, private val defaultValue: Boolean) : ApplicationPref() {
    var value: Boolean
        get() = sharedPreferences.getBoolean(prefName, defaultValue)
        set(value) = sharedPreferences.edit().putBoolean(prefName, value).apply()
}

@Suppress("unused")
abstract class NullableStringApplicationPref(private val sharedPreferences: SharedPreferences) : ApplicationPref() {
    var value: String?
        get() = sharedPreferences.getString(prefName, null)
        set(value) = sharedPreferences.edit().putString(prefName, value).apply()
}

@Suppress("unused")
abstract class NonNullStringApplicationPref(private val sharedPreferences: SharedPreferences, private val defaultValue: String) : ApplicationPref() {
    var value: String
        get() = sharedPreferences.getString(prefName, defaultValue)!!
        set(value) = sharedPreferences.edit().putString(prefName, value).apply()
}

abstract class ApplicationPref() {
    protected val prefName = this.javaClass.simpleName
}

My wrapper handles both nullable and non-nullable preferences (you need to provide a default value then). No need to check with exists(), as a preference will return null if it's not been set before.

javaxian
  • 1,815
  • 1
  • 21
  • 26