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

Use MVI Architecture in Kotlin × Android

itome
November 15, 2018

Use MVI Architecture in Kotlin × Android

itome

November 15, 2018
Tweet

More Decks by itome

Other Decks in Technology

Transcript

  1.  Koin×AndroidͰMVI
    ΞʔΩςΫνϟΛ࠾༻͢Δ
    #potatetips #56

    View full-size slide

  2. ࣗݾ঺հ
    0min

    View full-size slide

  3. ࣗݾ঺հ
    0min
    ௩ຊ෢ࢤ
    ! https://twitter.com/itometeam
    " https://github.com/itome
    # https://medium.com/@itometeam
    cyberagent/cats

    View full-size slide

  4. MVI Architectureͱ͸ʁ
    1min

    View full-size slide

  5. MVI Architectureͱ͸ʁ
    1min
    ୯ํ޲σʔλϑϩʔ
    • Model
    • View
    • Intent(≠android.content.Intent)
    ViewModelͰ
    Intent → Action
    → Result → Result
    ͱσʔλΛม׵͍ͯ͘͜͠ͱͰ
    ঢ়ଶΛมߋ͢Δ

    View full-size slide

  6. RxʹΑΔ࣮૷ྫ
    2min

    View full-size slide

  7. RxʹΑΔ࣮૷ྫ
    2min
    sealed class EventsIntent : MviIntent {
    object FetchFirstPageIntent : EventsIntent()
    data class FetchPageIntent(val pageNum: Int) : EventsIntent()
    object FetchLoginUserIntent : EventsIntent()
    }
    sealed class EventsAction : MviAction {
    object FetchFirstPageAction : EventsAction()
    data class FetchEventsPageAction(
    val pageNum: Int
    ) : EventsAction()
    object FetchLoginUserAction : EventsAction()
    }

    View full-size slide

  8. RxʹΑΔ࣮૷ྫ
    2min
    private fun actionFromIntent(intent: EventsIntent): EventsAction {
    return when (intent) {
    FetchFirstPageIntent -> FetchFirstPageAction
    is FetchPageIntent -> FetchEventsPageAction(intent.pageNum)
    FetchLoginUserIntent -> FetchLoginUserAction
    }
    }

    View full-size slide

  9. RxʹΑΔ࣮૷ྫ
    2min
    sealed class EventsResult : MviResult {
    sealed class FetchFirstPageResult : EventsResult() {
    data class Success(val events: List) : FetchFirstPageResult()
    data class Failure(val error: Throwable) : FetchFirstPageResult()
    object InFlight : FetchFirstPageResult()
    }
    sealed class FetchEventsPageResult : EventsResult() {
    data class Success(val events: List) : FetchEventsPageResult()
    data class Failure(val error: Throwable) : FetchEventsPageResult()
    object InFlight : FetchEventsPageResult()
    }
    sealed class FetchLoginUserResult : EventsResult() {
    data class Success(val user: User) : FetchLoginUserResult()
    data class Failure(val error: Throwable) : FetchLoginUserResult()
    object InFlight : FetchLoginUserResult()
    }
    }

    View full-size slide

  10. RxʹΑΔ࣮૷ྫ
    2min
    override val actionProcessor = mergeProcessor(
    fetchFirstPageProcessor to FetchFirstPageAction::class,
    fetchEventsPageProcessor to FetchEventsPageAction::class,
    fetchLoginUserProcessor to FetchLoginUserAction::class
    )
    private val fetchFirstPageProcessor =
    createProcessor {
    repository.getEvents(1)
    .toObservable()
    .map { user -> FetchFirstPageResult.Success(user) }
    .cast(FetchFirstPageResult::class.java)
    .onErrorReturn(FetchFirstPageResult::Failure)
    .subscribeOn(schedulerProvider.io())
    .observeOn(schedulerProvider.ui())
    .startWith(FetchFirstPageResult.InFlight)
    }

    View full-size slide

  11. RxʹΑΔ࣮૷ྫ
    2min
    data class EventsViewState(
    val loginUser: User?,
    val events: List,
    val error: Throwable?,
    val nextPage: Int,
    val isLoading: Boolean
    ) : MviViewState {
    companion object {
    fun idle(): EventsViewState {
    return EventsViewState(
    loginUser = null,
    events = emptyList(),
    error = null,
    nextPage = 1,
    isLoading = false
    )
    }
    }
    }

    View full-size slide

  12. RxʹΑΔ࣮૷ྫ
    2min
    private val reducer = { previousState: EventsViewState, result: EventsResult ->
    when (result) {
    is FetchFirstPageResult -> when (result) {
    is FetchFirstPageResult.Success ->
    previousState.copy(
    events = result.events,
    error = null,
    isLoading = false,
    nextPage = 2
    )
    is FetchFirstPageResult.Failure ->
    previousState.copy(error = result.error, isLoading = false)
    FetchFirstPageResult.InFlight ->
    previousState.copy(isLoading = true)
    }

    View full-size slide

  13. RxʹΑΔ࣮૷ྫ
    2min
    private fun compose(): Observable {
    return intentsSubject
    .map(this::actionFromIntent)
    .compose(actionProcessorHolder.actionProcessor)
    .scan(EventsViewState.idle(), reducer)
    .replay(1)
    .autoConnect(0)
    }

    View full-size slide

  14. RxʹΑΔ࣮૷ྫ
    2min
    ιʔείʔυ͸ https://github.com/itome/GithubMVI

    View full-size slide

  15. ϝϦοτ / σϝϦοτ
    2min

    View full-size slide

  16. ϝϦοτ
    2min
    ϓϩάϥϚʔʹର͢Δ੍໿͕ڧ͍
    ςετ͕༰қ
    ɾਓʹΑ࣮ͬͯ૷͕มΘΔ෦෼ΛͰ͖Δ͚ͩ཈͑ΒΕΔ
    ɾ֤ॲཧͰೖྗͱग़ྗ͕ඞͣ̍ΧॴʹͳΔͨΊɺ
    ɹςετ͢΂͖͜ͱ͕໌֬
    ΤϥʔϋϯυϦϯά͕༰қ
    ɾViewͷΠϕϯτ͔ΒViewͷߋ৽·Ͱ͕ҰຊͷετϦʔϜʹ
    ɹͳ͍ͬͯΔͨΊɺྫ֎ॲཧ͕Θ͔Γ΍͍͢

    View full-size slide

  17. σϝϦοτ
    2min
    ̍ճ͖ΓͷΠϕϯτΛ఻͑Δͷ͕೉͍͠
    ෳࡶͳϨΠΞ΢τͰ͸ViewStateͷߋ৽ͷॲཧ͕ॏ͘ͳΔ
    ɾViewModelͷग़ྗ͕ViewStateͷΈͰ͋ΔͨΊɺҰճ͖ΓͷΠϕϯτΛ
    ɹViewModelଆ͔Βى͍ͨ࣌͜͠͸ɺflagΛ࣋ͨͤΔͳͲͷ޻෉͕ඞཁ
    ɾAndroidʹ͸Reactͷࠩ෼ߋ৽ͷΑ͏ͳ࢓૊Έ͕ଘࡏ͠ͳ͍ͨΊɺ
    ɹViewStateͷҰ෦Λߋ৽͚ͨͩ͠ͰɺViewStateʹؔ࿈͢Δ
    ɹશͯͷView͕ߋ৽͞Εͯ͠·͏
    ৑௕ʹͳΓ΍͍͢
    ɾίʔυͷϘΠϥʔϓϨʔτ͕ଟ͍ͨΊɺখ͞ͳॲཧͰ΋͔ͳΓͷ
    ɹίʔυΛॻ͘ඞཁ͕͋Δɻ

    View full-size slide

  18. ·ͱΊ
    0.5min

    View full-size slide

  19. ͋Γ͕ͱ͏͍͟͝·ͨ͠
    ! https://twitter.com/itometeam
    " https://github.com/itome
    # https://medium.com/@itometeam
    Takeshi Tsukamoto
    cyberagent/cats

    View full-size slide