How to use Shared Preferences in MVP without Dagger and not causing Presenter to be Context dependent?
Solution 1
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.
Solution 2
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!
![Marian Paździoch](https://i.stack.imgur.com/BImS2.jpg?s=256&g=1)
Comments
-
Marian Paździoch about 2 years
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?
-
Nicolás Carrasco-Stevenson over 7 yearsWould it be much better if you make your code depend on a storage interface rather than on a storage implementation (like SharedPreferences is)? Having an interface would make the presenter truly framework agnostic since only the implementation of your storage interface would know about the Android framework
-
David Medenjak over 7 years@NicolásCarrasco of course you could add further abstraction if and where needed. This answer was primarily to show how not to be context dependent and I don't want to overcomplicate things.
-
MrJre over 7 yearsSo, why did you choose to make it a singleton?
-
eRaisedToX about 7 yearsprobably..,he did so, to make sure only one instance of shared pref exists throughout the app
-
eRaisedToX about 7 years@DavidMedenjak I m new to MVP ,kindly help me understand how this solution helps..because it feels to me like
presenter
depends onrespository
andrepository
depends onshared pref
...does that not meanpresenter
depends onshared pref
(indirectly) ..OR I m missing the core -
David Medenjak about 7 years@eRaisedToX That's right. But the presenter can now be tested with a mocked repository and has no dependency on the Android framework by itself. You could also switch from SharedPreferences to some other file based settings at any time, without changing the presenter at all. It's about clean code, easy testing, and the single responsibility princible
-
Thracian about 6 yearsI have the same issue. I need sharedPreferences but also i need to get local strings(context.getString(R.string...) in model. How can i achieve this?
-
Yatin almost 6 years@Much : How did you typed ca7eed88-2409-4de7-b529-52598af76734 in String ... its definitely not typed some sort of short cut