J'essaie de mettre en œuvre MVP sans poignard (à des fins d'apprentissage). Mais je suis arrivé au problème - j'utilise le modèle de référentiel pour obtenir des données brutes à partir du cache (Préférences partagées) ou du réseau:
Shared Prefs|
|<->Repository<->Model<->Presenter<->View
Network|
Mais pour mettre la main sur les préférences partagées, je dois mettre une ligne comme
presenter = new Presenter(getApplicationContext());
J'utilise la paire onRetainCustomNonConfigurationInstance
/getLastCustomNonConfigurationInstance
pour que Presenter soit "conservé".
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;
}
//...
}
Alors, comment utiliser les préférences partagées dans MVP sans poignard et ne pas rendre le présentateur dépendant du contexte?
Votre présentateur ne devrait pas être Context
dépendant en premier lieu. Si votre présentateur a besoin de SharedPreferences
, vous devriez le transmettre à le constructeur.
Si votre présentateur a besoin d'une Repository
, mettez-le à nouveau dans le constructeur. Je suggère fortement de regarder Google code propre pourparlers car ils font un très bon travail en expliquant pourquoi vous devriez utiliser une API appropriée.
Il s’agit d’une gestion appropriée des dépendances, qui vous aidera à rédiger du code propre, maintenable et testable . Que vous utilisiez un poignard, un autre outil DI ou fournissiez vous-même les objets n’a aucune pertinence.
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);
}
}
Cette création d’objet peut être simplifiée à l’aide d’un modèle d’usine, ou d’un framework DI tel que dagger, mais comme vous pouvez le voir ci-dessus, ni Repository
ni votre présentateur ne dépendent d’une Context
. Si vous voulez fournir votre SharedPreferences
réelle, seule leur création dépendra du contexte.
Votre référentiel dépend de certains clients API et de SharedPreferences
, votre présentateur dépend de Repository
. Les deux classes peuvent facilement être testées en leur fournissant simplement des objets fictifs.
Sans code statique. Sans aucun effet secondaire.
C'est comme ça que je le fais. J'ai une classe singleton "SharedPreferencesManager" qui gérera toutes les opérations de lecture/écriture vers les préférences partagées comme ci-dessous
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
}
Ensuite, chaque fois que l'accès aux préférences partagées est nécessaire, je passe l'objet SharedPreferencesManager dans le constructeur du présentateur approprié. Par exemple :
if(null == presenter){
presenter = new Presenter(SharedPreferencesManager.getInstance(getApplicationContext()));
}
J'espère que cela t'aides!
Vous pouvez utiliser le contexte Application
à la couche Repository
sans passer par Presenter
comme indiqué ici . Commencez par sous-classer votre classe Application et enregistrez son instance dans une variable statique.
public class MyApplication extends Application {
private static context = null;
public void onCreate(...) {
context = this;
...
}
public static Context getContext() {
return context;
}
}
Puis mentionnez le nom de votre classe d’application à la AndroidManifest
,
<application
Android:name=".MyApplication"
...
>
</application>
Vous pouvez maintenant utiliser le contexte de l'application à l'intérieur du référentiel (soit pour SharedPreferences, base de données SQLite, accès réseau) en utilisant MyApplication.context
.
Une autre approche peut également être trouvée dans les bibliothèques d'architecture Android:
Comme les préférences partagées dépendent d'un contexte, elles doivent uniquement le savoir. Pour avoir les choses au même endroit, j'ai choisi un Singleton pour gérer ça. Il se compose de deux classes: le gestionnaire (c'est-à-dire SharePreferenceManager ou ServiceManager ou autre) et un initialiseur qui injecte le contexte.
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...
}
L'initialiseur est essentiellement une Provider
( https://developer.Android.com/guide/topics/providers/content-providers.html ) vide, enregistrée dans le AndroidManifest.xml
et chargée au démarrage de l'application:
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;
}
}
Toutes les fonctions sont des implémentations par défaut, à l'exception de onCreate, qui injecte le contexte requis dans notre gestionnaire.
La dernière étape pour que cela fonctionne est d’enregistrer le fournisseur dans le manifeste:
<provider
Android:authorities="com.example.service-trojan"
Android:name=".interactor.impl.ServiceManagerInitializer"
Android:exported="false" />
De cette façon, votre gestionnaire de services est découplé de toute initialisation de contexte externe. Il peut maintenant être complètement remplacé par une autre implémentation indépendante du contexte.