I am trying to observe data changes in shared preferences. I found this similar question answered by #SimplyProgrammerand followed the steps that he directed and at the end of the day my observer was still not working.
Then I decided to seek some assistance to better understand why.
this is my implementation
I started by implementing the abstract live data
SharedPreferences preference;
String key;
T defValue;
public SharedPrefferenceLiveData(SharedPreferences preference,String key,T defValue){
this.preference=preference;
this.key=key;
this.defValue=defValue;
}
private SharedPreferences.OnSharedPreferenceChangeListener preferenceChangeListener=new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if(SharedPrefferenceLiveData.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));
preference.registerOnSharedPreferenceChangeListener(preferenceChangeListener);
}
#Override
protected void onInactive() {
preference.unregisterOnSharedPreferenceChangeListener(preferenceChangeListener);
super.onInactive();
}
}
I then implemented the live data type class
public class LocationLiveData extends SharedPrefferenceLiveData<String>{
public LocationLiveData(SharedPreferences preferences, String key, String string){
super(preferences,key,string);
}
#Override
public String getValueFromPreferences(String key, String defValue) {
return preference.getString(key,defValue);
}
}
I then added this to my Preference management class like so
instantiation and setting getters
private LocationLiveData sharedPreferenceLiveData;
public LocationLiveData getSharedPrefs(){
return sharedPreferenceLiveData;
}
then assigned values like so
public void saveUserLocation(Location location){
...
settings = context.getSharedPreferences(MyPREFERENCES, Context.MODE_PRIVATE);
editor = settings.edit();
editor.putString(User_Location, currentLocation);
editor.apply();
sharedPreferenceLiveData=new LocationLiveData(settings,User_Location,currentLocation);
}
then in my activity, I access sharedPreferenceLiveData like this
#Inject
SharedPreference sharedPreference;
...
...
LocationLiveData liveData;
...
#Override
protected void onCreate(Bundle savedInstanceState) {
...
...
...
liveData=sharedPreference.getSharedPrefs();
...
...
observeMarkerLocation();
}
...
...
...
//the observer
private void observeMarkerLocation() {
if(liveData!=null){
liveData.observe(this,locationString->{
if(locationString!=null){
if(!sharedPreference.getBoolValue(SharedPreference.IS_FOLLOWING_ALERT)){
Gson gson=new Gson();
Type type=new TypeToken<Location>(){}.getType();
Location userLocation=gson.fromJson(locationString,type);
currentLocation=userLocation;
}else{
Gson gson=new Gson();
Type type=new TypeToken<VictimFollowingResponse>(){}.getType();
VictimFollowingResponse victimFollowingResponse=gson.fromJson(locationString,type);
List<Point> points=victimFollowingResponse.getPoints();
List<LatLng> latLngPoints=new ArrayList<>();
for(Point point:victimFollowingResponse.getPoints()){
latLngPoints.add(new LatLng(point.getLat(),point.getLong()));
}
int pointListSize=points.size();
if(pointListSize>0){
victimUser.setLatitude(points.get(pointListSize-1).getLat());
victimUser.setLongitude(points.get(pointListSize-1).getLong());
}
drawPolyLIne(latLngPoints);
}
}
});
}
}
yeah thats it .
in this case, the live data keeps returning null in the activity even after being set in a service.
I think that your code is too complicated. You can simply listen for SharedPreferences changes, using registerOnSharedPreferenceChangeListener() listener, when your app is turned on.
public class MyApplication extends Application {
private static MyApplication singleton;
public SharedPreferences preferences;
public static MyApplication getInstance() {
return singleton;
}
#Override
public void onCreate() {
super.onCreate();
singleton = this;
preferences = PreferenceManager.getDefaultSharedPreferences(this);
preferences.registerOnSharedPreferenceChangeListener(listener);
}
private SharedPreferences.OnSharedPreferenceChangeListener listener = new SharedPreferences.OnSharedPreferenceChangeListener() {
#Override
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
//do your code here
}
};
public void unregisterListener() {
preferences.unregisterOnSharedPreferenceChangeListener(listener);
}
}
It overrides an application class, but you can also use this in any activity, but like docs say
Caution: The preference manager does not currently store a strong reference to the listener. You must store a strong reference to the listener, or it will be susceptible to garbage collection. We recommend you keep a reference to the listener in the instance data of an object that will exist as long as you need the listener.
In my example, you should also override an application tag in manifest file
<application
android:name=".MyApplication"
>
</application>
And remember to unregister listener in any activity, before you'll exit an app
#Override
protected void onPause() {
MyApplication.getInstance().unregisterListener();
super.onPause();
}
If you have your own preference file, just change
preferences = PreferenceManager.getDefaultSharedPreferences(this);
to
preferences = getSharedPreferences("pref_filename", MODE_PRIVATE);
To access that preferences, you no need any reference to it. You can get instance of sharedpreferences file in any activity you want. That will be work, because interface will listen for any changes in preferences file. Note that, if you'll pass a key with the same value which is already contained, listener doesn't recognize that as change. So, you can change int value eg. from 2 to 3, and it will work, but from 2 to 2 won't work.
Related
I have an application which manages data through SharedPreferences (SP).
I splitted them in different XML files to make the operations faster (clear, commit, apply, ...) and be more organized.
I also created a class for each one to abstract operations and create boilerplate code to avoid duplicating the same code on classes that need to update data on SP.
Since the idea of having a SP is to share information throughout the application, in my case it's better to use the application context to manipulate data on it, even though SP is a single instance.
But I'm not sure if it is a good practice to do this. Have you ever faced a similar situation?
My current implementation (snippet):
MyApplication.java
public class MyApplication extends Application {
private static PreferenceHelper mPref;
#Override
public void onCreate() {
mPref = new PreferenceHelper(this);
}
}
PreferenceHelper.java
public class PreferenceHelper {
private PreferenceUser mPrefUser;
private PreferenceTechnician mPrefTechnician;
public PreferenceHelper(Context context) {
this.mPrefUser = new PreferenceUser(context);
this.mPrefTechnician = new PreferenceTechnician(context);
}
public PreferenceUser getPrefUser() {
return mPrefUser;
}
public PreferenceTechnician getPrefTechnician() {
return mPrefTechnician;
}
}
PreferenceUser.java
public class PreferenceUser {
private static final String PREF_USER_FILENAME = "user";
private SharedPreferences mPref;
public PreferenceUser(Context context) {
mPref = context.getSharedPreferences(PREF_USER_FILENAME, Context.MODE_PRIVATE);
}
public void clear() {
mPref.edit().clear().commit();
}
public void saveNewUser(User user) {
SharedPreferences.Editor editor = mPref.edit();
editor.putString(user.getId(), gson.toJson(user));
editor.commit();
}
public List<User> getAllList() {
...
// get a map, convert to POJO using Gson,etc
...
}
}
PreferenceTechnician.java
Same as PreferenceUser, but for Techs
MainActivity.java (or any other that needs to read/write from/to SP)
public class MainActivity extends Activity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
writeSomethingOnSP();
}
private void writeSomethingOnSP() {
MyApplication.getPref().getPrefUser().saveNewUser(new User(1, "Ana", "Developer"));
}
}
Whenever I need to read or write something on the SharedPreference, I use MyApplication.getPref().getPrefNameOfPreference().doSomeAction();.
Can I have your opinion about this approach?
I had similiar issues and I dediced to fix with static methods.
Since SharedPreferences are not heavly used, I decided to not store any instance of them but to get them whenever I need them. Also, since all read/write operations are centered in the same class, I have the control that I need (save this setting on that file etc)
I did something like:
public class SettingsUtil {
private static final String PREF_USER_FILENAME = "user";
private static final String PREF_TECH_FILENAME = "tech";
public static void saveNewUser(Context context, User user) {
SharedPreferences sharedPref = context.getApplicationContext().getSharedPreferences(PREF_USER_FILENAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
....
editor.apply();
}
public static void addNewTech(Context context, Tech tech) {
SharedPreferences sharedPref = context.getApplicationContext().getSharedPreferences(PREF_TECH_FILENAME, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedPref.edit();
....
editor.apply();
}
...
}
Then, to call:
Settings.Util.addNewUser(this, new User(1, "Ana", "Developer"));
Not sure if this helps you. I found it simple and it gave the control that I was looking for.
What I would like to know is if there is a way to update multiple activities from one activity.
For example from my activityC (in my case EditProfileActivity) I update user profile. activityC is a child of activityB, which is a child of activityA, and open from mainActivity's navdrawer. I also have activityE that can be opened from activityC and activityF that can be open from activityA. I want to update all of them.
When I change a user name, photo, about or anything else, I want to send that changed information even without closing activityC. And if I go back or forward from it, it should have updated user information already. If user did not change anything it should not update those activities.
If it was only one activity I would do it via intent or onActivityResult.
Is there a way to do this without shared preferences, or startActivityForResult and onActivityResult?
you can use event bus for this purpose like this explained here:
you can create event like this:
public static class UpdateEvent { /* your getter and setter */ }
Prepare subscribers: Declare and annotate your subscribing method, optionally specify a thread mode:
#Subscribe(threadMode = ThreadMode.MAIN)
public void onUpdateEvent(UpdateEvent event) {
/* event fire here when you post event from other class or fragment */
};
Register - unregister your subscriber.
#Override
public void onStart() {
super.onStart();
EventBus.getDefault().register(this);//Register
}
#Override
public void onStop() {
super.onStop();
EventBus.getDefault().unregister(this);//unregister
}
Fire events like this:
EventBus.getDefault().post(new UpdateEvent());
What you can also use, and in my opinion is easier and better than EventBus, is to use a LocalBroadcastManager. It will allow you send broadcasts across your application which can be received by one or more activities. I guess it's pretty much what you want.
Documentation:
https://developer.android.com/reference/android/support/v4/content/LocalBroadcastManager.html
Simple solution is Broadcast Reciever
Try this
In your other activity class declare this
BroadcastReceiver broadCastNewMessage = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
// do your stuff here
}
};
Now in onCreate() register this
registerReceiver(this.broadCastNewMessage, new IntentFilter("bcNewMessage"));
And in onDestroy()
unregisterReceiver(broadCastNewMessage);
Now Call this method from the activity where u want to update other activity
sendBroadcast(new Intent().setAction("bcNewMessage"));
You can set information in sharedprefences and can get fetch information in any activity
I have created this Class for saving object in sharedprefrences
public class DroidPrefs
{
public static boolean contains(Context context, String key) {
return getDefaultInstance(context).contains(key);
}
public static <T> T get(Context context, String key, Class<T> cls) {
return getDefaultInstance(context).get(key, cls);
}
public static void apply(Context context, String key, Object value) {
getDefaultInstance(context).apply(key, value);
}
public static void commit(Context context, String key, Object value) {
getDefaultInstance(context).commit(key, value);
}
public static DroidPrefs getDefaultInstance(Context context) {
SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context);
return new DroidPrefs(sp);
}
private Gson mGson;
private SharedPreferences mSharedPrefs;
public DroidPrefs(SharedPreferences sharedPrefs)
{
mGson = new Gson();
mSharedPrefs = sharedPrefs;
}
public boolean contains(String key) {
return mSharedPrefs.contains(key);
}
public <T> T get(String key, Class<T> cls) {
if (contains(key)) {
return mGson.fromJson(mSharedPrefs.getString(key, null), cls);
}
try {
return cls.newInstance();
}
catch (Exception e) {
throw new IllegalArgumentException("class must have an empty constructor");
}
}
public void apply(String key, Object value) {
put(key, value).apply();
}
public void commit(String key, Object value) {
put(key, value).commit();
}
#SuppressLint("CommitPrefEdits")
private SharedPreferences.Editor put(String key, Object value) {
SharedPreferences.Editor e = mSharedPrefs.edit();
e.putString(key, mGson.toJson(value));
return e;
}
public void clear(){
SharedPreferences.Editor e = mSharedPrefs.edit();
e.clear().apply();
}
}
You can save value to any key by using apply or commit method
DroidPrefs.apply(context, "your key", yourobject);
You can fetch value by using get method
yourobject= DroidPrefs.get(this,"your key",yourclass.class);
This might sound like a silly question, but I'm flipping between two design models of a singleton and POJO that accesses Shared Prefs, and I want to prevent any mem. leaks in terms of context.
I know static objects are allocated to the heap, but for say a singleton design where the class remains unstatic and declares a static instance of itself and instantiates it, if this instance is passed context to do some type of method,
can it leak this context?
If not why not, if yes why and how could it be fixed?
Here's a current singleton design I've created
public class UserSettings {
private UserSettings settings = new UserSettings();
private UserSettings() {
}
public static UserSettings getInstance() {
return settings;
}
private SharedPreferences getPrefs(Context context) {
return context.getSharedPreferences(
USER_SETTINGS_FILE,
Context.MODE_PRIVATE
);
}
private SharedPreferences.Editor getEditor(Context context) {
return context.getSharedPreferences(
USER_SETTINGS_FILE,
Context.MODE_PRIVATE
).edit();
}
public Object get(Context context,int item) {
SharedPreferences sp = getPrefs(context);
switch (item) {
case TIMING:
return sp.getInt(KEY_USER_TIMING,60);
case NAME:
return sp.getString(KEY_USER_NAME,"Stranger");
// .. etc
default:
return 0;
}
}
public void update(Context context,int setting, Object updateValue) throws ClassCastException {
SharedPreferences.Editor sp = getPrefs(context).edit();
switch (setting) {
case TIMING:
if (Integer.class.isInstance(updateValue))
sp.putInt(KEY_USER_TIMING,(int)updateValue).commit();
else
throw new ClassCastException("User Time must be int");
break;
case NAME:
if (String.class.isInstance(updateValue))
sp.putString(KEY_USER_NAME,(String)updateValue).commit();
else
throw new ClassCastException("User Name must be String");
break;
// .... etc
default:
break;
}
}
}
If you're not creating a reference to a context but passing it in to each method call, it can't leak because it's not being retained. You would only be leaking a context if you stored a reference to it outside of its lifecycle. For instance, if I store use Activity context to initialize a singleton statically. Even when the Activity goes away, the context object won't be finalized because there's still a reference to it somewhere.
However, there are several design patterns to accomplish this neatly. I generally use Dagger
This doesn't result in a context leak, because the lifecycle of your Singleton SharedPreferencesHelper is tied to the lifecycle of your application and it uses the application context.
CustomApplication.java
private static ObjectGraph staticRefToObjectGraph;
#Override
public void onCreate() {
super.onCreate();
staticRefToObjectGraph = ObjectGraph.create(new MyModule(this));
}
public static void inject(Object obj) {
staticRefToObjectGraph.inject(obj);
}
MyModule.java
public MyModule(Context applicationContext) {
SharedPreferences prefs = PreferenceManager.getSharedPreferences(applicationContext);
prefsHelper = new SharedPreferencesHelper(prefs);
}
#Provides
#Singleton
public SharedPreferencesHelper providePrefsHelper() {
return prefsHelper;
}
MyActivity.java
#Inject SharedPreferencesHelper prefsHelper
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
MyApplication.performInjection(this);
}
This just an example of my application.
I have created a singleton shared preferences which used a custom Shared preference manager class to edit the shared preferences data values:
public class MySharedPrefManager {
private static MySharedPrefManager instance= null;
private static SharedPreferences SharedPref;
private static Editor SPEditor;
private MySharedPrefManager () {
}
public static MySharedPrefManager getInstance(){
if(instance==null)
instance= new MySharedPrefManager ();
return instance;
}
public void setSharedPreferences(Context context){
SharedPref= context.getSharedPreferences("MySharedPref", Context.MODE_PRIVATE);
SPEditor= SPEditor.edit();
}
public void setAdmin(boolean pAdmin) {
SharedPrefManager.editBoolean("isAdmin", pAdmin, SPEditor);
}
public boolean isAdmin() {
return SharedPref.getBoolean("isAdmin", false);
}
}
Shared preference manager:
public class SharedPrefManager {
public static void editString(String key, String value, Editor pEditor){
pEditor.putString(key, value);
pEditor.commit();
}
public static void editBoolean(String key, boolean value, Editor pEditor){
pEditor.putBoolean(key, value);
pEditor.commit();
}
I have lots of activities which goes like activities: A->B->C->D->E->F->G
Activity A, which is the start-up activity, i get the instance of MySharedPrefManager and set the SharedPreferences also:
public class ActivityA extends Activity{
private MySharedPrefManager myPref;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.initialization);
myPref= MySharedPrefManager.getInstance();
// I am setting the SharedPreference Context with getApplicationContext(),
// as it is singleton, and I am using it through out my application
myPref.setSharedPreferences(getApplicationContext());
// other stuff...
}
private void changeData(){
myPref.setAdmin(true);
}
private void check(){
if(myPref.isAdmin()){
// do- something
}
}
}
ActivityD :
public class ActivityD extends Activity{
private MySharedPrefManager myPref;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.initialization);
// Here i called only the instance not the setSharedPreferences.
myPref= MySharedPrefManager.getInstance();
// other stuff...
}
private void changeData(){
myPref.setAdmin(true);
}
private void check(){
if(myPref.isAdmin()){
// do- something
}
}
}
Now, the issue I am facing right now is that, some times I get Null pointer exception in myPref.isAdmin() when I call it both in Activity A and D. But most of the time it works.
Also do I need to set the SharedPreferences (call the setSharedPreferences() method) in each of the activities? I don't feel the need to set is as it is singleton class.
I repeat, I am setting the SharedPreference Context with getApplicationContext(),as it is singleton and I am using it through out my application.
Edited: There are 3-4 shared preferences that I am using with the same structure.. With the similar problem. Using this PreferenceManager.getDefaultSharedPreferences(context);, I will be able to use only ONE SharedPreference which is the default SharedPreference.
The null pointer exception is probably caused by your singleton not being initialized. I suppose this happens when you rotate your device?
I expect the following to be happening: In activity A you do getInstance() and setSharedPreferences(). In activity B you only do getInstance() and expect to get the same instance that was created in A. But if you rotate the device while in B, then B is recreated which does the getInstance(), but this time it has to create its instance itself. And this time it will not do the initialization with setSharedPreferences().
My advise is to make your prefs class simpler. Make sure you initialize on construction. I.e. do not separate out getInstance() and setSharedPreferences(). Do something like this:
public class MySharedPrefManager {
private SharedPreferences sharedPref;
private MySharedPrefManager(Context context) {
sharedPref = PreferenceManager.getDefaultSharedPreferences(context);
}
public static MySharedPrefManager from(Context context) {
return new MySharedPrefManager(context);
}
public void setAdmin(boolean pAdmin) {
sharedPref.edit().putBoolean("isAdmin", pAdmin).apply();
}
public boolean isAdmin() {
return sharedPref.getBoolean("isAdmin", false);
}
}
And use it like this when you need to do multiple settings:
MySharedPrefManager prefs = MySharedPrefManager.from(this);
prefs.setAdmin(true);
prefs.setSomethingElse();
boolean isFoo = prefs.isFoo();
Or use the shorter version if you only need one setting:
MySharedPrefManager.from(this).setAdmin(true);
Before using the data members or contents of the MySharedPrefManager, check for the null-check.
In MySharedPrefManager class add a getter method like this:
public SharedPreferences getSharedPreference(){
return SharedPref;
}
In the activity where you are accessing shared preference contents, in onCreate or onResume add this before using the contents:
myPref= MySharedPrefManager.getInstance();
if(myPref.getSharedPreference()==null){
myPref.setSharedPreferences(getApplicationContext());
}
This way you will be able to avoid NPE.
No need to, there already is a singleton. You can do:
PreferenceManager.getDefaultSharedPreferences(context);
I have a class Settings with static methods which I use to save and load my data. This is for example the Save() method:
public static void Save(SETTING_KEY key, String value)
{
SharedPreferences sp = _context.getSharedPreferences(prefName, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString(key.toString(), value);
editor.commit();
}
Now I'd like to make the class Settings observable. The problem is that extending my Settings-class from Observable does not work, as I don't instantiate this class as I only use its static methods.
Is there any easy way to make this static class observable?
Add your own observer mechanism. Simple implementation below
public interface Observer {
void onSettingsChanged(String key, String value);
}
private static Observer observer;
public static void setObserver(Observer observer_) {
observer = observer_;
}
public static void Save(SETTING_KEY key, String value) {
SharedPreferences sp = _context.getSharedPreferences(prefName, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString(key.toString(), value);
editor.commit();
if (observer != null) {
observer.onSettingsChanged(key, value);
}
}
You could also use a List<Observer> if you need more than 1 and you can also use some sort of event bus (e.g. Otto) system to publish events without having to provide a direct observer mechanism.
You could try using a Singleton pattern? (Java Singleton Pattern)
That way, you can extend observable.
You could combine a singleton with static methods.
class Settings extends Observable {
private static Settings instance;
private static synchronized Settings getInstance() {
if(instance == null) {
instance = new Settings();
}
return instance;
}
public static void Save(SETTING_KEY key, String value)
{
SharedPreferences sp = _context.getSharedPreferences(prefName, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sp.edit();
editor.putString(key.toString(), value);
editor.commit();
getInstance().notifyObservers(value); // or whatever
}
// can't override addObserver with a static method, so make a new method instead
public static void addObserverStatic(Observer observer) {
getInstance().addObserver(observer);
}
}
As others have said, static methods on a singleton is a bit naughty. Do it some other way if you can.