Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Reactive RecyclerView Inseption

Reactive RecyclerView Inseption

A good architecture is more than just a fancy structure of your project, or trendy thing that everyone has to follow. With so many great examples out there on how to build an MVP or MVI app, one might think that the job is done and there is nothing left to discover or create. But one burden of any architecture is that it has to scale with Android, and that is not an easy task.

In this small session we are going to dive deep into the challenges that one faces when adopting an architecture on an example of a complex RecyclerView that relies on an RxMVI approach.

Serj Lotutovici

June 08, 2017
Tweet

More Decks by Serj Lotutovici

Other Decks in Programming

Transcript

  1. Architecture? • It’s Contract! • Should be… • simple •

    easy to implement • easy to throw away* 㾺 • Separation of concerns
  2. RxMV[Something] /** Base contract for a Rx Presenter in a

    MVI implementation. */ interface Presenter<V : View<*>> { /** Binds the view to this and returns a [Disposable]. */ fun bind(view: V): Disposable } /** Base contract for a view that renders a specific model. */ interface View<in M : Any> { fun render(model: M) }
  3. RxMV[Something] /** Base contract for a Rx Presenter in a

    MVI implementation. */ interface Presenter<V : View<*>> { /** Binds the view to this and returns a [Disposable]. */ fun bind(view: V): Disposable } /** Base contract for a view that renders a specific model. */ interface View<in M : Any> { fun render(model: M) }
  4. RxMV[Something] /** Base contract for a Rx Presenter in a

    MVI implementation. */ interface Presenter<V : View<*>> { /** Binds the view to this and returns a [Disposable]. */ fun bind(view: V): Disposable } /** Base contract for a view that renders a specific model. */ interface View<in M : Any> { fun render(model: M) }
  5. RecyclerView - Adapter - ViewHolder abstract fun onBindViewHolder(holder: ViewHolder, position:

    Int) abstract fun getItemCount(): Int abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder
  6. RecyclerView - Adapter - ViewHolder abstract fun onBindViewHolder(holder: ViewHolder, position:

    Int) abstract fun getItemCount(): Int abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder
  7. RecyclerView - Adapter - ViewHolder abstract fun onBindViewHolder(holder: ViewHolder, position:

    Int) abstract fun getItemCount(): Int abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder sealed class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { class Simple(view: View): ViewHolder(view) class WithImage(view: View): ViewHolder(view) // ... }
  8. RecyclerView - Adapter - ViewHolder abstract fun onBindViewHolder(holder: ViewHolder, position:

    Int) abstract fun getItemCount(): Int abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder sealed class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { abstract fun bind(item: Item) class Simple(view: View): ViewHolder(view) class WithImage(view: View): ViewHolder(view) // ... }
  9. RecyclerView - Adapter - ViewHolder abstract fun onBindViewHolder(holder: ViewHolder, position:

    Int) abstract fun getItemCount(): Int abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder sealed class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { abstract fun bind(item: Item) class Simple(view: View): ViewHolder(view) class WithImage(view: View): ViewHolder(view) // ... }
  10. RecyclerView - Adapter - ViewHolder abstract fun onBindViewHolder(holder: ViewHolder, position:

    Int) abstract fun getItemCount(): Int abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder sealed class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { abstract fun bind(item: Item) class Simple(view: View): ViewHolder(view) class WithImage(view: View): ViewHolder(view) // ... }
  11. RecyclerView - Adapter - ViewHolder abstract fun onBindViewHolder(holder: ViewHolder, position:

    Int) abstract fun getItemCount(): Int abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder sealed class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { abstract fun bind(item: Item) class Simple(view: View): ViewHolder(view) class WithImage(view: View): ViewHolder(view) // ... }
  12. RecyclerView - Adapter - ViewHolder abstract fun onBindViewHolder(holder: ViewHolder, position:

    Int) abstract fun getItemCount(): Int abstract fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder sealed class ViewHolder(view: View) : RecyclerView.ViewHolder(view) { abstract fun bind(state: ViewHolderState) class Simple(view: View): ViewHolder(view) class WithImage(view: View): ViewHolder(view) // ... }
  13. MVI Contract? interface RecyclerViewMPI : RxView<ViewHolderState> { interface SimpleView :

    RecyclerViewMPI { fun action1(): Observable<Unit> } interface WithImageView : RecyclerViewMPI { fun action1(): Observable<Unit> fun action2(): Observable<Unit> } } class ViewHolderPresenter: Presenter<RecyclerViewMPI> { override fun bind(view: RecyclerViewMPI): Disposable { when (view) { is RecyclerViewMPI.SimpleView -> //... is RecyclerViewMPI.WithImageView -> //... } } }
  14. MVI Contract? interface RecyclerViewMPI : RxView<ViewHolderState> { interface SimpleView :

    RecyclerViewMPI { fun action1(): Observable<Unit> } interface WithImageView : RecyclerViewMPI { fun action1(): Observable<Unit> fun action2(): Observable<Unit> } } class SimpleHolderPresenter: Presenter<RecyclerViewMPI.SimpleView> { override fun bind(view: RecyclerViewMPI.SimpleView): Disposable { } } class WithImageHolderPresenter: Presenter<RecyclerViewMPI.WithImageView> { override fun bind(view: RecyclerViewMPI.WithImageView): Disposable { } }
  15. The Pain doesn’t end there • How to manage the

    dependency graph? • How to inject presenters? • How to stay sane?
  16. Dagger Scopes @Scope @Retention(AnnotationRetention.SOURCE) annotation class AppScope @Scope @Retention(AnnotationRetention.SOURCE) annotation

    class ActivityScope @Scope @Retention(AnnotationRetention.SOURCE) annotation class ViewScope @AppScope AppComponent @ActivityScope ActivityComponent @ViewScope ViewComponent
  17. Dagger Madness interface ViewConsumer<A : BaseActivity<A, C>, C : ActivityComponent<A>>

    { /** Called when the component is received from the host activity. */ fun onComponentReceived(component: C) } fun <V, A : BaseActivity<A, C>, C : ActivityComponent<A>> V.bindComponent(context: Context): Unit where V : RecyclerView.ViewHolder, V : ViewConsumer<A, C> = @Suppress("UNCHECKED_CAST") if (context is BaseActivity<*, *>) { val hostActivity = context as A val component = hostActivity.activityComponent onComponentReceived(component) } else { throw IllegalStateException("View must be inflated by a BaseActivity!") } class ViewHolder(view: View) : RecyclerView.ViewHolder(view), ViewConsumer<SomeActivity, SomeActivityComponent> { init { bindComponent(view.context) } override fun onComponentReceived(component: SalesFlowActivityComponent) { component.viewComponent().inject(this) } }
  18. Takeaways Even the simplest of things can bring much pain

    a suffering… © Someone on the Internet