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

Model View Intent

Model View Intent

A presentation about Model View Intent, what it is, and what components make up Model View Intent with kotlin and RxJava on Android

Yousuf Haque

October 10, 2018
Tweet

More Decks by Yousuf Haque

Other Decks in Programming

Transcript

  1. data class LoginViewState( val username: String, val password: String, val

    isUsernameFieldEnabled: Boolean, val isPasswordFieldEnabled: Boolean, val isProgressSpinnerVisible: Boolean, val isSubmitButtonEnabled: Boolean, val submitButtonCopy: String, val errorMessageOption: Option<String>, val submitButtonIntentOption: Option<LoginIntent>, val currentTimeStringOption: Option<String> )
  2. private fun View.update(viewState: LoginViewState) { progress_pb.isVisible = viewState.isProgressSpinnerVisible submit_btn.isEnabled =

    viewState.isSubmitButtonEnabled submit_btn.text = viewState.submitButtonCopy username_et.isEnabled = viewState.isUsernameFieldEnabled password_et.isEnabled = viewState.isPasswordFieldEnabled error_message_tv.setTextOrHide(viewState.errorMessageOption) current_time_tv.setTextOrHide(viewState.currentTimeStringOption) submit_btn.setOnClickListener { viewState.submitButtonIntentOption.forSome { intentRelay.accept(it) } } } // in onAttach() viewStateStream.subscribe { view.update(it) }
  3. data class LoginViewState( val username: String, val password: String, val

    isUsernameFieldEnabled: Boolean, val isPasswordFieldEnabled: Boolean, val isProgressSpinnerVisible: Boolean, val isSubmitButtonEnabled: Boolean, val submitButtonCopy: String, val errorMessageOption: Option<String>, val submitButtonIntentOption: Option<LoginIntent>, val currentTimeStringOption: Option<String> )
  4. sealed class LoginState { data class Entering( val username: String,

    val password: String, val currentTime: Option<Date> ) : LoginState() data class Submitting( val username: String, val password: String, val currentTime: Option<Date> ) : LoginState() data class Error( val username: String, val password: String, val error: LoginError, val currentTime: Option<Date> ) : LoginState() enum class LoginError { IncorrectCredentials, NetworkError } }
  5. fun LoginState.render(): LoginViewState { return LoginViewState( username = getUserName(), isPasswordFieldEnabled

    = isPasswordFieldEnabled(), isUsernameFieldEnabled = isUsernameFieldEnabled(), password = getPassword(), submitButtonCopy = getSubmitButtonCopy(), isSubmitButtonEnabled = isSubmitButtonEnabled(), errorMessageOption = getErrorMessageOption(), isProgressSpinnerVisible = isProgressSpinnerVisible(), submitButtonIntentOption = getSubmitButtonIntent(), currentTimeStringOption = getCurrentTimeStringOption() ) }
  6. fun LoginState.isSubmitButtonEnabled(): Boolean { return when (this) { is Entering

    -> username.isNotBlank() && password.isNotBlank() is Error -> username.isNotBlank() && password.isNotBlank() is Submitting -> false } }
  7. fun LoginState.getSubmitButtonCopy(): String { return when (this) { is Entering

    -> "Submit" is Error -> "Submit" is Submitting -> "Submitting" } }
  8. fun LoginState.getErrorMessageOption(): Option<String> { return when (this) { is Entering

    -> none() is Submitting -> none() is Error -> when (error) { IncorrectCredentials -> "Incorrect credentials".some() NetworkError -> "Network error".some() } } }
  9. fun LoginState.render(): LoginViewState { return LoginViewState( username = getUserName(), isPasswordFieldEnabled

    = isPasswordFieldEnabled(), isUsernameFieldEnabled = isUsernameFieldEnabled(), password = getPassword(), submitButtonCopy = getSubmitButtonCopy(), isSubmitButtonEnabled = isSubmitButtonEnabled(), errorMessageOption = getErrorMessageOption(), isProgressSpinnerVisible = isProgressSpinnerVisible(), submitButtonIntentOption = getSubmitButtonIntent(), currentTimeStringOption = getCurrentTimeStringOption() ) }
  10. fun buildLoginStateStream( intentStream: Observable<LoginIntent>, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, currentTimeStream: Observable<Date>,

    buildGoToLoggedInCompletable: (userId: String) -> Completable ): Observable<LoginState> { TODO("Build and return a state stream") }
  11. fun buildLoginStateStream( intentStream: Observable<LoginIntent>, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, currentTimeStream: Observable<Date>,

    buildGoToLoggedInCompletable: (userId: String) -> Completable ): Observable<LoginState> { val initialState = Entering( username = "", password = "", currentTime = none() ) val updateTimeReducerStream: Observable<LoginExampleStateReducer> = getCurrentTimeReducerStream(currentTimeStream) return updateTimeReducerStream .scan(initialState) { oldState: LoginState, reducer: LoginExampleStateReducer -> val newState = reducer(oldState) newState } }
  12. fun buildLoginStateStream( intentStream: Observable<LoginIntent>, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, currentTimeStream: Observable<Date>,

    buildGoToLoggedInCompletable: (userId: String) -> Completable ): Observable<LoginState> { val initialState = Entering( username = "", password = "", currentTime = none() ) val updateTimeReducerStream: Observable<LoginExampleStateReducer> = getCurrentTimeReducerStream(currentTimeStream) return updateTimeReducerStream .scan(initialState) { oldState: LoginState, reducer: LoginExampleStateReducer -> val newState = reducer(oldState) newState } }
  13. fun buildLoginStateStream( intentStream: Observable<LoginIntent>, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, currentTimeStream: Observable<Date>,

    buildGoToLoggedInCompletable: (userId: String) -> Completable ): Observable<LoginState> { val initialState = Entering( username = "", password = "", currentTime = none() ) val updateTimeReducerStream: Observable<LoginExampleStateReducer> = getCurrentTimeReducerStream(currentTimeStream) return updateTimeReducerStream .scan(initialState) { oldState: LoginState, reducer: LoginExampleStateReducer -> val newState = reducer(oldState) newState } }
  14. fun buildLoginStateStream( intentStream: Observable<LoginIntent>, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, currentTimeStream: Observable<Date>,

    buildGoToLoggedInCompletable: (userId: String) -> Completable ): Observable<LoginState> { val initialState = Entering( username = "", password = "", currentTime = none() ) val updateTimeReducerStream: Observable<LoginExampleStateReducer> = getCurrentTimeReducerStream(currentTimeStream) return updateTimeReducerStream .scan(initialState) { oldState: LoginState, reducer: LoginExampleStateReducer -> val newState = reducer(oldState) newState } }
  15. fun getCurrentTimeReducerStream( currentTimeStream: Observable<Date> ): Observable<LoginExampleStateReducer> { fun buildOnCurrentTimeUpdateReducer(date: Date):

    LoginExampleStateReducer { return { oldState: LoginState -> when (oldState) { is LoginState.Entering -> oldState.copy(currentTime = date.some()) is LoginState.Submitting -> oldState.copy(currentTime = date.some()) is LoginState.Error -> oldState.copy(currentTime = date.some()) } } } return currentTimeStream.map { buildOnCurrentTimeUpdateReducer(it) } }
  16. fun getCurrentTimeReducerStream( currentTimeStream: Observable<Date> ): Observable<LoginExampleStateReducer> { fun buildOnCurrentTimeUpdateReducer(date: Date):

    LoginExampleStateReducer { return { oldState: LoginState -> when (oldState) { is LoginState.Entering -> oldState.copy(currentTime = date.some()) is LoginState.Submitting -> oldState.copy(currentTime = date.some()) is LoginState.Error -> oldState.copy(currentTime = date.some()) } } } return currentTimeStream.map { buildOnCurrentTimeUpdateReducer(it) } }
  17. fun getCurrentTimeReducerStream( currentTimeStream: Observable<Date> ): Observable<LoginExampleStateReducer> { fun buildOnCurrentTimeUpdateReducer(date: Date):

    LoginExampleStateReducer { return { oldState: LoginState -> when (oldState) { is LoginState.Entering -> oldState.copy(currentTime = date.some()) is LoginState.Submitting -> oldState.copy(currentTime = date.some()) is LoginState.Error -> oldState.copy(currentTime = date.some()) } } } return currentTimeStream.map { buildOnCurrentTimeUpdateReducer(it) } }
  18. fun buildLoginStateStream( intentStream: Observable<LoginIntent>, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, currentTimeStream: Observable<Date>,

    buildGoToLoggedInCompletable: (userId: String) -> Completable ): Observable<LoginState> { val initialState = Entering( username = "", password = "", currentTime = none() ) val updateTimeReducerStream: Observable<LoginExampleStateReducer> = getCurrentTimeReducerStream(currentTimeStream) return updateTimeReducerStream .scan(initialState) { oldState: LoginState, reducer: LoginExampleStateReducer -> val newState = reducer(oldState) newState } }
  19. fun buildLoginStateStream( intentStream: Observable<LoginIntent>, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, currentTimeStream: Observable<Date>,

    buildGoToLoggedInCompletable: (userId: String) -> Completable ): Observable<LoginState> { val initialState = Entering( username = "", password = "", currentTime = none() ) val updateTimeReducerStream: Observable<LoginExampleStateReducer> = getCurrentTimeReducerStream(currentTimeStream) return updateTimeReducerStream .scan(initialState) { oldState: LoginState, reducer: LoginExampleStateReducer -> val newState = reducer(oldState) newState } }
  20. fun buildLoginStateStream( intentStream: Observable<LoginIntent>, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, currentTimeStream: Observable<Date>,

    buildGoToLoggedInCompletable: (userId: String) -> Completable ): Observable<LoginState> { val initialState = Entering( username = "", password = "", currentTime = none() ) val updateTimeReducerStream: Observable<LoginExampleStateReducer> = getCurrentTimeReducerStream(currentTimeStream) return updateTimeReducerStream .scan(initialState) { oldState: LoginState, reducer: LoginExampleStateReducer -> val newState = reducer(oldState) newState } }
  21. fun buildLoginStateStream( intentStream: Observable<LoginIntent>, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, currentTimeStream: Observable<Date>,

    buildGoToLoggedInCompletable: (userId: String) -> Completable ): Observable<LoginState> { val initialState = Entering( username = "", password = "", currentTime = none() ) val updateTimeReducerStream: Observable<LoginExampleStateReducer> = getCurrentTimeReducerStream(currentTimeStream) return updateTimeReducerStream .scan(initialState) { oldState: LoginState, reducer: LoginExampleStateReducer -> val newState = reducer(oldState) newState } }
  22. fun buildLoginStateStream( intentStream: Observable<LoginIntent>, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, currentTimeStream: Observable<Date>,

    buildGoToLoggedInCompletable: (userId: String) -> Completable ): Observable<LoginState> { val initialState = Entering( username = "", password = "", currentTime = none() ) val updateTimeReducerStream: Observable<LoginExampleStateReducer> = getCurrentTimeReducerStream(currentTimeStream) return updateTimeReducerStream .scan(initialState) { oldState: LoginState, reducer: LoginExampleStateReducer -> val newState = reducer(oldState) newState } }
  23. sealed class LoginIntent { data class UpdateCredentials( val username: String,

    val password: String ) : LoginIntent() data class SubmitLoginIntent( val username: String, val password: String ): LoginIntent() }
  24. fun getIntentReducerStream( intent: LoginIntent, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, buildGoToLoggedInCompletable: (userId:

    String) -> Completable ): Observable<LoginExampleStateReducer> { return when (intent) { is UpdateCredentials -> getUpdateCredentialsReducerStream(intent) is SubmitLoginIntent -> buildSubmitLoginRequestReducerStream( intent, loginRequestBuilder, buildGoToLoggedInCompletable ) } }
  25. fun buildLoginStateStream(...): Observable<LoginState> { val updateTimeReducerStream: Observable<LoginExampleStateReducer> = getCurrentTimeReducerStream(currentTimeStream) val

    intentReducerStream = intentStream.flatMap { getIntentReducerStream( it, loginRequestBuilder, buildGoToLoggedInCompletable ) } val reducerStream: Observable<LoginExampleStateReducer> = Observable.merge(intentReducerStream, updateTimeReducerStream) return reducerStream .scan(initialState) { oldState: LoginState, reducer: LoginExampleStateReducer -> val newState = reducer(oldState) newState } }
  26. fun buildLoginStateStream(...): Observable<LoginState> { val updateTimeReducerStream: Observable<LoginExampleStateReducer> = getCurrentTimeReducerStream(currentTimeStream) val

    intentReducerStream = intentStream.flatMap { getIntentReducerStream( it, loginRequestBuilder, buildGoToLoggedInCompletable ) } val reducerStream: Observable<LoginExampleStateReducer> = Observable.merge(intentReducerStream, updateTimeReducerStream) return reducerStream .scan(initialState) { oldState: LoginState, reducer: LoginExampleStateReducer -> val newState = reducer(oldState) newState } }
  27. fun buildLoginStateStream(...): Observable<LoginState> { val updateTimeReducerStream: Observable<LoginExampleStateReducer> = getCurrentTimeReducerStream(currentTimeStream) val

    intentReducerStream = intentStream.flatMap { getIntentReducerStream( it, loginRequestBuilder, buildGoToLoggedInCompletable ) } val reducerStream: Observable<LoginExampleStateReducer> = Observable.merge(intentReducerStream, updateTimeReducerStream) return reducerStream .scan(initialState) { oldState: LoginState, reducer: LoginExampleStateReducer -> val newState = reducer(oldState) newState } }
  28. fun buildLoginStateStream(...): Observable<LoginState> { val updateTimeReducerStream: Observable<LoginExampleStateReducer> = getCurrentTimeReducerStream(currentTimeStream) val

    intentReducerStream = intentStream.flatMap { getIntentReducerStream( it, loginRequestBuilder, buildGoToLoggedInCompletable ) } val reducerStream: Observable<LoginExampleStateReducer> = Observable.merge(intentReducerStream, updateTimeReducerStream) return reducerStream .scan(initialState) { oldState: LoginState, reducer: LoginExampleStateReducer -> val newState = reducer(oldState) newState } }
  29. fun getIntentReducerStream( intent: LoginIntent, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, buildGoToLoggedInCompletable: (userId:

    String) -> Completable ): Observable<LoginExampleStateReducer> { return when (intent) { is UpdateCredentials -> getUpdateCredentialsReducerStream(intent) is SubmitLoginIntent -> buildSubmitLoginRequestReducerStream( intent, loginRequestBuilder, buildGoToLoggedInCompletable ) } }
  30. fun getIntentReducerStream( intent: LoginIntent, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, buildGoToLoggedInCompletable: (userId:

    String) -> Completable ): Observable<LoginExampleStateReducer> { return when (intent) { is UpdateCredentials -> getUpdateCredentialsReducerStream(intent) is SubmitLoginIntent -> buildSubmitLoginRequestReducerStream( intent, loginRequestBuilder, buildGoToLoggedInCompletable ) } }
  31. fun buildSubmitLoginRequestReducerStream( intent: SubmitLoginIntent, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, buildGoToLoggedInCompletable: (userId:

    String) -> Completable ): Observable<LoginExampleStateReducer> { // Omitted Code return loginRequestBuilder( LoginRequest( username = intent.username, password = intent.password ) ) .flatMapObservable { loginResult -> loginResult.fold( ifFailure = { getOnErrorStateReducer(it).just() }, ifSuccess = { buildGoToLoggedInCompletable( it.userId ).toObservable<LoginExampleStateReducer>() } ) } .startWith(onSubmitStateReducer) }
  32. fun buildSubmitLoginRequestReducerStream( intent: SubmitLoginIntent, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, buildGoToLoggedInCompletable: (userId:

    String) -> Completable ): Observable<LoginExampleStateReducer> { // Omitted Code return loginRequestBuilder( LoginRequest( username = intent.username, password = intent.password ) ) .flatMapObservable { loginResult -> loginResult.fold( ifFailure = { getOnErrorStateReducer(it).just() }, ifSuccess = { buildGoToLoggedInCompletable( it.userId ).toObservable<LoginExampleStateReducer>() } ) } .startWith(onSubmitStateReducer) }
  33. fun buildSubmitLoginRequestReducerStream( intent: SubmitLoginIntent, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, buildGoToLoggedInCompletable: (userId:

    String) -> Completable ): Observable<LoginExampleStateReducer> { // Omitted Code return loginRequestBuilder( LoginRequest( username = intent.username, password = intent.password ) ) .flatMapObservable { loginResult -> loginResult.fold( ifFailure = { getOnErrorStateReducer(it).just() }, ifSuccess = { buildGoToLoggedInCompletable( it.userId ).toObservable<LoginExampleStateReducer>() } ) } .startWith(onSubmitStateReducer) }
  34. fun buildSubmitLoginRequestReducerStream( intent: SubmitLoginIntent, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, buildGoToLoggedInCompletable: (userId:

    String) -> Completable ): Observable<LoginExampleStateReducer> { // Omitted Code return loginRequestBuilder( LoginRequest( username = intent.username, password = intent.password ) ) .flatMapObservable { loginResult -> loginResult.fold( ifFailure = { getOnErrorStateReducer(it).just() }, ifSuccess = { buildGoToLoggedInCompletable( it.userId ).toObservable<LoginExampleStateReducer>() } ) } .startWith(onSubmitStateReducer) }
  35. fun buildSubmitLoginRequestReducerStream( intent: SubmitLoginIntent, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, buildGoToLoggedInCompletable: (userId:

    String) -> Completable ): Observable<LoginExampleStateReducer> { // Omitted Code return loginRequestBuilder( LoginRequest( username = intent.username, password = intent.password ) ) .flatMapObservable { loginResult -> loginResult.fold( ifFailure = { getOnErrorStateReducer(it).just() }, ifSuccess = { buildGoToLoggedInCompletable( it.userId ).toObservable<LoginExampleStateReducer>() } ) } .startWith(onSubmitStateReducer) }
  36. fun buildSubmitLoginRequestReducerStream( intent: SubmitLoginIntent, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, buildGoToLoggedInCompletable: (userId:

    String) -> Completable ): Observable<LoginExampleStateReducer> { // Omitted Code return loginRequestBuilder( LoginRequest( username = intent.username, password = intent.password ) ) .flatMapObservable { loginResult -> loginResult.fold( ifFailure = { getOnErrorStateReducer(it).just() }, ifSuccess = { buildGoToLoggedInCompletable( it.userId ).toObservable<LoginExampleStateReducer>() } ) } .startWith(onSubmitStateReducer) }
  37. fun buildSubmitLoginRequestReducerStream( intent: SubmitLoginIntent, loginRequestBuilder: (LoginRequest) -> Single<Try<UserInfo>>, buildGoToLoggedInCompletable: (userId:

    String) -> Completable ): Observable<LoginExampleStateReducer> { // Omitted Code return loginRequestBuilder( LoginRequest( username = intent.username, password = intent.password ) ) .flatMapObservable { loginResult -> loginResult.fold( ifFailure = { getOnErrorStateReducer(it).just() }, ifSuccess = { buildGoToLoggedInCompletable( it.userId ).toObservable<LoginExampleStateReducer>() } ) } .startWith(onSubmitStateReducer) }
  38. Resources 1. Managing State with RxJava by Jake Wharton a.

    https://www.youtube.com/watch?v=0IKHxjkgop4 2. Borrowing the Best of the Web to Make Native Better by Christina Lee a. https://www.youtube.com/watch?v=GOVMkQp3LZ4 3. Unidirectional data flow architectures by Andre Staltz a. https://www.youtube.com/watch?v=1c6XiQsnh_U b. https://staltz.com/unidirectional-user-interface-architectures.html 4. Reactive Apps with Model View Intent by Hannes Dorfmann a. http://hannesdorfmann.com/android/mosby3-mvi-1 5. My Take on Model View Intent by Zak Taccardi a. https://hackernoon.com/model-view-intent-mvi-part-1-state-renderer-187e270db15c 6. Login MVI by Yousuf Haque a. https://github.com/yousuf-haque/LoginMvi