この記事の目的
MVIが技術的にどのように実装されているかについては詳しく説明しません(複数の方法があり、それぞれに長所と短所があります)。短い記事での私の主な目標は、将来このトピックを研究することに興味を持ち、戦闘プロジェクトにこのパターンを実装するか、少なくとも宿題でチェックすることを奨励することです。
どのような問題に直面できますか
親愛なる友人、この状況を想像してみましょう。
作業するためのビューインターフェイスがあります。
interface ComplexView {
fun showLoading()
fun hideLoading()
fun showBanner()
fun hideBanner()
fun dataLoaded(names: List<String>)
fun showTakeCreditDialog()
fun hideTakeCreditDialog()
}
一見、複雑なことは何もないようです。このビューを操作する別のエンティティを選択し、それをプレゼンターと呼ぶだけです(出来上がり、MVPの準備ができています)。これらは
そして、ここにプレゼンター自体があります:
interface Presenter {
fun onLoadData(dataKey: String)
fun onLoadCredit()
}
簡単です。ビューは、データをロードする必要があるときにプレゼンターのメソッドをプルします。次に、プレゼンターは、ロードされた情報を表示し、進行状況を表示するためにビューをプルする権利を持ちます。しかし、ここで
たとえば、
view.hideTakeCreditDialog()
ただし、同時に、ダイアログを表示するときは、画面にダイアログが表示されている間は、読み込みを非表示にし、表示しないようにする必要があることを忘れないでください。さらに、ダイアログを表示している間は呼び出さないバナーを表示するメソッドがあります(または、ダイアログを閉じてからバナーを表示した後でのみ、すべて要件によって異なります)。次の写真があります。
いかなる場合でも、あなたは電話するべきではありません:
view.showBanner()
view.showLoading()
対話が表示されている間。そうでなければ、
そして今、あなたと一緒に考えて、あなたがまだバナーを表示したいと思ったとしましょう(ビジネスからのそのような要件)。何を覚えておく必要がありますか?
実際には、このメソッドを呼び出すと、次のようになります。
view.showBanner()
必ず電話してください:
view.hideLoading()
view.hideTakeCreditDialog()
繰り返しますが、画面上の残りの要素を飛び越えるものがないように、悪名高い一貫性。
それで、あなたが何か間違ったことをした場合、誰があなたを手に入れるのかという質問が出てきます。答えは簡単です-NOBODY。そのような実現では、あなたは絶対にコントロールできません。
おそらく将来的には、ビューにいくつかの機能を追加する必要があります。これは、すでに存在するものにも関連しています。これから得られる不利な点は何ですか?
- 元要素の状態依存性からの麺
- ある表示状態から別の表示状態への遷移のロジックは、
プレゼンター全体に不鮮明になります -
新しいバナーやダイアログを表示する前に何かを隠すのを忘れるリスクが高いため、新しい画面状態を追加することは非常に困難です。
そして、ビューに7つのメソッドしかない場合にケースを分析したのは、あなたと私でした。そしてここでも問題であることが判明しました。
しかし、そのような見解があります:
interface ChatView : IView<ChatPresenter> {
fun setMessage(message: String)
fun showFullScreenProgressBar()
fun updateExistingMessage(model: ChatMessageModel)
fun hideFullScreenProgressBar()
fun addNewMessage(localMessage: ChatMessageModel)
fun showErrorFromLoading(message: String)
fun moveChatToStart()
fun containsMessage(message: ChatMessageModel): Boolean
fun getChatMessagesSize(): Int fun getLastMessage(): ChatMessageModel?
fun updateMessageStatus(messageId: String, status: ChatMessageStatus)
fun setAutoLoading(autoLoadingEnabled: Boolean)
fun initImageInChat(needImageInChat: Boolean)
fun enableNavigationButton()
fun hideKeyboard()
fun scrollToFirstMessage()
fun setTitle(@StringRes titleRes: Int)
fun setVisibleSendingError(isVisible: Boolean)
fun removeMessage(localId: String)
fun setBottomPadding(hasPadding: Boolean)
fun initMessagesList(pageSize: Int)
fun showToast(@StringRes textRes: Int)
fun openMessageDialog(message: String)
fun showSuccessRating()
fun setRatingAvailability(isEnabled: Boolean)
fun showSuccessRatingWithResult(ratingValue: String)
}
ここに新しいものを追加したり、古いものを編集したりするのは非常に困難です。相互に何がどのように接続されているかを確認してから、テスターが何も見逃さないように
MVI
要点
肝心なのは、州と呼ばれるエンティティがあるということです。この状態に基づいて、ビューはその表示をレンダリングします。私は深くは行きませんので、私の仕事はあなたの興味をそそることです、それで私は例にまっすぐに行きます。そして、興味があれば、記事の最後に非常に役立つ情報源のリストがあります。
記事の冒頭での位置を思い出してください。ダイアログ、バナー
data class UIState(
val loading: Boolean = false,
val names: List<String>? = null,
val isBannerShowing: Boolean = false,
val isCreditDialogShowing: Boolean = false
)
ルールを設定しましょう。あなたと私はこの状態の助けを借りてのみビューを変更できます。そのようなインターフェイスがあります。
interface ComplexView {
fun renderState(state: UIState)
}
次に、もう1つのルールを設定しましょう。州の所有者(この場合はプレゼンターになります)には、1つのエントリポイントからのみ連絡できます。彼にイベントを送ることによって。これらのイベントをアクションと呼ぶことをお勧めします。
sealed class UIAction {
class LoadNamesAction(dataKey: String) : UIAction()
object LoadBannerAction : UIAction()
object LoadCreditDialogInfo : UIAction()
}
封印されたクラスのために私にトマトを投げないでください。彼らは現在の状況での生活を簡素化し、プレゼンターでアクションを処理するときに追加のキャストを排除します。例を以下に示します。プレゼンターインターフェイスは次のようになります。
interface Presenter {
fun processAction(action: UIAction)
}
それでは、全体を接続する方法について考えてみましょう。
fun processAction(action: UiAction): UIState {
return when (action) {
is UiAction.LoadNamesAction -> state.copy(
loading = true,
isBannerShowing = false,
isCreditDialogShowing = false
)
is UiAction.LoadBannerAction -> state.copy(
loading = false,
isBannerShowing = true,
isCreditDialogShowing = false
)
is UiAction.LoadCreditDialogInfo -> state.copy(
loading = false,
isBannerShowing = false,
isCreditDialogShowing = true
)
}
}
注意を払うと、ある表示状態から別の表示状態へのフローが1つの場所で発生するようになり、頭の中ですべてがどのように機能するかを簡単にまとめることができます。
それはとても簡単ではありませんが、あなたの人生はもっと楽になるはずです。さらに、私の例では、これは表示されませんが、前の状態に基づいて新しい状態を処理する方法を決定できます(これを実装するためのいくつかの概念もあります)。badooの人たちが達成した非常識な再利用性は言うまでもなく、この目標を達成するためのアシスタントの1人はMVIでした。
しかし、あなたは早く喜ぶべきではありません、この世界のすべてには賛否両論があります、そしてここに彼らはいます
- 通常のトーストショーは私たちを壊します
- 1つのチェックボックスを更新すると、状態全体が再度コピーされて
ビューに送信されます。つまり、何もしなければ不要な再描画が発生します。
現在のロジックに従って、通常のandroidトーストを表示したいとします。トーストを表示するために、状態にフラグを設定します。
data class UIState(
val showToast: Boolean = false,
)
最初
プレゼンターで状態を取得して変更し、showToast = trueに設定すると、発生する可能性のある最も単純なことは画面の回転です。すべてが破壊され、
さて、2番目
これはすでにビューでの不要なレンダリングの問題であり、状態のフィールドの1つだけが変更された場合でも毎回発生します。そして、この問題は、いくつかの最も美しい方法ではない場合があります(新しい意味について不平を言う前に、前の意味とは異なるという鈍いチェックによって解決される場合があります)。しかし、安定したバージョンでのcomposeのリリースにより、この問題は解決され、私の友人は変容した幸せな世界であなたと一緒に暮らすでしょう!
プロのための時間:
- ビューへの1つのエントリポイント
- 画面の現在の状態は常に手元にあります
- 実装段階でも、ある状態が
別の状態にどのように流れ込むか、そしてそれらの間の関係は何かについて考える必要があります。 - 一方向のデータフロー
アンドロイドが大好きで、モチベーションを失うことはありません!
私のインスピレーションのリスト
- www.youtube.com/watch?v=VsStyq4Lzxo&t=592s-宣言的なUIパターン(Google
I / O'19) - www.youtube.com/watch?v=pXw6r2kAvq8&t=2s-Zsolt
Kocsi、BadooENによる建築の旅
- www.youtube.com/watch?v=hBkQkjWnAjg&t=318s-Android
用のよくできたMVIを調理する方法 - www.youtube.com/watch?v=0IKHxjkgop4 -ジェイクによってRxJavaと状態の管理
ウォートン - hannesdorfmann.com/android/model-view-intent-HannesDoorfmannによる記事