LiveData with shared preferences
Asked Answered
P

9

38

I have a settings screen where I am setting some values. When I set those values it gets saved in shared preferences and these values are needed in my request to the network api call as parameters.

Now I can use a listener for shared preferences in my activity then make an api call and get fresh data, but I want to use this with LiveData.

How can I listen for changes in the shared preferences using LiveData and then make the network call using the new parameters.

Phocaea answered 1/6, 2018 at 18:16 Comment(1)
gist.github.com/rharter/1df1cd72ce4e9d1801bd2d49f2a96810Apocynaceous
B
14

Java Code by Idish, though he commented here, but not sure, why he didn't add it as answer.

Pasting the same code below:

public abstract class SharedPreferenceLiveData<T> extends LiveData<T> {

SharedPreferences sharedPrefs;
String key;
public T defValue;

public SharedPreferenceLiveData(SharedPreferences prefs, String key, T defValue) {
    this.sharedPrefs = prefs;
    this.key = key;
    this.defValue = defValue;
}

private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (SharedPreferenceLiveData.this.key.equals(key)) {
            setValue(getValueFromPreferences(key, defValue));
        }
    }
};
abstract T getValueFromPreferences(String key, T defValue);

@Override
protected void onActive() {
    super.onActive();
    setValue(getValueFromPreferences(key, defValue));
    sharedPrefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
}

@Override
protected void onInactive() {
    sharedPrefs.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener);
    super.onInactive();
}
public SharedPreferenceLiveData<Boolean> getBooleanLiveData(String key, Boolean defaultValue) {
    return new SharedPreferenceBooleanLiveData(sharedPrefs,key, defaultValue);
}
}

SharedPreferenceBooleanLiveData class

public class SharedPreferenceBooleanLiveData extends SharedPreferenceLiveData<Boolean>{

public SharedPreferenceBooleanLiveData(SharedPreferences prefs, String key, Boolean defValue) {
    super(prefs, key, defValue);
}

@Override
Boolean getValueFromPreferences(String key, Boolean defValue) {
    return sharedPrefs.getBoolean(key, defValue);
}

}

Calling this like the below code mentioned in this link:

 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
 SharedPreferenceStringLiveData sharedPreferenceStringLiveData = new SharedPreferenceStringLiveData(preferences, "cid", "");
 sharedPreferenceStringLiveData.getStringLiveData("cid", "").observe(this, cid -> {
            Toast.makeText(this, "Change in CID "+cid, Toast.LENGTH_SHORT).show();
 });

Again if using a Preference Helper class, the class can be called as below: Just a sample:

public class PreferenceManager {
private SharedPreferenceBooleanLiveData sharedPreferenceLiveData;

public SharedPreferenceBooleanLiveData getSharedPrefs(){
        return sharedPreferenceLiveData;
    }

public void setSharedPreferences(String key, boolean value) {

        SharedPreferences userDetails = context.getSharedPreferences(APP_PREFERENCE,
            Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = userDetails.edit();
        editor.putBoolean(key, value);
        editor.apply();
        sharedPreferenceLiveData = new SharedPreferenceBooleanLiveData(userDetails,key,value);
    }
}

And in the activity class call it as below: Create an object:

SharedPreferenceBooleanLiveData sharedPreferenceLiveData;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    sharedPreferenceLiveData = preferenceManager.getSharedPrefs();
}

And observe it as follows:

sharedPreferenceLiveData.getBooleanLiveData(PreferenceKey.KEY_LOCATION_PERMISSION,false).observe(this,check->{
        if(check){
            setPermissionGranted(check);
        }
    });
Billfold answered 17/7, 2019 at 10:53 Comment(2)
In my case using SharedPreferenceStringLiveData. For the same value (example, first storing "First" and second time also string "First") , the onSharedPreferenceChanged won't call.Merocrine
One can Overrride getVale() as well and return value directly from shared preference. It will be helpful if someone wants to check value of livedata at a given time but do not want to observe it. //Add this in your SharedPreferenceLiveData class override fun getValue(): T? { return getValueFromPreferences(key, defValue) }Quiz
K
40

The following awesome piece of code is LiveData Implementation of SharedPreference. It works perfectly.

package com.chargingwatts.chargingalarm.util.preference;

import android.arch.lifecycle.LiveData
import android.content.SharedPreferences

abstract class SharedPreferenceLiveData<T>(val sharedPrefs: SharedPreferences,
                                           val key: String,
                                           val defValue: T) : LiveData<T>() {

    private val preferenceChangeListener = SharedPreferences.OnSharedPreferenceChangeListener { sharedPreferences, key ->
        if (key == this.key) {
            value = getValueFromPreferences(key, defValue)
        }
    }

    abstract fun getValueFromPreferences(key: String, defValue: T): T

    override fun onActive() {
        super.onActive()
        value = getValueFromPreferences(key, defValue)
        sharedPrefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener)
    }

    override fun onInactive() {
        sharedPrefs.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener)
        super.onInactive()
    }
}

class SharedPreferenceIntLiveData(sharedPrefs: SharedPreferences, key: String, defValue: Int) :
        SharedPreferenceLiveData<Int>(sharedPrefs, key, defValue) {
    override fun getValueFromPreferences(key: String, defValue: Int): Int = sharedPrefs.getInt(key, defValue)
}

class SharedPreferenceStringLiveData(sharedPrefs: SharedPreferences, key: String, defValue: String) :
        SharedPreferenceLiveData<String>(sharedPrefs, key, defValue) {
    override fun getValueFromPreferences(key: String, defValue: String): String = sharedPrefs.getString(key, defValue)
}

class SharedPreferenceBooleanLiveData(sharedPrefs: SharedPreferences, key: String, defValue: Boolean) :
        SharedPreferenceLiveData<Boolean>(sharedPrefs, key, defValue) {
    override fun getValueFromPreferences(key: String, defValue: Boolean): Boolean = sharedPrefs.getBoolean(key, defValue)
}

class SharedPreferenceFloatLiveData(sharedPrefs: SharedPreferences, key: String, defValue: Float) :
        SharedPreferenceLiveData<Float>(sharedPrefs, key, defValue) {
    override fun getValueFromPreferences(key: String, defValue: Float): Float = sharedPrefs.getFloat(key, defValue)
}

class SharedPreferenceLongLiveData(sharedPrefs: SharedPreferences, key: String, defValue: Long) :
        SharedPreferenceLiveData<Long>(sharedPrefs, key, defValue) {
    override fun getValueFromPreferences(key: String, defValue: Long): Long = sharedPrefs.getLong(key, defValue)
}

class SharedPreferenceStringSetLiveData(sharedPrefs: SharedPreferences, key: String, defValue: Set<String>) :
        SharedPreferenceLiveData<Set<String>>(sharedPrefs, key, defValue) {
    override fun getValueFromPreferences(key: String, defValue: Set<String>): Set<String> = sharedPrefs.getStringSet(key, defValue)
}

fun SharedPreferences.intLiveData(key: String, defValue: Int): SharedPreferenceLiveData<Int> {
    return SharedPreferenceIntLiveData(this, key, defValue)
}

fun SharedPreferences.stringLiveData(key: String, defValue: String): SharedPreferenceLiveData<String> {
    return SharedPreferenceStringLiveData(this, key, defValue)
}

fun SharedPreferences.booleanLiveData(key: String, defValue: Boolean): SharedPreferenceLiveData<Boolean> {
    return SharedPreferenceBooleanLiveData(this, key, defValue)
}

fun SharedPreferences.floatLiveData(key: String, defValue: Float): SharedPreferenceLiveData<Float> {
    return SharedPreferenceFloatLiveData(this, key, defValue)
}

fun SharedPreferences.longLiveData(key: String, defValue: Long): SharedPreferenceLiveData<Long> {
    return SharedPreferenceLongLiveData(this, key, defValue)
}

fun SharedPreferences.stringSetLiveData(key: String, defValue: Set<String>): SharedPreferenceLiveData<Set<String>> {
    return SharedPreferenceStringSetLiveData(this, key, defValue)
}
Kramlich answered 28/10, 2018 at 5:7 Comment(3)
Here's a java code version of this beautiful piece: gist.github.com/idish/f46a8327da7f293f943a5bda31078c95Hinkley
For someone who is wondering why sharedPrefs.getString(key, defValue) could be null, this post explains it well.Postoperative
This can be improved by having the abstract class override getValue() and returning non-nullable super.getValue() ?: getValueFromPreferences(key, defValue), so non-nullable value can also be retrieved directly without observing.Chargeable
B
14

Java Code by Idish, though he commented here, but not sure, why he didn't add it as answer.

Pasting the same code below:

public abstract class SharedPreferenceLiveData<T> extends LiveData<T> {

SharedPreferences sharedPrefs;
String key;
public T defValue;

public SharedPreferenceLiveData(SharedPreferences prefs, String key, T defValue) {
    this.sharedPrefs = prefs;
    this.key = key;
    this.defValue = defValue;
}

private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (SharedPreferenceLiveData.this.key.equals(key)) {
            setValue(getValueFromPreferences(key, defValue));
        }
    }
};
abstract T getValueFromPreferences(String key, T defValue);

@Override
protected void onActive() {
    super.onActive();
    setValue(getValueFromPreferences(key, defValue));
    sharedPrefs.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
}

@Override
protected void onInactive() {
    sharedPrefs.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener);
    super.onInactive();
}
public SharedPreferenceLiveData<Boolean> getBooleanLiveData(String key, Boolean defaultValue) {
    return new SharedPreferenceBooleanLiveData(sharedPrefs,key, defaultValue);
}
}

SharedPreferenceBooleanLiveData class

public class SharedPreferenceBooleanLiveData extends SharedPreferenceLiveData<Boolean>{

public SharedPreferenceBooleanLiveData(SharedPreferences prefs, String key, Boolean defValue) {
    super(prefs, key, defValue);
}

@Override
Boolean getValueFromPreferences(String key, Boolean defValue) {
    return sharedPrefs.getBoolean(key, defValue);
}

}

Calling this like the below code mentioned in this link:

 SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
 SharedPreferenceStringLiveData sharedPreferenceStringLiveData = new SharedPreferenceStringLiveData(preferences, "cid", "");
 sharedPreferenceStringLiveData.getStringLiveData("cid", "").observe(this, cid -> {
            Toast.makeText(this, "Change in CID "+cid, Toast.LENGTH_SHORT).show();
 });

Again if using a Preference Helper class, the class can be called as below: Just a sample:

public class PreferenceManager {
private SharedPreferenceBooleanLiveData sharedPreferenceLiveData;

public SharedPreferenceBooleanLiveData getSharedPrefs(){
        return sharedPreferenceLiveData;
    }

public void setSharedPreferences(String key, boolean value) {

        SharedPreferences userDetails = context.getSharedPreferences(APP_PREFERENCE,
            Context.MODE_PRIVATE);
        SharedPreferences.Editor editor = userDetails.edit();
        editor.putBoolean(key, value);
        editor.apply();
        sharedPreferenceLiveData = new SharedPreferenceBooleanLiveData(userDetails,key,value);
    }
}

And in the activity class call it as below: Create an object:

SharedPreferenceBooleanLiveData sharedPreferenceLiveData;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    sharedPreferenceLiveData = preferenceManager.getSharedPrefs();
}

And observe it as follows:

sharedPreferenceLiveData.getBooleanLiveData(PreferenceKey.KEY_LOCATION_PERMISSION,false).observe(this,check->{
        if(check){
            setPermissionGranted(check);
        }
    });
Billfold answered 17/7, 2019 at 10:53 Comment(2)
In my case using SharedPreferenceStringLiveData. For the same value (example, first storing "First" and second time also string "First") , the onSharedPreferenceChanged won't call.Merocrine
One can Overrride getVale() as well and return value directly from shared preference. It will be helpful if someone wants to check value of livedata at a given time but do not want to observe it. //Add this in your SharedPreferenceLiveData class override fun getValue(): T? { return getValueFromPreferences(key, defValue) }Quiz
M
5

Android recently released DataStore which is:

Jetpack DataStore is a data storage solution that allows you to store key-value pairs or typed objects with protocol buffers. DataStore uses Kotlin coroutines and Flow to store data asynchronously, consistently, and transactionally.

If you're currently using SharedPreferences to store data, consider migrating to DataStore instead.

So here is the breakdown:

In the build.gradle of the project:

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    
    kotlinOptions {
        jvmTarget = JavaVersion.VERSION_1_8.toString()
    }
}

dependencies {
    ...
    implementation "androidx.datastore:datastore-preferences:1.0.0-alpha04"
}

The database class would look like:

class SettingsSharedPreference private constructor(context: Context) {

    private val dataStore = context.createDataStore(name = "settings")

    companion object {

        val SCREEN_ORIENTATION = preferencesKey<String>("screen_orientation")

        @Volatile
        private var instance: SettingsSharedPreference? = null

        private val lock = Any()

        operator fun invoke(context: Context) = instance ?: synchronized(lock) {
            instance ?: SettingsSharedPreference(context).also { instance = it }
        }
    }

    val screenOrientationFlow: Flow<String> = dataStore.data
        .map { preferences ->
            preferences[SCREEN_ORIENTATION] ?: "landscape"
        }

    //TODO: You should use enum for screenOrientation, this is just an example
    suspend fun setScreenOrientation(screenOrientation: String) {
        dataStore.edit { preferences ->
            preferences[SCREEN_ORIENTATION] = screenOrientation
        }
    }
}

In the Activity:

val settingsSharedPreference by lazy {
    SettingsSharedPreference.invoke(this)
}
...
settingsSharedPreference.setScreenOrientation("portrait")    
...
settingsSharedPreference.screenOrientationFlow.asLiveData().observe(this) { screenOrientation ->
    ...
}
Mountbatten answered 1/12, 2020 at 0:9 Comment(1)
Unfortunately, it only works in Kotlin and RxJava. Waiting for it in plain Java with Jetpack Lifecycle/ GuavaRochette
B
4

Assuming your network request is already providing you a LiveData class. For example with Retrofit and a CallAdapter like LiveDataCallAdapter.

Once you have the last observed value by either:

Then you can apply the previous LiveData to either:

This is the related example for the Transformations.switchMap in UserViewModel.kt:

private val _login = MutableLiveData<String>()
val login: LiveData<String>
    get() = _login
val repositories: LiveData<Resource<List<Repo>>> = Transformations
    .switchMap(_login) { login ->
        if (login == null) {
            AbsentLiveData.create()
        } else {
            repoRepository.loadRepos(login)
        }
    }
Britney answered 10/8, 2018 at 16:48 Comment(0)
S
2

I've create a pure kotlin lib to do this - https://github.com/Jintin/PreferencesExtension

All we need to do is something like:

val preferenceLiveData = preference.liveData<String>(MY_KEY)

preferenceLiveData.observe(this) {
    // get update here
}

And sure the preferenceLiveData can be inside ViewModel and let Activity/Fragment observe it. Check out the example here: Activity, ViewModel

Sheenasheeny answered 5/12, 2020 at 15:44 Comment(0)
T
1

i see your challenge is calling Shared Preferences Value when API Calling in ViewModel or LiveData.

You may define your shared preference as global in Application class ( so it will be global)

public class MyApplication extends Application {
   public static AppPreferences shared_preference;

   /*On Create Application Create AppPreferences*/
   @Override
   public void onCreate() {
     super.onCreate();
     shared_preference = AppPreferences.getInstance(this);
   }
}

And you may update or save value by calling Shared Preference in your ViewModel

/*Save to Shared Preferences*/
MyApplication.shared_preference.setLogin(true);

Wish this help you, cause i see your problem there, This is works for me and able to insert param in API Call.

Truncated answered 11/6, 2018 at 11:51 Comment(1)
It is not observing changes. He wants to observe whenever SharedPreference value is changed , and perform some action then.Fibrosis
H
1

Add below dependency in build.gradle(:app)

 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.6"  // replace with updated version 
 implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.6"// replace with updated version 

Add Below code inyYour preference class/utils

private var mPrefs: SharedPreferences =
        mContext.getSharedPreferences(AppConstant.PREF_NAME, Context.MODE_PRIVATE)

 private val _constSate = MutableStateFlow(mPrefs.getBoolean(IS_NOTIFY,false))

// function for set data to preference and add to Stateflow. 

fun setData(isNotify: Boolean){
       // mPrefs = instance of your preference 
        mPrefs.edit().putBoolean(IS_NOTIFY, isNotify).apply()
        _constSate.value = isNotify
    }

//function for get observer/flow/live boolean value 

fun getNotifyFlow() : StateFlow<Boolean> = _constSate

//get your observer/flow/live value on other class e.g MainActivity.class etc..

 CoroutineScope(Dispatchers.Main).launch{      
     appPreferences.getNotifyFlow().collect{
                   if (it){
                    Log.d("jai","true")
                   }else{
                     Log.d("jai","false")
                   }
                }
      }
Hinkle answered 20/8, 2020 at 7:17 Comment(0)
M
0

you can organize all your SharedPreferences value in single LiveData

public class SharedPreferencesLiveData extends LiveData<ObservableArrayMap<String, Object>> {

    private final SharedPreferences mSharedPreference;

    public SharedPreferencesLiveData(final SharedPreferences sharedPreferences) {
        super(new ObservableArrayMap<String, Object>() {
            @Nullable
            @Override
            public Object put(String key, Object value) {
                final Object lastValue = super.get(key);
                if (!Objects.equals(value, lastValue)) {
                    if (value instanceof Boolean) {
                        sharedPreferences.edit().putBoolean(key, (Boolean) value).apply();
                    } else if (value instanceof Float) {
                        sharedPreferences.edit().putFloat(key, (Float) value).apply();
                    } else if (value instanceof String) {
                        sharedPreferences.edit().putString(key, (String) value).apply();
                    } else if (value instanceof Integer) {
                        sharedPreferences.edit().putInt(key, (Integer) value).apply();
                    } else if (value instanceof Long) {
                        sharedPreferences.edit().putLong(key, (Long) value).apply();
                    } else if (value instanceof Set) {
                        sharedPreferences.edit().putStringSet(key, (Set<String>) value).apply();
                    } else if (value == null) {
                        sharedPreferences.edit().remove(key).apply();
                    } else {
                        throw new IllegalArgumentException("value=" + value + " data type not support");
                    }
                    return super.put(key, value);
                } else
                    return lastValue;
            }
        });
        mSharedPreference = sharedPreferences;
    }

    @Override
    protected void onActive() {
        super.onActive();
        getValue().clear();
        getValue().putAll((SimpleArrayMap<? extends String, ?>) new ArrayMap<String, Object>() {{
            putAll(mSharedPreference.getAll());
        }});
        mSharedPreference.registerOnSharedPreferenceChangeListener(mOnSharedPreferenceChangeListener);
    }

    @Override
    protected void onInactive() {
        super.onInactive();
        mSharedPreference.unregisterOnSharedPreferenceChangeListener(mOnSharedPreferenceChangeListener);
    }

    SharedPreferences.OnSharedPreferenceChangeListener mOnSharedPreferenceChangeListener = new SharedPreferences.OnSharedPreferenceChangeListener() {
        @Override
        public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, String key) {
            if (sharedPreferences.contains(key)) {
                getValue().put(key, sharedPreferences.getAll().get(key));
            } else {
                getValue().remove(key);
            }
        }
    };
}

then declare your LiveData of SharedPreferences in your ViewModel

public static class ViewModel extends AndroidViewModel {
    public final SharedPreferencesLiveData sharedPreferenceLiveData = new SharedPreferencesLiveData(getApplication().getSharedPreferences("XXX", 0));

    public ViewModel(@NonNull Application application) {
        super(application);
    }
}

then access & update (two-way databinding) your SharedPreferences from XML layout

<?xml version="1.0" encoding="utf-8"?>
<layout>

    <data>

        <variable
            name="viewModel"
            type="com.xxx.sharedpreferencedatabindingtest.MainActivity.ViewModel" />
    </data>

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        tools:context=".MainActivity">

        <CheckBox
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:checked='@={((Boolean)viewModel.sharedPreferenceLiveData["XXXX"])}'
            android:text="XXX" />

        <CheckBox
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:checked='@={((Boolean)viewModel.sharedPreferenceLiveData["XXXX"])}'
            android:text="XXX" />

        <SeekBar
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:max="1000"
            android:min="0"
            android:progress='@={(Integer)(viewModel.sharedPreferenceLiveData["YYYY"])}' />

        <SeekBar
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:max="1000"
            android:min="0"
            android:progress='@={(Integer)(viewModel.sharedPreferenceLiveData["YYYY"])}' />

    </LinearLayout>
</layout>
Micron answered 16/12, 2020 at 3:39 Comment(0)
S
0

I also wondered how to listen for changes in the preferences made in a Preferences activity and it's fragment from PreferenceFragmentCompat, but it turned out that if the MainActivity is refreshed after pressing the "Up" button then this is not necessary to "subscribe" for changes. In AndroidManifest, if android:launchMode is set, it should be "standard" (this will cause the MainActivity to be refreshed after navigating to it from another activity). Then you can get a preference simply using PreferenceManager.getDefaultSharedPreferences which is accessible from anywhere as described in the documentation.

Sculley answered 1/2, 2023 at 16:29 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.