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

Android Architecture: From Medieval to Modern

Ivan Maric
October 16, 2018

Android Architecture: From Medieval to Modern

The biggest decision you need to make at the beginning of each project is which architecture to use. This lays down the foundation on which the project will be built up. You want to build fast, test faster and be able to implement each change on the fly. Is that really possible? I will tell you how we do this at Infinum. The main point is that an architecture is always evolving so should you!

Ivan Maric

October 16, 2018
Tweet

More Decks by Ivan Maric

Other Decks in Programming

Transcript

  1. USAGE • Display UI • User interaction • Manipulate storage

    • Network calls • Business logic • Track user location • Take picture • Play media
  2. USAGE • Display UI • User interaction • Manipulate storage

    • Network calls • Business logic • Track user location • Take picture • Play media • World peace
  3. =

  4. INFINUM ANDROID TEAM • 10+ members (25+ now) • 10+

    ongoing projects • Maintenance • Skill level: Junior - Pro
  5. V P M Model Presenter View Activity 1 Fragment 1

    CustomViews 1 Presenter 1 Presenter 1 Presenter 1 Interactor Repository Util/Manager Interface Interface
  6. END RESULT • Keep It Simple Stupid Views • Testable

    Presenters • Maintainable project • Easy transition
  7. private void checkForUpdates() { versionViewModel.checkForUpdates().observe(this, result -> { if (result

    instanceof NewUpdate) { showUpdateDialog(((NewUpdate) result).url); } else if (result instanceof NoUpdate) { login(); } else if (result instanceof Error) { showErrorDialog(); } else { Timber.wtf(new IllegalArgumentException("Impossible case")); }a }); }b
  8. private void checkForUpdates() { versionViewModel.checkForUpdates().observe(this, result -> { if (result

    instanceof NewUpdate) { showUpdateDialog(((NewUpdate) result).url); } else if (result instanceof NoUpdate) { login(); } else if (result instanceof Error) { showErrorDialog(); } else { Timber.wtf(new IllegalArgumentException("Impossible case")); }a }); }b
  9. private void checkForUpdates() { versionViewModel.checkForUpdates().observe(this, result -> { if (result

    instanceof NewUpdate) { showUpdateDialog(((NewUpdate) result).url); } else if (result instanceof NoUpdate) { login(); } else if (result instanceof Error) { showErrorDialog(); } else { Timber.wtf(new IllegalArgumentException("Impossible case")); }a }); }b
  10. private void checkForUpdates() { versionViewModel.checkForUpdates().observe(this, result -> { if (result

    instanceof NewUpdate) { showUpdateDialog(((NewUpdate) result).url); } else if (result instanceof NoUpdate) { login(); } else if (result instanceof Error) { showErrorDialog(); } else { Timber.wtf(new IllegalArgumentException("Impossible case")); }a }); }b
  11. private void checkForUpdates() { versionViewModel.checkForUpdates().observe(this, result -> { if (result

    instanceof NewUpdate) { showUpdateDialog(((NewUpdate) result).url); } else if (result instanceof NoUpdate) { login(); } else if (result instanceof Error) { showErrorDialog(); } else { Timber.wtf(new IllegalArgumentException("Impossible case")); }a }); }b
  12. viewModel.viewStateRender().observe(this, Observer { when (it) { is TwoFaNeeded -> displayCodeInput()

    is WrongCredentials -> showCredentialError() is LoginSuccess -> startActivity<DashboardActivity>() is OpenForgotPassword -> startActivity<ForgotPasswordFlowActivity>() }a }) VIEW
  13. VIEW viewModel.viewStateRender().observe(this, Observer { when (it) { is TwoFaNeeded ->

    displayCodeInput() is WrongCredentials -> showCredentialError() is LoginSuccess -> startActivity<DashboardActivity>() is OpenForgotPassword -> startActivity<ForgotPasswordFlowActivity>() }a })
  14. viewModel.viewStateRender().observe(this, Observer { when (it) { is TwoFaNeeded -> displayCodeInput()

    is WrongCredentials -> showCredentialError() is LoginSuccess -> startActivity<DashboardActivity>() is OpenForgotPassword -> startActivity<ForgotPasswordFlowActivity>() }a }) VIEW
  15. viewModel.viewStateRender().observe(this, Observer { when (it) { is TwoFaNeeded -> displayCodeInput()

    is WrongCredentials -> showCredentialError() is LoginSuccess -> startActivity<DashboardActivity>() is OpenForgotPassword -> startActivity<ForgotPasswordFlowActivity>() }a }) VIEW
  16. viewModel.viewStateRender().observe(this, Observer { when (it) { is TwoFaNeeded -> displayCodeInput()

    is WrongCredentials -> showCredentialError() is LoginSuccess -> startActivity<DashboardActivity>() is OpenForgotPassword -> startActivity<ForgotPasswordFlowActivity>() }a }) VIEW
  17. fun action(action: LoginAction) { when (action) { is CheckCredentials ->

    verifyCredentials(action.username, action.password) is ForgotPassword -> viewStateLiveData.postValue(OpenForgotPassword()) }a }b ACTION
  18. fun action(action: LoginAction) { when (action) { is CheckCredentials ->

    verifyCredentials(action.username, action.password) is ForgotPassword -> viewStateLiveData.postValue(OpenForgotPassword()) }a }b ACTION
  19. fun action(action: LoginAction) { when (action) { is CheckCredentials ->

    verifyCredentials(action.username, action.password) is ForgotPassword -> viewStateLiveData.postValue(OpenForgotPassword()) }a }b ACTION
  20. fun action(action: LoginAction) { when (action) { is CheckCredentials ->

    verifyCredentials(action.username, action.password) is ForgotPassword -> viewStateLiveData.postValue(OpenForgotPassword()) }a }b ACTION
  21. private fun verifyCredentials(username: String, password: String) { authInteractor.execute(LoginData(user = username,

    password = password)) .subscribe(object : ErrorHandlingSingleObserver<AuthResponse>() { override fun onSuccess(response: AuthResponse) { viewStateLiveData.postValue(LoginSuccess()) }a override fun onErrorResponse(error: ErrorResponse) { when (error.errorCode) { ErrorCode.UserDisabled -> commonStateLiveData.value = ErrorMessage(R.string.error_user_disabled) ErrorCode.IncorrectCredentials -> viewStateLiveData.postValue(WrongCredentials()) }b }c }) }d
  22. private fun verifyCredentials(username: String, password: String) { authInteractor.execute(LoginData(user = username,

    password = password)) .subscribe(object : ErrorHandlingSingleObserver<AuthResponse>() { override fun onSuccess(response: AuthResponse) { viewStateLiveData.postValue(LoginSuccess()) }a }) }d
  23. private fun verifyCredentials(username: String, password: String) { authInteractor.execute(LoginData(user = username,

    password = password)) .subscribe(object : ErrorHandlingSingleObserver<AuthResponse>() { override fun onSuccess(response: AuthResponse) { viewStateLiveData.postValue(LoginSuccess()) }a }) }d
  24. private fun verifyCredentials(username: String, password: String) { authInteractor.execute(LoginData(user = username,

    password = password)) .subscribe(object : ErrorHandlingSingleObserver<AuthResponse>() { override fun onSuccess(response: AuthResponse) { viewStateLiveData.postValue(LoginSuccess()) }aA }) }d
  25. private fun verifyCredentials(username: String, password: String) { authInteractor.execute(LoginData(user = username,

    password = password)) .subscribe(object : ErrorHandlingSingleObserver<AuthResponse>() { override fun onSuccess(response: AuthResponse) { viewStateLiveData.postValue(LoginSuccess()) }aA }) }d
  26. private fun verifyCredentials(username: String, password: String) { authInteractor.execute(LoginData(user = username,

    password = password)) .subscribe(object : ErrorHandlingSingleObserver<AuthResponse>() { override fun onErrorResponse(error: ErrorResponse) { when (error.errorCode) { ErrorCode.UserDisabled -> commonStateLiveData.value = ErrorMessage(R.string.error_user_disabled) ErrorCode.IncorrectCredentials -> viewStateLiveData.postValue(WrongCredentials()) }b }c }) }d
  27. private fun verifyCredentials(username: String, password: String) { authInteractor.execute(LoginData(user = username,

    password = password)) .subscribe(object : ErrorHandlingSingleObserver<AuthResponse>() { override fun onErrorResponse(error: ErrorResponse) { when (error.errorCode) { ErrorCode.UserDisabled -> commonStateLiveData.value = ErrorMessage(R.string.error_user_disabled) ErrorCode.IncorrectCredentials -> viewStateLiveData.postValue(WrongCredentials()) }b }c }) }d
  28. private fun verifyCredentials(username: String, password: String) { authInteractor.execute(LoginData(user = username,

    password = password)) .subscribe(object : ErrorHandlingSingleObserver<AuthResponse>() { override fun onErrorResponse(error: ErrorResponse) { when (error.errorCode) { ErrorCode.UserDisabled -> commonStateLiveData.value = ErrorMessage(R.string.error_user_disabled) ErrorCode.IncorrectCredentials -> viewStateLiveData.postValue(WrongCredentials()) }b }c }) }d
  29. private fun verifyCredentials(username: String, password: String) { authInteractor.execute(LoginData(user = username,

    password = password)) .subscribe(object : ErrorHandlingSingleObserver<AuthResponse>() { override fun onErrorResponse(error: ErrorResponse) { when (error.errorCode) { ErrorCode.UserDisabled -> commonStateLiveData.value = ErrorMessage(R.string.error_user_disabled) ErrorCode.IncorrectCredentials -> viewStateLiveData.postValue(WrongCredentials()) }b }c }) }d
  30. private fun verifyCredentials(username: String, password: String) { authInteractor.execute(LoginData(user = username,

    password = password)) .subscribe(object : ErrorHandlingSingleObserver<AuthResponse>() { override fun onErrorResponse(error: ErrorResponse) { when (error.errorCode) { ErrorCode.UserDisabled -> commonStateLiveData.value = ErrorMessage(R.string.error_user_disabled) ErrorCode.IncorrectCredentials -> viewStateLiveData.postValue(WrongCredentials()) }b }c }) }d
  31. sealed class LoadingState sealed class ErrorState data class CommonState(a val

    loadingState: LoadingState, val errorState: ErrorState )b open class ViewState<State>(c var viewState: State? = null, var commonState: CommonState = IDLE_STATE )d
  32. sealed class LoadingState sealed class ErrorState data class CommonState(a val

    loadingState: LoadingState, val errorState: ErrorState )b open class ViewState<State>(c var viewState: State? = null, var commonState: CommonState = IDLE_STATE )d
  33. sealed class LoadingState sealed class ErrorState data class CommonState(a val

    loadingState: LoadingState, val errorState: ErrorState )b open class ViewState<State>(c var viewState: State? = null, var commonState: CommonState = IDLE_STATE )d
  34. sealed class LoadingState sealed class ErrorState data class CommonState(a val

    loadingState: LoadingState, val errorState: ErrorState )b open class ViewState<State>(c var viewState: State? = null, var commonState: CommonState = IDLE_STATE )d
  35. sealed class LoadingState sealed class ErrorState data class CommonState(a val

    loadingState: LoadingState, val errorState: ErrorState )b open class ViewState<State>(c var viewState: State? = null, var commonState: CommonState = IDLE_STATE )d
  36. protected var viewState: State? = null get() = mutableLiveData.value?.viewState ?:

    field set(value) { val viewState = mutableLiveData.value if (viewState == null) { mutableLiveData.postValue(ViewState(viewState = value)) } else { mutableLiveData.value?.viewState = value mutableLiveData.post() }a }b
  37. protected var viewState: State? = null get() = mutableLiveData.value?.viewState ?:

    field set(value) { val viewState = mutableLiveData.value if (viewState == null) { mutableLiveData.postValue(ViewState(viewState = value)) } else { mutableLiveData.value?.viewState = value mutableLiveData.post() }a }b
  38. protected var viewState: State? = null get() = mutableLiveData.value?.viewState ?:

    field set(value) { val viewState = mutableLiveData.value if (viewState == null) { mutableLiveData.postValue(ViewState(viewState = value)) } else { mutableLiveData.value?.viewState = value mutableLiveData.post() }a }b
  39. protected var commonState: CommonState = IDLE_STATE get() = mutableLiveData.value?.commonState ?:

    field set(value) { val viewState = mutableLiveData.value if (viewState == null) { mutableLiveData.postValue(ViewState(commonState = value)) } else { mutableLiveData.value?.commonState = value mutableLiveData.post() } }
  40. protected var commonState: CommonState = IDLE_STATE get() = mutableLiveData.value?.commonState ?:

    field set(value) { val viewState = mutableLiveData.value if (viewState == null) { mutableLiveData.postValue(ViewState(commonState = value)) } else { mutableLiveData.value?.commonState = value mutableLiveData.post() } }
  41. protected var commonState: CommonState = IDLE_STATE get() = mutableLiveData.value?.commonState ?:

    field set(value) { val viewState = mutableLiveData.value if (viewState == null) { mutableLiveData.postValue(ViewState(commonState = value)) } else { mutableLiveData.value?.commonState = value mutableLiveData.post() } }
  42. fun <State> LiveData<ViewState<State>>.render(baseActivity: BaseActivity, action: (State) -> Unit) { observe(baseActivity,

    ViewModelObserver(baseActivity, action)) } class ViewModelObserver<T>(val baseActivity: RenderComponent, val action: (T) -> Unit) : Observer<ViewState<T>> { private var lastState: T? = null private var lastCommonState: CommonState? = null override fun onChanged(t: ViewState<T>?) { if (t == null) { Timber.e("LiveData.value is [NULL]") return } val state = t.viewState if (t.commonState != lastCommonState) { baseActivity.handleBaseState(t.commonState) lastCommonState = t.commonState } if (state != null && state != lastState) { lastState = state action(state) } } }
  43. fun <State> LiveData<ViewState<State>>.render(baseActivity: BaseActivity, action: (State) -> Unit) { observe(baseActivity,

    ViewModelObserver(baseActivity, action)) } class ViewModelObserver<T>(val baseActivity: RenderComponent, val action: (T) -> Unit) : Observer<ViewState<T>> { private var lastState: T? = null private var lastCommonState: CommonState? = null override fun onChanged(t: ViewState<T>?) { if (t == null) { Timber.e("LiveData.value is [NULL]") return } val state = t.viewState if (t.commonState != lastCommonState) { baseActivity.handleBaseState(t.commonState) lastCommonState = t.commonState } if (state != null && state != lastState) { lastState = state action(state) } } }
  44. fun <State> LiveData<ViewState<State>>.render(baseActivity: BaseActivity, action: (State) -> Unit) { observe(baseActivity,

    ViewModelObserver(baseActivity, action)) } class ViewModelObserver<T>(val baseActivity: RenderComponent, val action: (T) -> Unit) : Observer<ViewState<T>> { private var lastState: T? = null private var lastCommonState: CommonState? = null override fun onChanged(t: ViewState<T>?) { if (t == null) { Timber.e("LiveData.value is [NULL]") return } val state = t.viewState if (t.commonState != lastCommonState) { baseActivity.handleBaseState(t.commonState) lastCommonState = t.commonState } if (state != null && state != lastState) { lastState = state action(state) } } }
  45. fun <State> LiveData<ViewState<State>>.render(baseActivity: BaseActivity, action: (State) -> Unit) { observe(baseActivity,

    ViewModelObserver(baseActivity, action)) } class ViewModelObserver<T>(val baseActivity: RenderComponent, val action: (T) -> Unit) : Observer<ViewState<T>> { private var lastState: T? = null private var lastCommonState: CommonState? = null override fun onChanged(t: ViewState<T>?) { if (t == null) { Timber.e("LiveData.value is [NULL]") return } val state = t.viewState if (t.commonState != lastCommonState) { baseActivity.handleBaseState(t.commonState) lastCommonState = t.commonState } if (state != null && state != lastState) { lastState = state action(state) } } }
  46. MVP VS MVVM interface LoginView : BaseMvp.View { fun updatesFound(String

    version) fun loginSuccess() } sealed class LoginState class LoginUpdatesFound(val version: String) : LoginState() class LoginSuccess : LoginState()
  47. MVP VS MVVM class BaseMvp { interface View { fun

    showProgress() fun hideProgress() fun showError(title: String, message: String) } } sealed class CommonState class CommonLoading() : CommonState() class CommonIdle : CommonState() class CommonError(val title: String, val message: String) : CommonState()
  48. ORIENTATION CHANGE • ViewModel is not destroyed • State preserved

    without SaveInstance • Latest known state rendered