Android - create callback using Flow - android

I'm trying to understand Flow but few thing are not clear to me. I have a simple interface :
interface Operation<T> {
fun performAsync(callback: (T? , Throwable?) -> Unit)
fun cancel()
}
then I have a manager class with function :
fun<T : Any> Operation<T>.perform(): Flow<T> =
callbackFlow {
performAsync {
value , exception ->
when {
exception !=null -> close(exception) //operation had failed
value == null -> close() //operation had succeeded
else -> offer(value as T)
}
}
awaitClose { cancel() }
}
let's say I have a very simple operation - trying do use gson to serialize object to JSON :
fun convert () {
try {
val carJSON = gson.toJson(carObj)
//send Car value
} catch (e : Exception) {
//here I want to send exception and receive it in callback in activity/fragment.
}
}
Can you explain me, please, how to observe Exception (or T value) and send/receive it in Activity/Fragment ?

How about you use a sealed class.
sealed class Operation<out V, out E> {
data class Result<out V> (val data: V) : Operation<V, Nothing>()
data class Error<out E> (val error: E) : Operation<Nothing, E>()
companion object {
inline fun <V> build(operation : () -> V) : Response<V, Exception> {
return try {
Value(operation.invoke())
} catch (e: Exception) {
Error(e)
}
}
}
Then when you need response from some callback function as flow do the following.
fun someFunction() : Operation<Flow<String>, Exception> {
val flow = callbackFlow<String> {
val callback = object : someCallback {
onResult(result: String) {
offer(result)
}
onError(e: Exception) {
throw e
}
}
awaitClose { callback.remove() } //Just an example of a callback
}
return Operation.build { flow }
}
Now in Activity,
Simply get the result within some method like,
when(someFunction) {
is Operation.Result -> collect(someFunction().data)
is Operation.Error -> handleErrorAsHoweverYouWant(someFunction().error)
}

Related

Handling Api errors with wrong querys

When I enter the city name correctly everything goes fine but when the user enters the wrong city name it causes this error
{
"error": {
"code": 1006,
"message": "No matching locations found."}
}
How can I handle this error?
Api
interface Api{
#GET("forecast.json")
suspend fun getCurrentTemp(#Query("key")key : String, #Query("q")q: String,
#Query("days")days : Int): Response<Weatherapi>
companion object {
operator fun invoke(
):Api{
return Retrofit.Builder().baseUrl("https://api.weatherapi.com/v1/")
.addConverterFactory(GsonConverterFactory.create())
.build().create(Api::class.java)
}
}
}
Repository:
abstract class repositoryApi {
suspend fun <T : Any> CustomResponse(work: () ->Response <T>): T {
val response: Response<T> = work.invoke()
if (response.isSuccessful)
return response.body()!!
throw Exception(response.message())
}
}
handelRequst:
object handelRequst: repositoryApi() {
suspend fun <T:Any> Requst (response: Response<T>) = CustomResponse { response } }
handelCoroutins:
object handelCoroutins {
fun <T:Any> ThreadMain(work:suspend (() -> T) ,callback : ((T) -> Unit),ErrorMessage :
((String) -> Unit))=
CoroutineScope(Dispatchers.Main).launch {
try{
val data :T = CoroutineScope(Dispatchers.IO).async rt#{
return#rt work()
}.await()
callback(data)
}catch(e : IOException){
ErrorMessage.invoke("Error C")
}
}
}
viewModel:
class viewModelapi: ViewModel() {
val LivedataErrorhandel = MutableLiveData<String>()
var weather = MutableLiveData<Weatherapi>()
lateinit var job: Job
fun Gethome(key :String , q :String ,days :Int) {
try {
job = handelCoroutins.ThreadMain(
{
handelRequst.Requst(Api.invoke().getCurrentTemp(key ,q ,days))
},
{
weather.value = it
}, {
LivedataErrorhandel.value = it
}
)
} catch (e: IOException) {
LivedataErrorhandel.value = "Error C"
}
}
}
main Activity :
viewmodel.weather.observe(requireActivity(), Observer{
textViewtemp.text = it.current.temp_c.toString()
}
I'm not giving the full answer I just give you an idea of how you can handle this. Here's some code you might look at carefully I hope you can take this your way.
if (response.isSuccessful) {
return response.body()!!
} else {
//this is a json object that you should return for handle error
var error:JSONObject? = null
try {
//heres I convert error response to json object
error = JSONObject(response.errorBody()!!.charStream().readText())
//you may know how can you get this exception on your api implementation area
throw CustomException(error)
} catch (e: JSONException) {
throw Exception("Something is wrong !! ")
}
}
CustomException class
class CustomException(error:JsonObject):Exception()
heres how you should implement
try {
job = handelCoroutins.ThreadMain(
{
handelRequst.Requst(Api.invoke().getCurrentTemp(key ,q ,days))
},
{
weather.value = it
}, {
LivedataErrorhandel.value = it
}
)
} catch (e: IOException) {
LivedataErrorhandel.value = "Error C"
}catch(error:CustomException){
//heres you got the json object }

How to emit value from the catch block of Kotlin flow

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?

How to implement loadstate with adapter, recyclerView?

I would like to implement a feature like loadStateFlow in Paging 3.
I do not use pagination in my implementation and it is not necessary in my case.
Could I make it another way?
I have found something like LoadingStateAdapter library
https://developer.android.com/reference/kotlin/androidx/paging/LoadStateAdapter
For now I get a list using method in the fragment:
private fun collectNotificationItems() {
vm.notificationData.collectWith(viewLifecycleOwner) {
notificationAdapter.items = it
}
}
This is implementation I would like to achieve, example is in paging3:
private fun collectItems() {
vm.items.collectWith(viewLifecycleOwner, adapter::submitData)
adapter.loadStateFlow.collectWith(viewLifecycleOwner) { loadState ->
vm.setLoadingState(loadState.refresh is LoadState.Loading)
val isEmpty =
loadState.source.refresh is LoadState.NotLoading && loadState.append.endOfPaginationReached && archiveAdapter.itemCount < 1
vm.setEmptyStateVisible(isEmpty)
}
}
Where methods are:
in ViewModel
fun setLoadingState(isLoading: Boolean) {
_areShimmersVisible.value = isLoading && !_isSwipingToRefresh.value
if (!isLoading) _isSwipingToRefresh.value = false
}
areShimmers and isSwiping are MutableStateFlow
Could you recommend any other options?
EDIT:
I have the whole implementation a little bit different.
I have use case to make it
class GetListItemDetailsUseCase #Inject constructor(private val dao: Dao): BaseFlowUseCase<Unit, List<ItemData>>() {
override fun create(params: Unit): Flow<List<ItemData>> {
return flow{
emit(dao.readAllData())
}
}
}
For now it looks like the code above.
How to use DateState in that case?
EDIT2:
class GetNotificationListItemDetailsUseCase #Inject constructor(private val notificationDao: NotificationDao): BaseFlowUseCase<Unit, DataState<List<NotificationItemsResponse.NotificationItemData>>>() {
override fun create(params: Unit): Flow<DataState<List<NotificationItemsResponse.NotificationItemData>>> {
return flow{
emit(DataState.Loading)
try {
emit(DataState.Success(notificationDao.readAllDataState()))
} catch(e: Exception) {
emit(DataState.Error(e)) // error, and send the exception
}
}
}
}
DAO
#Query("SELECT * FROM notification_list ORDER BY id ASC")
abstract suspend fun readAllDataState(): DataState<List<NotificationItemsResponse.NotificationItemData>>
/\ error beacause of it:
error: Not sure how to convert a Cursor to this method's return type
fragment
private suspend fun collectNotificationItems() {
vm.notificationData.collectLatest { dataState ->
when(dataState) {
is DataState.Error -> {
collectErrorState()
Log.d("collectNotificationItems", "Collect ErrorState")
}
DataState.Loading -> {
Log.d("collectNotificationItems", "Collect Loading")
}
is DataState.Success<*> -> {
vm.notificationData.collectWith(viewLifecycleOwner) {
notificationAdapter.items = it
notificationAdapter.notifyDataSetChanged()
Log.d("collectNotificationItems", "Collect Sucess")
}
}
}
}
You could use a utility class (usually called DataState or something like that).
sealed class DataState<out T> {
data class Success<out T>(val data: T) : DataState<T>()
data class Error(val exception: Exception) : DataState<Nothing>()
object Loading : DataState<Nothing>()
}
Then, you change your flow's return type from Flow<YourObject> to Flow<DataState<YourObject>> and emit the DataStates within a flow {} or channelFlow {} block.
val notificationsFlow: Flow<DataState<YourObject>> get() = flow {
emit(DataState.Loading) // when you collect, you will receive this DataState telling you that it's loading
try {
// networking/database stuff
emit(DataState.Success(yourResultObject))
} catch(e: Exception) {
emit(DataState.Error(e)) // error, and send the exception
}
}
Finally, just change your collect {} to be like:
notificationsFlow.collectLatest { dataState ->
when(dataState) {
is DataState.Error -> { } // error occurred, deal with it here
DataState.Loading -> { } // it's loading, show progress bar or something
is DataState.Success -> { } // data received from the flow, access it with dataState.data
}
}
For more information on this regard, check this out.

Replace custom callback interface by coroutine?

Android Studio 3.6
My custom callback interface:
interface RecoveryPasswordConfirmCodeCallback {
fun onSuccess()
fun onError(ex: Throwable?)
}
Use:
val result = TransportService.recoverPasswordConfirmCode(
confirmCode,
ex,
object : RecoveryPasswordConfirmCodeCallback {
override fun onSuccess() {
}
override fun onError(ex: Throwable?) {
if (ex is InvalidOtpException) {
toastMessage.value = SingleEvent(
getApplication<Application>().applicationContext.getString(
R.string.incorrect_confirm_code
)
)
} else {
toastMessage.value = SingleEvent(
getApplication<Application>().applicationContext.getString(
R.string.default_error_message
))
}
}
})
fun recoverPasswordConfirmCode(
confirmCode: String,
ex: NeedTfaException,
callBack: RecoveryPasswordConfirmCodeCallback
) {
//some code here
}
Nice. It's work fine. But... is it possible to replace my custom callback interface by Kotlin's coroutine. I don't want to create custom interface only for execute method recoverPasswordConfirmCode
You can convert recoverPasswordConfirmCode() to a suspend function and return the result in the form of a sealed class to indicate if it's an error or the valid response. Something like this:
// Generic response class
sealed class Response<out T>{
data class Error(val ex: Throwable) : Response<Nothing>()
data class Data<T>(val data: T) : Response<T>()
}
// in your TransportService class
suspend fun recoverPasswordConfirmCode(confirmCode, ex): Response<RecoverPasswordResponse>{
// Do your stuff here
// return Response.Data<RecoverPasswordResponse>(/* your data object here */)
}
Then call it like this and check the response type:
val result = TransportService.recoverPasswordConfirmCode(confirmCode, ex)
when(result){
is Response.Error -> // Do something
is Response.Data -> // Do something
}
Note that you will have to call the suspend function inside a coroutine context.
You don't need to create a custom interface. Consume your API like this:
suspend fun recoverPasswordConfirmCode(confirmCode: String): YourReturnType = suspendCancellableCoroutine { cont ->
try {
val result = //Do your blocking API calls here
if(result.code == confirmCode) //Check confirm code is correct
cont.resume(YourResult) //Return your result here
else
cont.resumeWithException(YourException) //Throw an exception otherwise
} catch (e: Exception) {
cont.resumeWithException(e)
}
}
Call recoverPasswordConfirmCode method inside a Coroutine Scope.

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