singleton shared preferences issue - java

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);

Related

Observing SharedPreferences Data

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.

Application onCreate called(not called) after Activity onCreate

In crash logs, I've found very strange application bug which happens on android 7.0-8.0 for some small amount of users, but quite frequently. I was not able to reproduce the issue, here the code which reflects the current application status.
I have a static reference to my application class.
public class MyApplication extends Application {
private static MyApplication sInstance;
public static MyApplication get() {
return sInstance;
}
#Override
public void onCreate() {
super.onCreate();
sInstance = this;
}
}
In the main activity I do initialization of a singleton:
public class MainActivity extends AppCompatActivity{
public void onCreate(final Bundle savedInstanceState) {
initSingletone();
super.onCreate(createBundleNoFragmentRestore(savedInstanceState))
}
public void initSingleTone(){
Singleton singleton = Singleton.getInstance();
}
}
The singleton:
public class Singleton{
public static Singleton instance;
public static Singleton getInstance(){
if(instance == null){
instance = new Singleton();
}
return ;
}
public Singleton(){
Context context = MyApplication.get();
final File baseDir = context.getCacheDir();
....
}
}
The NullPointerException occurs on the following line.
final File baseDir = context.getCacheDir();
Because for some reasons MyApplication.get() returns null.
Seems onCreate of the Application was not called in the moment of onCreate of MainActivity, really weird.
Have anyone faced with the same problem?
What could be a reason for such strange initialization process of the Android components?
You can use a safe method:
public static MyApplication get() {
if(sInstance == null){
sInstance = this;
}
return sInstance;
}

SharedPreferences manager class for multiple XMLs files

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.

Can Static Objects (such as Singletons) leak non-static Context? Why?

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);
}

Application Context in Non Activity Class?

There is alot of info on this topic but none of it assist me.
I have several global variables/fields inside the app's application class:
public class App extends Application {
private static App instance
public string PUBNUB_SUB = BuildConfig.PUBNUB_SUB_KEY;
public string PUBNUB_PUB = BuildConfig.PUBNUB_PUB_KEY;
public void onCreate() {
instance = this;
}
public static App getInstance(){
return instance;
}
}
In Activities/Fragments I'm successfully accessing those variables like so:
class ActivityA extend Activity {
App baseApp;
Pubnub pubnub;
public void onCreate(Bundle savedInstanceState){
baseApp = new(App)getApplicationContext();
Pubnub(baseApp.PUBNUB_SUB, baseApp.PUBNUB_PUB)
}
Now How do I access getApplicationContext() in a non-Activity/non-Fragment Class?
public class Events {
App baseApp;
Context mContext;
app = new(LoQooApp)mContext.getApplication(); ???
public Events(Context context) {
app = new(LoQooApp)mContext.getApplication(); ???
}
The above doesnt work, where should
app = new(LoQooApp) getApplication(); go?
Looks like PUBNUB_SUB and PUBNUB_PUB are constants, so you could declare them as public static final and then access using a static reference: App.PUBNUB_SUB and App.PUBNUB_PUB

Categories