• Marcio Granzotto Rodrigues
  • 4 years ago
  • Categories:Opinion, Technical
  • Tags.Hope!Android, アーキテクチャ, 開発, iOS, kotiln, モバイル, VIPER

Android開発者としてスタートし、後にiOSも扱うようになった私は、いくつかの異なるプロジェクトのアーキテクチャと接触しました。

iOS プロジェクトで VIPER アーキテクチャに出会い、8 か月間一緒に仕事をするまで、Android では MVP アーキテクチャを喜んで使っていました。 Android に戻ったとき、他の開発者が Android で iOS アーキテクチャを使用するのは意味がないと言ったにもかかわらず、私は VIPER を Android に適用して実装することにしました。 AndroidとiOSのフレームワークの根本的な違いを考えると、VIPERがAndroidでどれだけ役に立つのか、疑問がありました。 それは可能なのか、努力する価値はあるのか?

VIPERとは

VIPERとは、主にiOSアプリ開発で使用されるクリーンなアーキテクチャの1つです。

VIPER は View Interactor Presenter Entity Router の略で、単一責任原則に従って、明確に定義された責任を持つクラスです。 詳しくは、この優れた記事を参照してください。

Android アーキテクチャ

Androidには、すでにいくつかの非常に優れたアーキテクチャがあります。 より有名なのは、MVVM (Model-View-ViewModel) と MVP (Model-View-Presenter) です。

MVVM は、データ バインディングと一緒に使用すれば多くの意味を持ちますが、私はデータ バインディングのアイデアがあまり好きではないので、私が取り組んできたプロジェクトでは常に MVP を使用してきました。 しかし、プロジェクトが大きくなると、プレゼンターは多くのメソッドを持つ巨大なクラスとなり、メンテナンスや理解が困難になります。 それは、UIイベント、UIロジック、ビジネスロジック、ネットワーク、データベースクエリなど、多くのことを担当するために起こることです。 これは単一責任原則に違反しており、VIPER が修正できることです。

Let’s fix it!

これらの問題を念頭に置いて、私は新しい Android プロジェクトを始め、MVP + Interactor (VIPE) を使用することにしました。 これにより、いくつかの責任をプレゼンターからインターアクターに移すことができました。 プレゼンターには、UIイベントの処理と、インターアクターから送られてくるデータをViewに表示する準備を任せます。 その後、インターアクターはビジネス ロジックと DB または API からのデータ取得にのみ責任を負います。

また、私はモジュール間のリンクにインターフェイスを使用し始めました。 そうすれば、インターフェイス上で宣言されたメソッド以外にはアクセスできなくなります。 これは構造を保護し、各モジュールの責任を明確に定義するのに役立ち、間違った場所にロジックを置くような開発者の間違いを避けることができます。

 class LoginContracts { interface View { fun goToHomeScreen(user: User) fun showError(message: String) } interface Presenter { fun onDestroy() fun onLoginButtonPressed(username: String, password: String) } interface Interactor { fun login(username: String, password: String) } interface InteractorOutput { fun onLoginSuccess(user: User) fun onLoginError(message: String) } } 

そして、これらのインターフェイスを実装するクラスを説明するコードをいくつか示します (Kotlin で書かれていますが、Java も同じはずです)。

 class LoginActivity: BaseActivity, LoginContracts.View { var presenter: LoginContracts.Presenter? = LoginPresenter(this) override fun onCreate() { //... loginButton.setOnClickListener { onLoginButtonClicked() } } override fun onDestroy() { presenter?.onDestroy() presenter = null super.onDestroy() } private fun onLoginButtonClicked() { presenter?.onLoginButtonClicked(usernameEditText.text, passwordEditText.text) } fun goToHomeScreen(user: User) { val intent = Intent(view, HomeActivity::class.java) intent.putExtra(Constants.IntentExtras.USER, user) startActivity(intent) } fun showError(message: String) { //shows the error on a dialog } } class LoginPresenter(var view: LoginContracts.View?): LoginContracts.Presenter, LoginContracts.InteractorOutput { var interactor: LoginContracts.Interactor? = LoginInteractor(this) fun onDestroy() { view = null interactor = null } fun onLoginButtonPressed(username: String, password: String) { interactor?.login(username, password) } fun onLoginSuccess(user: User) { view?.goToNextScreen(user) } fun onLoginError(message: String) { view?.showError(message) } } class LoginInteractor(var output: LoginContracts.InteractorOutput?): LoginContracts.Interactor { fun login(username: String, password: String) { LoginApiManager.login(username, password) ?.subscribeOn(Schedulers.io()) ?.observeOn(AndroidSchedulers.mainThread()) ?.subscribe({ //does something with the user, like saving it or the token output?.onLoginSuccess(it) }, { output?.onLoginError(it.message ?: "Error!") }) } } 

コードの全文はこの Gist にあります。

起動時にモジュールを作成してリンクすることがわかります。 Activity が作成されると、Presenter を初期化し、コンストラクタで自身を View として渡します。 次にPresenterはInteractorをInteractorOutputとして渡して初期化します。

iOSのVIPERプロジェクトでは、これはルーターで処理され、UIViewControllerを作成するか、ストーリーボードから取得し、すべてのモジュールを一緒に配線することになります。 しかし、Android では Activity を自分で作成しません。Intent を使用する必要があり、前の Activity から新しく作成された Activity にアクセスすることができません。 これはメモリリークを防ぐのに役立ちますが、新しいモジュールにデータを渡したいだけなら面倒です。 また、PresenterはParcelableSerializableである必要があるため、Intentの番外編に置くことはできません。

そのため、このプロジェクトでは Router を省略しました。

VIPE + Router

上記の VIPE の実装は、プレゼンターとインタラクターの責任を分担し、MVP の問題のほとんどを解決しています。 ビューは、すべての通常のビューの責任と他のモジュールへのルーティングを処理する必要があります。 これは、その責任であってはならず、私たちはもっとうまくやることができます。

ここで、「VIPE」と VIPER の違いを説明します。

 class LoginContracts { interface View { fun showError(message: String) //fun goToHomeScreen(user: User) //This is no longer a part of the View's responsibilities } interface Router { fun goToHomeScreen(user: User) // Now the router handles it } } class LoginPresenter(var view: LoginContracts.View?): LoginContracts.Presenter, LoginContracts.InteractorOutput { //now the presenter has a instance of the Router and passes the Activity to it on the constructor var router: LoginContracts.Router? = LoginRouter(view as? Activity) //... fun onLoginSuccess(user: User) { router?.goToNextScreen(user) } //... } class LoginRouter(var activity: Activity?): LoginContracts.Router { fun goToHomeScreen(user: User) { val intent = Intent(view, HomeActivity::class.java) intent.putExtra(Constants.IntentExtras.USER, user) activity?.startActivity(intent) } } 

完全なコードはここで入手できます。 これは、startActivity メソッドを呼び出すことができるように Activity のインスタンスを必要とするだけです。 iOS VIPER のようにすべてを一緒に配線することはまだできませんが、少なくとも単一責任原則を尊重しています。

結論

MVP + インタラクターでプロジェクトを開発し、同僚が VIPER Android プロジェクト全体を開発するのを支援したので、私はこのアーキテクチャが Android で機能することとその価値があると安全に言うことができます。 クラスが小さくなり、保守性が向上します。 また、アーキテクチャによってコードがどこに書かれるべきかが明確になるため、開発プロセスの指針にもなります。

Cheesecake Labs では、新しいプロジェクトのほとんどに VIPER を使用することを計画しています。 また、iOS プロジェクトから Android プロジェクトへ、またはその逆も簡単に行えます。 もちろん、これは進化する適応であり、ここに刻まれたものは何もありません。 それについてのフィードバックを喜んでお受けします!

Articles

コメントを残す

メールアドレスが公開されることはありません。