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

Leveraging Sealed Classes in Kotlin

Leveraging Sealed Classes in Kotlin

Sealed classes are much more than fancy enums. They are powerful composite types which can be useful in many ways. Common problems like exception handling and state representation become easier to manage with sealed classes. We will contrast traditional solutions, with modern ones using Kotlin.

Sanchita Agarwal

June 22, 2019
Tweet

Other Decks in Technology

Transcript

  1. fun login(username: String, password: String) { //TODO Make login request

    //TODO Parse login response //TODO Save user data //TODO Go to home screen }
  2. fun login(username: String, password: String) { //TODO Make login request

    //TODO Parse login response //TODO Save user data //TODO Go to home screen }
  3. fun login(username: String, password: String) { //TODO Make login request

    //TODO Parse login response //TODO Save user data //TODO Go to home screen }
  4. fun login(username: String, password: String) { //TODO Make login request

    //TODO Parse login response //TODO Save user data //TODO Go to home screen }
  5. fun login(username: String, password: String) { //TODO Make login request

    //TODO Parse login response //TODO Save user data //TODO Go to home screen }
  6. fun login(username: String, password: String) { val response = loginCall

    .execute(username, password) //TODO Parse login response //TODO Save user state //TODO Go to home screen }
  7. fun login(username: String, password: String) { val response = loginCall

    .execute(username, password) if (response.isSuccessful) { } //TODO Save user state //TODO Go to home screen }
  8. fun login(username: String, password: String) { val response = loginCall

    .execute(username, password) val data = response.data if (response.isSuccessful) { saveToken(data.authToken) } //TODO Go to home screen }
  9. fun login(username: String, password: String) { val response = loginCall

    .execute(username, password) val data = response.data if (response.isSuccessful) { saveToken(data.authToken) showHomeScreen() } }
  10. fun login(username: String, password: String) { val response = loginCall

    .execute(username, password) val data = response.data if (response.isSuccessful) { saveToken(data.authToken) showHomeScreen() } }
  11. fun login(username: String, password: String) { val response = loginCall

    .execute(username, password) val data = response.data if (response.isSuccessful) { saveToken(data.authToken) showHomeScreen() } }
  12. fun login(username: String, password: String) { val response = loginCall

    .execute(username, password) val data = response.data if (response.isSuccessful) { saveToken(data.authToken) showHomeScreen() } else { showErrorDialog("Our servers are down") } }
  13. fun login(username: String, password: String) { val response = loginCall

    .execute(username, password) val data = response.data if (response.isSuccessful) { saveToken(data.authToken) showHomeScreen() } else { showErrorDialog("Our servers are down") } }
  14. try { val response = loginCall .execute(username, password) val data

    = response.data if (response.isSuccessful) { saveToken(data.authToken) showHomeScreen() } else { showErrorDialog("Our servers are down!") } } catch (error: IOException) { Log.e("Exception!", error.stackTrace.toString()) }
  15. try { val response = loginCall .execute(username, password) val data

    = response.data if (response.isSuccessful) { saveToken(data.authToken) showHomeScreen() } else { showErrorDialog("Our servers are down!") } } catch (error: IOException) { Log.e("Exception!", error.stackTrace.toString()) }
  16. try { val response = apiCall.execute() val data = response.data

    if (response.isSuccessful) { saveToken(data.authToken) showHomeScreen() } else { when (response.code()) { 408 -> showErrorDialog("Connection issue") 500 -> showErrorDialog("Our servers are down!") else -> showErrorDialog("Something went wrong") } } } catch (error: IOException) { Log.e("Exception!", error.stackTrace.toString()) }
  17. try { val response = apiCall.execute() val data = response.data

    if (response.isSuccessful) { saveToken(data.authToken) showHomeScreen() } else { when (response.code()) { 408 -> showErrorDialog("Connection issue") 500 -> showErrorDialog("Our servers are down!") else -> showErrorDialog("Something went wrong") } } } catch (error: IOException) { Log.e("Exception!", error.stackTrace.toString()) }
  18. try { val response = apiCall.execute() val data = response.data

    if (response.isSuccessful) { if (data.authToken != null) { saveToken(data.authToken) showHomeScreen() } else { showErrorDialog(data.errorMessage) } } else { when (response.code()) { 408 -> showErrorDialog("Connection issue") 500 -> showErrorDialog("Our servers are down!") else -> showErrorDialog("Something went wrong") } } } catch (error: IOException) { Log.e("Exception!", error.stackTrace.toString()) }
  19. try { val response = apiCall.execute() val data = response.data

    if (response.isSuccessful) { if (data.authToken != null) { saveToken(data.authToken) showHomeScreen() } else { showErrorDialog(data.errorMessage) } } else { when (response.code()) { 408 -> showErrorDialog("Connection issue") 500 -> showErrorDialog("Our servers are down!") else -> showErrorDialog("Something went wrong") } } } catch (error: IOException) { Log.e("Exception!", error.stackTrace.toString()) }
  20. try { val response = apiCall.execute() val data = response.data

    if (response.isSuccessful) { if (data.authToken != null) { saveToken(data.authToken) showHomeScreen() } else { showErrorDialog(data.errorMessage) } } else { when (response.code()) { 408 -> showErrorDialog("Connection issue") 500 -> showErrorDialog("Our servers are down!") else -> showErrorDialog("Something went wrong") } } } catch (error: IOException) { Log.e("Exception!", error.stackTrace.toString()) }
  21. fun login(username: String, password: String) { //TODO Make login request

    //TODO Parse login response //TODO Save user data //TODO Go to home screen } Multiple responsibilities
  22. fun login(username: String, password: String) { //TODO Make login request

    //TODO Parse login response //TODO Save user data //TODO Go to home screen } Separating concerns
  23. try { val response = apiCall.execute() val data = response.data

    if (response.isSuccessful) { if (data.authToken != null) { saveToken(data.authToken) showHomeScreen() } else { showErrorDialog(data.errorMessage) } } else { when (response.code()) { 408 -> showErrorDialog("Connection issue") 500 -> showErrorDialog("Our servers are down!") else -> showErrorDialog("Something went wrong") } } } catch (error: IOException) { Log.e("Exception!", error.stackTrace.toString()) }
  24. try { val response = apiCall.execute() val data = response.data

    if (response.isSuccessful) { if (data.authToken != null) { saveToken(data.authToken) showHomeScreen() } else { LoginFailure(data.errorMessage) } } else { when (response.code()) { 400 -> LoginFailure("Connection issue") 500 -> LoginFailure("Our servers are down!") else -> LoginFailure("Something went wrong") } } } catch (error: IOException) { LoginFailure("Exception occurred") }
  25. try { val response = apiCall.execute() val data = response.data

    if (response.isSuccessful) { if (data.authToken != null) { saveToken(data.authToken) showHomeScreen() } else { LoginFailure(data.errorMessage) } } else { when (response.code()) { 408 -> LoginFailure("Connection issue") 500 -> LoginFailure("Our servers are down!") else -> LoginFailure("Something went wrong") } } } catch (error: IOException) { LoginFailure("Exception occurred") }
  26. try { val response = apiCall.execute() val data = response.data

    if (response.isSuccessful) { if (data.authToken != null) { LoginSuccess(data.authToken) } else { LoginFailure(data.errorMessage) } } else { when (response.code()) { 408 -> LoginFailure("Connection issue") 500 -> LoginFailure("Our servers are down!") else -> LoginFailure("Something went wrong") } } } catch (error: IOException) { LoginFailure("Exception occurred") }
  27. return try { val response = apiCall.execute() val data =

    response.data if (response.isSuccessful) { if (data.authToken != null) { Success(data.authToken) } else { Failure(data.errorMessage) } } else { when (response.code()) { 408 -> Failure("Connection issue") 500 -> Failure("Our servers are down!") else -> Failure("Something went wrong") } } } catch (error: IOException) { Failure("Exception occurred") }
  28. return try { val response = apiCall.execute() val data =

    response.data if (response.isSuccessful) { if (data.authToken != null) { Success(data.authToken) } else { Failure(data.errorMessage) } } else { when (response.code()) { 408 -> Failure("Connection issue") 500 -> Failure("Our servers are down!") else -> Failure("Something went wrong") } } } catch (error: IOException) { Failure(“Exception occurred”) }
  29. return try { val response = apiCall.execute() val data =

    response.data if (response.isSuccessful) { if (data.authToken != null) { Success(data.authToken) } else { Failure(data.errorMessage) } } else { when (response.code()) { 408 -> Failure("Connection issue") 500 -> Failure("Our servers are down!") else -> Failure("Something went wrong") } } } catch (error: IOException) { Failure("Exception occurred") }
  30. Failure(data.errorMessage) -> INVALID_USER Failure("Connection issue") -> CONNECTION_ERROR
 Failure("Our servers are

    down!") -> SERVER_ERROR Failure("Something went wrong") -> UNEXPECTED_ERROR Failure("Exception occurred") -> EXCEPTION_OCCURRED
  31. fun login(username: String, password: String): LoginResult { return try {

    val response = apiCall.execute() val data = response.data if (response.isSuccessful) { if (data.authToken != null) Success(data.authToken) else INVALID_USER } else { when (response.code()) { 408 -> CONNECTION_ERROR 500 -> SERVER_ERROR else -> UNEXPECTED_ERROR } } } catch (error: IOException) { EXCEPTION_OCCURRED } }
  32. fun login(username: String, password: String): LoginResult { return try {

    val response = apiCall.execute() val data = response.data if (response.isSuccessful) { if (data.authToken != null) SUCCESS(data.authToken) else INVALID_USER } else { when (response.code()) { 408 -> CONNECTION_ERROR 500 -> SERVER_ERROR else -> UNEXPECTED_ERROR } } } catch (error: IOException) { EXCEPTION_OCCURRED } }
  33. Sealed classes • Represent restricted class hierarchies • A value

    can have one of the types from a limited set, but cannot have any other type.
  34. sealed class LoginResult { class Success : LoginResult() class InvalidUser

    : LoginResult() class ConnectionError : LoginResult() class ServerError : LoginResult() class UnexpectedError : LoginResult() class NetworkException : LoginResult() }
  35. sealed class LoginResult { class Success(val data: String) : LoginResult()

    class InvalidUser : LoginResult() class ConnectionError : LoginResult() class ServerError : LoginResult() class UnexpectedError : LoginResult() class NetworkException : LoginResult() }
  36. sealed class LoginResult { data class Success(val data: String) :

    LoginResult() class InvalidUser : LoginResult() class ConnectionError : LoginResult() class ServerError : LoginResult() class UnexpectedError : LoginResult() class NetworkException : LoginResult() }
  37. sealed class LoginResult { data class Success(val data: String) :

    LoginResult() class InvalidUser : LoginResult() class ConnectionError : LoginResult() class ServerError : LoginResult() class UnexpectedError : LoginResult() class NetworkException : LoginResult() }
  38. sealed class LoginResult { data class Success(val data: String) :

    LoginResult() object InvalidUser : LoginResult() object ConnectionError : LoginResult() class ServerError : LoginResult() class UnexpectedError : LoginResult() class NetworkException : LoginResult() }
  39. sealed class LoginResult { data class Success(val data: String) :

    LoginResult() object InvalidUser : LoginResult() object ConnectionError : LoginResult() class ServerError : LoginResult() class UnexpectedError : LoginResult() class NetworkException : LoginResult() }
  40. sealed class LoginResult { data class Success(val data: String) :

    LoginResult() object InvalidUser : LoginResult() object ConnectionError : LoginResult() data class ServerError(val error: String) : LoginResult() class UnexpectedError : LoginResult() class NetworkException : LoginResult() }
  41. sealed class LoginResult { data class Success(val data: String) :

    LoginResult() object InvalidUser : LoginResult() object ConnectionError : LoginResult() data class ServerError(val error: String) : LoginResult() class UnexpectedError : LoginResult() class NetworkException : LoginResult() }
  42. sealed class LoginResult { data class Success(val data: String) :

    LoginResult() object InvalidUser : LoginResult() object ConnectionError : LoginResult() data class ServerError(val error: String) : LoginResult() data class UnexpectedError(val code: Int) : LoginResult() class NetworkException : LoginResult() }
  43. sealed class LoginResult { data class Success(val data: String) :

    LoginResult() object InvalidUser : LoginResult() object ConnectionError : LoginResult() data class ServerError(val error: String) : LoginResult() data class UnexpectedError(val code: Int) : LoginResult() class NetworkException : LoginResult() }
  44. sealed class LoginResult { data class Success(val data: String) :

    LoginResult() object InvalidUser : LoginResult() object ConnectionError : LoginResult() data class ServerError(val error: String) : LoginResult() data class UnexpectedError(val code: Int) : LoginResult() class NetworkException(val cause: Exception) : LoginResult() }
  45. sealed class LoginResult { data class Success(val data: String) :

    LoginResult() object InvalidUser : LoginResult() object ConnectionError : LoginResult() data class ServerError(val error: String) : LoginResult() data class UnexpectedError(val code: Int) : LoginResult() class NetworkException(val cause: Exception) : LoginResult() }
  46. fun login(username: String, password: String): LoginResult { return try {

    val response = apiCall.execute() val data = response.data if (response.isSuccessful) { if (data.authToken != null) Success(data.authToken) else InvalidUser } else { when (response.code()) { 408 -> ConnectionError 500 -> ServerError(response.errorMessage) else -> UnexpectedError(response.code()) } } } catch (e: Exception) { NetworkException(e) } }
  47. sealed class LoginResult { data class Success(val data: String) :

    LoginResult() object InvalidUser : LoginResult() object ConnectionError : LoginResult() data class ServerError(val error: String) : LoginResult() data class UnexpectedError(val code: Int) : LoginResult() class NetworkException(val cause: Exception) : LoginResult() }
  48. sealed class LoginResult { data class Success(val data: String) :

    LoginResult() object InvalidUser : LoginResult() data class UserLocked(val duration: Long): LoginResult() object ConnectionError : LoginResult() data class ServerError(val error: String) : LoginResult() data class UnexpectedError(val code: Int) : LoginResult() class NetworkException(val cause: Exception) : LoginResult() }
  49. when (response) { is LoginResult.Success -> { saveToken(response.data.accessToken) showHomeScreen() }

    is LoginResult.InvalidUser -> { showErrorDialog("User not valid") } //and so on… }
  50. when (response) { is LoginResult.Success -> { saveToken(response.data.accessToken) showHomeScreen() }

    is LoginResult.ConnectionError -> { showErrorDialog("No internet connection!") } //and so on… }
  51. 1. All your possible outcomes in one place 2. Helps

    you think of all the cases 3. Easier to perform different actions Advantages of Sealed class
  52. fun login(username: String, password: String): LoginResult { return try {

    val response = apiCall.execute() val data = response.data if (response.isSuccessful) { if (data.authToken != null) Success(data.authToken) else InvalidUser } else { when (response.code()) { 408 -> ConnectionError 500 -> ServerError(response.errorMessage) else -> UnexpectedError(response.code()) } } } catch (error: IOException) { NetworkException(error) } }
  53. Failures Failure is the inability of a system to perform

    required function according to its specification.
  54. Exception An exception occurs during the execution of a program,

    that disrupts the normal flow of the program's instructions.
  55. sealed class LoginResult { data class Success(val data: String) :

    LoginResult() object InvalidUser : LoginResult() object ConnectionError : LoginResult() data class ServerError(val error: String) : LoginResult() data class UnexpectedError(val code: Int) : LoginResult() class NetworkException(val cause: Exception) : LoginResult() }
  56. sealed class LoginResult { data class Success(val data: String): LoginResult()

    object InvalidUser: LoginResult() object ConnectionError: LoginResult() data class ServerError(val error: String): LoginResult() data class UnexpectedError(val code: Int): LoginResult() }
  57. sealed class ApiExceptions(val cause: Throwable) { class NetworkRelated(cause: Throwable): ApiExceptions(cause)

    class Unexpected(cause: Throwable): NetworkExceptions(cause) class Unauthorised(cause: Throwable): NetworkExceptions(cause) }
  58. sealed class ApiExceptions(val cause: Throwable) { class NetworkRelated(cause: Throwable): ApiExceptions(cause)

    class Unexpected(cause: Throwable): ApiExceptions(cause) class Unauthorised(cause: Throwable): ApiExceptions(cause) }
  59. sealed class ApiExceptions(val cause: Throwable) { class NetworkRelated(cause: Throwable): ApiExceptions(cause)

    class Unexpected(cause: Throwable): ApiExceptions(cause) class Unauthorised(cause: Throwable): ApiExceptions(cause) }
  60. fun searchMovie(movieTitle: String) { //TODO Show loading bar //TODO Search

    movie name from IMDb //TODO Hide loading bar //TODO Show movie results or, //TODO Show error }
  61. fun searchMovie(movieTitle: String) { //TODO Show loading bar //TODO Search

    movie name from IMDb //TODO Hide loading bar //TODO Show movie results or, //TODO Show error }
  62. fun searchMovie(movieTitle: String) { //TODO Show loading bar //TODO Search

    movie name from IMDb //TODO Hide loading bar //TODO Show movie results or, //TODO Show error }
  63. fun searchMovie(movieTitle: String) { //TODO Show loading bar //TODO Search

    movie name from IMDb //TODO Hide loading bar //TODO Show movie results or, //TODO Show error }
  64. fun searchMovie(movieTitle: String) { //TODO Show loading bar //TODO Search

    movie name from IMDb //TODO Hide loading bar //TODO Show movie results or, //TODO Show error }
  65. fun searchMovie(movieTitle: String) { //TODO Show loading bar //TODO Search

    movie name from IMDb //TODO Hide loading bar //TODO Show movie results or, //TODO Show error }
  66. fun searchMovie(movieTitle: String) { showLoadingBar() //TODO Search movie name from

    IMDb //TODO Hide loading bar //TODO Show movie results or, //TODO Show error }
  67. fun searchMovie(movieTitle: String) { showLoadingBar() search(movieTitle) .onSuccess { data: List<Movie>

    -> //TODO Hide loading bar //TODO Show movie results } .onError() //TODO Hide loading bar //TODO Show error }
  68. fun searchMovie(movieTitle: String) { showLoadingBar() search(movieTitle) .onSuccess { data: List<Movie>

    -> hideLoadingBar() showData(data) } .onError() //TODO Hide loading bar //TODO Show error }
  69. fun searchMovie(movieTitle: String) { showLoadingBar() search(movieTitle) .onSuccess { data: List<Movie>

    -> hideLoadingBar() showData(data) } .onError { error: String -> //TODO Hide loading bar //TODO Show error } }
  70. fun searchMovie(movieTitle: String) { showLoadingBar() search(movieTitle) .onSuccess { data: List<Movie>

    -> hideLoadingBar() showData(data) } .onError { error: String -> hideLoadingBar() showError(error) } }
  71. fun searchMovie(movieTitle: String) { showLoadingBar() search(movieTitle) .onSuccess { data: List<Movie>

    -> hideLoadingBar() showData(data) } .onError { error: String -> hideLoadingBar() showError(error) } }
  72. fun searchMovie(movieTitle: String) { showLoadingBar() search(movieTitle) .onSuccess { data: List<Movie>

    -> hideLoadingBar() showData(data) } .onError { error: String -> hideLoadingBar() showError(error) } }
  73. fun searchMovie(movieTitle: String) { showLoadingBar() search(movieTitle) .onSuccess { data: List<Movie>

    -> hideLoadingBar() showData(data) } .onError { error: String -> hideLoadingBar() showError(error) } }
  74. fun refresh(movieTitle: String) { showLoadingBar() search(movieTitle) .onSuccess { data: List<Movie>

    -> hideLoadingBar() showData(data) } .onError { error: String -> hideLoadingBar() showError(error) } }
  75. fun refresh(movieTitle: String) { showRefreshIcon() search(movieTitle) .onSuccess { data: List<Movie>

    -> hideRefreshIcon() showData(data) } .onError { error: String -> hideRefreshIcon() showError(error) } }
  76. fun searchMovie(movieTitle: String) { hideError() showLoadingBar() search(movieTitle) .onSuccess { data:

    List<Movie> -> hideLoadingBar() val sortedData = data.sortedBy { it.date } showData(sortedData) } .onError { error: String -> hideLoadingBar() showError(error) } }
  77. fun refresh(movieTitle: String) { showRefreshIcon() search(movieTitle) .onSuccess { data: List<Movie>

    -> hideRefreshIcon() val sortedData = data.sortedBy { it.date } showData(sortedData) } .onError { error: String -> hideRefreshIcon() showError(error) } }
  78. fun searchMovie(movieTitle: String) { showLoadingBar() search(movieTitle) .onSuccess { data: List<Movie>

    -> hideLoadingBar() showData(data) } .onError { error: String -> hideLoadingBar() showError(error) } }
  79. fun searchMovie(movieTitle: String) { hideError() showLoadingBar() search(movieTitle) .onSuccess { data:

    List<Movie> -> hideLoadingBar() showData(data) } .onError { error: String -> hideLoadingBar() showError(error) } }
  80. Possible problem • Business logic mixed with UI • Hard

    to update/maintain code • Unpredictable UI states
  81. Possible problem • Business logic mixed with UI • Hard

    to update/maintain code • Unpredictable UI states
  82. Possible problem • Business logic mixed with UI • Hard

    to update/maintain code • Unpredictable UI states
  83. fun searchMovie() { //TODO Read movie name -> UserEvents //TODO

    Handle search button clicks -> UserEvents //TODO Show loading bar //TODO Search movie name from IMDb //TODO Hide loading bar //TODO Show movie results or, //TODO Show error }
  84. fun searchMovie() { //TODO Read movie name -> UserEvents //TODO

    Handle search button clicks -> UserEvents //TODO Show loading bar -> View state //TODO Search movie name from IMDb //TODO Hide loading bar -> View state //TODO Show movie results -> View state //TODO Show error -> View state }
  85. fun searchMovie() { //TODO Read movie name -> UserEvents //TODO

    Handle search button clicks -> UserEvents //TODO Show loading bar -> View state //TODO Search movie name from IMDb //TODO Hide loading bar -> View state //TODO Show movie results -> View state //TODO Show error -> View state }
  86. fun searchMovie() { //TODO Read movie name -> UserEvents //TODO

    Handle search button clicks -> UserEvents //TODO Show loading bar -> View state //TODO Search movie name from IMDb -> Action/Logic //TODO Hide loading bar -> View state //TODO Show movie results -> View state //TODO Show error -> View state }
  87. fun searchMovie() { //TODO Read movie name -> UserEvents //TODO

    Handle search button clicks -> UserEvents //TODO Show loading bar //TODO Search movie name from IMDb //TODO Hide loading bar //TODO Show movie results or, //TODO Show error }
  88. object SearchInput : UserEvent data class SearchClicked(val searchText: String) :

    UserEvent data class PulledToRefresh(val searchText: String) : UserEvent
  89. object SearchInput : UserEvent data class SearchClicked(val searchText: String) :

    UserEvent data class PulledToRefresh(val searchText: String) : UserEvent
  90. fun searchMovie() { //TODO Read movie name -> UserEvents //TODO

    Handle search button clicks -> UserEvents //TODO Show loading/refresh bar -> View state //TODO Search movie name from IMDb //TODO Hide loading/refresh bar -> View state //TODO Show movie results -> View state //TODO Show error -> View state }
  91. data class SearchViewState( val showLoader: Boolean = false, val showRefresh:

    Boolean = false, val movies: List<Movie> = emptyList(), val error: String = "" )
  92. data class SearchViewState( val showLoader: Boolean = false, val showRefresh:

    Boolean = false, val movies: List<Movie> = emptyList(), val error: String = "" )
  93. fun searchMovie() { //TODO Read movie name -> UserEvents //TODO

    Handle search button clicks -> UserEvents //TODO Show loading bar -> Search View changes //TODO Search movie name from IMDb -> Action/Logic //TODO Hide loading bar -> Search View changes //TODO Show movie results -> Search View changes //TODO Show error -> Search View changes }
  94. fun actionOnEvent(event: UserEvent) { when (event) { is SearchButtonClicked ->

    //TODO Show loader //TODO Search is PulledToRefresh -> //TODO Show refresh //TODO Search } }
  95. fun actionOnEvent(event: UserEvent) { when (event) { is SearchButtonClicked ->

    { render(SearchViewState(showLoader = true)) //TODO Search } is PulledToRefresh -> //TODO Show refresh //TODO Search } }
  96. fun actionOnEvent(event: UserEvent) { when (event) { is SearchButtonClicked ->

    { render(SearchViewState(showLoader = true)) render(searchAction(event.searchText)) } is PulledToRefresh -> //TODO Show refresh //TODO Search } }
  97. fun actionOnEvent(event: UserEvent) { when (event) { is SearchButtonClicked ->

    { render(SearchViewState(showLoader = true)) render(searchAction(event.searchText)) } is PulledToRefresh -> //TODO Show refresh //TODO Search } }
  98. fun actionOnEvent(event: UserEvent) { when (event) { is SearchButtonClicked ->

    { render(SearchViewState(showLoader = true)) render(searchAction(event.searchText)) } is PulledToRefresh -> render(SearchViewState(showRefresh = true)) //TODO Search } }
  99. fun actionOnEvent(event: UserEvent) { when (event) { is SearchButtonClicked ->

    { render(SearchViewState(showLoader = true)) render(searchAction(event.searchText)) } is PulledToRefresh -> { render(SearchViewState(showRefresh = true)) render(searchAction(event.searchText)) } } }
  100. fun searchAction(searchText: String): SearchViewState { return search(searchText) .onSuccess( { data:

    List<Movie> -> SearchViewState(movies = data) } ) .onError( { error: String -> SearchViewState(error = error) } ) }
  101. fun searchAction(searchText: String): SearchViewState { return search(searchText) .onSuccess( { data:

    List<Movie> -> SearchViewState(movies = data) } ) .onError( { error: String -> SearchViewState(error = error) } ) }
  102. fun searchAction(searchText: String): SearchViewState { return search(searchText) .onSuccess( { data:

    List<Movie> -> SearchViewState(movies = data) } ) .onError( { error: String -> SearchViewState(error = error) } ) }
  103. fun searchAction(searchText: String): SearchViewState { return search(searchText) .onSuccess( { data:

    List<Movie> -> SearchViewState(movies = data) } ) .onError( { error: String -> SearchViewState(error = error) } ) }
  104. fun actionOnEvent(event: UserEvent) { when (event) { is SearchButtonClicked ->

    { render(SearchViewState(showLoader = true)) render(searchAction(event.searchText)) } is PulledToRefresh -> { render(SearchViewState(showRefresh = true)) render(searchAction(event.searchText)) } } }
  105. Possible problem • Business logic mixed with UI • Hard

    to update/maintain code • Unpredictable UI states
  106. fun render(state: SearchViewState){ if(state.showLoader) { showLoadingBar() } else if(state.movies.isNotEmpty()){ showData(state.movies)

    } else if(state.error.isNotBlank()){ showError(state.error) } else if(state.showRefresh){ showRefreshIcon() } }
  107. fun render(state: SearchViewState){ if(state.showLoader) { showLoadingBar() } else if(state.movies.isNotEmpty()){ showData(state.movies)

    } else if(state.error.isNotBlank()){ showError(state.error) } else if(state.showRefresh){ showRefreshIcon() } }
  108. fun render(state: SearchViewState){ if(state.showLoader) { showLoadingBar() } else if(state.movies.isNotEmpty()){ showData(state.movies)

    } else if(state.error.isNotBlank()){ showError(state.error) } else if(state.showRefresh){ showRefreshIcon() } }
  109. fun searchAction(searchText: String): SearchViewState { return search(searchText) .onSuccess { data:

    List<Movie> -> SearchViewState(movies = data)} .onError { error: String -> SearchViewState(error = error) } }
  110. fun searchAction(searchText: String): SearchViewState { return search(searchText) .onSuccess { data:

    List<Movie> -> SearchViewState(movies = data, showLoader = false)} .onError { error: String -> SearchViewState(error = error) } }
  111. fun render(state: SearchViewState){ if(state.showLoader) { showLoadingBar() } else if(state.movies.isNotEmpty() &&

    !state.showLoader) { showData(state.movies) hideLoadingBar() } else if(state.error.isNotBlank()){ showError(state.error) } else if(state.pullToRefresh){ showRefreshIcon() } }
  112. Possible problem • Business logic mixed with UI • Hard

    to update/maintain code • Unpredictable UI states
  113. Possible problem • Still Business logic mixed with UI •

    Hard to update/maintain code • Still Unpredictable UI states
  114. SearchViewState(showLoader = true, error = null) SearchViewState(showLoader = false, movies

    = data) SearchViewState(showLoader = false, error = error, showRefresh = false) SearchViewState(showRefresh = true)
  115. data class SearchViewState( val showLoader: Boolean = false, val showRefresh:

    Boolean = false, val movies: List<Movie> = emptyList(), val error: String = "" )
  116. sealed class SearchViewState { object LoadingState : SearchViewState() object PullToRefreshState

    : SearchViewState() data class ErrorState(val error: String) : SearchViewState() data class DataState(val movies: List<Movie>) : SearchViewState() }
  117. sealed class SearchViewState { object LoadingState : SearchViewState() object PullToRefreshState

    : SearchViewState() data class ErrorState(val error: String) : SearchViewState() data class DataState(val movies: List<Movie>) : SearchViewState() }
  118. sealed class SearchViewState { object LoadingState : SearchViewState() object PullToRefreshState

    : SearchViewState() data class ErrorState(val error: String) : SearchViewState() data class DataState(val movies: List<Movie>) : SearchViewState() }
  119. sealed class SearchViewState { object LoadingState : SearchViewState() object PullToRefreshState

    : SearchViewState() data class ErrorState(val error: String) : SearchViewState() data class DataState(val movies: List<Movie>) : SearchViewState() }
  120. sealed class SearchViewState { object LoadingState : SearchViewState() object PullToRefreshState

    : SearchViewState() data class ErrorState(val error: String) : SearchViewState() data class DataState(val movies: List<Movie>) : SearchViewState() }
  121. fun searchAction(searchText: String): SearchViewState { return search(searchText) .onSuccess { data:

    List<Movie> -> SearchViewState( movies = data, showLoader = false ) } .onError { error: String -> SearchViewState( showLoader = false, error = error, showRefresh = false ) } }
  122. fun searchAction(searchText: String): SearchViewState { return search(searchText) .onSuccess { data:

    List<Movie> -> SearchViewState.DataState(data) } .onError { error: String -> SearchViewState.ErrorState(error) } }
  123. fun actionOnEvent(event: UserEvent) { when (event) { is SearchButtonClicked ->

    { render(SearchViewState(showLoader = true, error = null)) render(searchAction(event.searchText)) } is PulledToRefresh -> { render(SearchViewState(showRefresh = true)) render(searchAction(event.searchText)) } } }
  124. fun actionOnEvent(event: UserEvent) { when (event) { is SearchButtonClicked ->

    { render(SearchViewState.LoadingState) render(searchAction(event.searchText)) } is PulledToRefresh -> { render(SearchViewState.PullToRefreshState) render(searchAction(event.searchText)) } } }
  125. Possible problem • Business logic mixed with UI • Hard

    to update/maintain code • Still Unpredictable UI states
  126. fun render(state: SearchViewState){ if(state.showLoader && state.error.isBlank()) { showLoadingBar() hideError() }

    else if(state.movies.isNotEmpty() && !state.showLoader) { showData(state.movies) hideLoadingBar() } else if(state.error.isNotBlank() && !state.showLoader && !state.showRefresh){ showError(state.error) hideLoadingBar() hideRefreshIcon() } else if(state.showRefresh){ showRefreshIcon() } }
  127. fun render(state: SearchViewState) { when (state) { LoadingState -> showLoadingState()

    PullToRefreshState -> showRefreshState() is DataState -> showDataState(state.movies) is ErrorState -> showErrorState(state.error) } }
  128. Possible problem • Business logic mixed with UI • Hard

    to update/maintain code • Unpredictable UI states
  129. fun searchAction(searchText: String): SearchViewState { return search(searchText) .onSuccess { data:

    List<Movie> -> SearchViewState.DataState(data) } .onError { error: String -> SearchViewState.ErrorState(error) } }
  130. fun render(state: SearchViewState) { when (state) { LoadingState -> showLoadingState()

    PullToRefreshState -> showRefreshState() is DataState -> showDataState(state.movies) is ErrorState -> showErrorState(state.error) } }
  131. 1. All your possible outcomes in one place 2. Helps

    you think of all the cases 3. Easier to perform different actions 4. Mutually exclusive states 5. Keep similar and related things together Advantages of Sealed class