2015-12-16 5 views
42

Ich habe seit ein paar Wochen mit dem MVP-Muster herum gespielt und bin zu dem Punkt gekommen, wo ich den Kontext brauche, um eine service zu starten und auf Shared Preferences zuzugreifen.Hat der Moderator, der die Aktivität/den Kontext kennt, eine schlechte Idee im MVP-Muster?

Ich habe gelesen, dass der Zweck der MVP den Blick von der Logik zu entkoppeln ist und diesen Zweck vereiteln können (korrigieren Sie mich, wenn ich das falsch bin) innerhalb eines Presentercontext mit.

Derzeit habe ich eine LoginActivity, die etwa wie folgt aussieht:

LoginActivity.java

public class LoginActivity extends Activity implements ILoginView { 

    private final String LOG_TAG = "LOGIN_ACTIVITY"; 

    @Inject 
    ILoginPresenter mPresenter; 
    @Bind(R.id.edit_login_password) 
    EditText editLoginPassword; 
    @Bind(R.id.edit_login_username) 
    EditText editLoginUsername; 
    @Bind(R.id.progress) 
    ProgressBar mProgressBar; 

    @Override 
    protected void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     setContentView(R.layout.activity_login); 
     MyApplication.getObjectGraphPresenters().inject(this); 
     mPresenter.setLoginView(this, getApplicationContext()); 
    } 

    @Override 
    public void onStart() { 
     mPresenter.onStart(); 
     ButterKnife.bind(this); 
     super.onStart(); 
    } 

    @Override 
    public void onResume() { 
     mPresenter.onResume(); 
     super.onResume(); 
    } 

    @Override 
    public void onPause() { 
     mPresenter.onPause(); 
     super.onPause(); 
    } 

    @Override 
    public void onStop() { 
     mPresenter.onStop(); 
     super.onStop(); 
    } 

    @Override 
    public void onDestroy() { 
     ButterKnife.unbind(this); 
     super.onDestroy(); 
    } 

    @OnClick(R.id.button_login) 
    public void onClickLogin(View view) { 
     mPresenter.validateCredentials(editLoginUsername.getText().toString(), 
       editLoginPassword.getText().toString()); 
    } 

    @Override public void showProgress() { mProgressBar.setVisibility(View.VISIBLE); } 

    @Override public void hideProgress() { 
     mProgressBar.setVisibility(View.GONE); 
    } 

    @Override public void setUsernameError() { editLoginUsername.setError("Username Error"); } 

    @Override public void setPasswordError() { editLoginPassword.setError("Password Error"); } 

    @Override public void navigateToHome() { 
     startActivity(new Intent(this, HomeActivity.class)); 
     finish(); 
    } 
} 

Presenter Schnittstelle ILoginPresenter.java

public interface ILoginPresenter { 
    public void validateCredentials(String username, String password); 


    public void onUsernameError(); 

    public void onPasswordError(); 

    public void onSuccess(LoginEvent event); 

    public void setLoginView(ILoginView loginView, Context context); 

    public void onResume(); 

    public void onPause(); 

    public void onStart(); 

    public void onStop(); 
} 

Schließlich meine Presenter :

LoginPresenterImpl.java

public class LoginPresenterImpl implements ILoginPresenter { 

    @Inject 
    Bus bus; 

    private final String LOG_TAG = "LOGIN_PRESENTER"; 
    private ILoginView loginView; 
    private Context context; 
    private LoginInteractorImpl loginInteractor; 

    public LoginPresenterImpl() { 
     MyApplication.getObjectGraph().inject(this); 
     this.loginInteractor = new LoginInteractorImpl(); 
    } 

    /** 
    * This method is set by the activity so that way we have context of the interface 
    * for the activity while being able to inject this presenter into the activity. 
    * 
    * @param loginView 
    */ 
    @Override 
    public void setLoginView(ILoginView loginView, Context context) { 
     this.loginView = loginView; 
     this.context = context; 

     if(SessionUtil.isLoggedIn(this.context)) { 
      Log.i(LOG_TAG, "User logged in already"); 
      this.loginView.navigateToHome(); 
     } 
    } 

    @Override 
    public void validateCredentials(String username, String password) { 
     loginView.showProgress(); 
     loginInteractor.login(username, password, this); 
    } 

    @Override 
    public void onUsernameError() { 
     loginView.setUsernameError(); 
     loginView.hideProgress(); 
    } 

    @Override 
    public void onPasswordError() { 
     loginView.setPasswordError(); 
     loginView.hideProgress(); 
    } 

    @Subscribe 
    @Override 
    public void onSuccess(LoginEvent event) { 
     if (event.getIsSuccess()) { 
      SharedPreferences.Editor editor = 
        context.getSharedPreferences(SharedPrefs.LOGIN_PREFERENCES 
          .isLoggedIn, 0).edit(); 
      editor.putString("logged_in", "true"); 
      editor.commit(); 

      loginView.navigateToHome(); 
      loginView.hideProgress(); 
     } 
    } 

    @Override 
    public void onStart() { 
     bus.register(this); 
    } 

    @Override 
    public void onStop() { 
     bus.unregister(this); 

    } 

    @Override 
    public void onPause() { 

    } 

    @Override 
    public void onResume() { 
    } 
} 

Wie Sie sehen können, habe ich den Kontext aus den Activity in meine Presenter nur so kann ich die Shared Preferences zugreifen. Ich bin ziemlich besorgt darüber, den Kontext in meinen Moderator zu übertragen. Ist das eine gute Sache? Oder sollte ich es anders machen?

EDIT Implementiert Jahnold der 3. Präferenz

sie also die Schnittstelle und Implementierung ignorieren, weil es so ziemlich die ganze Sache. So, jetzt bin ich injecting die Schnittstelle für die Sharedreference in meinem Presenter. Hier ist mein Code für die AppModule

AppModule.java

@Module(library = true, 
    injects = { 
      LoginInteractorImpl.class, 
      LoginPresenterImpl.class, 
      HomeInteractorImpl.class, 
      HomePresenterImpl.class, 

    } 
) 
public class AppModule { 

    private MyApplication application; 

    public AppModule(MyApplication application) { 
     this.application = application; 
    } 

    @Provides 
    @Singleton 
    public RestClient getRestClient() { 
     return new RestClient(); 
    } 

    @Provides 
    @Singleton 
    public Bus getBus() { 
     return new Bus(ThreadEnforcer.ANY); 
    } 

    @Provides 
    @Singleton 
    public ISharedPreferencesRepository getSharedPreferenceRepository() { return new SharedPreferencesRepositoryImpl(application.getBaseContext()); } 

    } 
} 

Die Art und Weise ich den Kontext von MyApplication.java ist

Wenn die Anwendung beginnt, ich sicherstellen, dass dieses Objekt Graph mit dieser Codezeile zu erstellen:

objectGraph = ObjectGraph.create(new AppModule(this)); 

Ist das okay? Ich meine, ich muss jetzt nicht den Kontext von der Aktivität in meinen Moderator übertragen, aber ich habe immer noch Kontext der Anwendung.

Antwort

57

Es ist schon eine Weile her, dass Sie diese Frage gestellt haben, aber ich dachte, es wäre nützlich, trotzdem eine Antwort zu geben. Ich würde dringend empfehlen, dass der Moderator kein Konzept des Android Context (oder anderer Android-Klassen) haben sollte. Indem Sie Ihren Presenter-Code vollständig vom Android-Systemcode trennen, können Sie ihn ohne komplizierte Systemkomponenten auf der JVM testen.

Um dies zu erreichen, denke ich, haben Sie drei Möglichkeiten.

Zugang SharedPreferences aus der Sicht

Dies ist meine unbeliebtesten der drei als SharedPreferences Zugriff ist nicht eine Ansicht Aktion. Es hält jedoch den Android-Systemcode in der Aktivität vom Moderator fern. In Ihrer Sicht Schnittstelle haben eine Methode:

boolean isLoggedIn(); 

, die von der Presenter aufgerufen werden kann.

Inject SharedPreferences Mit Dolch

Wie Sie bereits Dagger werden mit dem Event-Bus injizieren Sie SharedPreferences zu Ihrem ObjectGraph und als solche hinzufügen könnte, wäre eine SharedPreferences Instanz erhalten, die die Application konstruiert wurde verwenden. Das hast du bekommen, ohne einen Kontext an deinen Moderator weitergeben zu müssen.

Der Nachteil dieses Ansatzes ist, dass Sie immer noch in einer Android-Systemklasse (SharedPreferences) übergeben und würde es verspotten, wenn Sie den Presenter testen wollten.

erstellen SharePreferencesRepository Schnittstelle

Dies ist meine bevorzugte Methode für SharedPreferences Daten aus einem Presenter zugreifen. Grundsätzlich behandeln Sie SharedPreferences als Modell und haben eine Repository-Schnittstelle dafür.

Ihre Schnittstelle würde ähnlich sein:

public interface SharedPreferencesRepository { 

    boolean isLoggedIn(); 
} 

können Sie dann eine konkrete Umsetzung dieses:

public class SharedPreferencesRepositoryImpl implements SharedPreferencesRepository { 

    private SharedPreferences prefs; 

    public SharedPreferencesRepositoryImpl(Context context) { 

     prefs = PreferenceManager.getDefaultSharedPreferences(context); 
    } 

    @Override 
    public boolean isLoggedIn() { 

     return prefs.getBoolean(Constants.IS_LOGGED_IN, false); 
    } 

} 

Es ist die SharedPreferencesRepository Schnittstelle, die Sie dann mit Dolch in Ihrem Presenter injizieren. Auf diese Weise kann während der Tests ein sehr einfacher Mock zur Laufzeit bereitgestellt werden. Während des normalen Betriebs ist die konkrete Implementierung vorgesehen.

+0

Ich mag die letzte auch, aber mit Blick auf die Implementierung, würde ich noch Kontext brauchen, oder? Also muss ich in meinem DI-Modul irgendwo den richtigen Kontext angeben? Ich frage das, weil ich keine Ahnung habe, wie man diese Injektion einrichtet. Kann das Gleiche auch mit Diensten gemacht werden? –

+0

Vergiss es, ich habe es herausgefunden. Arbeiten gut, aber ich bin mir nicht sicher, wie das hinter den Kulissen funktioniert. Ich werde meine Frage aktualisieren, um Ihnen zu zeigen, was ich getan habe, um die Injektion durchzuführen und lassen Sie mich wissen, wenn dies nicht optimal ist. –

+0

Was Sie getan haben, sieht gut aus. Der Anwendungskontext ist jetzt vor dem Presenter verborgen, da er im SharedPreferencesRepository gekapselt ist. Der Präsentator weiß alles über das Repository. – Jahnold

3

Diese Frage wurde vor einiger Zeit beantwortet, und unter der Annahme, dass die Definition von MVP ist, was OP in seinem Code verwendet, ist die Antwort von @Jahnold wirklich gut.

Es sollte jedoch darauf hingewiesen werden, dass MVP ein High-Level-Konzept ist, und es kann viele Implementierungen nach MVP-Prinzipien geben - es gibt mehrere Möglichkeiten, die Katze zu häuten.

There is another implementation of MVP, die auf der Idee basiert, dass Activities in Android are not UI Elements, die Activity und Fragment als MVP Presenter bezeichnet. In dieser Konfiguration haben MVP-Moderatoren direkten Zugriff auf Context.

By the way, auch in der oben genannten Implementierung von MVP, würde ich verwenden Context um keinen Zugang zu SharedPreferences in Moderator zu bekommen - ich würde immer noch eine Wrapper-Klasse für SharedPreferences definieren und in Präsentator injizieren.

1

Die meisten Domänenelemente, wie DB oder Netzwerk, benötigen Context. Thay kann nicht in View erstellt werden, da View kein Wissen über Model haben kann. Sie müssen dann in Presenter erstellt werden. Sie können von Dolch injiziert werden, aber es verwendet auch Context.Also Kontext ist in Presenter xP verwendet

Der Hack ist, dass, wenn wir Context in Presenter vermeiden möchten, dann können wir nur den Konstruktor, der all diese Model-Objekte von Context erstellen und nicht speichern. Aber meiner Meinung nach ist es dumm. Neue JUnit in Android hat Zugriff auf Kontext.

Ein weiterer Hack ist, Context nullbar zu machen, und in Domain-Objekten sollte es einen Mechanismus geben, um eine Testinstanz im Falle von Null im Kontext bereitzustellen. Ich mag diesen Hack auch nicht.

Verwandte Themen