Androidアプリを段階的に構築する、パヌト1





この蚘事では、RxJavaずRetrofitを䜿甚しお、MVPパタヌンに基づいたアヌキテクチャ蚭蚈ずモバむルアプリケヌションの䜜成に぀いお説明したす。 トピックは非垞に倧きいこずが刀明したため、別々の郚分で提䟛されたす最初はアプリケヌションを蚭蚈および䜜成し、2番目はDagger 2でDIを実行しお単䜓テストを䜜成し、3番目では統合および機胜テストを远加し、Android開発の珟実のTDDに぀いおも怜蚎したす。



内容





はじめに



コヌドの理解を深め、順次耇雑にするために、蚭蚈を2぀の段階に分割したす。プリミティブ最小限実行可胜ず埓来のアヌキテクチャです。 プリミティブでは、最小限のコヌドずファむルを管理しおから、このコヌドを改善したす。

すべおの゜ヌスコヌドはgithubにありたす。 リポゞトリのブランチは、蚘事のステップに察応しおいたす。 ステップ1単玔なアヌキテクチャ -最初のステップ、 ステップ2耇雑なアヌキテクチャ-2番目のステップ。

たずえば、Github APIを䜿甚しお、特定のナヌザヌのリポゞトリのリストを取埗しおみたしょう。



このアプリケヌションでは、Rxを䜿甚しおいるため、この蚘事を理解するには、このテクノロゞヌの䞀般的な考え方が必芁です。GrokaiRxJavaの䞀連の出版物を読むこずをお勧めしたす。



ステップ1.シンプルなアヌキテクチャ



階局化、MVP

アヌキテクチャを蚭蚈するずきは、MVPパタヌンを順守したす。 詳现はこちらをご芧ください

https://ru.wikipedia.org/wiki/Model-View-Presenter

http://habrahabr.ru/post/131446/



プログラム党䜓を3぀の䞻芁な局に分割したす。

モデル -ここでデヌタを取埗しお保存したす。 出力は芳枬可胜です。

プレれンタヌ -このレむダヌには、すべおのアプリケヌションロゞックが栌玍されたす。 Observableを取埗し、サブスクラむブし、結果をビュヌに枡したす。

ビュヌ -衚瀺レむダヌ。すべおのビュヌ芁玠、アクティベヌション、フラグメントなどが含たれたす。



条件付きスキヌム




モデル



デヌタレむダヌは、Observable <List <Repo >>を提䟛する必芁がありたす。むンタヌフェむスを䜜成したしょう。



public interface Model { Observable<List<Repo>> getRepoList(String name); }
      
      





埌付け


ネットワヌクでの䜜業を簡玠化するために、Retrofitを䜿甚したす。 RetrofitはREST APIを操䜜するためのラむブラリです。ネットワヌクでのすべおの䜜業を凊理したす。むンタヌフェむスず泚釈を䜿甚しおリク゚ストを蚘述するこずしかできたせん。



レトロフィット2
Runetのレトロフィットに関する資料は倚数ありたす http://www.pvsm.ru/android/58484、http://tttzof351.blogspot.ru/2014/01/java-retrofit.html 。

2番目のバヌゞョンず最初のバヌゞョンの䞻な違いは、同期メ゜ッドず非同期メ゜ッドの違いがなくなったこずです。 ここでCall <Data>を取埗したす。これは、同期芁求の堎合はexecuteを、非同期芁求の堎合はexecuteコヌルバックを呌び出すこずができたす。 たた、リク゚ストをキャンセルする埅望の機䌚がありたしたcall.cancel。 以前ず同様に、特別なプラグむンの助けを借りお、Observable <Data>を取埗できたす





リポゞトリデヌタを取埗するためのむンタヌフェむス



 public interface ApiInterface { @GET("users/{user}/repos") Observable<List<Repo>> getRepositories(@Path("user") String user); }
      
      





モデルの実装
 public class ModelImpl implements Model { ApiInterface apiInterface = ApiModule.getApiInterface(); @Override public Observable<List<Repo>> getRepoList(String name) { return apiInterface.getRepositories(name) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); } }
      
      





デヌタの操䜜、POJO


レトロフィットおよびその䞭のGSONは、POJOPlain Old Java Objectで動䜜したす。 これは、次の圢匏のJSONからオブゞェクトを取埗するこずを意味したす。



 { "id":3, "name":"Andrey", "phone":"511 55 55" }
      
      





GSONが倀を曞き蟌むUserクラスが必芁になりたす。



 public class User { private int id; private String name; private String phone; public int getId() { return id; } public void setId(int id) { this.id = id; } // etc }
      
      





そのようなクラスを自分の手で生成する必芁は圓然ありたせん;これには、䟋えばwww.jsonschema2pojo.orgのような特別なゞェネレヌタヌがありたす。



JSONをフィヌドし、遞択したす



゜ヌスタむプJSON

泚釈スタむルGson

ゲッタヌずセッタヌを含める



ファむルのコヌドを取埗したす。 zipたたはjarずしおダりンロヌドしお、プロゞェクトに入れるこずができたす。 リポゞトリヌに぀いおは、所有者、蚱可、リポゞトリヌの3぀のオブゞェクトが取埗されたした。



サンプル生成コヌド
 public class Permissions { @SerializedName("admin") @Expose private boolean admin; @SerializedName("push") @Expose private boolean push; @SerializedName("pull") @Expose private boolean pull; /** * @return The admin */ public boolean isAdmin() { return admin; } /** * @param admin The admin */ public void setAdmin(boolean admin) { this.admin = admin; } /** * @return The push */ public boolean isPush() { return push; } /** * @param push The push */ public void setPush(boolean push) { this.push = push; } /** * @return The pull */ public boolean isPull() { return pull; } /** * @param pull The pull */ public void setPull(boolean pull) { this.pull = pull; } }
      
      





発衚者



プレれンタヌは、ダりンロヌドする内容、゚ラヌが発生した堎合の察凊方法などを知っおいたす。 ぀たり、ロゞックをプレれンテヌションから分離したす。 この堎合の衚瀺は、可胜な限り簡単であるこずがわかりたした。 プレれンタヌは、怜玢ボタンのクリックを凊理し、ダりンロヌドを初期化し、デヌタを送信し、アクティビティが停止した堎合に登録を解陀できる必芁がありたす。



プレれンタヌむンタヌフェむス



 public interface Presenter { void onSearchClick(); void onStop(); }
      
      





プレれンタヌの実装
 public class RepoListPresenter implements Presenter { private Model model = new ModelImpl(); private View view; private Subscription subscription = Subscriptions.empty(); public RepoListPresenter(View view) { this.view = view; } @Override public void onSearchButtonClick() { if (!subscription.isUnsubscribed()) { subscription.unsubscribe(); } subscription = model.getRepoList(view.getUserName()) .subscribe(new Observer<List<Repo>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { view.showError(e.getMessage()); } @Override public void onNext(List<Repo> data) { if (data != null && !data.isEmpty()) { view.showData(data); } else { view.showEmptyList(); } } }); } @Override public void onStop() { if (!subscription.isUnsubscribed()) { subscription.unsubscribe(); } } }
      
      





衚瀺する



Viewは、受信したデヌタの衚瀺、゚ラヌの衚瀺、空のリストの通知、プレれンタヌからのリク゚ストに応じたナヌザヌ名の衚瀺が可胜なアクティビティずしおViewを実装したす。 むンタヌフェヌス



 public interface IView { void showList(List<Repo> RepoList); void showError(String error); void showEmptyList(); String getUserName(); }
      
      





メ゜ッド実装の衚瀺
 @Override public void showData(List<Repo> list) { adapter.setRepoList(list); } @Override protected void onStop() { super.onStop(); if (presenter != null) { presenter.onStop(); } } @Override public void showError(String error) { makeToast(error); } @Override public void showEmptyList() { makeToast(getString(R.string.empty_repo_list)); } @Override public String getUserName() { return editText.getText().toString(); }
      
      







その結果、レむダヌに分割されたシンプルなアプリケヌションができたした。



スキヌム







改善が必芁なものもありたすが、䞀般的な考え方は明確です。 次に、新しい機胜を远加しおタスクを耇雑にしたす。



パヌト2.耇雑なアヌキテクチャ



アプリケヌションに新しい機胜を远加し、リポゞトリに関する情報を衚瀺したす。 ブランチず貢献者のリストを衚瀺したす。これらはAPIからのさたざたなリク゚ストによっお取埗されたす。



レトロラムダ


ラムダなしでRxを䜿甚するのは苊痛で、すぐに疲れるたびに匿名クラスを曞く必芁がありたす。 AndroidはJava 8ずラムダをサポヌトしおいたせんが、Retrolambda https://github.com/evant/gradle-retrolambda が助けになりたす。 ラムダ匏の詳现 http : //habrahabr.ru/post/224593/



レむダヌごずに異なるデヌタモデル。


ご芧のずおり、3぀のレむダヌすべおで同じRepoデヌタオブゞェクトを䜿甚しおいたす。 このアプロヌチは単玔なアプリケヌションには適しおいたすが、実際には、APIの倉曎、オブゞェクトを倉曎する必芁性、たたは䜕か他のものに出くわすこずがありたす。 耇数の人がプロゞェクトに取り組んでいる堎合、別のレむダヌの利益のためにクラスを倉曎するリスクがありたす。



そのため、倚くの堎合、1぀のレむダヌ= 1぀のデヌタ圢匏ずいうアプロヌチが適甚されたす。 モデルの䞀郚のフィヌルドが倉曎されおも、これはビュヌレむダヌに圱響したせん。 Presenterレむダヌに倉曎を加えるこずはできたすが、Viewでは厳密に定矩されたオブゞェクトクラスを提䟛したす。 このため、デヌタモデルからのレむダヌの独立性が達成され、各レむダヌには独自のモデルがありたす。 モデルを倉曎するずきは、マッパヌを曞き換えお、レむダヌ自䜓には觊れないようにする必芁がありたす。 これはコントラクトプログラミングに䌌おいたす。どのオブゞェクトがレむダヌに到達し、どのオブゞェクトをさらに提䟛する必芁があるかを正確に把握しおいるため、予枬䞍可胜な結果から自分自身ず同僚を保護したす。



この䟋では、DTO-デヌタ転送オブゞェクトJSONオブゞェクトを完党にコピヌずビュヌオブゞェクト衚瀺甚に調敎されたオブゞェクトの2皮類のデヌタで十分です。 より耇雑なアプリケヌションがある堎合、ビゞネスオブゞェクトビゞネスプロセス甚たたはデヌタベヌスオブゞェクトデヌタベヌスに耇雑なオブゞェクトを保存するためが必芁になる堎合がありたす



送信されたデヌタの抂略図




リポゞトリの名前をRepositoryDTOに倉曎し、新しいリポゞトリクラスを䜜成し、むンタヌフェむスFunc1 <List <RepositoryDTO >>、List <Repository >>を実装するマッパヌを蚘述したす。

リスト<RepositoryDTO>からリスト<Repository>ぞの翻蚳



オブゞェクトのマッパヌ
 public class RepoBranchesMapper implements Func1<List<BranchDTO>, List<Branch>> { @Override public List<Branch> call(List<BranchDTO> branchDTOs) { List<Branch> branches = Observable.from(branchDTOs) .map(branchDTO -> new Branch(branchDTO.getName())) .toList() .toBlocking() .first(); return branches; } }
      
      









モデル



レむダヌごずに異なるデヌタモデルを導入したした。ModelむンタヌフェむスはDTOオブゞェクトを提䟛するようになりたした。それ以倖はすべお同じです。



 public interface Model { Observable<List<RepositoryDTO>> getRepoList(String name); Observable<List<BranchDTO>> getRepoBranches(String owner, String name); Observable<List<ContributorDTO>> getRepoContributors(String owner, String name); }
      
      





発衚者



Presenterレむダヌでは、共通のクラスが必芁です。 プレれンタヌは、さたざたな機胜を実行できたす。ダりンロヌドショヌの簡単なプレれンタヌ、アむテムをロヌドする必芁があるリスト、サむト䞊のオブゞェクトをリク゚ストするマップ、および他の倚くの゚ンティティがありたす。 しかし、それらはすべお、メモリリヌクを回避するためにObservableからサブスクラむブを解陀する必芁性によっお統䞀されおいたす。 残りは発衚者のタむプによっお異なりたす。



耇数のObservableを䜿甚する堎合、onStopで䞀床にすべおのサブスクリプションを解陀する必芁がありたす。 これを行うには、CompositeSubscriptionを䜿甚できたす。すべおのサブスクリプションをそこに远加し、コマンドでサブスクラむブを解陀したす。



たた、プレれンタヌに状態の保存を远加したす。 これを行うには、onCreateBundledInstanceStateおよびonSaveInstanceStateBundle outStateメ゜ッドを䜜成しお実装したす。 DTOをVOに倉換するには、マッパヌを䜿甚したす。



コヌド䟋
 public void onSearchButtonClick() { String name = view.getUserName(); if (TextUtils.isEmpty(name)) return; Subscription subscription = dataRepository.getRepoList(name) .map(repoListMapper) .subscribe(new Observer<List<Repository>>() { @Override public void onCompleted() { } @Override public void onError(Throwable e) { view.showError(e.getMessage()); } @Override public void onNext(List<Repository> list) { if (list != null && !list.isEmpty()) { repoList = list; view.showRepoList(list); } else { view.showEmptyList(); } } }); addSubscription(subscription); } public void onCreate(Bundle savedInstanceState) { if (savedInstanceState != null) { repoList = (List<Repository>) savedInstanceState.getSerializable(BUNDLE_REPO_LIST_KEY); if (!isRepoListEmpty()) { view.showRepoList(repoList); } } } private boolean isRepoListEmpty() { return repoList == null || repoList.isEmpty(); } public void onSaveInstanceState(Bundle outState) { if (!isRepoListEmpty()) { outState.putSerializable(BUNDLE_REPO_LIST_KEY, new ArrayList<>(repoList)); } }
      
      









プレれンタヌレむダヌの䞀般的なスキヌム






衚瀺する



アクティビティを䜿甚しおフラグメントを管理したす。 各゚ンティティには独自のフラグメントがあり、ベヌスフラグメントから継承されたす。 ベヌスプレれンタヌむンタヌフェむスを䜿甚するベヌスフラグメントは、onStopでサブスクラむブ解陀されたす。



たた、状態の埩元にも泚意しおください。すべおのロゞックがプレれンタヌに移動したした。ビュヌはできるだけシンプルにする必芁がありたす。



基本フラグメントコヌド
 @Override public void onStop() { super.onStop(); if (getPresenter() != null) { getPresenter().onStop(); } }
      
      









ビュヌレむダヌの䞀般的なレむアりト






2番目のステップでのアプリケヌションの䞀般的なスキヌム クリック可胜 





結論たたは継続...



その結果、抜象化の必芁なすべおのレベルずコンポヌネント ゜ヌス による責任の明確な分割に準拠した実甚的なアプリケヌションを手に入れたした。 このようなコヌドは保守ず補完が容易であり、開発者のチヌムがそれに取り組むこずができたす。 しかし、䞻な利点の1぀は非垞に簡単なテストです。 次の蚘事では、Dagger 2の導入を怜蚎し、TDDの原則に埓っお、既存のコヌドをテストでカバヌし、新しい機胜を䜜成したす。



曎新

Androidアプリを段階的に構築する、パヌト2

Androidアプリを段階的に構築する、パヌト3



All Articles