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がコンテキスト依存にならないように共有設定を使用する方法は?
そもそもプレゼンターは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
に依存します。両方のクラスは、モックされたオブジェクトをクラスに提供するだけで簡単にテストできます。
静的コードなし。副作用なし。
これが私のやり方です。以下のような共有設定へのすべての読み取り書き込み操作を処理するシングルトン「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()));
}
お役に立てれば!
これは私がそれを実装する方法です。アプリとテストの実装が異なるインターフェイスを使用して設計できます。 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);
}
}
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データベース、ネットワークアクセス)を使用できます。
別のアプローチは、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...
}
初期化子は基本的に空のProvider
( https://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" />
これにより、サービスマネージャーは外部コンテキストの初期化から切り離されます。現在では、コンテキストに依存しない別の実装に完全に置き換えることができます。