Android
MVPアーキテクチャ
サーチ…
前書き
このトピックでは、AndroidのMVP(Model-View-Presenter)アーキテクチャにさまざまな例を示します。
備考
Androidアプリを設計する方法はたくさんあります。しかし、それらのすべてがテスト可能であるとは限らず、アプリケーションをテストするのが簡単なようにコードを構造化することができます。テスト可能なアーキテクチャーの重要なアイデアは、アプリケーションの部分を分離して、保守、拡張、およびテストを互いに分離しやすくすることです。
MVP定義
モデル
階層構造が良好なアプリケーションでは、このモデルはドメイン層またはビジネスロジックへのゲートウェイにすぎません。ビューに表示するデータのプロバイダとして参照してください。
ビュー
通常、 Activity
またはFragment
によって実装されるビューには、 プレゼンターへの参照が含まれます。ビューが行う唯一のことは、インターフェイスアクションがあるたびにPresenterからメソッドを呼び出すことです。
プレゼンター
プレゼンターは、ビューとモデルの中間の人間として行動する責任があります。モデルからデータを取得し、ビューにフォーマットされたデータを返します。しかし、典型的なMVCとは異なり、Viewと対話するときに何が起こるかを決定します。
推奨されるアプリの構造(必須ではありません)
アプリは機能ごとにパッケージ化されている必要があります 。これにより、読みやすさが向上し、アプリの一部が互いに独立して変更できるようにモジュール化されます。アプリの各主要機能は、独自のJavaパッケージに含まれています。
Model View Presenter(MVP)パターンのログイン例
簡単なログイン画面を使ってMVPを実際に見てみましょう。ログインアクションには2つのButton
があり、登録画面には2つのButton
があります。メール用の2つのEditText
とパスワード用のもう1つのEditText
。
LoginFragment(ビュー)
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(プレゼンター)
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(モデル)
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);
}
}
クラス図
ノート:
- この例では、 Volleyをネットワーク通信に使用していますが、このライブラリはMVPには必要ありません
-
UrlUtils
はAPIエンドポイントのすべてのリンクを含むクラスです -
ResponseErrorListener.ErrorListener
は、VolleyのResponse.ErrorListener
をimplements
するErrorResponse
エラーをリスンするinterface
です。これらのクラスは、この例に直接含まれていないため、ここには含まれていません
MVPにおける単純なログインの例
必要なパッケージ構造
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>
アクティビティクラス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);
}
}
ILoginViewインターフェイスの作成
次のように、ビューフォルダのPresenterからの更新情報用のILoginView
インタフェースを作成します。
public interface ILoginView {
public void onClearText();
public void onLoginResult(Boolean result, int code);
public void onSetProgressBarVisibility(int visibility);
}
ILoginPresenterインターフェイスの作成
LoginActivity
(Views)と通信するためにILoginPresenter
インタフェースを作成し、ログイン機能を処理してアクティビティに戻るためのLoginPresenterCompl
クラスを作成します。 LoginPresenterCompl
クラスは、 ILoginPresenter
インターフェイスを実装してILoginPresenter
ます。
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");
}
}
ユーザーモデルの作成
LoginActivity
PojoクラスのようなUserModel
を作成します。 IUser
検証のためのIUser
インターフェイスを作成します。
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
モデルビュープレゼンター(MVP)は、モデルビューコントローラー(MVC)アーキテクチャーパターンの派生品です。主にユーザーインターフェイスを構築するために使用され、次のような利点があります。
- ビューはモデルからより離れています。プレゼンターは、モデルとビューの間のメディエーターです。
- ユニットテストを作成する方が簡単です。
- 一般的に、ViewとPresenterの間には1対1のマッピングがあり、複雑なビューに対して複数のPresenterを使用できます。