How to use Shared Preferences in MVP without Dagger and not causing Presenter to be Context dependent?
我正在尝试在没有Dagger的情况下实现MVP(用于学习目的)。 但我遇到了问题 - 我使用Repository模式从缓存(共享首选项)或网络获取原始数据:
1 2 3 | Shared Prefs| |<->Repository<->Model<->Presenter<->View Network| |
但要把手放在共享首选项上,我必须把它放在某个地方
1 | presenter = new Presenter(getApplicationContext()); |
我使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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; } //... } |
那么如何在没有Dagger的MVP中使用共享首选项并且不会导致Presenter依赖于上下文?
您的Presenter首先不应该
如果您的演示者需要
这是正确的依赖关系管理,它将帮助您编写干净,可维护且可测试的代码。
无论你使用匕首,其他一些DI工具,还是自己提供物品都是无关紧要的。
1 2 3 4 5 6 7 8 9 10 | 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); } } |
通过使用工厂模式或某些DI框架(如匕首)可以简化此对象创建,但正如您在上面看到的那样,
您的存储库依赖于某些API客户端和
没有任何静态代码。没有任何副作用。
我就是这样做的。我有一个单独的"SharedPreferencesManager"类,它将处理对共享prefs的所有读写操作,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | 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 } |
然后,只要需要访问共享首选项,我就会在相关的Presenter构造函数中传递SharedPreferencesManager对象。例如 :
1 2 3 | if(null == presenter){ presenter = new Presenter(SharedPreferencesManager.getInstance(getApplicationContext())); } |
希望这可以帮助!
这就是我实现它的方式。您可以使用一个界面来设计它,在该界面中您的应用程序和测试具有不同的实现。我使用了接口PersistentStorage,我从UI /测试中提供了depdencdy。这只是一个想法,随意修改它。
来自您的活动/片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 | 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); } } |
另一种方法也可以在Android架构库中找到:
由于共享首选项取决于上下文,因此它应该只知道它。为了把东西放在一个地方,我选择了一个Singleton来管理它。它由两个类组成:Manager(即SharePreferenceManager或ServiceManager或其他),以及注入Context的初始化程序。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | 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... } |
初始化程序基本上是一个空的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | 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; } } |
除了onCreate之外,所有函数都是默认实现,它将所需的上下文注入到我们的管理器中。
要使其工作的最后一步是在清单中注册提供程序:
1 2 3 4 | <provider android:authorities="com.example.service-trojan" android:name=".interactor.impl.ServiceManagerInitializer" android:exported="false" /> |
这样,您的服务管理器就可以与任何外部上下文初始化分离。它现在可以完全替换为与上下文无关的另一个实现。
您可以在
1 2 3 4 5 6 7 8 9 10 11 12 | public class MyApplication extends Application { private static context = null; public void onCreate(...) { context = this; ... } public static Context getContext() { return context; } } |
然后在
1 2 3 4 5 6 | <application android:name=".MyApplication" ... > </application> |
现在,您可以使用