Android
MVP-Architektur
Suche…
Einführung
In diesem Thema wird die MVP- Architektur (Model-View-Presenter) von Android mit verschiedenen Beispielen beschrieben.
Bemerkungen
Es gibt viele Möglichkeiten, eine Android-App zu erstellen. Aber nicht alle sind testbar und ermöglichen es uns, unseren Code so zu strukturieren, dass die App leicht zu testen ist. Die Schlüsselidee einer testbaren Architektur besteht darin, Teile der Anwendung voneinander zu trennen, wodurch sie einfacher zu warten, zu erweitern und zu testen sind.
MVP-Definition
Modell
In einer Anwendung mit einer guten mehrschichtigen Architektur wäre dieses Modell nur das Gateway zur Domänenschicht oder Geschäftslogik. Sehen Sie es als den Anbieter der Daten, die wir in der Ansicht anzeigen möchten.
Aussicht
Die Ansicht, die normalerweise von einer Activity
oder einem Fragment
implementiert wird, enthält einen Verweis auf den Präsentator . Das einzige, was die Ansicht tun wird, ist, eine Methode aus dem Presenter aufzurufen, wenn eine Schnittstellenaktion vorliegt.
Moderator
Der Moderator ist dafür verantwortlich, als Mittler zwischen Ansicht und Modell zu fungieren. Er ruft Daten aus dem Modell ab und gibt sie formatiert an die Ansicht zurück. Im Gegensatz zum typischen MVC entscheidet es auch, was passiert, wenn Sie mit der Ansicht interagieren.
* Definitionen aus Antonio Leivas Artikel.
Empfohlene App-Struktur (nicht erforderlich)
Die App sollte nach Paket pro Feature strukturiert sein. Dies verbessert die Lesbarkeit und moduliert die App so, dass Teile davon unabhängig voneinander geändert werden können. Jede Schlüsselfunktion der App befindet sich in einem eigenen Java-Paket.
Anmeldebeispiel für das Model View Presenter (MVP) -Muster
Lassen Sie uns MVP in Aktion mit einem einfachen Anmeldebildschirm sehen. Es gibt zwei Button
- eine für Anmeldeaktionen und eine weitere für einen Registrierungsbildschirm. zwei EditText
- EditText
- eine für die E-Mail und die andere für das Passwort.
LoginFragment (Die Ansicht)
public class LoginFragment extends Fragment implements LoginContract.PresenterToView, View.OnClickListener {
private View view;
private EditText email, password;
private Button login, register;
private LoginContract.ToPresenter presenter;
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_login, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
email = (EditText) view.findViewById(R.id.email_et);
password = (EditText) view.findViewById(R.id.password_et);
login = (Button) view.findViewById(R.id.login_btn);
login.setOnClickListener(this);
register = (Button) view.findViewById(R.id.register_btn);
register.setOnClickListener(this);
presenter = new LoginPresenter(this);
presenter.isLoggedIn();
}
@Override
public void onLoginResponse(boolean isLoginSuccess) {
if (isLoginSuccess) {
startActivity(new Intent(getActivity(), MapActivity.class));
getActivity().finish();
}
}
@Override
public void onError(String message) {
Toast.makeText(getActivity(), message, Toast.LENGTH_SHORT).show();
}
@Override
public void isLoggedIn(boolean isLoggedIn) {
if (isLoggedIn) {
startActivity(new Intent(getActivity(), MapActivity.class));
getActivity().finish();
}
}
@Override
public void onClick(View view) {
switch (view.getId()) {
case R.id.login_btn:
LoginItem loginItem = new LoginItem();
loginItem.setPassword(password.getText().toString().trim());
loginItem.setEmail(email.getText().toString().trim());
presenter.login(loginItem);
break;
case R.id.register_btn:
startActivity(new Intent(getActivity(), RegisterActivity.class));
getActivity().finish();
break;
}
}
}
LoginPresenter (Der Moderator)
public class LoginPresenter implements LoginContract.ToPresenter {
private LoginContract.PresenterToModel model;
private LoginContract.PresenterToView view;
public LoginPresenter(LoginContract.PresenterToView view) {
this.view = view;
model = new LoginModel(this);
}
@Override
public void login(LoginItem userCredentials) {
model.login(userCredentials);
}
@Override
public void isLoggedIn() {
model.isLoggedIn();
}
@Override
public void onLoginResponse(boolean isLoginSuccess) {
view.onLoginResponse(isLoginSuccess);
}
@Override
public void onError(String message) {
view.onError(message);
}
@Override
public void isloggedIn(boolean isLoggedin) {
view.isLoggedIn(isLoggedin);
}
}
LoginModel (Das Modell)
public class LoginModel implements LoginContract.PresenterToModel, ResponseErrorListener.ErrorListener {
private static final String TAG = LoginModel.class.getSimpleName();
private LoginContract.ToPresenter presenter;
public LoginModel(LoginContract.ToPresenter presenter) {
this.presenter = presenter;
}
@Override
public void login(LoginItem userCredentials) {
if (validateData(userCredentials)) {
try {
performLoginOperation(userCredentials);
} catch (JSONException e) {
e.printStackTrace();
}
} else {
presenter.onError(BaseContext.getContext().getString(R.string.error_login_field_validation));
}
}
@Override
public void isLoggedIn() {
DatabaseHelper database = new DatabaseHelper(BaseContext.getContext());
presenter.isloggedIn(database.isLoggedIn());
}
private boolean validateData(LoginItem userCredentials) {
return Patterns.EMAIL_ADDRESS.matcher(userCredentials.getEmail()).matches()
&& !userCredentials.getPassword().trim().equals("");
}
private void performLoginOperation(final LoginItem userCredentials) throws JSONException {
JSONObject postData = new JSONObject();
postData.put(Constants.EMAIL, userCredentials.getEmail());
postData.put(Constants.PASSWORD, userCredentials.getPassword());
JsonObjectRequest request = new JsonObjectRequest(Request.Method.POST, Url.AUTH, postData,
new Response.Listener<JSONObject>() {
@Override
public void onResponse(JSONObject response) {
try {
String token = response.getString(Constants.ACCESS_TOKEN);
DatabaseHelper databaseHelper = new DatabaseHelper(BaseContext.getContext());
databaseHelper.login(token);
Log.d(TAG, "onResponse: " + token);
} catch (JSONException e) {
e.printStackTrace();
}
presenter.onLoginResponse(true);
}
}, new ErrorResponse(this));
RequestQueue queue = Volley.newRequestQueue(BaseContext.getContext());
queue.add(request);
}
@Override
public void onError(String message) {
presenter.onError(message);
}
}
Klassen Diagramm
Lassen Sie uns die Aktion in Form eines Klassendiagramms sehen.
Anmerkungen:
- In diesem Beispiel wird Volley für die Netzwerkkommunikation verwendet. Diese Bibliothek ist jedoch für MVP nicht erforderlich
-
UrlUtils
ist eine Klasse, die alle Links für meine API-Endpunkte enthält -
ResponseErrorListener.ErrorListener
ist eineinterface
, die auf Fehler inErrorResponse
,implements
VolleysResponse.ErrorListener
implements
. Diese Klassen sind hier nicht enthalten, da sie nicht direkt Teil dieses Beispiels sind
Einfaches Login-Beispiel in MVP
Erforderliche Paketstruktur
XML activity_login
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical"
android:orientation="vertical"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin">
<EditText
android:id="@+id/et_login_username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="USERNAME" />
<EditText
android:id="@+id/et_login_password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="PASSWORD" />
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/btn_login_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginRight="4dp"
android:layout_weight="1"
android:text="Login" />
<Button
android:id="@+id/btn_login_clear"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="4dp"
android:layout_weight="1"
android:text="Clear" />
</LinearLayout>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="3dp"
android:text="correct user: mvp, mvp" />
<ProgressBar
android:id="@+id/progress_login"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="40dp" />
</LinearLayout>
Aktivitätsklasse LoginActivity.class
public class LoginActivity extends AppCompatActivity implements ILoginView, View.OnClickListener {
private EditText editUser;
private EditText editPass;
private Button btnLogin;
private Button btnClear;
private ILoginPresenter loginPresenter;
private ProgressBar progressBar;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
//find view
editUser = (EditText) this.findViewById(R.id.et_login_username);
editPass = (EditText) this.findViewById(R.id.et_login_password);
btnLogin = (Button) this.findViewById(R.id.btn_login_login);
btnClear = (Button) this.findViewById(R.id.btn_login_clear);
progressBar = (ProgressBar) this.findViewById(R.id.progress_login);
//set listener
btnLogin.setOnClickListener(this);
btnClear.setOnClickListener(this);
//init
loginPresenter = new LoginPresenterCompl(this);
loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.btn_login_clear:
loginPresenter.clear();
break;
case R.id.btn_login_login:
loginPresenter.setProgressBarVisiblity(View.VISIBLE);
btnLogin.setEnabled(false);
btnClear.setEnabled(false);
loginPresenter.doLogin(editUser.getText().toString(), editPass.getText().toString());
break;
}
}
@Override
public void onClearText() {
editUser.setText("");
editPass.setText("");
}
@Override
public void onLoginResult(Boolean result, int code) {
loginPresenter.setProgressBarVisiblity(View.INVISIBLE);
btnLogin.setEnabled(true);
btnClear.setEnabled(true);
if (result){
Toast.makeText(this,"Login Success",Toast.LENGTH_SHORT).show();
}
else
Toast.makeText(this,"Login Fail, code = " + code,Toast.LENGTH_SHORT).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
}
@Override
public void onSetProgressBarVisibility(int visibility) {
progressBar.setVisibility(visibility);
}
}
Erstellen einer ILoginView-Schnittstelle
Erstellen Sie eine ILoginView
Schnittstelle für Update-Informationen aus dem Presenter-Ordner "Ordner" wie folgt:
public interface ILoginView {
public void onClearText();
public void onLoginResult(Boolean result, int code);
public void onSetProgressBarVisibility(int visibility);
}
Erstellen einer ILoginPresenter-Schnittstelle
Erstellen Sie eine ILoginPresenter
Schnittstelle, um mit LoginActivity
(Views) zu kommunizieren, und erstellen Sie die LoginPresenterCompl
Klasse zum LoginPresenterCompl
der LoginPresenterCompl
und zum LoginPresenterCompl
an die Aktivität. Die LoginPresenterCompl
Klasse implementiert die ILoginPresenter
Schnittstelle:
ILoginPresenter.class
public interface ILoginPresenter {
void clear();
void doLogin(String name, String passwd);
void setProgressBarVisiblity(int visiblity);
}
LoginPresenterCompl.class
public class LoginPresenterCompl implements ILoginPresenter {
ILoginView iLoginView;
IUser user;
Handler handler;
public LoginPresenterCompl(ILoginView iLoginView) {
this.iLoginView = iLoginView;
initUser();
handler = new Handler(Looper.getMainLooper());
}
@Override
public void clear() {
iLoginView.onClearText();
}
@Override
public void doLogin(String name, String passwd) {
Boolean isLoginSuccess = true;
final int code = user.checkUserValidity(name,passwd);
if (code!=0) isLoginSuccess = false;
final Boolean result = isLoginSuccess;
handler.postDelayed(new Runnable() {
@Override
public void run() {
iLoginView.onLoginResult(result, code);
}
}, 5000);
}
@Override
public void setProgressBarVisiblity(int visiblity){
iLoginView.onSetProgressBarVisibility(visiblity);
}
private void initUser(){
user = new UserModel("mvp","mvp");
}
}
Ein UserModel erstellen
Erstellen Sie ein UserModel
das einer UserModel
Klasse für LoginActivity
. Erstellen Sie eine IUser
Schnittstelle für Pojo-Validierungen:
UserModel.class
public class UserModel implements IUser {
String name;
String passwd;
public UserModel(String name, String passwd) {
this.name = name;
this.passwd = passwd;
}
@Override
public String getName() {
return name;
}
@Override
public String getPasswd() {
return passwd;
}
@Override
public int checkUserValidity(String name, String passwd){
if (name==null||passwd==null||!name.equals(getName())||!passwd.equals(getPasswd())){
return -1;
}
return 0;
}
IUser.class
public interface IUser {
String getName();
String getPasswd();
int checkUserValidity(String name, String passwd);
}
MVP
Ein Model-View-Presenter (MVP) ist eine Ableitung des MVC-Architekturmusters (Model-View-Controller). Es wird hauptsächlich zum Erstellen von Benutzeroberflächen verwendet und bietet die folgenden Vorteile:
- Ansichten sind mehr von Modellen getrennt. Der Presenter ist der Vermittler zwischen Model und View.
- Es ist einfacher, Komponententests zu erstellen.
- Im Allgemeinen gibt es eine Eins-zu-Eins-Zuordnung zwischen Ansicht und Präsentator, mit der Möglichkeit, mehrere Präsentatoren für komplexe Ansichten zu verwenden.