Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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?

Slide 12

Slide 12 text

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)

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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())

Slide 17

Slide 17 text

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())

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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) } }

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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(this@NetworkResultCall, Response.success(networkResult)) } override fun onFailure(call: Call, t: Throwable) { val networkResult = Exception(t) callback.onResponse(this@NetworkResultCall, 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() }

Slide 33

Slide 33 text

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(this@NetworkResultCall, Response.success(networkResult)) } override fun onFailure(call: Call, t: Throwable) { val networkResult = Exception(t) callback.onResponse(this@NetworkResultCall, 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() }

Slide 34

Slide 34 text

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(this@NetworkResultCall, Response.success(networkResult)) } override fun onFailure(call: Call, t: Throwable) { val networkResult = Exception(t) callback.onResponse(this@NetworkResultCall, 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() }

Slide 35

Slide 35 text

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(this@NetworkResultCall, Response.success(networkResult)) } override fun onFailure(call: Call, t: Throwable) { val networkResult = Exception(t) callback.onResponse(this@NetworkResultCall, 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() }

Slide 36

Slide 36 text

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(this@NetworkResultCall, Response.success(networkResult)) } override fun onFailure(call: Call, t: Throwable) { val networkResult = NetworkResult.Exception(t) callback.onResponse(this@NetworkResultCall, 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() }

Slide 37

Slide 37 text

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(this@NetworkResultCall, Response.success(networkResult)) } override fun onFailure(call: Call, t: Throwable) { val networkResult = NetworkResult.Exception(t) callback.onResponse(this@NetworkResultCall, 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() }

Slide 38

Slide 38 text

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(this@NetworkResultCall, Response.success(networkResult)) } override fun onFailure(call: Call, t: Throwable) { val networkResult = NetowrkResult.Exception(t) callback.onResponse(this@NetworkResultCall, 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() }

Slide 39

Slide 39 text

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

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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() } }

Slide 46

Slide 46 text

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() } }

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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) } }

Slide 50

Slide 50 text

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) } }

Slide 51

Slide 51 text

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) } }

Slide 52

Slide 52 text

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}") } }

Slide 53

Slide 53 text

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 } }

Slide 54

Slide 54 text

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 } }

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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.

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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 } }

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

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