【问题标题】:How to use Shared Preferences in MVP without Dagger and not causing Presenter to be Context dependent?如何在没有 Dagger 的情况下在 MVP 中使用共享首选项并且不会导致 Presenter 依赖于上下文?
【发布时间】:2016-09-05 09:04:50
【问题描述】:

我正在尝试在没有 Dagger 的情况下实现 MVP(出于学习目的)。但我遇到了问题 - 我使用存储库模式从缓存(共享首选项)或网络获取原始数据:

Shared Prefs| 
            |<->Repository<->Model<->Presenter<->View
     Network|

但是为了让我的手放在共享首选项上,我必须放在某个地方,比如

presenter = new Presenter(getApplicationContext());

我使用 onRetainCustomNonConfigurationInstance/getLastCustomNonConfigurationInstance 对来保持 Presenter “保留”。

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 中使用 Shared Preferences 并且不会导致 Presenter 依赖于上下文?

【问题讨论】:

    标签: android dependency-injection dependencies sharedpreferences mvp


    【解决方案1】:

    我就是这样做的。我有一个单例“SharedPreferencesManager”类,它将处理对共享首选项的所有读写操作,如下所示

    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 对象。例如:

    if(null == presenter){
        presenter = new Presenter(SharedPreferencesManager.getInstance(getApplicationContext()));
    }
    

    希望这会有所帮助!

    【讨论】:

    • 那么,你为什么选择让它成为单例呢?
    • 可能......,他这样做是为了确保整个应用程序中只存在一个共享首选项实例
    • @Much : 你是如何在 String 中输入 ca7eed88-2409-4de7-b529-52598af76734 的......它绝对没有输入某种捷径
    【解决方案2】:

    首先,您的演示者不应该依赖于 Context。如果您的演示者需要SharedPreferences,您应该将它们传递给 构造函数
    如果您的演示者需要Repository,再次将其放入构造函数。我强烈建议观看Google clean code talks,因为他们很好地解释了为什么您应该使用适当的 API。

    这是正确的依赖管理,它将帮助您编写干净、可维护和可测试的代码。 并且无论您使用匕首、其他 DI 工具还是自己提供对象都无关紧要。

    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 框架(如 dagger)来简化此对象创建,但正如您在上面看到的那样,Repository 和您的演示者都不依赖于 Context。如果您想提供实际的SharedPreferences,则只有它们的创建将取决于上下文。

    您的存储库依赖于一些 API 客户端和 SharedPreferences,您的演示者依赖于 Repository。只需向它们提供模拟对象即可轻松测试这两个类。

    没有任何静态代码。没有任何副作用。

    【讨论】:

    • 如果你让你的代码依赖于存储接口而不是存储实现(就像 SharedPreferences 一样)会更好吗?拥有一个接口将使演示者真正与框架无关,因为只有存储接口的实现才会知道 Android 框架
    • @NicolásCarrasco 当然,如果需要,您可以添加进一步的抽象。这个答案主要是为了展示如何不依赖于上下文,我不想让事情变得过于复杂。
    • @DavidMedenjak 我是 MVP 新手,请帮助我了解此解决方案如何提供帮助。因为我觉得 presenter 取决于 respositoryrepository 取决于 shared pref。 ..这不意味着presenter 取决于shared pref(间接)..或者我错过了核心
    • @eRaisedToX 没错。但是演示者现在可以使用模拟存储库进行测试,并且本身不依赖于 Android 框架。您也可以随时从 SharedPreferences 切换到其他一些基于文件的设置,而无需更改演示者。它是关于干净的代码、简单的测试和单一责任原则
    • 我也有同样的问题。我需要 sharedPreferences 但我还需要在模型中获取本地字符串(context.getString(R.string...)。我该如何实现?
    【解决方案3】:

    另一种方法也可以在 Android 架构库中找到:

    由于共享首选项取决于上下文,它应该只知道它。为了将东西放在一个地方,我选择了一个 Singleton 来管理它。它由两个类组成:Manager(即 SharePreferenceManager 或 ServiceManager 或其他),以及注入 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...
    
    }
    

    初始化器基本上是一个空的Providerhttps://developer.android.com/guide/topics/providers/content-providers.html),注册在AndroidManifest.xml中,应用启动时加载:

    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,它将所需的上下文注入到我们的管理器中。

    完成这项工作的最后一步是在清单中注册提供程序:

    <provider
                android:authorities="com.example.service-trojan"
                android:name=".interactor.impl.ServiceManagerInitializer"
                android:exported="false" />
    

    这样,您的服务管理器与任何外部上下文初始化分离。它现在可以完全替换为与上下文无关的另一个实现。

    【讨论】:

      【解决方案4】:

      这就是我实现它的方式。您可以使用一个界面来设计它,您可以在其中为您的应用程序和测试提供不同的实现。我使用了接口 PersistentStorage,我从 UI/测试中提供了 depdencdy。这只是一个想法,请随时修改。

      来自您的活动/片段

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-01-05
        • 2017-02-15
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多