EastBanc Technologiesでは、Androidの開発で発生するアーキテクチャ上の問題に苦労することにうんざりし、それを修正することにしました:)。 私たちは、すべての要件を満たすソリューションを探していました。
そして、よくあることですが、既成のソリューションはありませんでした。独自のライブラリを作成する必要がありました。
どのような問題を解決しましたか:
- アクティビティ、フラグメント、ビューなど、スクリーンのライフサイクルから逃れます
- 各画面の状態を保存および復元するコードを記述する必要を回避
- 安定性の向上:迷惑なクラッシュやメモリリークから保護します
- 電話UIとタブレットUI間のコードの再利用を改善する

  叙情的な余談。  なぜリアンプするのですか? 
      
        
        
        
      
      それはエレキギターを録音するためのそのような迷いのようなものですか? 
      
        
        
        
      
      もちろん、私たちの場合、Reampは録音とは何の関係もありません。  最初は、MとP(モデルとプレゼンター)、A-理由は覚えていませんが、RE-試薬に書かれているため、略語になると考えました。  しかし、私たちはすでに試薬を捨てていて、クールな名前が残っていました。 
実装プロセスでは、マニフェストを追跡しようとしました。
- ライブラリは非常に簡単に習得できなければなりません。
- ライブラリは実行が非常に簡単である必要があります。最小限の依存関係、バイトコードの操作およびコード生成はありません
- ライブラリは拡張可能である必要があります。
- ライブラリは、他の一般的なコンパニオンソリューションと簡単に統合する必要があります
その結果、MVP / MVVMライブラリができました。これは1年以上使用されており、まだ変更する予定はありません。 今こそ、それを一般大衆と共有する時だと信じています!
なんで?
ほとんどすべてのモバイルアプリケーションの最も一般的な問題、つまり承認に対する解決策を見てみましょう。
ログインとパスワードの入力フィールド、ログインボタン、操作の進行状況を表示するProgressBar、および結果を表示するTextViewがあります。


このような画面の動作の要件は非常に典型的です。
- 入力フィールドがいっぱいになるまでログインボタンをロックする必要があります
- サーバー要求の進行中は、ログインボタンをロックする必要があります
- 画面が回転するとき、ユーザーはすべてを再度入力するべきではなく、ログイン操作はリセットされるべきではありません
そのような問題を解決するとき、開発者が考えるべきことを分析しましょう。
検証
何がそんなに複雑なの?  changeListener
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    では、 loginEditText
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    ハングアップしchangeListener
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     。これは、 login
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    空または空でないときにボタンをオンまたはオフにしlogin
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     ! 
 loginEditText.addTextChangeListener = { text -> button.setEnabled(text.length() > 0) }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      はい。ただし、これは1つのフィールドでのみ機能します。 そして、まだパスワードがあります:
 loginEditText.addTextChangeListener = { text -> validate() } passwordEditText.addTextChangeListener = { text -> validate() } private void validate() { boolean loginValid = loginEditText.getText().toString().lenght() > 0 boolean passwordValid = passwordEditText.getText().toString().lenght() > 0 button.setEnabled(loginValid && passwordValid) }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      さて、これですべてです! 非同期ログイン操作もありますが、その間はボタンをロックする必要があります。
  OK、リクエストを実行する前にボタンをオフにするだけで... loginEditText
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    またはpasswordEditText
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    テキストを変更することでオンにできpasswordEditText
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     。 
  validate()
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    メソッド内にアクティブなリクエストが存在するかどうかのチェックを追加する方が正しいでしょう。 
      
        
        
        
      
     おそらく、あなたはすでにこのポイントの目的を推測しているでしょう。  UIに影響を与える可能性のあるものとその関係を覚えておく必要があります。 
別の入力フィールドまたはスイッチを追加して検証する必要がある場合、それらについて簡単に忘れてしまいます。
ここに新しい工夫があります
 入るには、 AsyncTask
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    であろうとRxJava
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     + Scheduler
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    であろうとAsyncTask
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    なく、非同期操作が必要です。 
 重要なことは、画面が回転したときに停止したくないため、 Activity
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    内に書き込むことができないことです。 
 後でこのタスクのステータスを確認したり、結果を取得したりできるように、タスクを開始するときにActivity
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    のスコープから取り出し、何らかの種類の識別子を見つけて覚えておく必要があります。 
そして、それらの多くがあるので、そのような操作のために何らかの種類のマネージャーを書くか、既製のものから取る必要があります。
状態
画面の状態は、常に対処しなければならないものです。
逆説的に、それは事実です-多くの開発者は、アプリケーションの画面の状態を無視し続けており、彼のプログラムが一方向のみで動作することを正当化しています。
  EditText
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    は、入力されたテキストを個別に保存できますが、ログインボタンの状態は、入力されたテキストと現在のネットワーク操作に従って復元する必要があります。 
  Activity
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    で保存および復元する必要のあるデータが多ければ多いほど、それらを追跡することが難しくなり、何かを見逃しやすくなります。 
Reampが提供するソリューションは何ですか?
  Reampでは、 Presenter
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    を使用して画面の動作を実装し、StateModelを使用してこの画面に必要なデータを保存します。 
 すべてが非常に簡単です。  Presenter
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     、画面のライフサイクルPresenter
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    実質的に無関係です。 
      
        
        
        
      
     必要ないくつかの操作を実行して、 Presenter
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    はStateModel
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    オブジェクトStateModel
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    さまざまな必要なデータを入力します。 
  Presenter
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    は、最新のデータを画面に表示する必要があると考えるたびに、これを自分のView
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    に報告しView
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     。 
コードを見せてください!
実際には、これは次のように機能します。
  LoginState
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    画面に表示される内容に関する情報を含むクラス: 
      
        
        
        
      
     ログインボタンの状態、入力テキストフィールドに書かれている内容などをProgressBarに表示する必要があるかどうか 
  LoginPresenter
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    は、 LoginActivity
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    からイベントを受け取ります(テキストを入力し、ボタンを押しました) 
      
        
        
        
      
     必要な操作を実行し、 LoginState
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    クラスLoginState
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    必要なデータを入力し、「レンダリング」のために「 LoginActivity
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
     」に送信します。 
  LoginActivity
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    は、 LoginState
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    のデータLoginState
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    変更されたイベントを受け取り、それらに従ってレイアウトをカスタマイズします。 
 //LoginState public class LoginState extends SerializableStateModel { public String login; public String password; public boolean showProgress; public Boolean loggedIn; public boolean isSuccessLogin() { return loggedIn != null && loggedIn; } } //LoginPresenter public class LoginPresenter extends MvpPresenter<LoginState> { @Override public void onPresenterCreated() { super.onPresenterCreated(); //     getStateModel().setLogin(""); getStateModel().setPassword(""); getStateModel().setLoggedIn(null); getStateModel().setShowProgress(false); sendStateModel(); // LoginState  "" } //   View,     public void login() { getStateModel().setShowProgress(true); //      getStateModel().setLoggedIn(null); //     sendStateModel(); //      "" //      new Handler() .postDelayed(new Runnable() { @Override public void run() { getStateModel().setLoggedIn(true); //     getStateModel().setShowProgress(false); //    sendStateModel(); //      "" } }, 5000); } public void loginChanged(String login) { getStateModel().setLogin(login); //  ,    } public void passwordChanged(String password) { getStateModel().setPassword(password); //  ,    } } //LoginActivity public class LoginActivity extends MvpAppCompatActivity<LoginPresenter, LoginState> { /***/ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_login); /***/ loginActionView.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { getPresenter().login(); //     } }); //   ,    loginInput.addTextChangedListener(new SimpleTextWatcher() { @Override public void afterTextChanged(Editable s) { getPresenter().loginChanged(s.toString()); //     } }); //   ,    passwordInput.addTextChangedListener(new SimpleTextWatcher() { @Override public void afterTextChanged(Editable s) { getPresenter().passwordChanged(s.toString()); //     } }); } //  ,       LoginState @Override public LoginState onCreateStateModel() { return new LoginState(); } //  ,       LoginPresenter @Override public MvpPresenter<LoginState> onCreatePresenter() { return new LoginPresenter(); } //    ,     @Override public void onStateChanged(LoginState stateModel) { progressView.setVisibility(stateModel.showProgress ? View.VISIBLE : View.GONE); //      loginActionView.setEnabled(!stateModel.showProgress); //   ,    successView.setVisibility(stateModel.isSuccessLogin() ? View.VISIBLE : View.GONE); //    ""  } }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
      一見、重要な動的データをLoginStateに取り出し、コードの一部(ログインリクエストなど)をActivityからPresenterに転送するだけでした。 一見したところ、これは確かにそうです:)私たちにとって退屈な作業はすべてReampによって行われているためです:
-  画面を回転させても、プレゼンターの操作と入力要求には影響しません。  LoginActivity
 
 
 
 を再作成すると、すぐに最後のLoginState
 
 
 
 状態をLoginState
 
 
 
 ます。 リクエストがまだ実行中の場合、LoginState
 
 
 
 にはログインボタンが非アクティブであり、ダウンロードインジケーターが表示されるという情報が含まれます。 画面が回転した瞬間にログイン操作が完了する時間があれば、プレゼンターはLoginState
 
 
 
 にログイン結果を入力し、将来のLoginActivityはこの結果をすぐに受け取ります。
-  システムが画面状態の保存を要求すると、 LoginState
 
 
 
 すべてのデータがBundle savedState
 
 
 
 に送信されます。 もちろん、プログラムが以前にメモリからアンロードされていた場合、LoginState
 
 
 
 はBundle
 
 
 
 からLoginStateを回復できます。 デフォルトでは、オブジェクトをシリアル化するメカニズムを使用してLoginState
 
 
 
 を保存しますが、必要に応じて独自のオブジェクトをいつでも作成できます。
-  ログイン要求がすでにProgressBar
 
 
 
 場合、ProgressBar
 
 
 
 を表示することを忘れないように、LoginActivity
 
 
 
 時にsavedState
 
 
 
 をnull
 
 
 
 にチェックする必要はありません。 現在の状態を表示するすべてのコードは1箇所に集中しており、常にLoginState
 
 
 
 からのデータ全体を考慮します。 このアプローチにより、UI上のデータの一貫性が保証されます。
-  他のいくつかのMVPライブラリで行われているように、UIで何かを行う前にActivity
 
 
 
 の可用性を確認する必要はありません。 つまり、if (view != null)
 
 
 
 チェックが無限に続くわけではありません。 プレゼンターでは、いつでも利用可能な状態を直接操作します。
  Reampがボイラープレートコードを削除するのにどのように役立つかをリストしましたが、これはライブラリを使用することの全体的な利益とはほど遠いものです。  Reampを使用すると、アプリケーションの安定性が向上します。Reampは、 onStateChanged(...)
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    メソッドの呼び出しが常にメインスレッドで発生するようにします。 
  onStateChanged(...)
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    呼び出し内で発生するすべての例外は、アプリケーションプロセスをドロップしません。  Javaでの例外の正しい作業は高度なスキルですが、最高のUIレベル(レイアウトのセットアップ時)で発生する例外は、意図的なイベントよりも厄介な誤解であることが多く、ここでのプログラムクラッシュは絶対に不要です。 
  Reampを使用すると、 Activity
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    リークを恐れることはできません。 常にプレゼンタークラスと状態クラスを直接操作します。 
最後になりましたが、Reampを使用すると、コードの品質が向上します。
 コードはよりテストしやすくなっています。 実際、 Instrumentation
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    テストも必要ありません。なぜなら、 プレゼンターをテストし、各操作の後にLoginState
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    に正しいデータセットがあることを確認してください 
 状態クラスは、UIロジックを保存するための優れた候補です。  LoginState
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    がログインの進行状況、入力されたログインとパスワードを知っている場合、ログインボタンを有効にするかどうかを決定するためのすべてのソースデータを既に持っています。 
 public class LoginState extends SerializableStateModel { /***/ public boolean isLoginActionEnabled() { return !showProgress && (loggedIn == null || !loggedIn) && !TextUtils.isEmpty(login) && !TextUtils.isEmpty(password); } }
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
      
       このアプローチは、責任の分離の原則とよく一致しており、 LoginActicity
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    クラスコードを大幅にオフロードします。 
 コードが再利用可能になります。  LoginPresenter
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    は、この画面のUIコンポーネントを変更するだけで同様の画面を実装する必要がある他のプロジェクトでLoginPresenter
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    使用できます。 
同様のソリューションとの比較
もちろん、ReampだけがMVP / MVVMライブラリではありません。
  Reampを始めたとき、私たちは意識的に必要なものを書きたいと思っていました。 
      
        
        
        
      
     そして、もちろん、私たちは最善を尽くし、私たちが好きではなかったものを避けるために、その時に利用可能な代替案を研究しました:) 
ホリバーを配置する気はありませんが、誰かに指を突っ込むことはあまりありません。Reampの好きな点と避けたいことを単純にまとめています。
まず、Reampは非常に使いやすいです。 コード生成は使用せず、ライブラリ自体にのみ必要な最小限の新しいクラスを導入しようとします。
たとえば、新しいAndroidアーキテクチャコンポーネントとは異なり、同じ問題を解決するために補助的な技術クラスやアノテーションの動物園全体を必要としません。
2番目のポイントは、最初のポイントの結果の一部です。 アンロードされたアーキテクチャと最小限の依存関係により、多くの一般的な最新技術と簡単に統合できます。
 たとえば、DataBindingでは、 StateModel
      
      
        
        
        
      
    
        
        
        
      
      
        
        
        
      
    
    既にDataBindingが機能する必要があるデータの真髄であるためです。 
バイトコードに魔法がない別の例では、Kotlinで問題なくReampプログラミングを使用します。
第三に、既存のプロジェクトをグローバルに変更する必要はありません。既存のプロジェクトでReampの使用を開始できます。
ある記事では、必要なものすべてについて話すことは困難ですが、 デモアプリケーションを使用して、最も簡単なソリューションから最も複雑なソリューションまで、Reampのすべての機能を段階的に説明します。
 
      参照資料
GitHubのリアンプ-https ://github.com/eastbanctechru/Reamp
デモアプリ-https ://github.com/eastbanctechru/Reamp/tree/master/sample
 プロジェクトでReampを試したい場合、または詳細情報が必要な場合は、 
      
        
        
        
      
     プロジェクトのWiki 、特にFAQセクションをご覧ください 。