I have two quite similar functions and I'm trying to avoid duplication in my code by the use of generics. The functions have both a try catch block and notify its observers with two MutableLiveData of two different types:
val noWasteRecipesPosts: MutableLiveData<List<Recipe>> = MutableLiveData()
val lastArticlesPosts: MutableLiveData<List<Article>> = MutableLiveData()
fun getNoWasteRecipesPosts() {
makeCall(service.getRecipes(), noWasteRecipesPosts)
scope.launch {
try {
val response = service.getRecipes().await()
when (response.isSuccessful) {
true -> {
response.body()?.let {
noWasteRecipesPosts.postValue(ArrayList(response.body()))
} ?: run {
errorLiveData.postValue(response.message())
}
}
false -> errorLiveData.postValue(response.message())
}
} catch (e: Exception) {
noConnectionLiveData.postValue(true)
}
}
}
fun getLastArticlesPosts(excludeRecipes: Boolean) {
scope.launch {
try {
val response = when (excludeRecipes) {
true -> service.getLastArticles(categoriesToExclude = arrayListOf(BlogCategories.NO_WASTE_RECIPES.id))
.await()
false -> service.getLastArticles()
.await()
}
when (response.isSuccessful) {
true -> {
response.body()?.let {
lastArticlesPosts.postValue(ArrayList(response.body()))
} ?: run {
errorLiveData.postValue(response.message())
}
}
false -> errorLiveData.postValue(response.message())
}
} catch (e: Exception) {
noConnectionLiveData.postValue(true)
}
}
}
To avoid code repeating I'm trying to use generics, but probably in the wrong way. I've defined a function that takes the Deferred api response as first parameter and I would like to pass a MutableLiveData to notify observers as the second parameter:
fun makeCall(function: Deferred<Response<*>>, successLiveData: MutableLiveData<*>) {
scope.launch {
try {
val response = function.await()
when (response.isSuccessful) {
true -> {
response.body()?.let {
successLiveData.postValue(it) // Compile error here
} ?: run {
errorLiveData.postValue(response.message())
}
}
false -> errorLiveData.postValue(response.message())
}
} catch (e: Exception) {
noConnectionLiveData.postValue(true)
}
}
}
Unfortunately I'm missing something and the IDE is giving me a Type mismatch error trying to post the LiveData value:
Type mismatch: Required : Nothing! Found: Any.
I'm quite confused, do you have any suggestion to make about MutableLiveData and Generics in kotlin?
The response.body() type and the MutableLiveData type must match. The function signature should be something like this:
fun <T> makeCall(function: Deferred<Response<T>>, successLiveData: MutableLiveData<T>)
Related
I have a situation where I have to execute 3 network requests one after the other collect their results (which are of different types).
Following is the relevant part of the code :
Resource.kt
sealed class Resource<T>(val data: T? = null, val message: String? = null) {
class Loading<T>(data: T? = null): Resource<T>(data)
class Success<T>(data: T?): Resource<T>(data)
class Error<T>(message: String, data: T? = null): Resource<T>(data, message)
}
Repository.kt
override fun getReportData(profileId: Int): Flow<Resource<ProfileReport>> =
flow {
emit(Resource.Loading<ProfileReport>())
var report: ProfileReport? = null
try {
// Api is available as a retrofit implementation
report = api.getReport(profileId).toProfileReport()
} catch (e: HttpException) {
emit(
Resource.Error<ProfileReport>(
message = "An unknown http exception occured"
)
)
}
if (report!= null) {
emit(Resource.Success<ProfileReport>(data = report))
}
}
Say I have 3 such flows to fetch data in my repository and they have different return types (ex: ProfileReport, ProfileInfo, ProfileStatus).
Now in my viewmodel I have a function to execute these flows and perform actions on the values emitted such as :
ViewModel.kt
fun getProfileData(profileId: Int) {
getReportData(profileId)
.onEach { result ->
when (result) {
is Resource.Loading -> {
_loading.value = true
}
is Resource.Error -> {
_loading.value = false
// UI event to display error snackbar
}
is Resource.Success -> {
_loading.value = false
if (result.data != null) {
_report.value = _report.value.copy(
// Use result here
)
}
}
}
}.launchIn(viewModelScope)
}
This works ok for one flow but how can I execute 3 flows one after the other.
That is, execute first one and if its successful, execute second one and so on, and if all of them are successful use the results.
I did it like this :
fun getProfileData(profileId: Int) {
getReportData(profileId)
.onEach { result1 ->
when (result1) {
is Resource.Loading -> {/*do stuff*/}
is Resource.Error -> {/*do stuff*/}
is Resource.Success -> {
getProfileStatus(profileId)
.onEach { result2 ->
is Resource.Loading -> {/*do stuff*/}
is Resource.Error -> {/*do stuff*/}
is Resource.Success -> {
getProfileInfo(profileId)
.onEach { result3 ->
is Resource.Loading -> {/*do stuff*/}
is Resource.Error -> {/*do stuff*/}
is Resource.Success -> {
/*
Finally update viewmodel state
using result1, result2 and result3
*/
}
}.launchIn(viewModelScope)
}
}.launchIn(viewModelScope)
}
}
}.launchIn(viewModelScope)
}
But, this feels too cumbersome and probably there is a better way to chain flows based on success condition and collect results at the end. I checked some ways that use combine() or flatMapMerge() but was unable to use them in this situation.
Is there a way to achieve this? Or is this approach itself wrong from a design perspective maybe?
I think this could be modeled much more cleanly using imperative coroutines than with flows. Since you're overriding functions, this depends on you being able to modify the supertype abstract function signatures.
This solution doesn't use Resource.Loading, so you should remove that to make smart casting easier.
suspend fun getReportData(profileId: Int): Resource<ProfileReport> =
try {
val report = api.getReport(profileId).toProfileReport()
Resource.Success<ProfileReport>(data = report)
} catch (e: HttpException) {
Resource.Error<ProfileReport>(
message = "An unknown http exception occured"
)
}
//.. similar for the other two functions that used to return flows.
fun getProfileData(profileId: Int) {
viewModelScope.launch {
// do stuff to indicate 1st loading state
when(val result = getReportData(profileId)) {
Resource.Error<ProfileReport> -> {
// do stuff for error state
return#launch
}
Resource.Success<ProfileReport> -> {
// do stuff with result
}
}
// Since we returned when there was error, we know first
// result was successful.
// do stuff to indicate 2nd loading state
when(val result = getProfileStatus(profileId)) {
Resource.Error<ProfileStatus> -> {
// do stuff for error state
return#launch
}
Resource.Success<ProfileStatus> -> {
// do stuff with result
}
}
// do stuff to indicate 3rd loading state
when(val result = getProfileInfo(profileId)) {
Resource.Error<ProfileInfo> -> {
// do stuff for error state
return#launch
}
Resource.Success<ProfileInfo> -> {
// do stuff with result
}
}
}
}
If you want to keep your current Flows, you could collect your flows this way to avoid the deep nesting. This works because your source flows are designed to be finite (they aren't repeatedly emitting new values indefinitely, but have only one final result).
fun getProfileData(profileId: Int) = viewModelScope.launch {
var shouldBreak = false
getReportData(profileId).collect { result ->
when (result) {
is Resource.Loading -> { /*do stuff*/ }
is Resource.Error -> {
/*do stuff*/
shouldBreak = true
}
is Resource.Success -> { /*do stuff*/ }
}
}
if (shouldBreak) return#launch
getProfileStatus(profileId).collect { result ->
when (result) {
is Resource.Loading -> { /*do stuff*/ }
is Resource.Error -> {
/*do stuff*/
shouldBreak = true
}
is Resource.Success -> { /*do stuff*/ }
}
}
if (shouldBreak) return#launch
getProfileInfo(profileId).collect { result ->
when (result) {
is Resource.Loading -> { /*do stuff*/ }
is Resource.Error -> { /*do stuff*/ }
is Resource.Success -> { /*do stuff*/ }
}
}
}
I am trying to map the error body from an exception into into a useful model with now luck, using moshi and retrofit 2.9.0
I found numerous posts discussing the same issue but none of the solutions worked for me.
My code is the following
private fun getErrorMessageFromGenericResponse(httpException: HttpException): String? {
var errorMessage: String? = null
try {
val body = httpException.response()!!.errorBody()!!
val errorResponse = body.string().toObject(ErrorResponse::class.java)
errorMessage = errorResponse?.message
} catch (e: IOException) {
e.printStackTrace()
} finally {
return errorMessage
}
}
fun <T> String.toObject(objectClass: Class<T>): T? {
val moshi = Moshi.Builder().build()
val adapter = moshi.adapter(objectClass).lenient()
return adapter.fromJson(this)
}
I tried also using this but it also does not work:
httpException.response()!!.errorBody()!!.source().buffer.snapshot().utf8()
I am probably missing something really simple as I think its a common usecase.
Thanks in advance.
fun handleErrorResponse(e: Throwable): String {
return when (e) {
is HttpException -> {
parseHTTPError(e.response()!!.errorBody())
}
is SocketTimeoutException -> {
ApiConstants.TIME_OUT
}
is IOException -> {
ApiConstants.SERVERERROR
}
else -> ApiConstants.SERVERERROR
}
}
fun parseHTTPError(responseBody: ResponseBody?): String {
try {
val jsonObject=JSONObject(responseBody!!.string())
try {
val error=jsonObject.getJSONArray("message")
return error[0].toString()
}
catch (ex: Exception) {
responseBody!!.close()
return ""
}
responseBody.close()
return ""
}
I have the following setup
Service
// ItunesService
suspend fun searchItunesPodcast(#Query("term") term: String): Response<PodcastResponse>
Repository
// ItunesRepo
override suspend fun searchByTerm(term: String) = withContext(ioDispatcher) {
return#withContext itunesService.searchItunesPodcast(term)
}
ViewModel
fun searchPodcasts(term: String) {
viewModelScope.launch {
_res.value = Result.loading()
try {
val response = itunesRepo.searchByTerm(term)
if (response.isSuccessful) { // Nothing from here when no internet
_res.value = Result.success(response.body())
} else {
_res.value = Result.error(response.errorBody().toString())
}
} catch (e: Exception) {
_res.value = Result.exception(e)
}
}
}
Everything works great until i turn off mobile data/internet on my testing device. _res value stuck on Loading state. I have tried adding break point at if (response.isSuccessful) when there is no internet and it seams like val response = itunesRepo.searchByTerm(term) never returns how can I fix this
I switched to using Flow api on my Repository
override suspend fun searchPodcasts(term: String) = flow {
emit(Result.Loading)
try {
val res = itunesService.searchItunesPodcast(term)
if (res.isSuccessful)
emit(Result.Success(res.body()))
else
emit(Result.Error("Generic error: ${res.code()}"))
} catch (e: Exception) {
emit(Result.Error("Unexpected error", e))
}
}.flowOn(ioDispatcher)
Then collect the results on my ViewModels
I'm new to coroutines and having a hard time figuring out how to correctly wrap an existing callback in a coroutine.
My goal is to be able to do the following:
lifecycleScope.launch {
withContext(Dispatchers.Main) {
val theResult = getPreRollAd() //1. call this suspending func and wait for result
doSomethingWithResult(theResult) //2. now that the result is returned from AdsWizz API (below), do something with it
}
}
Here is the AdsWizz API call that I'd like to "wrap":
val adReqInterface: AdRequestHandlerInterface = object :
AdRequestHandlerInterface {
override fun onResponseError(error: AdswizzSDKError) {
Timber.e("onResponseError $error")
}
override fun onResponseReady(adResponse: AdResponse) {
Timber.d( "onResponseReadySingleAd")
//this contains the url to the ad, title, etc..
!!!*** I WANT TO RETURN THE adResponse.mediaFile?.source string back to "theResult" variable above (in lifecycleScope.launch {.... )
}
}
try {
AdswizzSDK.getAdsLoader().requestAd(adReqParams, adReqInterface)
} catch (e: IllegalArgumentException) {
Timber.d( "IllegalArgumentException")
} catch (e: SecurityException) {
Timber.d( "SecurityException")
} catch (e: Exception) {
Timber.d( "other exception")
e.printStackTrace()
}
I've tried using suspendCoroutine {... to wrap but nothing is working. Really appreciate someones help re the right way to achieve this.
the right way to do it is to use suspendCancellableCoroutine. It can return a result or can be cancelled with an exception.
suspend fun getPreRollAd(): AdResponse {
return suspendCancellableCoroutine {
...
val adReqInterface: AdRequestHandlerInterface = object : AdRequestHandlerInterface {
override fun onResponseError(error: AdswizzSDKError) {
Timber.e("onResponseError $error")
it.cancel(error)
}
override fun onResponseReady(adResponse: AdResponse) {
Timber.d( "onResponseReadySingleAd")
it.resume(adResponse)
}
}
AdswizzSDK.getAdsLoader().requestAd(adReqParams, adReqInterface)
}
}
viewModelScope.launch {
val result = try {
getPreRollAd()
} catch(e: Throwable) {
null
}
...
}
Is there anything similar in Kotlin that provides same ability as the Swift keyword 'defer' ?
What the defer key word does is, it ensure that the code inside a defer block get executed before returning from a function.
Below is an example imagining that defer keyword existed in Kotlin.
class MyClass {
var timeStamp = 0L
fun isEdible(fruit: Fruit): Boolean {
defer {
timeStamp = System.currentTimeMillis()
}
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
In the case above, regardless of at what point the function returns, the block inside defer will get executed and timestamp's value will get updated, just before the function ends.
I know Java there is the finally {} keyword used along with try{} catch{}, but it's is not exactly what defer offers.
There's no such keyword in Kotlin, but you can make a construct yourself that will work quite similarly. Something like this (note that this does not handle exceptions in the deferred blocks):
class Deferrable {
private val actions: MutableList<() -> Unit> = mutableListOf()
fun defer(f: () -> Unit) {
actions.add(f)
}
fun execute() {
actions.forEach { it() }
}
}
fun <T> defer(f: (Deferrable) -> T): T {
val deferrable = Deferrable()
try {
return f(deferrable)
} finally {
deferrable.execute()
}
}
Then you can use it like this:
class MyClass {
var timeStamp = 0L
fun isEdible(fruit: Fruit): Boolean = defer { d ->
d.defer {
timeStamp = System.currentTimeMillis()
}
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
The closest equivalent is try/finally. catch is not necessary if there's no exceptions thrown.
try {
println("do something")
// ... the rest of your method body here
}
finally {
println("Don't forget about me!");
}
In Swift, defer is usually used to ensure you don't forget to clean up some kind of resource or another (file handle, database connection, shared memory map, etc.). For this purpose, Kotlin use with, which takes a closure, to which the resource is passed as an argument. The resource is valid for the lifetime of the closure, and is automatically closed at the end.
FileWriter("test.txt")
.use { it.write("something") }
// File is closed by now
Solution with exception handling:
class DeferContext {
private val list = mutableListOf<() -> Unit>()
fun defer(payload: () -> Unit) {
list += payload
}
/** lombok `#Cleanup` analog */
fun AutoCloseable.deferClose() = apply {
defer { close() }
}
fun executeDeferred(blockError: Throwable?) {
var error: Throwable? = blockError
for (element in list.reversed()) {
try {
element()
} catch (e: Throwable) {
if (error == null) {
error = e
} else {
error.addSuppressed(e)
}
}
}
error?.let { throw it }
}
}
inline fun <T> deferBlock(payload: DeferContext.() -> T): T {
val context = DeferContext()
var error: Throwable? = null
var result: T? = null
try {
result = context.payload()
} catch (e: Throwable) {
error = e
} finally {
context.executeDeferred(error)
}
return result as T
}
IMHO, main point of defer functionality is execution of deferred actions regardless of previously thrown exceptions.
usage:
deferBlock {
defer { println("block exited") }
val stream = FileInputStream("/tmp/a").deferClose()
}
I came across the same question today.
While I think the answer provided by marstran is good, I decided to refactor it a little bit.
fun <T> deferred(f: ((() -> Unit) -> Unit) -> T): T {
val actions: MutableList<() -> Unit> = mutableListOf()
try {
return f(actions::add)
} finally {
actions.asReversed().forEach { it() }
}
}
I got rid of the Deferrable class by using the list directly in the deffered function. This also solves the fact that the whole Deferrable object was passed to the calling code needing to call it.defer/d.defer. In this version the add method of the mutable list is directly passed into the lambda allowing to have a code that is closer to its go/swift version.
To address the suggestion given by mvndaai to use Stack I decided to call .asReversed() on the list. Maybe there is a LI-FO type in kotlin that is also available in non JVM variants, but if not I think this is a good solution.
the given sample would look like:
class MyClass {
var timeStamp = 0L
fun isEdible(fruit: Fruit): Boolean = deferred { defer ->
defer {
timeStamp = System.currentTimeMillis()
}
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
If the class is Closeable you can use use block:
class MyClass : Closeable {
var timeStamp = 0L
override fun close() {
timeStamp = System.currentTimeMillis()
}
fun test(): Boolean {
this.use {
if (fruit.isExpired) {
return false
}
if (fruit.isRipe) {
return true
}
return false
}
}
}