web-dev-qa-db-ja.com

Daggerを使用せず、Presenterがコンテキストに依存しないようにするMVPで共有設定を使用する方法

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

    //...
}

だから、MaggerでDaggerを使用せず、Presenterがコンテキスト依存にならないように共有設定を使用する方法は?

35

そもそもプレゼンターはContextに依存しないようにしてください。プレゼンターにSharedPreferencesが必要な場合は、それらを constructor に渡す必要があります。
プレゼンターがRepositoryを必要とする場合は、再び constructor に入れてください。 Google clean code talks をよくお読みになることをお勧めします。なぜなら、彼らは非常に良い説明をしてくれるからですwhy適切な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フレームワークを使用することで簡素化できますが、上記のようにRepositoryもプレゼンターもContextに依存しません。実際のSharedPreferencesを提供したい場合、それらのcreationのみがコンテキストに依存します。

リポジトリはいくつかのAPIクライアントとSharedPreferencesに依存し、プレゼンターはRepositoryに依存します。両方のクラスは、モックされたオブジェクトをクラスに提供するだけで簡単にテストできます。

静的コードなし。副作用なし。

69
David Medenjak

これが私のやり方です。以下のような共有設定へのすべての読み取り書き込み操作を処理するシングルトン「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
}

次に、共有設定へのアクセスが必要なときはいつでも、関連するプレゼンターのコンストラクターでSharedPreferencesManagerオブジェクトを渡します。例えば ​​:

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

お役に立てれば!

2
Much Overflow

これは私がそれを実装する方法です。アプリとテストの実装が異なるインターフェイスを使用して設計できます。 UI/testsから依存関係を提供するインターフェイスPersistentStorageを使用しました。これは単なるアイデアであり、自由に変更してください。

アクティビティ/フラグメントから

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

Applicationを通過することなく、RepositoryレイヤーでPresenterコンテキストを使用できます ここで説明したように 。最初にApplicationクラスをサブクラス化し、そのインスタンスを静的変数に保存します。

public class MyApplication extends Application {
    private static context = null;

    public void onCreate(...) {
        context = this;
        ...
    }

    public static Context getContext() {
        return context;
    }
}

次に、AndroidManifestでアプリケーションクラス名を指定します。

<application
    Android:name=".MyApplication"
    ...
    >

</application>

これで、MyApplication.contextを使用して、リポジトリ内のアプリケーションコンテキスト(SharedPreferences、SQLiteデータベース、ネットワークアクセス)を使用できます。

0
Bob

別のアプローチは、Android Architecture libraries:

共有設定はコンテキストに依存するため、それについてのみ知る必要があります。 1つの場所に物事を置くために、私はこれを管理するシングルトンを選択します。これは、2つのクラスで構成されます:Manager(つまり、SharePreferenceManagerまたはServiceManagerまたはその他)、およびコンテキストを注入する初期化子。

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を除くすべての関数はデフォルトの実装です。onCreateは、必要なコンテキストをマネージャーに注入します。

これを機能させるための最後のステップは、マニフェストにプロバイダーを登録することです。

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

これにより、サービスマネージャーは外部コンテキストの初期化から切り離されます。現在では、コンテキストに依存しない別の実装に完全に置き換えることができます。

0
Denis Loh