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

Modeling Retrofit responses with Sealed classes and Coroutines

Modeling Retrofit responses with Sealed classes and Coroutines

Modeling Retrofit responses with Sealed classes and Coroutines at Android Worldwide.

Jaewoong

April 20, 2022
Tweet

More Decks by Jaewoong

Other Decks in Programming

Transcript

  1. G E T S T R E A M . I O
    Modeling Retrofit
    Responses
    With Sealed Classes + Coroutines

    View Slide

  2. G E T S T R E A M . I O
    skydoves
    @github_skydoves
    Android Developer Advocate @ Stream
    Jaewoong Eum

    View Slide

  3. G E T S T R E A M . I O
    Architecture Network Request
    Domain
    Interactors,
    Use case
    Interfaces
    Threading
    Presentation
    States
    View
    ViewModel
    Data
    Repository
    Remote
    DataSource
    Local
    DataSource

    View Slide

  4. G E T S T R E A M . I O
    Architecture Network Request
    Domain
    Interactors,
    Use case
    Interfaces
    Threading
    Data
    Repository
    Remote
    DataSource
    Local
    DataSource
    Presentation
    States
    View
    ViewModel

    View Slide

  5. G E T S T R E A M . I O
    Architecture Network Request
    Data
    Repository
    Remote
    DataSource
    Local
    DataSource
    Response
    Success
    - body
    - headers
    - status code
    Failure
    - error body
    - headers
    - status code
    Exception
    - IOException
    - UnKnownHostException
    - SSLHandshakeException
    - …
    Error

    View Slide

  6. G E T S T R E A M . I O
    Architecture Network Request Scenario
    Domain
    Interactors,
    Use case
    Interfaces
    Threading
    Presentation
    States
    View
    ViewModel
    Data
    Repository
    Remote
    DataSource
    Local
    DataSource

    View Slide

  7. G E T S T R E A M . I O
    Architecture
    Domain
    Interactors,
    Use case
    Interfaces
    Threading
    Presentation
    States
    View
    ViewModel
    Data
    Repository
    Remote
    DataSource
    Local
    DataSource
    Success Success
    Network Request Scenario

    View Slide

  8. G E T S T R E A M . I O
    Architecture
    Domain
    Interactors,
    Use case
    Interfaces
    Threading
    Presentation
    States
    View
    ViewModel
    Data
    Repository
    Remote
    DataSource
    Local
    DataSource
    Error
    Error
    Success Success
    Network Request Scenario

    View Slide

  9. G E T S T R E A M . I O
    Architecture
    Domain
    Interactors,
    Use case
    Interfaces
    Threading
    Presentation
    States
    View
    ViewModel
    Data
    Repository
    Remote
    DataSource
    Local
    DataSource
    Error
    Error
    Exception? Exception? Exception?
    Success Success
    Network Request Scenario

    View Slide

  10. G E T S T R E A M . I O
    interface PosterService {
    @GET("DisneyPosters.json")
    suspend fun fetchPosters(): List
    }
    class PosterRemoteDataSource(
    private val posterService: PosterService
    ) {
    suspend operator fun invoke(): List = try {
    posterService.fetchPosters()
    } catch (e: HttpException) {
    // error handling
    emptyList()
    } catch (e: Throwable) {
    // error handling
    emptyList()
    }
    }
    Retrofit API calls with Coroutines

    View Slide

  11. G E T S T R E A M . I O
    Retrofit API calls with Coroutines
    interface PosterService {
    @GET("DisneyPosters.json")
    suspend fun fetchPosters(): List
    }
    class PosterRemoteDataSource(
    private val posterService: PosterService
    ) {
    suspend operator fun invoke(): List = try {
    posterService.fetchPosters()
    } catch (e: HttpException) {
    // error handling
    emptyList()
    } catch (e: Throwable) {
    // error handling
    emptyList()
    }
    }
    Problem
    ● Results are ambiguous to callers
    ● Callers don't know the exception types
    val data = posterRemoteDataSource.invoke()
    if (data.isNotEmpty()) {
    ...
    }
    Success? or Failure?

    View Slide

  12. G E T S T R E A M . I O
    Architecture Sealed Classes
    Data
    Repository
    Remote
    DataSource
    Local
    DataSource
    Response
    Success
    - body
    - headers
    - status code
    Failure
    - error body
    - headers
    - status code
    Exception
    - IOException
    - UnKnownHostException
    - SSLHandshakeException
    - …
    Error
    Sealed Classes (=Result)

    View Slide

  13. G E T S T R E A M . I O
    Architecture
    Domain
    Interactors,
    Use case
    Interfaces
    Threading
    Presentation
    States
    View
    ViewModel
    Data
    Repository
    Remote
    DataSource
    Local
    DataSource
    Error
    Error
    try-catch
    Success Success
    Network Request Scenario

    View Slide

  14. G E T S T R E A M . I O
    Modeling responses Sealed Class
    sealed class NetworkResult {
    class Success(val data: T) : NetworkResult()
    class Error(val code: Int, val message: String?) : NetworkResult()
    class Exception(val e: Throwable) : NetworkResult()
    }
    ● NetowrkResult.Success: Success from the network request.
    ● NetowrkResult.Error: Failed from the network request.
    ● NetowrkResult.Exception: Unexpected exception. i.e. IOException, UnknownHostException

    View Slide

  15. G E T S T R E A M . I O
    Modeling responses Sealed Interfaces
    sealed interface ApiResult
    class ApiSuccess(val data: T) : ApiResult
    class ApiError(val code: Int, val message: String?) : ApiResult
    class ApiException(val e: Throwable) : ApiResult
    ● ApiSuccess: Success from the network request.
    ● ApiError: Failed from the network request.
    ● ApiException: Unexpected exception. i.e. IOException, UnknownHostException

    View Slide

  16. G E T S T R E A M . I O
    Modeling responses Sealed Interfaces
    sealed interface ApiResult
    sealed class ApiError(val code: Int, val message: String?) : ApiResult
    class BadRequest(val response: Response): ApiError(response.code(), response.message())
    class Unauthorized(val response: Response): ApiError(response.code(), response.message())
    class Forbidden(val response: Response): ApiError(response.code(), response.message())

    View Slide

  17. G E T S T R E A M . I O
    Modeling responses Sealed Interfaces
    when (val response = posterRemoteDataSource.invoke()) {
    is ApiError → {
    when (response) {
    is ApiError.BadRequest → Unit
    is ApiError.Unauthorized → Unit
    is ApiError.Forbidden → Unit
    }
    }
    sealed interface ApiResult
    sealed class ApiError(val code: Int, val message: String?) : ApiResult
    class BadRequest(val response: Response): ApiError(response.code(), response.message())
    class Unauthorized(val response: Response): ApiError(response.code(), response.message())
    class Forbidden(val response: Response): ApiError(response.code(), response.message())

    View Slide

  18. G E T S T R E A M . I O
    Modeling responses Sealed Classes vs Sealed Interfaces
    Sealed Classes Sealed Interfaces
    Declaration restrictions Same file Same module
    Inheritance limitations Single parent class Multiple sealed hierarchies
    API surfaces Selectively public Must be public

    View Slide

  19. G E T S T R E A M . I O
    Modeling responses Handling Retrofit responses
    Retrofit Response NetworkResult
    handleApi

    View Slide

  20. G E T S T R E A M . I O
    Modeling responses Handling Retrofit responses
    suspend fun handleApi(
    execute: suspend () -> Response
    ): NetworkResult {
    return try {
    val response = execute()
    val body = response.body()
    if (response.isSuccessful && body != null) {
    NetworkResult.Success(body)
    } else {
    NetworkResult.Error(code = response.code(), message = response.message())
    }
    } catch (e: HttpException) {
    NetworkResult.Error(code = e.code(), message = e.message())
    } catch (e: Throwable) {
    NetworkResult.Exception(e)
    }
    }

    View Slide

  21. G E T S T R E A M . I O
    Modeling responses
    suspend fun handleApi(
    execute: suspend () -> Response
    ): NetworkResult {
    return try {
    val response = execute()
    val body = response.body()
    if (response.isSuccessful && body != null) {
    NetworkResult.Success(body)
    } else {
    NetworkResult.Error(code = response.code(), message = response.message())
    }
    } catch (e: HttpException) {
    NetworkResult.Error(code = e.code(), message = e.message())
    } catch (e: Throwable) {
    NetworkResult.Exception(e)
    }
    }
    Handling Retrofit responses

    View Slide

  22. G E T S T R E A M . I O
    Modeling responses
    suspend fun handleApi(
    execute: suspend () -> Response
    ): NetworkResult {
    return try {
    val response = execute()
    val body = response.body()
    if (response.isSuccessful && body != null) {
    NetworkResult.Success(body)
    } else {
    NetworkResult.Error(code = response.code(), message = response.message())
    }
    } catch (e: HttpException) {
    NetworkResult.Error(code = e.code(), message = e.message())
    } catch (e: Throwable) {
    NetworkResult.Exception(e)
    }
    }
    Handling Retrofit responses

    View Slide

  23. G E T S T R E A M . I O
    Modeling responses
    suspend fun handleApi(
    execute: suspend () -> Response
    ): NetworkResult {
    return try {
    val response = execute()
    val body = response.body()
    if (response.isSuccessful && body != null) {
    NetworkResult.Success(body)
    } else {
    NetworkResult.Error(code = response.code(), message = response.message())
    }
    } catch (e: HttpException) {
    NetworkResult.Error(code = e.code(), message = e.message())
    } catch (e: Throwable) {
    NetworkResult.Exception(e)
    }
    }
    Handling Retrofit responses

    View Slide

  24. G E T S T R E A M . I O
    Modeling responses
    suspend fun handleApi(
    execute: suspend () -> Response
    ): NetworkResult {
    return try {
    val response = execute()
    val body = response.body()
    if (response.isSuccessful && body != null) {
    NetworkResult.Success(body)
    } else {
    NetworkResult.Error(code = response.code(), message = response.message())
    }
    } catch (e: HttpException) {
    NetworkResult.Error(code = e.code(), message = e.message())
    } catch (e: Throwable) {
    NetworkResult.Exception(e)
    }
    }
    Handling Retrofit responses

    View Slide

  25. G E T S T R E A M . I O
    interface PosterService {
    @GET("DisneyPosters.json")
    suspend fun fetchPosters(): Response>
    }
    class PosterRemoteDataSource(
    private val posterService: PosterService
    ) {
    suspend operator fun invoke(): NetworkResult> =
    handleApi { posterService.fetchPosters() }
    }
    Modeling responses Data Layer

    View Slide

  26. G E T S T R E A M . I O
    viewModelScope.launch {
    when (val response = posterRemoteDataSource.invoke()) {
    is NetworkResult.Success → posterFlow.emit(response.data)
    is NetworkResult.Error → errorFlow.emit("${response.code} ${response.message}")
    is NetworkResult.Exception → errorFlow.emit("${response.e.message}")
    }
    }
    Modeling responses ViewModel

    View Slide

  27. G E T S T R E A M . I O
    viewModelScope.launch {
    when (val response = posterRemoteDataSource.invoke()) {
    is NetworkResult.Success → posterFlow.emit(response.data)
    is NetworkResult.Error → errorFlow.emit(
    when (response.code) {
    400 → "BadRequest"
    401 → "Unauthorized"
    else → "Unknown"
    }
    )
    is NetworkResult.Exception → errorFlow.emit(response.e.message)
    }
    }
    Modeling responses ViewModel

    View Slide

  28. G E T S T R E A M . I O
    Architecture API data flow
    Presentation
    Repository
    Retrofit
    Domain

    View Slide

  29. G E T S T R E A M . I O
    Architecture API data flow
    Domain Presentation
    Repository
    Retrofit
    handleApi
    handleApi
    handleApi

    View Slide

  30. G E T S T R E A M . I O
    Architecture API data flow
    Domain Presentation
    Repository
    Retrofit
    handleApi
    handleApi
    handleApi

    View Slide

  31. G E T S T R E A M . I O
    Retrofit
    Retrofit
    CallAdapter
    Modeling responses Retrofit CallAdapter
    Response
    Exception
    Response Type

    View Slide

  32. G E T S T R E A M . I O
    class NetworkResultCall(
    private val proxy: Call
    ) : Call> {
    override fun enqueue(callback: Callback>) {
    proxy.enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) {
    val networkResult = handleApi { response }
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    override fun onFailure(call: Call, t: Throwable) {
    val networkResult = Exception(t)
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    })
    }
    Modeling responses Custom Retrofit Call
    override fun execute(): Response> =
    throw NotImplementedError()
    override fun clone(): Call> =
    NetworkResultCall(proxy.clone())
    override fun request(): Request = proxy.request()
    override fun timeout(): Timeout = proxy.timeout()
    override fun isExecuted(): Boolean = proxy.isExecuted
    override fun isCanceled(): Boolean = proxy.isCanceled
    override fun cancel() {
    proxy.cancel()
    }

    View Slide

  33. G E T S T R E A M . I O
    class NetworkResultCall(
    private val proxy: Call
    ) : Call> {
    override fun enqueue(callback: Callback>) {
    proxy.enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) {
    val networkResult = handleApi { response }
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    override fun onFailure(call: Call, t: Throwable) {
    val networkResult = Exception(t)
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    })
    }
    Modeling responses Custom Retrofit Call
    override fun execute(): Response> =
    throw NotImplementedError()
    override fun clone(): Call> =
    NetworkResultCall(proxy.clone())
    override fun request(): Request = proxy.request()
    override fun timeout(): Timeout = proxy.timeout()
    override fun isExecuted(): Boolean = proxy.isExecuted
    override fun isCanceled(): Boolean = proxy.isCanceled
    override fun cancel() {
    proxy.cancel()
    }

    View Slide

  34. G E T S T R E A M . I O
    class NetworkResultCall(
    private val proxy: Call
    ) : Call> {
    override fun enqueue(callback: Callback>) {
    proxy.enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) {
    val networkResult = handleApi { response }
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    override fun onFailure(call: Call, t: Throwable) {
    val networkResult = Exception(t)
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    })
    }
    Modeling responses Custom Retrofit Call
    override fun execute(): Response> =
    throw NotImplementedError()
    override fun clone(): Call> =
    NetworkResultCall(proxy.clone())
    override fun request(): Request = proxy.request()
    override fun timeout(): Timeout = proxy.timeout()
    override fun isExecuted(): Boolean = proxy.isExecuted
    override fun isCanceled(): Boolean = proxy.isCanceled
    override fun cancel() {
    proxy.cancel()
    }

    View Slide

  35. G E T S T R E A M . I O
    class NetworkResultCall(
    private val proxy: Call
    ) : Call> {
    override fun enqueue(callback: Callback>) {
    proxy.enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) {
    val networkResult = handleApi { response }
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    override fun onFailure(call: Call, t: Throwable) {
    val networkResult = Exception(t)
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    })
    }
    Modeling responses Custom Retrofit Call
    override fun execute(): Response> =
    throw NotImplementedError()
    override fun clone(): Call> =
    NetworkResultCall(proxy.clone())
    override fun request(): Request = proxy.request()
    override fun timeout(): Timeout = proxy.timeout()
    override fun isExecuted(): Boolean = proxy.isExecuted
    override fun isCanceled(): Boolean = proxy.isCanceled
    override fun cancel() {
    proxy.cancel()
    }

    View Slide

  36. G E T S T R E A M . I O
    class NetworkResultCall(
    private val proxy: Call
    ) : Call> {
    override fun enqueue(callback: Callback>) {
    proxy.enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) {
    val networkResult = handleApi { response }
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    override fun onFailure(call: Call, t: Throwable) {
    val networkResult = NetworkResult.Exception(t)
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    })
    }
    Modeling responses Custom Retrofit Call
    override fun execute(): Response> =
    throw NotImplementedError()
    override fun clone(): Call> =
    NetworkResultCall(proxy.clone())
    override fun request(): Request = proxy.request()
    override fun timeout(): Timeout = proxy.timeout()
    override fun isExecuted(): Boolean = proxy.isExecuted
    override fun isCanceled(): Boolean = proxy.isCanceled
    override fun cancel() {
    proxy.cancel()
    }

    View Slide

  37. G E T S T R E A M . I O
    class NetworkResultCall(
    private val proxy: Call
    ) : Call> {
    override fun enqueue(callback: Callback>) {
    proxy.enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) {
    val networkResult = handleApi { response }
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    override fun onFailure(call: Call, t: Throwable) {
    val networkResult = NetworkResult.Exception(t)
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    })
    }
    Modeling responses Custom Retrofit Call
    override fun execute(): Response> =
    throw NotImplementedError()
    override fun clone(): Call> =
    NetworkResultCall(proxy.clone())
    override fun request(): Request = proxy.request()
    override fun timeout(): Timeout = proxy.timeout()
    override fun isExecuted(): Boolean = proxy.isExecuted
    override fun isCanceled(): Boolean = proxy.isCanceled
    override fun cancel() {
    proxy.cancel()
    }

    View Slide

  38. G E T S T R E A M . I O
    class NetworkResultCall(
    private val proxy: Call
    ) : Call> {
    override fun enqueue(callback: Callback>) {
    proxy.enqueue(object : Callback {
    override fun onResponse(call: Call, response: Response) {
    val networkResult = handleApi { response }
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    override fun onFailure(call: Call, t: Throwable) {
    val networkResult = NetowrkResult.Exception(t)
    callback.onResponse([email protected],
    Response.success(networkResult))
    }
    })
    }
    Modeling responses Custom Retrofit Call
    override fun execute(): Response> =
    throw NotImplementedError()
    override fun clone(): Call> =
    NetworkResultCall(proxy.clone())
    override fun request(): Request = proxy.request()
    override fun timeout(): Timeout = proxy.timeout()
    override fun isExecuted(): Boolean = proxy.isExecuted
    override fun isCanceled(): Boolean = proxy.isCanceled
    override fun cancel() {
    proxy.cancel()
    }

    View Slide

  39. G E T S T R E A M . I O
    class NetworkResultCallAdapter(
    private val resultType: Type
    ) : CallAdapter>> {
    override fun responseType(): Type = resultType
    override fun adapt(call: Call): Call> {
    return NetworkResultCall(call)
    }
    }
    Modeling responses Retrofit CallAdapter

    View Slide

  40. G E T S T R E A M . I O
    class NetworkResultCallAdapter(
    private val resultType: Type
    ) : CallAdapter>> {
    override fun responseType(): Type = resultType
    override fun adapt(call: Call): Call> {
    return NetworkResultCall(call)
    }
    }
    Modeling responses Retrofit CallAdapter

    View Slide

  41. G E T S T R E A M . I O
    class NetworkResultCallAdapterFactory private constructor() : CallAdapter.Factory() {
    override fun get(returnType: Type, annotations: Array, retrofit: Retrofit): CallAdapter<*, *>? {
    if (getRawType(returnType) != Call::class.java) {
    return null
    }
    val callType = getParameterUpperBound(0, returnType as ParameterizedType)
    if (getRawType(callType) != NetworkResult::class.java) {
    return null
    }
    val resultType = getParameterUpperBound(0, callType as ParameterizedType)
    return NetworkResultCallAdapter(resultType)
    }
    companion object {
    fun create(): NetworkResultCallAdapterFactory = NetworkResultCallAdapterFactory()
    }
    }
    Modeling responses Retrofit CallAdapter Factory

    View Slide

  42. G E T S T R E A M . I O
    class NetworkResultCallAdapterFactory private constructor() : CallAdapter.Factory() {
    override fun get(returnType: Type, annotations: Array, retrofit: Retrofit): CallAdapter<*, *>? {
    if (getRawType(returnType) != Call::class.java) {
    return null
    }
    val callType = getParameterUpperBound(0, returnType as ParameterizedType)
    if (getRawType(callType) != NetworkResult::class.java) {
    return null
    }
    val resultType = getParameterUpperBound(0, callType as ParameterizedType)
    return NetworkResultCallAdapter(resultType)
    }
    companion object {
    fun create(): NetworkResultCallAdapterFactory = NetworkResultCallAdapterFactory()
    }
    }
    Modeling responses Retrofit CallAdapter Factory

    View Slide

  43. G E T S T R E A M . I O
    class NetworkResultCallAdapterFactory private constructor() : CallAdapter.Factory() {
    override fun get(returnType: Type, annotations: Array, retrofit: Retrofit): CallAdapter<*, *>? {
    if (getRawType(returnType) != Call::class.java) {
    return null
    }
    val callType = getParameterUpperBound(0, returnType as ParameterizedType)
    if (getRawType(callType) != NetworkResult::class.java) {
    return null
    }
    val resultType = getParameterUpperBound(0, callType as ParameterizedType)
    return NetworkResultCallAdapter(resultType)
    }
    companion object {
    fun create(): NetworkResultCallAdapterFactory = NetworkResultCallAdapterFactory()
    }
    }
    Modeling responses Retrofit CallAdapter Factory

    View Slide

  44. G E T S T R E A M . I O
    val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(MoshiConverterFactory.create())
    .addCallAdapterFactory(NetworkResultCallAdapterFactory.create())
    .build()
    Modeling responses Retrofit CallAdapter

    View Slide

  45. G E T S T R E A M . I O
    val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(MoshiConverterFactory.create())
    .addCallAdapterFactory(NetworkResultCallAdapterFactory.create())
    .build()
    Modeling responses Retrofit CallAdapter
    interface PosterService {
    @GET("DisneyPosters.json")
    fun fetchPosters(): Call>>
    }
    class PosterRemoteDataSource(
    private val posterService: PosterService
    ) {
    suspend operator fun invoke(): NetworkResult> {
    return posterService.fetchPosters().await()
    }
    }

    View Slide

  46. G E T S T R E A M . I O
    val retrofit = Retrofit.Builder()
    .baseUrl(BASE_URL)
    .addConverterFactory(MoshiConverterFactory.create())
    .addCallAdapterFactory(NetworkResultCallAdapterFactory.create())
    .build()
    Modeling responses Retrofit CallAdapter
    interface PosterService {
    @GET("DisneyPosters.json")
    suspend fun fetchPosters(): NetworkResult>
    }
    class PosterRemoteDataSource(
    private val posterService: PosterService
    ) {
    suspend operator fun invoke(): NetworkResult> {
    return posterService.fetchPosters()
    }
    }

    View Slide

  47. G E T S T R E A M . I O
    Domain Presentation
    Repository
    Retrofit
    Retrofit
    CallAdapter
    Modeling responses Retrofit CallAdapter

    View Slide

  48. G E T S T R E A M . I O
    Modeling responses Extensions
    NetworkResult NetworkResult
    Map
    Combine
    Transformer

    View Slide

  49. G E T S T R E A M . I O
    Modeling responses Extensions
    suspend fun NetworkResult.onSuccess(
    executable: suspend (T) → Unit
    ): NetworkResult = apply {
    if (this is NetworkResult.Success) {
    executable(data)
    }
    }
    suspend fun NetworkResult.onError(
    executable: suspend (code: Int, message: String?) -> Unit
    ): NetworkResult = apply {
    if (this is NetworkResult.Error) {
    executable(code, message)
    }
    }
    suspend fun NetworkResult.onException(
    executable: suspend (e: Throwable) → Unit
    ): NetworkResult = apply {
    if (this is NetworkResult.Exception) {
    executable(e)
    }
    }

    View Slide

  50. G E T S T R E A M . I O
    Modeling responses Extensions
    suspend fun NetworkResult.onSuccess(
    executable: suspend (T) -> Unit
    ): NetworkResult = apply {
    if (this is NetworkResult.Success) {
    executable(data)
    }
    }
    suspend fun NetworkResult.onError(
    executable: suspend (code: Int, message: String?) -> Unit
    ): NetworkResult = apply {
    if (this is NetworkResult.Error) {
    executable(code, message)
    }
    }
    suspend fun NetworkResult.onException(
    executable: suspend (e: Throwable) -> Unit
    ): NetworkResult = apply {
    if (this is NetworkResult.Exception) {
    executable(e)
    }
    }

    View Slide

  51. G E T S T R E A M . I O
    Modeling responses Extensions
    suspend fun NetworkResult.onSuccess(
    executable: suspend (T) -> Unit
    ): NetworkResult = apply {
    if (this is NetworkResult.Success) {
    executable(data)
    }
    }
    suspend fun NetworkResult.onError(
    executable: suspend (code: Int, message: String?) -> Unit
    ): NetworkResult = apply {
    if (this is NetworkResult.Error) {
    executable(code, message)
    }
    }
    suspend fun NetworkResult.onException(
    executable: suspend (e: Throwable) -> Unit
    ): NetworkResult = apply {
    if (this is NetworkResult.Exception) {
    executable(e)
    }
    }

    View Slide

  52. G E T S T R E A M . I O
    Modeling responses Extensions
    viewModelScope.launch {
    val response = posterRemoteDataSource.invoke()
    response.onSuccess { posterList →
    posterFlow.emit(posterList)
    }.onError { code, message →
    errorFlow.emit("$code $message")
    }.onException {
    errorFlow.emit("${it.message}")
    }
    }

    View Slide

  53. G E T S T R E A M . I O
    Modeling responses Extensions
    viewModelScope.launch {
    val response = posterRemoteDataSource.invoke()
    response.onSuccess { posterList →
    posterFlow.emit(posterList)
    }.onError { code, message →
    errorFlow.emit("$code $message")
    }.onException {
    errorFlow.emit("${it.message}")
    }
    }
    suspend fun NetworkResult.map(
    transformer: (value: T) -> R
    ): NetworkResult {
    return if (this is NetworkResult.Success) {
    NetworkResult.Success(transformer(data))
    } else {
    this as NetworkResult
    }
    }

    View Slide

  54. G E T S T R E A M . I O
    Modeling responses Extensions
    viewModelScope.launch {
    val response = posterRemoteDataSource.invoke()
    response.map { posterList →
    posterList.first()
    }.onSuccess { poster →
    posterFlow.emit(poster)
    }.onError { code, message →
    errorFlow.emit("$code $message")
    }.onException {
    errorFlow.emit("${it.message}")
    }
    }
    suspend fun NetworkResult.map(
    transformer: (value: T) -> R
    ): NetworkResult {
    return if (this is NetworkResult.Success) {
    NetworkResult.Success(transformer(data))
    } else {
    this as NetworkResult
    }
    }

    View Slide

  55. G E T S T R E A M . I O
    Summarize
    ● Encapsulate raw data/exception from a network result
    ● Advanced error/exception handling
    ● We can expect the return type and improve them with functional operators
    ○ Monad
    ○ Functional Programming
    ○ Railway oriented programming
    ● Clear behaviors in domain/presentation layers by propagating the result

    View Slide

  56. G E T S T R E A M . I O
    Sandwich
    Other Solutions
    ● ApiResponse.Success: Success from the network request.
    ● ApiResponse.Error: Failed from the network request.
    ● ApiResponse.Exception: Unexpected exception. i.e. IOException, UnknownHostException.

    View Slide

  57. G E T S T R E A M . I O
    ● ApiResponse.getOrNull
    ● ApiResponse.getOrElse
    ● ApiResponse.getOrThrow
    Retrieve body data
    ● ApiSuccessModelMapper
    ● ApiErrorModelMapper
    ● map extensions
    Mapper
    ● ApiResponseOperator
    ● ApiResponseSuspendOperator
    ● Sandwich Global Operator
    Operator
    ● Merge extensions
    ● ApiResponseMergePolicy
    Merge
    ● ApiResponse.Success.statusCode
    ● ApiResponse.Failure.Error.statusCode
    StatusCode
    ● ApiResponse.toFlow()
    ● ApiResponse.toLiveData()
    Interoperability
    Sandwich
    Other Solutions

    View Slide

  58. G E T S T R E A M . I O
    Other Solutions Kotlin Result

    View Slide

  59. G E T S T R E A M . I O
    Other Solutions
    interface PosterService {
    @GET("DisneyPosters.json")
    suspend fun fetchPosters(): List
    }
    class PosterRemoteDataSource(
    private val posterService: PosterService
    ) {
    suspend operator fun invoke(): Result> {
    return kotlin.runCatching { posterService.fetchPosters() }
    }
    }
    Kotlin Result

    View Slide

  60. G E T S T R E A M . I O
    Other Solutions Arrow - Typed Functional Programming
    suspend operator fun invoke(): Either> {
    return Either.catch {
    posterService.fetchPosters()
    }.mapLeft { it }
    }

    View Slide

  61. G E T S T R E A M . I O
    Resources
    ● Retrofit
    ○ https://github.com/square/retrofit
    ○ https://square.github.io/retrofit/2.x/retrofit/retrofit2/CallAdapter.html
    ● Kotlin Sealed classes/interfaces
    ○ https://kotlinlang.org/docs/sealed-classes.html
    ● Functional Programming
    ○ https://en.wikipedia.org/wiki/Functional_programming
    ● Sandwich library
    ○ https://github.com/skydoves/sandwich
    ● Kotlin Result
    ○ https://kotlinlang.org/api/latest/jvm/stdlib/kotlin/-result/
    ○ https://github.com/Kotlin/KEEP/blob/master/proposals/stdlib/result.md
    ● Arrow
    ○ https://github.com/arrow-kt/arrow

    View Slide

  62. G E T S T R E A M . I O
    https://github.com/skydoves
    [email protected]
    https://twitter.com/github_skydoves
    https://medium.com/@skydoves
    Contact

    View Slide

  63. G E T S T R E A M . I O
    Thank you.

    View Slide