How to get HTTP status code (400, 200...) on Observer - android

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

Related

What is the simplest way to make a post request in Kotlin for Android app

The question about post requests in android has been asked before, but all the solutions I've tried have not worked properly. On top of that, a lot of them seem to be overly complicated as well. All I wish to do is make a post to a specific sight with a few body parameters. Is there any simple way to do that?
Let me explain my request calling structure using Retrofit.
build.gradle(app)
// Retrofit + GSON
implementation 'com.squareup.okhttp3:logging-interceptor:4.4.0'
implementation "com.squareup.retrofit2:retrofit:2.9.0"
implementation "com.squareup.retrofit2:converter-gson:2.9.0"
ApiClient.kt
object ApiClient {
private const val baseUrl = ApiInterface.BASE_URL
private var retrofit: Retrofit? = null
private val dispatcher = Dispatcher()
fun getClient(): Retrofit? {
val logging = HttpLoggingInterceptor()
if (BuildConfig.DEBUG)
logging.level = HttpLoggingInterceptor.Level.BODY
else
logging.level = HttpLoggingInterceptor.Level.NONE
if (retrofit == null) {
retrofit = Retrofit.Builder()
.client(OkHttpClient().newBuilder().readTimeout(120, TimeUnit.SECONDS)
.connectTimeout(120, TimeUnit.SECONDS).retryOnConnectionFailure(false)
.dispatcher(
dispatcher
).addInterceptor(Interceptor { chain: Interceptor.Chain? ->
val newRequest = chain?.request()!!.newBuilder()
return#Interceptor chain.proceed(newRequest.build())
}).addInterceptor(logging).build()
)
.baseUrl(baseUrl)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
return retrofit
}
}
ApiClient will be used to initialize Retrofit singleton object, also initialize logging interceptors so you can keep track of the requests and responses in the logcat by using the keyword 'okhttp'.
SingleEnqueueCall.kt
object SingleEnqueueCall {
var retryCount = 0
lateinit var snackbar: Snackbar
fun <T> callRetrofit(
activity: Activity,
call: Call<T>,
apiName: String,
isLoaderShown: Boolean,
apiListener: IGenericCallBack
) {
snackbar = Snackbar.make(
activity.findViewById(android.R.id.content),
Constants.CONST_NO_INTERNET_CONNECTION, Snackbar.LENGTH_INDEFINITE
)
if (isLoaderShown)
activity.showAppLoader()
snackbar.dismiss()
call.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
hideAppLoader()
if (response.isSuccessful) {
retryCount = 0
apiListener.success(apiName, response.body())
} else {
when {
response.errorBody() != null -> try {
val json = JSONObject(response.errorBody()!!.string())
Log.e("TEGD", "JSON==> " + response.errorBody())
Log.e("TEGD", "Response Code==> " + response.code())
val error = json.get("message") as String
apiListener.failure(apiName, error)
} catch (e: Exception) {
e.printStackTrace()
Log.e("TGED", "JSON==> " + e.message)
Log.e("TGED", "Response Code==> " + response.code())
apiListener.failure(apiName, Constants.CONST_SERVER_NOT_RESPONDING)
}
else -> {
apiListener.failure(apiName, Constants.CONST_SERVER_NOT_RESPONDING)
return
}
}
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
hideAppLoader()
val callBack = this
if (t.message != "Canceled") {
Log.e("TGED", "Fail==> " + t.localizedMessage)
if (t is UnknownHostException || t is IOException) {
snackbar.setAction("Retry") {
snackbar.dismiss()
enqueueWithRetry(activity, call, callBack, isLoaderShown)
}
snackbar.show()
apiListener.failure(apiName, Constants.CONST_NO_INTERNET_CONNECTION)
} else {
retryCount = 0
apiListener.failure(apiName, t.toString())
}
} else {
retryCount = 0
}
}
})
}
fun <T> enqueueWithRetry(
activity: Activity,
call: Call<T>,
callback: Callback<T>,
isLoaderShown: Boolean
) {
activity.showAppLoader()
call.clone().enqueue(callback)
}
}
SingleEnqueueCall will be used for calling the retrofit, it is quite versatile, written with onFailure() functions and by passing Call to it, we can call an API along with ApiName parameter so this function can be used for any possible calls and by ApiName, we can distinguish in the response that which API the result came from.
Constants.kt
object Constants {
const val CONST_NO_INTERNET_CONNECTION = "Please check your internet
connection"
const val CONST_SERVER_NOT_RESPONDING = "Server not responding!
Please try again later"
const val USER_REGISTER = "/api/User/register"
}
ApiInterface.kt
interface ApiInterface {
companion object {
const val BASE_URL = "URL_LINK"
}
#POST(Constants.USER_REGISTER)
fun userRegister(#Body userRegisterRequest: UserRegisterRequest):
Call<UserRegisterResponse>
}
UserRegisterRequest.kt
data class UserRegisterRequest(
val Email: String,
val Password: String
)
UserRegisterResponse.kt
data class UserRegisterResponse(
val Message: String,
val Code: Int
)
IGenericCallBack.kt
interface IGenericCallBack {
fun success(apiName: String, response: Any?)
fun failure(apiName: String, message: String?)
}
MyApplication.kt
class MyApplication : Application() {
companion object {
lateinit var apiService: ApiInterface
}
override fun onCreate() {
super.onCreate()
apiService = ApiClient.getClient()!!.create(ApiInterface::class.java)
}
}
MyApplication is the application class to initialize Retrofit at the launch of the app.
AndroidManifest.xml
android:name=".MyApplication"
You have to write above tag in AndroidManifest inside Application tag.
MainActivity.kt
class MainActivity : AppCompatActivity(), IGenericCallBack {
private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
val call = MyApplication.apiService.userRegister(UserRegisterRequest(email, password))
SingleEnqueueCall.callRetrofit(this, call, Constants.USER_REGISTER, true, this)
}
override fun success(apiName: String, response: Any?) {
val model = response as UserRegisterResponse
}
override fun failure(apiName: String, message: String?) {
if (message != null) {
showToastMessage(message)
}
}
}
Firstly, we create a call object by using the API defined in ApiInterface and passing the parameters (if any). Then using SingleEnqueueCall, we pass the call to the retrofit along with ApiName and the interface listener IGenericCallBack by using this. Remember to implement it to respective activity or fragment as above.
Secondly, you will have the response of the API whether in success() or failure() function overriden by IGenericCallBack
P.S: You can differentiate which API got the response by using the ApiName parameter inside success() function.
override fun success(apiName: String, response: Any?) {
when(ApiName) {
Constants.USER_REGISTER -> {
val model = response as UserRegisterResponse
}
}
}
The whole concept is to focus on reusability, now every API call has to create a call variable by using the API's inside ApiInterface then call that API by SingleEnqueueCall and get the response inside success() or failure() functions.

Elegant way of handling error using Retrofit + Kotlin Flow

I have a favorite way of doing network request on Android (using Retrofit). It looks like this:
// NetworkApi.kt
interface NetworkApi {
#GET("users")
suspend fun getUsers(): List<User>
}
And in my ViewModel:
// MyViewModel.kt
class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
val usersLiveData = flow {
emit(networkApi.getUsers())
}.asLiveData()
}
Finally, in my Activity/Fragment:
//MyActivity.kt
class MyActivity: AppCompatActivity() {
private viewModel: MyViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
viewModel.usersLiveData.observe(this) {
// Update the UI here
}
}
}
The reason I like this way is because it natively works with Kotlin flow, which is very easy to use, and has a lot of useful operations (flatMap, etc).
However, I am not sure how to elegantly handle network errors using this method. One approach that I can think of is to use Response<T> as the return type of the network API, like this:
// NetworkApi.kt
interface NetworkApi {
#GET("users")
suspend fun getUsers(): Response<List<User>>
}
Then in my view model, I can have an if-else to check the isSuccessful of the response, and get the real result using the .body() API if it is successful. But it will be problematic when I do some transformation in my view model. E.g.
// MyViewModel.kt
class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
val usersLiveData = flow {
val response = networkApi.getUsers()
if (response.isSuccessful) {
emit(response.body()) // response.body() will be List<User>
} else {
// What should I do here?
}
}.map { // it: List<User>
// transform Users to some other class
it?.map { oneUser -> OtherClass(oneUser.userName) }
}.asLiveData()
Note the comment "What should I do here?". I don't know what to do in that case. I could wrap the responseBody (in this case, a list of Users) with some "status" (or simply just pass through the response itself). But that means that I pretty much have to use an if-else to check the status at every step through the flow transformation chain, all the way up to the UI. If the chain is really long (e.g. I have 10 map or flatMapConcat on the chain), it is really annoying to do it in every step.
What is the best way to handle network errors in this case, please?
You should have a sealed class to handle for different type of event. For example, Success, Error or Loading. Here is some of the example that fits your usecases.
enum class ApiStatus{
SUCCESS,
ERROR,
LOADING
} // for your case might be simplify to use only sealed class
sealed class ApiResult <out T> (val status: ApiStatus, val data: T?, val message:String?) {
data class Success<out R>(val _data: R?): ApiResult<R>(
status = ApiStatus.SUCCESS,
data = _data,
message = null
)
data class Error(val exception: String): ApiResult<Nothing>(
status = ApiStatus.ERROR,
data = null,
message = exception
)
data class Loading<out R>(val _data: R?, val isLoading: Boolean): ApiResult<R>(
status = ApiStatus.LOADING,
data = _data,
message = null
)
}
Then, in your ViewModel,
class MyViewModel(private val networkApi: NetworkApi): ViewModel() {
// this should be returned as a function, not a variable
val usersLiveData = flow {
emit(ApiResult.Loading(true)) // 1. Loading State
val response = networkApi.getUsers()
if (response.isSuccessful) {
emit(ApiResult.Success(response.body())) // 2. Success State
} else {
val errorMsg = response.errorBody()?.string()
response.errorBody()?.close() // remember to close it after getting the stream of error body
emit(ApiResult.Error(errorMsg)) // 3. Error State
}
}.map { // it: List<User>
// transform Users to some other class
it?.map { oneUser -> OtherClass(oneUser.userName) }
}.asLiveData()
In your view (Activity/Fragment), observe these state.
viewModel.usersLiveData.observe(this) { result ->
// Update the UI here
when(result.status) {
ApiResult.Success -> {
val data = result.data <-- return List<User>
}
ApiResult.Error -> {
val errorMsg = result.message <-- return errorBody().string()
}
ApiResult.Loading -> {
// here will actually set the state as Loading
// you may put your loading indicator here.
}
}
}
//this class represent load statement management operation
/*
What is a sealed class
A sealed class is an abstract class with a restricted class hierarchy.
Classes that inherit from it have to be in the same file as the sealed class.
This provides more control over the inheritance. They are restricted but also allow freedom in state representation.
Sealed classes can nest data classes, classes, objects, and also other sealed classes.
The autocomplete feature shines when dealing with other sealed classes.
This is because the IDE can detect the branches within these classes.
*/
ٍٍٍٍٍ
sealed class APIResponse<out T>{
class Success<T>(response: Response<T>): APIResponse<T>() {
val data = response.body()
}
class Failure<T>(response: Response<T>): APIResponse<T>() {
val message:String = response.errorBody().toString()
}
class Exception<T>(throwable: Throwable): APIResponse<T>() {
val message:String? = throwable.localizedMessage
}
}
create extention file called APIResponsrEX.kt
and create extextion method
fun <T> APIResponse<T>.onSuccess(onResult :APIResponse.Success<T>.() -> Unit) : APIResponse<T>{
if (this is APIResponse.Success) onResult(this)
return this
}
fun <T> APIResponse<T>.onFailure(onResult: APIResponse.Failure<*>.() -> Unit) : APIResponse<T>{
if (this is APIResponse.Failure<*>)
onResult(this)
return this
}
fun <T> APIResponse<T>.onException(onResult: APIResponse.Exception<*>.() -> Unit) : APIResponse<T>{
if (this is APIResponse.Exception<*>) onResult(this)
return this
}
merge it with Retrofit
inline fun <T> Call<T>.request(crossinline onResult: (response: APIResponse<T>) -> Unit) {
enqueue(object : retrofit2.Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
// success
onResult(APIResponse.Success(response))
} else {
//failure
onResult(APIResponse.Failure(response))
}
}
override fun onFailure(call: Call<T>, throwable: Throwable) {
onResult(APIResponse.Exception(throwable))
}
})
}

Retrofit & Moshi: Get request with sealed class & generics - Is it possible?

I have a sealed class for state handling of my Retrofit responses. It's members take a generic type. I would like to get Retrofit to be able to return the proper object, but I am stuck at this error: Unable to create converter for com.my.app.DataResult<?> - Cannot serialize abstract class com.my.app.DataResult
This is my DataResult class:
sealed class DataResult<out T> {
data class Success<out T>(val data: T?) : DataResult<T>()
data class Error<out T>(val code: Int? = null, val error: Exception? = null) : DataResult<T>()
object NetworkError : DataResult<Nothing>()
fun isSuccess() = this is Success<*>
fun isError() = this is Error<*>
fun data() = if (isSuccess()) (this as Success<T>).data else null
}
fun successResult() = DataResult.Success(null)
fun <T> successResult(data: T?) = DataResult.Success(data)
fun errorResult() = DataResult.Error<Nothing>(null)
This is the rest of my current implementation:
class NetworkClient(private val httpClient: HttpClient) {
private val baseUrl: String = "some url"
private val retrofit = Retrofit.Builder()
.baseUrl(mockend)
.addCallAdapterFactory(MyCallAdapterFactory())
.addConverterFactory(MoshiConverterFactory.create())
.client(httpClient.get())
.build()
private val apiService: ApiService = retrofit.create(StaApiService::class.java)
suspend fun <T> sendGet(endPoint: EndPoint, input: String): DataResult<T> {
val result = apiService.sendGetRequest<T>(endPoint.stringValue, queryMapOf(Pair("query", input)))
when (result) {
// do stuff here?
}
return result
}
}
interface ApiService {
#GET
suspend fun <T> sendGetRequest(
#Url url: String,
#QueryMap parameters: Map<String, String>): DataResult<T>
#GET
suspend fun <T> sendGetListRequest(
#Url url: String,
#QueryMap parameters: Map<String, String>): DataResult<List<T>>
}
abstract class CallDelegate<TIn, TOut>(
protected val proxy: Call<TIn>
) : Call<TOut> {
override fun execute(): Response<TOut> = throw NotImplementedError()
final override fun enqueue(callback: Callback<TOut>) = enqueueImpl(callback)
final override fun clone(): Call<TOut> = cloneImpl()
override fun cancel() = proxy.cancel()
override fun request(): Request = proxy.request()
override fun isExecuted() = proxy.isExecuted
override fun isCanceled() = proxy.isCanceled
abstract fun enqueueImpl(callback: Callback<TOut>)
abstract fun cloneImpl(): Call<TOut>
}
class ResultCall<T>(proxy: Call<T>) : CallDelegate<T, DataResult<T>>(proxy) {
override fun enqueueImpl(callback: Callback<DataResult<T>>) = proxy.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val code = response.code()
val result: DataResult<T> = if (code in 200 until 300) {
val body = response.body()
DataResult.Success(body)
} else {
DataResult.Error(code)
}
callback.onResponse(this#ResultCall, Response.success(result))
}
override fun onFailure(call: Call<T>, t: Throwable) {
val result: DataResult<Nothing> = if (t is IOException) {
DataResult.NetworkError
} else {
DataResult.Error(null)
}
callback.onResponse(this#ResultCall, Response.success(result))
}
})
override fun cloneImpl() = ResultCall(proxy.clone())
}
class ResultAdapter(
private val type: Type
) : CallAdapter<Type, Call<DataResult<Type>>> {
override fun responseType() = type
override fun adapt(call: Call<Type>): Call<DataResult<Type>> = ResultCall(call)
}
class MyCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit
) = when (getRawType(returnType)) {
Call::class.java -> {
val callType = getParameterUpperBound(0, returnType as ParameterizedType)
when (getRawType(callType)) {
Result::class.java -> {
val resultType = getParameterUpperBound(0, callType as ParameterizedType)
ResultAdapter(resultType)
}
else -> null
}
}
else -> null
}
}
The above code is largely inspired by this answer to another question,
but I'm trying to add Generics to the mix, so I don't have to put every request into the interface by hand. Is it possible or not? I have tried for hours, also tried to build an adapter for the sealed class but failed. Has someone a good resource how this can be done?
As you can also see in the code I'd like to also be able to receive lists. Any tips here are much appreciated too.

Retrofit call using abstraction with kotlin is not calling onChange

I want to abstract the retrofit call, so I don't need to write the same boiler plate code when I need a request.
The abstraction
open class NetworkCall<T> {
lateinit var call: Call<T>
var result: MutableLiveData<Resource<T>> = MutableLiveData()
fun makeCall(call: Call<T>) {
this.call = call
val callBackKt = CallBackKt<T>()
callBackKt.result.value = Resource.loading(null)
this.call.enqueue(callBackKt)
result = callBackKt.result
}
class CallBackKt<T> : Callback<T> {
var result: MutableLiveData<Resource<T>> = MutableLiveData()
override fun onFailure(call: Call<T>, t: Throwable) {
result.value = Resource.error()//APIError()
t.printStackTrace()
}
override fun onResponse(call: Call<T>, response: Response<T>) {
if (response.isSuccessful) {
result.value = Resource.success(response.body())
}
else {
result.value = Resource.error()
}
}
}
}
Repository
class DetailsRepository(val application: Application) {
private val myListDao =
AppDatabase.getDatabase(application)?.MyListDao()
var movie: MutableLiveData<Resource<DetailsDTO>> = MutableLiveData()
fun getDetails(id: Int) {
val networkCall = NetworkCall<DetailsDTO>()
networkCall.makeCall(Apifactory.tmdbApi.getDetails(id))
movie = networkCall.result
}
}
ViewModel
class DetailsViewModel(application: Application) : AndroidViewModel(application) {
private val repository: DetailsRepository = DetailsRepository(application)
internal var movie: LiveData<Resource<DetailsDTO>> = repository.movie
fun insert(myListItem: MyListItem) {
repository.insert(myListItem)
}
fun fetchDetails(id: Int) {
repository.getDetails(id)
}
}
Activity
viewModel.movie.observe(this, Observer {
it?.apply {
detail_title.text = data?.title
}
})
Log with in every method says it's being called in right order and that the request is working fine. Althoght onChange is not being called.
fetchDetails called
getDetails called
makeCall called
onResponse successful, result.value.data = DetailsDTO(adult=false, backdrop_path=/y8lEIjYZCi2VFP4ixtHSn2klpth.jpg, ...
...
What would be done wrong?

Return custom object from onError() of Rx java Android instead of throw-able object

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

Categories

Resources