Reputation: 5403
inline fun <T> rest(request: () -> T): T = try {
request()
} catch (e: HttpException) {
val requestId = e.response().raw().request().header(REQUEST_ID_HEADER)
if (requestId != null) {
Dialog(requestId, R.string.oops).show(fragmentManager, null)
} else {
throw e
}
}
It should perform some REST request (in the request
parameter) and if it fails, and it contained the specified HTTP header, display dialog with that header.
But the compiler complains at the line with Dialog, that it doesn't return T
, but Unit
. But that's basicaly what I want! How can I do this?
One solution that crosses my mind it to set function's return type to T?
and return null
, but it feel dirty doing this in Kotlin.
Upvotes: 2
Views: 1266
Reputation: 29924
Returning null might feel ugly at first, but what are the options?
Option 1: Throwing an exception
If you need further information on why the code failed you should throw an exception.
Let's take a look at Kotlin's single function as an example:
listOf<Int>().single()
will throw
NoSuchElementException: List is empty.
listOf<Int>(1, 1).single { it == 1 }
will throw
IllegalArgumentException: Collection contains more than one matching element.
This is what I mean with further information. The type of exception and the given message might provide you with ways to decide how to continue.
Option 2: Returning null
In case you just want to know whether it failed or not returning null
would a good way to signal that. Even Kotlin's standard library does that for example with singleOrNull()
.
listOf<Int>().singleOrNull() // returns null
Providing a fallback is also really concise using the elvis operator:
listOf<Int>().singleOrNull() ?: 1 // default element
Upvotes: 0
Reputation: 37879
Returning null is not dirty per se. Using nulls can be abused sometimes, but this is a perfectly valid use case for using null. Kotlin allows you to use null in safe and nice ways, so don't be afraid to use it!
Another option is to throw an exception also in the case you show the dialog (whether or not the header is present).
In order to choose, you have to ask yourself what the code calling rest()
will do in case the dialog is shown. It has to deal with the absence of T
in one way or another (null or exception). This is because showing a dialog is not something that ends the execution of your function.
Last but not least, there is also an option to deal with the result outside of the rest()
method. Improving on Taras' answer:
sealed class Result<out T : Any> {
class Success<out T : Any>(val value: T) : Result<T>()
class ErrorWithId(val exception: Exception, val requestId: String) : Result<Nothing>()
class Error(val exception: Exception) : Result<Nothing>()
}
inline fun <T : Any> rest(request: () -> T): Result<T> = try {
Result.Success(request())
} catch (e: HttpException) {
val requestId = e.response().raw().request().header(REQUEST_ID_HEADER)
if (requestId != null) {
Result.RecoverableError(e, requestId)
} else {
Result.Error(e)
}
}
private fun thingCallingRest() {
val result = rest(::testRequest)
when (result) {
is Result.Success -> Log.v("__DEBUG", "success: ${result.value}")
is Result.ErrorWithId -> Dialog(result.requestId, R.string.oops).show(fragmentManager, null)
is Result.Error -> throw result.exception
}
}
Upvotes: 5
Reputation: 604
You could solve it by generic Result
type:
sealed class Result<out T : Any> {
class Success<out T : Any>(val value: T) : Result<T>()
class Error(val exception: Exception, val requestId: String?) : Result<Nothing>()
}
inline fun <T : Any> rest(request: () -> T): Result<T> = try {
Result.Success(request())
} catch (e: HttpException) {
val requestId = e.response().raw().request().header(REQUEST_ID_HEADER)
Result.Error(e, requestId)
}
private fun testRest() {
val result = rest(::testRequest)
when (result) {
is Result.Success -> Log.v("__DEBUG", "success: ${result.value}")
is Result.Error -> {
result.requestId?.let {
Dialog(it, R.string.oops).show(fragmentManager, null)
} ?: run {
throw result.exception
}
}
}
}
Upvotes: 1