I've created a class like this:
sealed class Either<out L, out R> {
//Failure
data class Left<out L>(val value: L) : Either<L, Nothing>()
//Success
data class Right<out R>(val value: R) : Either<Nothing, R>()
val isRight get() = this is Right<R>
val isLeft get() = this is Left<L>
}
I am using retrofit and I am planning to return this:
#GET(__SOMEPATH__)
suspend fun pews(__Whatever__) : Either<Throwable, POJO>
But when Gson tries to create the object, an exception is thrown:
java.lang.RuntimeException: Failed to invoke private com.pew.pew.base.networking.Either() with no args
And also
Caused by: java.lang.InstantiationException: Can't instantiate abstract class com.pew.pew.base.networking.Either
Is there any way to encapsulate Error and Result on retrofit response?
EDIT
Now I have another sealed class
sealed class Result<T> {
data class Success<T>(val data: T) : Result<T>()
data class Unauthorized(val exception: Exception) : Result<Nothing>()
data class Timeout(val exception: Exception) : Result<Nothing>()
data class Error(val exception: Exception) : Result<Nothing>()
}
fun <A, B> Result<A>.map(mapper: (A) -> B): Result<out B> {
return when (this) {
is Success -> Success(mapper(data))
is Unauthorized -> Unauthorized(exception)
is Timeout -> Timeout(exception)
is Error -> Error(exception)
}
}
And then in my RepositoryImpl I need to define wether it is a Success or an Error. How do I do that? Before I was using a fold, which allowed me to get the Success or Error.
Can I do something like change from Call<T> to Result<T>?
inline fun <reified T> execute(f: () -> Call<T>): ResultWrapper<T> =
try {
when (T::class) {
Unit::class -> f().execute().let {
ResultWrapper.Success(Unit as T)
}
else -> f().execute().body()?.let {
ResultWrapper.Success(it)
} ?: ResultWrapper.Error(Throwable())
}
} catch (exception: Exception) {
ResultWrapper.Network(serverError)
}
But it says
Related
I have the below function which works with Kotlin flow
sealed class State<out T> {
data class Loading<out T>(val progress: Int = 0) : State<T>()
data class Success<out T>(val data: T?) : State<T>()
data class Error(val exception: Throwable) : State<Nothing>()
}
suspend fun getReviews(movieId: Long): Flow<State<List<Review>>> = withContext(IO) {
flow { emit(api.getReviews(movieId)) }
.map { it.sortedWith { item, _ -> if (item.userId == pref.getUserId()) -1 else 0 } }
.map { State.Success(it) }
.catch { error -> emit(State.Error(error)).also { Timber.e(error) } }
}
I'm unable to emit an error state from the catch block when there is an exception. It gives me an error stating Type mismatch: inferred type is State.Error but State.Success<List<Review>> was expected
Why can't I emit the sub class of the State from the catch block?
I am developing android app and I have implemented success and failure cases in viemodel class but I am getting following mismatch Type mismatch.
Required:
Result!
Found:
Result<Response>
below my NewsViewModel where I have implemented success and failure cases when I am getting data
class NewsViewModel(private val repository: NewsRepository) : ViewModel() {
private var _newsResponse= MutableLiveData<Result<NewsResponse>>()
// Expose to the outside world
val news: LiveData<Result<NewsResponse>> = _newsResponse
#UiThread
fun getNews() {
viewModelScope.launch(Dispatchers.IO) {
try {
val response = repository.getNews()
_newsResponse.postValue(Result.success(response))
} catch (ioe: IOException) {
_newsResponse.postValue(Result.Failure("[IO] error please retry", ioe))
} catch (he: HttpException) {
_newsResponse.postValue(Result.Failure("[HTTP] error please retry", he))
}
}
}
}
below my NewsRepository.ktclass
NewsRepository(
private val apiInterface:NewsInterface
){
suspend fun getNews() = apiInterface.getNews()
}
below my Result class
sealed class Result<out T> {
data class Success<out R>(val value: R): Result<R>()
data class Failure(
val message: String?,
val throwable: Throwable?
): Result<Nothing>()
}
I want to know where I exactly I am making mistake what I have to do in order to fix that problem
below my news Interface
import com.example.newsworldwide.model.NewsResponse
import retrofit2.Response
import retrofit2.http.GET
interface NewsInterface {
#GET("ApiKey")
suspend fun getNews(): Response<NewsResponse>
}
Your NewsInterface is returning Response<NewsResponse> & in your NewsViewModel you're passing it directly to response so it becomes Result.Success<Response<NewsResponse>> at the time of posting. That's why this error.
Solution:
Get value from body() of retrofit response class.
Make it Non-nullable using(!!) as your _newsResponse live-data is accepting NewsResponse which is non-nullable. You might want to handle null case here.
So your final code would look something like this.
class NewsViewModel(private val repository: NewsRepository) : ViewModel() {
private var _newsResponse= MutableLiveData<Result<NewsResponse>>()
// Expose to the outside world
val news: LiveData<Result<NewsResponse>> = _newsResponse
#UiThread
fun getNews() {
viewModelScope.launch(Dispatchers.IO) {
try {
val response = repository.getNews().body()!! //change this line
_newsResponse.postValue(Result.Success(response))
} catch (ioe: IOException) {
_newsResponse.postValue(Result.Failure("[IO] error please retry", ioe))
} catch (he: HttpException) {
_newsResponse.postValue(Result.Failure("[HTTP] error please retry", he))
}
}
}
}
I have a lot of methods that look like that:
override suspend fun getBalance(): Result<BigDecimal> = withContext(Dispatchers.IO) {
Log.d(TAG, "Fetching balance from data store")
val balance = balancePreferencesFlow.firstOrNull()
?: return#withContext Result.Error(CacheIsInvalidException)
return#withContext when (balance) {
is Result.Success -> {
if ((balance.data.timestamp + ttl) <= getCurrentTime()) {
deleteBalance()
Result.Error(CacheIsInvalidException)
} else {
resultOf { balance.data.toDomainType() }
}
}
is Result.Error -> balance
}
}
There I am collecting a Flow of some type from DataStore, then if it is a Success Result(with data parameter of type T), I should get its timestamp(it is a data class field), and if the condition is true delete invalid data and if it's false return the converted Result.
The convertion functions look somehow like that:
fun BigDecimal.toPersistenceType(): Balance = Balance(
balanceAmount = this,
timestamp = getCurrentTime()
)
fun Balance.toDomainType(): BigDecimal = this.balanceAmount
I've tried to make an abstract method in this way, but I don't completely understand how I should pass a lambda to it.
suspend inline fun <reified T : Any, reified V : Any> getPreferencesDataStoreCache(
preferencesFlow: Flow<Result<V>>,
ttl: Long,
deleteCachedData: () -> Unit,
getTimestamp: () -> Long,
convertData: () -> T
): Result<T> {
val preferencesResult = preferencesFlow.firstOrNull()
return when (preferencesResult) {
is Result.Success -> {
if ((getTimestamp() + ttl) <= getCurrentTime()) {
deleteCachedData()
Result.Error(CacheIsInvalidException)
} else {
resultOf { preferencesResult.data.convertData() }
}
}
is Result.Error -> preferencesResult
else -> Result.Error(CacheIsInvalidException)
}
}
And a lambda for convertion should look like an extension method.
The Result class:
sealed class Result<out T : Any> {
data class Success<out Type : Any>(val data: Type) : Result<Type>()
data class Error(val exception: Exception) : Result<Nothing>()
}
First of all, I see here some cache work, that from my point should be placed in one interface.
interface Cache {
val timestamp: Long
fun clear()
}
You can make timestamp property nullable to return null if your cache is still empty - it's up to you.
Then universal method you need I assume to place inside Result class as it seems to be only its own work.
sealed class Result<out T : Any> {
data class Success<out Type : Any>(val data: Type) : Result<Type>()
data class Error(val exception: Exception) : Result<Nothing>()
fun <R : Any> convertIfValid(cache: Cache, ttl: Long, converter: (T) -> R) : Result<R> =
when (this) {
is Success -> {
if (cache.timestamp + ttl <= getCurrentTime()) {
cache.clear()
Error(CacheIsInvalidException())
} else {
Success(converter(data))
}
}
is Error -> this
}
}
May be it would be better to place getCurrentTime method in some injected entity too, but it's not important in this post.
By the way, as you can see here in when I didn't place else state as it is unnecessary for sealed classes.
From your code I can make an example of cache implementation only for balance:
class BalanceCache : Cache {
var balanceValue = Balance()
override val timestamp: Long
get() = balanceValue.timestamp
override fun clear() {
deleteBalance()
}
}
If you need more examples from me, please give me more details about your code where you want to use it.
I want to handle api response using sealed class
sealed class Result<out T> {
data class Success<out T>(val value: T) : Result<T>()
data class Failure<out T>(val throwable: Throwable) : Result<T>()
}
My api is working properly for
#POST("/api/doctor_app/UpdateProfile")
fun UpdateProfile(#Body request: Doctor): Single<GenericResponse>
and when I updated my api to
#POST("/api/doctor_app/UpdateProfile")
fun UpdateProfile(#Body request: Doctor): Single<Result<GenericResponse>>
Failed to invoke private com.utils.Result() with no args
How can I achieve response like
configService.UpdateProfile(doctor)
.subscribeOnIO()
.map {
when(it){
is Result.Success -> Result.Success(it.value)
is Result.Failure -> Result.Failure<GenericResponse>(it.throwable)
}
}
where subscribeOnIO
fun <T> Single<T>.subscribeOnIO(): Single<T> {
return this.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
}
did you try something like this:
#POST("/api/doctor_app/UpdateProfile")
fun UpdateProfile(#Body request: Doctor): Single<Result.Success<GenericResponse>>
and then
configService.UpdateProfile(doctor)
.onErrorReturn { Result.Failure(it) }
.subscribeOnIO()
.map {
when(it){
is Result.Success -> Result.Success(it.value)
is Result.Failure -> Result.Failure<GenericResponse>(it.throwable)
}
}
I have the following method :
override suspend fun getData(pageNr: Int): NetworkResult {
// make the request
val response = getDataApi.getData(pageNr)
// based on response, return either Success or Error
return if(response.isSuccessful){
NetworkResult.Success(response.body())
} else{
NetworkResult.Error(response.code())
}
}
Android Studio complains about the return type. It says:
"One type argument expected for class NetworkResult<out R>"
My NetworkResult.kt is as follows:
sealed class NetworkResult<out R> {
data class Success<out T>(val data: T) : NetworkResult<T>()
data class Error(val errorCode: Int) : NetworkResult<Nothing>()
object Loading : NetworkResult<Nothing>()
override fun toString(): String {
return when (this) {
is Success<*> -> "Success[data=$data]"
is Error -> "Error[code=$errorCode]"
Loading -> "Loading"
}
}
}
Why this error is popping up ? Success and Error are both subtypes of NetworkResult...so why is returning one of them not allowed ?
For the sake of completeness, I also added Retrofit interface call method:
#GET("getData/")
suspend fun getData(#Query("page") page:Int) : Response<MyData>
Add generic parameter to returning type of getData function:
override suspend fun getData(pageNr: Int): NetworkResult<MyData> {
// ...
}
You can either change generic type to MyData? or if you are sure response.body() doesn't return null, add !! operator: NetworkResult.Success(response.body()!!)