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)
}
}
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?
Is there a way to get HTTP Status code (like 200, 400...) before you observe a live data ?
This is my implementation :
Result
sealed class Result<out T: Any> {
data class Success<out T : Any>(val data: T?): Result<T>()
data class Error(val exception: Exception): Result<Nothing>()
}
BaseRepository
open class BaseRepository {
suspend fun <T: Any> safeApiCall(call: suspend () -> Response<T>, errorMessage: String): T? {
val result: Result<T> = safeApiResult(call, errorMessage)
var data: T? = null
when(result) {
is Result.Success ->
data = result.data
is Result.Error -> {
Logger.getLogger("1.DataRepository").log(Level.INFO, "$errorMessage & Exception - ${result.exception}")
}
}
return data
}
private suspend fun <T: Any> safeApiResult(call: suspend ()-> Response<T>, errorMessage: String): Result<T> {
val response = call.invoke()
if (response.isSuccessful) return Result.Success(response.body())
return Result.Error(IOException("Error Occurred during getting safe Api result, Custom ERROR - $errorMessage"))
}
}
My repository
class UserRepository (private val api : UserService) : BaseRepository() {
suspend fun getFavorites() : MutableList<Favorite>? {
return safeApiCall(
call = {api.getFavorites().await()},
errorMessage = "Error Fetching Favorites"
)?.toMutableList()
}
}
ViewModel
class UserViewModel : ViewModel() {
private val parentJob = Job()
private val coroutineContext: CoroutineContext
get() = parentJob + Dispatchers.Default
private val scope = CoroutineScope(coroutineContext)
private val repository: UserRepository = UserRepository(ApiFactory.Apifactory.userService)
val favoritesLiveData = MutableLiveData<MutableList<Favorite>>()
fun fetchFavorites() {
scope.launch {
val favorites = repository.getFavorites()
favoritesLiveData.postValue(favorites)
}
}
}
In my code I do something like this :
userViewModel.fetchFavorites()
userViewModel.favoritesLiveData.observe(this, Observer {
})
Where can I check the status code ? I can't figure out where to implement the status code return.
You are not doing it from live data as it's the last (or to be exact one before the last) step your data is presented to the user by populating the views in your activity/fragment etc...
In order to accomplish this, you need to check it in your repository or your viewModel.
before you parse your result to your models you can check the code of your response
Something like
public abstract class SuccessCallback<T> extends BaseCallBack<T> implements Callback<T>{
#Override
public void onResponse(Call<T> call, Response<T> response) {
if(response.code()>= 400 && response.code() < 599){
onFailure(response);
}
else {
onSuccess(response);
}
}
#Override
public void onFailure(Call<T> call, Throwable t){
}
#Override
public void onSuccess(Response<T> response) {
}
#Override
public void onFailure(Response<T> response) {
}
}
You can see other solutions (including this one) from link
Edit
I think you will have access to the code from the response object in safeApiResult function. Just try response.code
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
I would like to get a simple string from an API.
Usually, i can get everything I want from an API with the following functions :
class OrderRepositoryImpl(val orderService: OrderService) : OrderRepository {
override fun getPaymentMethods(id: String, success: (List<PaymentMode>) -> Unit, failure: (Throwable) -> Unit): Subscription {
return orderService.getPaymentMethods(id)
.subscribeOn(Schedulers.io())
.map { it.entrySet() }
.map { it.map { it.value }.map {it.asJsonObject } }
.map { it.map { PaymentMode().apply { loadFromJson(it) } } }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ success.invoke(it) }, { failure.invoke(it) })
}
}
And in OrderService :
#GET("api/order/payment/modes/list/{id}")
fun getPaymentMethods(#Path("id") id: String): Observable<JsonObject>
This works perfectly, on an API with regular Json Objects.
But today, I have a problem : I have an API with a unique string, like this :
"validated"
or :
"draft"
So I made the followin function (in OrderRepositoryImpl class) :
override fun getOrderStatus(id: String, success: (String) -> Unit, failure: (Throwable) -> Unit) =
orderService.getOrderStatus(id)
.subscribeOn(Schedulers.io())
.map { it }
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ success.invoke(it.toString()) }, { failure.invoke(it) } )
And in Order Service :
#GET("api/order/checkout/{id}/status")
fun getOrderStatus(#Path("id") id: String): Observable<JsonObject>
I call this method like this :
fun getOrderStatus(id : Long) {
orderRepository.getOrderStatus(id.toString(), {
println("SUCCESS !")
println("STATUS == $it")
}, {
println("FAILURE...")
})
}
But I don't get anything from "success.invoke" line. When I call this method in my code, i always have "FAILURE" in my logs... Even if one of the logs lines is :
D/OkHttp: "validated"
which is exactly what I want to see in case of success.
I know it is pretty strange I can get and parse json objects and not a string, but I kind of learned on the job...
How can I get a simple string from an API ?
Okay, I am SO ashamed.
So, I printed the error in my "FAILURE..." section, and I got something like :
got jsonPrimitive but expected jsonObject
As the API was just returning me a string, and NOT an object, it is call a Json Primitive.
So I just changed the return of my functions in OrderService :
#GET("api/order/checkout/{id}/status")
fun getOrderStatus(#Path("id") id: String): Observable<JsonPrimitive>
Thanks for your helpful comments.
interface ServiceInterFace {
#POST("api/order/checkout/{id}/status")
fun getOrderStatus(#Path("id") id: String): Call<String>
}
// calling from your main class
val id: String? = null
val retrofit = Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.baseUrl("----Your link here-----")
.build()
val scalarService = retrofit.create(ServiceInterFace::class.java!!)
val stringCall = scalarService.getOrderStatus(id)
stringCall.enqueue(object : Callback<String> {
override fun onResponse(call: Call<String>, response: Response<String>) {
if (response.isSuccessful) {
val responseString = response.body()//get response here
}
}
override fun onFailure(call: Call<String>, t: Throwable) {
Toast.makeText(this#Main2Activity, "Failed to connect server",
Toast.LENGTH_SHORT).show()
}
})
//import
implementation 'com.squareup.retrofit2:retrofit:2.3.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.3.0'
I am new to RX Java. While implementing Rx java with Retrofit i found i am getting throw-able object in my doOnError(){}
But what i want my doOnError() of RX Java should return ErrorBase() -> that is my custom class. instead of throwable.
it will help me to handle error at central. i will pass my throw-able object to my ErrorBase class where i have handled custom messages.
Below is doOnError(). Where i want to return ErrorBase object
apiInterface.getLoginDetails(auth)
.doOnNext {
//LoginResponse
}
doOnError{
return ErrorBase(throwable)
}
Code of other classes.
Api Interface class
interface ApiInterface {
#POST("login")
fun getLoginDetails(#Header(Constants.AUTHORIZATION) auth: String): Observable<LoginResponseModel>
}
LoginRepository
class LoginRepository #Inject constructor(private val apiInterface: ApiInterface,
val utils: Utils) {
fun getLoginDetails(auth: String): Observable<LoginResponseModel> {
return apiInterface.getLoginDetails(auth)
.doOnNext {
}
.doOnError {
//Right now having throw-able object
}
}
}
ErrorBase
class ErrorBase(private val throwable: Throwable) {
private var message: String?
private var statusCode: Int
init {
statusCode = getStatusCode()
message = getMessage()
}
private fun getStatusCode(): Int {
if (throwable is HttpException) {
val exception = throwable
return exception.code()
}
return -1
}
private fun getMessage() =
when (throwable) {
is IOException -> "Something Went Wrong"
is UnknownHostException -> "No internet connectivity"
is SocketTimeoutException -> "Slow Internet connectivity"
else -> throwable.message
}
}
LoginvViewModel
class LoginViewModel #Inject constructor(
private val loginRepository: LoginRepository) : ViewModel() {
private val TAG = this.javaClass.name
private var loginResult: MutableLiveData<LoginResponseModel> = MutableLiveData()
private var loginError: MutableLiveData<String> = MutableLiveData()
private var loginLoader: MutableLiveData<Boolean> = MutableLiveData()
private lateinit var disposableObserver: DisposableObserver<LoginResponseModel>
fun loginResult(): LiveData<LoginResponseModel> {
return loginResult
}
fun loginError(): LiveData<String> {
return loginError
}
fun loginLoader(): LiveData<Boolean> {
return loginLoader
}
private fun getLoginData(auth: String) {
loginLoader.postValue(true)
initLoginObserver()
loginRepository.getLoginDetails(auth)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.debounce(400, MILLISECONDS)
.subscribe(disposableObserver)
}
private fun initLoginObserver() {
disposableObserver = object : DisposableObserver<LoginResponseModel>() {
override fun onComplete() {
}
override fun onNext(loginDetails: LoginResponseModel) {
loginResult.postValue(loginDetails)
loginLoader.postValue(false)
}
override fun onError(e: Throwable) {
loginError.postValue(e.message)
loginLoader.postValue(false)
}
}
}
fun disposeElements() {
if (null != disposableObserver && !disposableObserver.isDisposed) disposableObserver.dispose()
}
fun loginClicked() {
getLoginData("auth")
}}
Firstly, doOnError isn't aimed to transform/return some data, but helps to handle side-effects like logging.
Second thing, ErrorBase doesn't fit well together with LoginResponseModel cause they don't have any common parent.
Thus, I suggest you following solution:
Create one base class for your response:
sealed class LoginResponse {
class Result( ..your data here.. ) : LoginResponse()
class Error( ... ) : LoginResponse()
}
Make function return LoginResponse and do following changes:
fun getLoginDetails(auth: String): Observable<LoginResponse> {
return apiInterface.getLoginDetails(auth)
.map { data -> LoginResponse.Result(data) }
.onErrorReturn { throwable -> LoginResponse.Error(throwable) }
}
Now both results have one common parent and you can use getLoginDetails in the following way:
fun doRequest() {
loginRepository.getLoginDetails(auth)
.subscribe { result ->
when (result) {
is LoginResponse.Result -> //do something with result
is LoginResponse.Error -> //do something with error
}
}
}
Some explanation.
onErrorReturn does exactly what you need - returns your custom value in case if error occurs
If you don't add LoginResponse you have to make Observable<Any> which is loosely typed and doesn't really well describes your interface.
Making LoginResponse sealed allows to check only 2 cases whether emitted data is Result or Error. Otherwise Kotlin compiler forces you to add additional else branch
Update In case if you need to do same thing in multiple places you can go with this:
sealed class Response<T> {
data class Result<T>(val result: T) : Response<T>()
data class Error<T>(val throwable: Throwable) : Response<T>()
}
fun getLoginDetails(auth: String): Observable<Response<LoginResponseModel>> {
return apiInterface.getLoginDetails(auth)
.map<Response<LoginResponseModel>> { data -> Response.Result(data) }
.onErrorReturn { throwable -> LoginResponse.Error(throwable) }
}
..and somewhere in your code..
fun handleResponse(response: Response<LoginData>) {
when (response) {
is Response.Result -> response.result
is Response.Error -> response.throwable
}
}