web-dev-qa-db-fra.com

Comment utiliser les préférences partagées dans MVP sans poignard et ne rendant pas le présentateur dépendant du contexte?

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?

31

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.

67
David Medenjak

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!

2
Much Overflow

Vous pouvez utiliser le contexte Application à la couche Repository sans passer par Presentercomme 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.

0
Bob

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. 

0
Denis Loh