SharedPreferences manager class for multiple XMLs files - java

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.

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.

Calling SharedPreferences, Intent and Retrofit without Activity

I am able to call Retrofit and SharedPreferences and Intent fine from an Activity and all works. But it gets messy real fast and lots of repetition thus I want to call them from a helper class instead. It makes it complicated cos I end up having to extend the Helper class with the Application class, followed by IDE complaining I have missing XML files which are actually not needed for a helper class.
Feel like I am over complicating things. What's the solution here? Write directly on Activity classes and endure the code repetitions or is there an elegant solution to this?
This is how I tried to extract the logic for Retrofit and Intent away from an Activity class Using a helper.
public class RetrofitHelper extends Application {
private final String ERROR_MSG = "username is invalid";
private static final String TAG = "RetrofitHelper";
private static final String API_KEY = "";
private VerificationHelper verificationHelper;
public RetrofitHelper(VerificationHelper verificationHelper) {
this.verificationHelper = verificationHelper;
}
public Retrofit getRetrofit(String baseUrl){
Retrofit.Builder builder = new Retrofit.Builder()
.baseUrl(baseUrl)
.client(setTimeout())
.addConverterFactory(GsonConverterFactory.create());
return builder.build();
}
public void performCallBack(Call<User> call){
call.enqueue(new Callback<User>() {
#Override
public void onResponse(Call<User> call, Response<User> response) {
String usernameToken = Objects.requireNonNull(response.body()).getUsername();
String username = null;
if(usernameToken != null){
username = verificationHelper.parseToken(usernameToken, API_KEY);
}
if (username != null && !username.equals(ERROR_MSG)){
Log.i(TAG, "onResponse: Success " + username);
// verificationHelper.setPreferences(username);
verificationHelper.goToMainActivity();
}
else {
Log.i(TAG, "onResponse: Incorrect response.");
}
}
#Override
public void onFailure(Call<User> call, Throwable t) {
Log.i(TAG, "onFailure: server error");
}
});
}
private OkHttpClient setTimeout(){
return new OkHttpClient().newBuilder()
.connectTimeout(60, TimeUnit.MINUTES)
.readTimeout(60, TimeUnit.MINUTES)
.writeTimeout(60, TimeUnit.MINUTES)
.build();
}
}
For reference, VerificationHelper called from the above Helper.
public class VerificationHelper extends Application {
private static boolean isValidCredential = false;
public static boolean isValidCredential() {
return isValidCredential;
}
static void setValidCredential(boolean validCredential) {
isValidCredential = validCredential;
}
private <T> String convertObjToJson(T object){
// some logic
return "";
}
public String createToken(String apiKey, User user, long ttlMillis) {
//some logic
return "";
}
String parseToken(String token, String apiKey) {
//some logic
return "";
}
//trying to keep this here so I call at many places.
void setPreferences(String username){
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("username", username);
editor.putBoolean("isLoggedIn", true);
editor.apply();
}
//trying to keep this here so I call at many places.
void goToMainActivity(){
Intent mainIntent = new Intent(this, MainActivity.class);
startActivity(mainIntent);
}
}
Make a Helper Class (no Activity extended) and pas the context to the functino that you want to call. An Exmaple with shared preferences:
public class SharedPreferenceHelper {
private final static String PREF_FILE = "<Shared_PReferences_Name>";
/**
* Set a string shared preference
* #param context - Context (Activity that is calling the function)
* #param key - Key to set shared preference
* #param value - Value for the key
*/
public static void setSharedPreferenceString(Context context, String key, String value){
SharedPreferences settings = context.getSharedPreferences(PREF_FILE, 0);
SharedPreferences.Editor editor = settings.edit();
editor.putString(key, value);
editor.apply();
}
}
The you can call the fucntino like this:
SharedPreferenceHelper.setSharedPreferenceString(MainActivity.this, <key>, <value>);
Source: https://github.com/nickescobedo/Android-Shared-Preferences-Helper/blob/master/SharedPreferenceHelper.java
From the documentation you can see getDefaultSharedPreferences method takes a context as a parameter. This context can be an Activity, a Fragment or a Service. The simplest solution would be to pass the context to your helper class methods via dependency injection. Something like:
void setUsernamePref(String username, Context context){
SharedPreferences preferences =
PreferenceManager.getDefaultSharedPreferences(context);
SharedPreferences.Editor editor = preferences.edit();
editor.putString("username", username);
editor.putBoolean("isLoggedIn", true);
editor.apply();
}
Remember to avoid storing context statically in the helper class, otherwise you'll risk to fall into unwanted memory leaks

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

singleton shared preferences issue

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

Make 'static' class observable

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.

Categories