I wanna refactor my project and it has a lot of unnecessary code with hardcoded string. So I wanted to create singleton SharedPrefsManager, i will use Application context. does it occur memory leak cause of static SharedPrefs in my manager class.
public class SharedPrefsManager {
private static SharedPrefsManager sharePref = new SharedPrefsManager();
private static SharedPreferences sharedPreferences;
private SharedPrefsManager() {}
public static SharedPrefsManager getInstance(Context context) {
if (sharedPreferences == null) {
sharedPreferences = context.getSharedPreferences(context.getPackageName(), Activity.MODE_PRIVATE);
}
return sharePref;
}
}
Holding SharedPreferences in static field doesn't provoke memory leaks because Preferences doesn't contain Context itself
No, it will not get any memory Leak if you use an application context. Create the SharedPrefsManager as a singleton and Initialize from your Application class. so that you can access from anywhere
Create like this,
public class SharedPrefsManager {
private static SharedPrefsManager mInstance= null;
private SharedPreferences sharedPreferences;
private SharedPreferences.Editor prefsEditor;
private SharedPrefsManager(Context context) {
sharedPreferences = context.getSharedPreferences(context.getPackageName(), Activity.MODE_PRIVATE);
prefsEditor = sharedPreferences.edit();
}
public static void init(Context context) {
mInstance = new SharedPrefsManager(context);
}
public static SharedPrefsManager getInstance() {
if (mInstance == null) {
throw new RuntimeException(
"Must run init(Application application) before an instance can be obtained");
}
return mInstance;
}
/**
* To get the Stored string value in Preference.
*
* #param key
* #param defaultvalue
* #return stored string value.
*/
public String getStringValue(final String key, final String defaultvalue) {
return sharedPreferences.getString(key, defaultvalue);
}
/**
* To store the string value in prefernce.
*
* #param key
* #param value
*/
public void setStringValue(final String key, final String value) {
prefsEditor.putString(key, value);
prefsEditor.commit();
}
}
From Application class you can initialize like this,
public class StackApp extends Application {
#Override
public void onCreate() {
super.onCreate();
SharedPrefsManager.init(this);
}
}
So that you can use from any other class without context.
Ex: you can use like this,
SharedPrefsManager.getInstance().setStringValue("key", "value");
Related
I developing an app which may often use ability to store/get some pair/value data in any app place such as an activity or it fragments. I decided use for it singleton. Is my class coded in right way?
public class StorageManager {
private static StorageManager instance;
public static final String PREF_NAME = "app_settings";
private SharedPreferences preferences;
private SharedPreferences.Editor editor;
public static StorageManager getInstance(Context context) {
if(instance == null)
instance = new StorageManager(context.getApplicationContext());
return instance;
}
private StorageManager(Context context) {
preferences = context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE);
editor = preferences.edit();
}
public void setUserEmail(String token)
{
editor.putString("email", token);
editor.commit();
}
public String getUserEmail()
{
return preferences.getString("email", "");
}
}
Another clean way to achieve the same :)
public enum AppSharedPref {
instance;
SharedPreferences sharedPreferences;
// setter of the property.
public void setWeatherUpadte(int value) {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(AppLevelConstraints.getAppContext());
sharedPreferences.edit().putInt("Weather", value).apply();
}
// getter of the property.
public int getWeatherUpadte() {
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(AppLevelConstraints.getAppContext());
return sharedPreferences.getInt("Weather", 0);
}
}
Simply access the property with :
AppSharedPref.instance.getWeatherUpadte();
Simple and Easy way to use SharedPrefrences
public class User {
SharedPreferences sharedPreferences;
public String getEnroll() {
enroll=sharedPreferences.getString("userdata","");
return enroll;
}
public void setEnroll(String enroll) {
this.enroll = enroll;
sharedPreferences.edit().putString("userdata",enroll).commit();
}
public void remove(){
sharedPreferences.edit().clear().commit();
}
String enroll;
Context context;
public User(Context context){
this.context=context;
sharedPreferences=context.getSharedPreferences("userinfo",Context.MODE_PRIVATE);
}
}
Create a user class and call it whenever you want to save the user login session
edt_enroll_login=(EditText)findViewById(R.id.edt_roll_login);
user_roll = edt_enroll_login.getText().toString().trim();
User user=new User(LOGIN.this);//pass the context to user class
user.setEnroll(user_roll);//save user session(here by enrollment number) in userinfo.xml file
Here i used login.java file to call user class
Just create User class object to check and remove userdata(session) -User user=new User(yourcallingactivity.this);
And to check if user is already login use user.getEnroll() method of user class,if user is already login then jump to home activity for user
For removing the session of user after logout use user.remove() method of user class.
I'm trying to implement MVP without Dagger (for learning purposes). But I got to the problem - I use Repository patter to get raw data either from cache (Shared Preferences) or network:
Shared Prefs|
|<->Repository<->Model<->Presenter<->View
Network|
But to put my hands on Shared Preferences I have to put somewhere line like
presenter = new Presenter(getApplicationContext());
I use onRetainCustomNonConfigurationInstance/getLastCustomNonConfigurationInstance pair to keep Presenter "retained".
public class MyActivity extends AppCompatActivity implements MvpView {
#Override
protected void onCreate(Bundle savedInstanceState) {
//...
presenter = (MvpPresenter) getLastCustomNonConfigurationInstance();
if(null == presenter){
presenter = new Presenter(getApplicationContext());
}
presenter.attachView(this);
}
#Override
public Object onRetainCustomNonConfigurationInstance() {
return presenter;
}
//...
}
So how to use Shared Preferences in MVP without Dagger and not causing Presenter to be Context dependent?
Your Presenter should not be Context dependent in the first place. If your presenter needs SharedPreferences you should pass them in the constructor.
If your presenter needs a Repository, again, put that in the constructor. I highly suggest watching Google clean code talks since they do a really good job explaining why you should use a proper API.
This is proper dependency management, which will help you write clean, maintainable, and testable code.
And whether you use dagger, some other DI tool, or supply the objects yourself is irrelevant.
public class MyActivity extends AppCompatActivity implements MvpView {
#Override
protected void onCreate(Bundle savedInstanceState) {
SharedPreferences preferences = // get your preferences
ApiClient apiClient = // get your network handling object
Repository repository = new Repository(apiClient, preferences);
presenter = new Presenter(repository);
}
}
This object creation can be simplified by using a factory pattern, or some DI framework like dagger, but as you can see above neither Repository nor your presenter depends on a Context. If you want to supply your actual SharedPreferences only their creation of them will depend on the context.
Your repository depends on some API client and SharedPreferences, your presenter depends on the Repository. Both classes can easily be tested by just supplying mocked objects to them.
Without any static code. Without any side effects.
This is how I do it. I have a singleton "SharedPreferencesManager" class that will handle all the read write operations to shared prefs like below
public final class SharedPreferencesManager {
private static final String MY_APP_PREFERENCES = "ca7eed88-2409-4de7-b529-52598af76734";
private static final String PREF_USER_LEARNED_DRAWER = "963dfbb5-5f25-4fa9-9a9e-6766bfebfda8";
... // other shared preference keys
private SharedPreferences sharedPrefs;
private static SharedPreferencesManager instance;
private SharedPreferencesManager(Context context){
//using application context just to make sure we don't leak any activities
sharedPrefs = context.getApplicationContext().getSharedPreferences(MY_APP_PREFERENCES, Context.MODE_PRIVATE);
}
public static synchronized SharedPreferencesManager getInstance(Context context){
if(instance == null)
instance = new SharedPreferencesManager(context);
return instance;
}
public boolean isNavigationDrawerLearned(){
return sharedPrefs.getBoolean(PREF_USER_LEARNED_DRAWER, false);
}
public void setNavigationDrawerLearned(boolean value){
SharedPreferences.Editor editor = sharedPrefs.edit();
editor.putBoolean(PREF_USER_LEARNED_DRAWER, value);
editor.apply();
}
... // other shared preference accessors
}
Then whenever access to shared preference is needed I pass the SharedPreferencesManager object in the relevant Presenter's constructor. For example :
if(null == presenter){
presenter = new Presenter(SharedPreferencesManager.getInstance(getApplicationContext()));
}
Hope this helps!
Another approach can also be found in the Android Architecture libraries:
As the Shared Preferences depends on a context, it solely should know about it. To have things in one place, I choose a Singleton to manage this. It consists of two classes: the Manager (i.e. the SharePreferenceManager or ServiceManager or whatever), and an initializer which injects the Context.
class ServiceManager {
private static final ServiceManager instance = new ServiceManager();
// Avoid mem leak when referencing context within singletons
private WeakReference<Context> context
private ServiceManager() {}
public static ServiceManager getInstance() { return instance; }
static void attach(Context context) { instance.context = new WeakReference(context); }
... your code...
}
The initializer is basically an empty Provider (https://developer.android.com/guide/topics/providers/content-providers.html), which is registered in the AndroidManifest.xml and loaded when the app starts:
public class ServiceManagerInitializer extends ContentProvider {
#Override
public boolean onCreate() {
ServiceManager.init(getContext());
return false;
}
#Nullable
#Override
public Cursor query(#NonNull Uri uri, #Nullable String[] projection, #Nullable String selection, #Nullable String[] selectionArgs, #Nullable String sortOrder) {
return null;
}
#Nullable
#Override
public String getType(#NonNull Uri uri) {
return null;
}
#Nullable
#Override
public Uri insert(#NonNull Uri uri, #Nullable ContentValues values) {
return null;
}
#Override
public int delete(#NonNull Uri uri, #Nullable String selection, #Nullable String[] selectionArgs) {
return 0;
}
#Override
public int update(#NonNull Uri uri, #Nullable ContentValues values, #Nullable String selection, #Nullable String[] selectionArgs) {
return 0;
}
}
All function are default implementations except the onCreate, which injects the required context into our manager.
Last step to get this working is to register the provider in the manifest:
<provider
android:authorities="com.example.service-trojan"
android:name=".interactor.impl.ServiceManagerInitializer"
android:exported="false" />
This way, your service manager is decoupled from any external context initialization. It now can be completely replaced with another implementation which is context-independent.
This is how I implement it. You can design it with an interface where you have different implementation for your app and test. I have used interface PersistentStorage which I provide depdencdy from UI/tests. This is just an idea, feel free to modify it.
From your Activity/Fragment
public static final String PREF_NAME = "app_info_cache";
#Inject
DataManager dataManager;
void injectDepedendency(){
DaggerAppcompnent.inject(this);//Normal DI withDagger
dataManager.setPersistentStorage(new PersistentStorageImp(getSharedPreferences()));
}
//In case you need to pass from Fragment then you need to resolve getSharedPreferences with Context
SharedPreferences getSharedPreferences() {
return getSharedPreferences(PREF_NAME,
Context.MODE_MULTI_PROCESS | Context.MODE_MULTI_PROCESS);
}
//This is how you can use in Testing
#Inject
DataManager dataManager;
#Before
public void injectDepedendency(){
DaggerTestAppcompnent.inject(this);
dataManager.setPersistentStorage(new MockPersistentStorageImp());
}
#Test
public void testSomeFeature_ShouldStoreInfo(){
}
/**
YOUR DATAMANAGER
*/
public interface UserDataManager {
void setPersistentStorage(PersistentStorage persistentStorage);
}
public class UserDataManagerImp implements UserDataManager{
PersistentStorage persistentStorage;
public void setPersistentStorage(PersistentStorage persistentStorage){
this.persistentStorage = persistentStorage;
}
}
public interface PersistentStorage {
/**
Here you can define all the methods you need to store data in preferences.
*/
boolean getBoolean(String arg, boolean defaultval);
void putBoolean(String arg, boolean value);
String getString(String arg, String defaultval);
void putString(String arg, String value);
}
/**
PersistentStorage Implementation for Real App
*/
public class PersistentStorageImp implements PersistentStorage {
SharedPreferences preferences;
public PersistentStorageImp(SharedPreferences preferences){
this.preferences = preferences;
}
private SharedPreferences getSharedPreferences(){
return preferences;
}
public String getString(String arg, String defaultval) {
SharedPreferences pref = getSharedPreferences();
return pref.getString(arg, defaultval);
}
public boolean getBoolean(String arg, boolean defaultval) {
SharedPreferences pref = getSharedPreferences();
return pref.getBoolean(arg, defaultval);
}
public void putBoolean(String arg, boolean value) {
SharedPreferences pref = getSharedPreferences();
SharedPreferences.Editor editor = pref.edit();
editor.putBoolean(arg, value);
editor.commit();
}
public void putString(String arg, String value) {
SharedPreferences pref = getSharedPreferences();
SharedPreferences.Editor editor = pref.edit();
editor.putString(arg, value);
editor.commit();
}
}
/**
PersistentStorage Implementation for testing
*/
public class MockPersistentStorageImp implements PersistentStorage {
private Map<String,Object> map = new HashMap<>();
#Override
public boolean getBoolean(String key, boolean defaultval) {
if(map.containsKey(key)){
return (Boolean) map.get(key);
}
return defaultval;
}
#Override
public void putBoolean(String key, boolean value) {
map.put(key,value);
}
#Override
public String getString(String key, String defaultval) {
if(map.containsKey(key)){
return (String) map.get(key);
}
return defaultval;
}
#Override
public void putString(String key, String value) {
map.put(key,value);
}
}
I have a problem where my shared preferences are not working in a class file.I am confused and not able to solve it.Below is my file globalfile which saves data as follows.
public class globalfile extends Activity {
SharedPreferences sharedpreferences;
public static final String mypreference = "mypref";
public static final String Pwd = "pwdKey";
public static final String Email = "emailKey";
private static String global_username = "null/", global_pwd = "null/";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
sharedpreferences = getSharedPreferences(mypreference,
Context.MODE_PRIVATE);
}
public String getusername() {
global_username = sharedpreferences.getString(Email, "");
return global_username;
}
public String getuserpwd() {
global_pwd = sharedpreferences.getString(Pwd, "");
return global_pwd;
}
public void setusername(String someVariable) {
global_username = someVariable;
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Email,global_username);
editor.commit();
}
public void setuserpwd(String someVariable) {
global_pwd = someVariable;
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Pwd,global_pwd);
editor.commit();
}
}
I first called setuserpwd() & setusername() then getuserpwd() & getusername() from another activity using object of class globalfile.But always returns null.although if I use this code without shared pref.it is working fine
create an instance of Activity with Activity class object
Technically, you can create an instance of an Activity class. but, activity instance would be useless because its underlying Context would not have been set up.
The rule is that you should never ever create instances of Android components (Activity, Service, BroadcastReceiver, Provider) yourself (using the new keyword or other means). These classes should only ever be created by the Android framework, because Android sets up the underlying Context for these objects and also manages the lifecycle.
I think your newly created activity object can't get actual context. so try to avoid your current flow. I suggest you to create a separate class that may use as factory class and I think your problem will solved.
Or another solution would be like this :
Context context = /*get application context*/
SharedPreferences sharedPref = context.getSharedPreferences(
getString(R.string.preference_file_key), Context.MODE_PRIVATE);
/*get value from this sharedPref*/
So you don't need to create activity class object and here you should avoid your previous getter method to get value.
change you globalfile to be a utility class and not an activity so it can be used really easily by other parts of your app (that aren't an activity but have access to an android context).
public class Global {
private final SharedPreferences sharedpreferences;
public static final String mypreference = "mypref";
public static final String Pwd = "pwdKey";
public static final String Email = "emailKey";
private static String global_username = "null/",
global_pwd = "null/";
public Global(Context context) {
this.sharedpreferences = context.getSharedPreferences(mypreference,
Context.MODE_PRIVATE);
}
public String getusername() {
global_username = sharedpreferences.getString(Email, "null/");
return global_username;
}
public String getuserpwd() {
global_pwd = sharedpreferences.getString(Pwd, "null/");
return global_pwd;
}
public void setusername(String someVariable) {
global_username = someVariable;
sharedpreferences.edit().putString(Email,global_username).commit();
}
public void setuserpwd(String someVariable) {
global_pwd = someVariable;
sharedpreferences.edit().putString(Pwd,global_pwd).commit();
}
}
And here's how to use your new util class
Global global = new Global(context);
global.setusername("foo");
Log.d("TAG", "username from prefs = " + global.getusername());
global.setuserpwd("bar");
Log.d("TAG", "password from prefs = " + global.getusername());
How to save values in preferences (and read) with quite compact way without creating several instances - i want be sure that i have only one instance of pref
A SharedPreferences object is a natural singleton. Your first call to read in a SharedPreferences (e.g., PreferenceManager.getDefaultSharedPreferences()) will create it. Second and subsequent calls within the same process will return that same instance. Hence, so long as you are consistent about where you get your SharedPreferences from, you will only ever have one instance (at most) in your process.
CommonsWare is right, but as i understood, you want somehow to structure you job with Preferences, i use singleton for this, something like this:
public class Preferences {
private static final String USERNAME = "username";
private static final String LOG_TAG = Preferences.class.getSimpleName();
private static Preferences sInstance;
private SharedPreferences mSharedPreferences;
private SharedPreferences.Editor mEditor;
private Context mContext;
private Preferences(Context context) {
mContext = context;
mSharedPreferences = PreferenceManager.getDefaultSharedPreferences(mContext);
mEditor = mSharedPreferences.edit();
}
public static Preferences getInstance(Context context) {
if (sInstance == null) {
sInstance = new Preferences(context);
}
return sInstance;
}
public void setUsername(String username) {
mEditor.putString(USERNAME, username);
commit();
}
public String getUsername() {
return mSharedPreferences.getString(USERNAME, null);
}
public boolean commit() {
return mEditor.commit();
}
}
And where it is necessary write
Preferences.getInstance(this).setUsername("USERNAME");
and read
Preferences.getInstance(this).getUsername();
How to pass application context from Singleton class to SharedPreferences? I have a fragment and a GridView inside its onActivityCreated(Bundle savedInstanceState) method , on item-click, I am getting NullPointerException in logcat:
03-30 05:12:54.784: E/AndroidRuntime(1950): FATAL EXCEPTION: main
03-30 05:12:54.784: E/AndroidRuntime(1950): java.lang.NullPointerException
03-30 05:12:54.784: E/AndroidRuntime(1950): at android.preference.PreferenceManager.getDefaultSharedPreferencesName(PreferenceManager.java:374)
03-30 05:12:54.784: E/AndroidRuntime(1950): at android.preference.PreferenceManager.getDefaultSharedPreferences(PreferenceManager.java:369)
03-30 05:12:54.784: E/AndroidRuntime(1950): at com.example.duaapp.utility.SharedPreferencesSupplication.save(SharedPreferencesSupplication.java:35)
Singleton Class
public class SingletonClass {
public static Context applicationContext;
public static int fontSizeMin = 17;
public static int fontSizeMax = 35;
public static final String keySelVerseFromList = "keySelVerseFromList";
public static final String keyFavVerses = "keyFavVerses";
public static final String keyListOfVerses = "keyListOfVerses";
public static final String keyIsFavSelected = "keyIsFavSelected";
}
SharedPreferences
public class SharedPreferencesSupplication {
public void save(String valueKey, String value) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SingletonClass.applicationContext);
SharedPreferences.Editor edit = prefs.edit();
edit.putString(valueKey, value);
edit.commit();
}
public void save(String valueKey, int value) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SingletonClass.applicationContext);
SharedPreferences.Editor edit = prefs.edit();
edit.putInt(valueKey, value);
edit.commit();
}
public void save(String valueKey, boolean value) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SingletonClass.applicationContext);
SharedPreferences.Editor edit = prefs.edit();
edit.putBoolean(valueKey, value);
edit.commit();
}
public void save(String valueKey, long value) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(SingletonClass.applicationContext);
SharedPreferences.Editor edit = prefs.edit();
edit.putLong(valueKey, value);
edit.commit();
}
}
On gridview_item_Click, whenever new SharedPreferencesSupplication().save(SingletonClass.keyIsFavSelected, false); is called, the app crashes and nullpointer exception is raised in logcat. Where am I going wrong?
Never use static references to Context in Android, this will cause you an important memory leak since Context has references to all the application resources.
Most of the Android UI classes have a dynamic reference to the context, in fragments you can do getActivity(), in views getContext() consider using those instead of context singletons
More information about this here
While Guillermo Merino concern is valid, an application Context used in a singleton class should not be automatically considered a memory leak.
Calling context.getApplicationContext() will, regardless of where or how it is accessed, always return the same instance from within your process. It will be "alive" for at least as long as the singleton will, therefore holding a reference to it should do no harm.
That said, it might not be enough to perform certain operations, as Dave Smith describes in his blog post.
Since OP is trying to access default SharedPreferences, this could be achieved using just application Context, e.g.:
// Custom Application class.
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
SingletonClass.INSTANCE.init(getApplicationContext());
}
}
// Singleton.
public enum SingletonClass {
INSTANCE;
private Context applicationContext;
public static int fontSizeMin = 17;
public static int fontSizeMax = 35;
public static final String keySelVerseFromList = "keySelVerseFromList";
public static final String keyFavVerses = "keyFavVerses";
public static final String keyListOfVerses = "keyListOfVerses";
public static final String keyIsFavSelected = "keyIsFavSelected";
// Make sure you only call this once - from MyApplication.
public void init(final Context context) {
applicationContext = context.getApplicationContext();
}
public Context getApplicationContext() {
if (null == applicationContext) {
throw new IllegalStateException("have you called init(context)?");
}
return applicationContext;
}
}
I don't believe this would cause any memory leaks, but if I'm mistaken, please do correct me.