How to use Shared Preferences in MVP without Dagger and not causing Presenter to be Context dependent? How to use Shared Preferences in MVP without Dagger and not causing Presenter to be Context dependent? android android

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.