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.
Related
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.
I am reading about Kotlin coroutine in Google 's documentation. I'm adviced to use withContext(Dispacher.IO) to a different thread to main-safety. But I have a problem , fetchData() done before response from server so fetchData() return null result. Any help that I appreciate.
https://developer.android.com/kotlin/coroutines/coroutines-best-practices#main-safe
class GameRemoteDataSource #Inject constructor(val api : GameApi) {
val IODispatcher: CoroutineDispatcher = Dispatchers.IO
suspend fun fetchData() : Resource<ListGameResponse> {
var resource : Resource<ListGameResponse> = Resource.loading(null)
withContext(IODispatcher){
Log.d("AAA Thread 1", "${Thread.currentThread().name}")
api.getAllGame(page = 1).enqueue(object : Callback<ListGameResponse>{
override fun onResponse(
call: Call<ListGameResponse>,
response: Response<ListGameResponse>
) {
if(response.code()==200){
resource = Resource.success(response.body())
}else{
resource = Resource.success(response.body())
}
Log.d("AAA code",response.code().toString())
}
override fun onFailure(call: Call<ListGameResponse>, t: Throwable) {
resource = Resource.error(t.message.toString(),null)
Log.d("AAA Thread", "${Thread.currentThread()}")
}
})
Log.d("AAA Thread", "${Thread.currentThread()}")
Log.d("AAA resource",resource.data.toString()+ resource.status.toString())
}
return resource
}
}
withContext is not helpful for converting an asynchronous function with callback into suspending code that can be used in a coroutine. It is more applicable to converting synchronous blocking code. Your non-working strategy of creating an empty variable and trying to fill it in the callback to synchronously return is described in the answers to this question.
For an asynchronous function with callback, if it returns a single value like your code above, this is typically converted to a suspend function using suspendCoroutine or suspendCancellableCoroutine. If it returns a series of values over time (calls the callback multiple times), it would be fitting to use callbackFlow to convert it to a Flow that can be collected in a coroutine.
But it looks like you're using Retrofit, which already has a suspend function alternatives to enqueue so you don't need to worry about all this. You can use the await() or awaitResponse() functions instead. In this case, await() would return ListGameResponse and awaitResponse() would return Response<ListGameResponse>. So awaitResponse() is better if you need to check the response code.
Awaiting returns the response and throws an exception if there's an error, so you can use try/catch instead of adding a failure listener.
class GameRemoteDataSource #Inject constructor(val api : GameApi) {
suspend fun fetchData(): Resource<ListGameResponse> {
return try {
val response = api.getAllGame(page = 1).awaitResponse()
Log.d("AAA code", response.code().toString())
Resource.success(response.body())
} catch (exception: Exception) {
Resource.error(exception.message.toString(),null)
}
}
}
You should use suspendCancellableCoroutine to convert asynchronous API into a coroutine flow, like this
suspend fun fetchData(): ListGameResponse = withTimeout(Duration.seconds(60)) {
suspendCancellableCoroutine<ListGameResponse> { cont ->
api.getAllGame(page = 1).enqueue(object : Callback<ListGameResponse> {
override fun onResponse(
call: Call<ListGameResponse>,
response: Response<ListGameResponse>
) {
Log.d("AAA code", response.code().toString())
cont.resume(response.body())
}
override fun onFailure(call: Call<ListGameResponse>, t: Throwable) {
cont.resumeWithException(t)
}
})
}
}
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))
}
})
}
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)
}
How can I return a value after a callback in kotlin, I tried using Thread.sleep but it doesn't work
fun searchColorFromAPI(): Colors {
val service: RetrofitService = ServiceGenerator.createService(RetrofitService::class.java)
val result: MutableList<String> = arrayListOf()
val call: Call<Colors?>? = service.unityConverter(result)
call?.enqueue(object : Callback<Colors?> {
override fun onResponse(call: Call<Colors?>?, response: Response<Colors?>) {
//switchProgressVisibility()
if (response.isSuccessful) {
val serviceResponse: Colors? = response.body()
if (serviceResponse != null) {
mColors = serviceResponse
}
else {
//buildToast(getString(R.string.null_response))
}
}
else {
//buildToast(getString(R.string.response_unsuccessful))
val errorBody: ResponseBody = response.errorBody()
Log.e(TAG, errorBody.toString())
}
}
override fun onFailure(call: Call<Colors?>?, t: Throwable?) {
/* buildToast(getString(R.string.error_calling_service))
Log.e(TAG, t?.message)*/
}
})
return mColors
}
Always, the mColors is returned before the onFailure or onResponse because they're asynchronous. Before this code was in MainActivity but I was advised to take off, but now when I try get mColors I get the empty value before and after the onResponse is executed, please I'm still learning Kotlin and Android.
Your problem stems from the fact that Retrofit call is asynchronous, so as soon as you call searchColorFromAPI it returns you mColors but the API call may not have been made yet, so you get the mColors value before API call.
To solve this issue, you can do
Use callback, this will require little modification in your current setup, but the 2nd option is preferable over this. Using callback your function should look like this.
/* Now instead of returning a value, your function takes a function (named callback)
as parameter. when your api call finishes, you can call the callback function and
pass the api response.
*/
fun searchColorFromAPI(callback: (Colors?) -> Unit) {
val service: RetrofitService = ServiceGenerator.createService(RetrofitService::class.java)
val result: MutableList<String> = arrayListOf()
val call: Call<Colors?>? = service.unityConverter(result)
call?.enqueue(object : Callback<Colors?> {
override fun onResponse(call: Call<Colors?>?, response: Response<Colors?>) {
//switchProgressVisibility()
if (response.isSuccessful) {
val serviceResponse: Colors? = response.body()
/** pass API response to callback */
callback(serviceResponse)
}
else {
val errorBody: ResponseBody = response.errorBody()
Log.e(TAG, errorBody.toString())
callback(null)
}
}
override fun onFailure(call: Call<Colors?>?, t: Throwable?) {
callback(null)
}
})
}
And in your activity declare a function as follows.
// This function will be called when your api call finishes
// and it will give you the api response
fun apiCallback(colors: Colors?){
if(colors == null){
// API Call failed
}
else{
// use colors as returned by API
}
}
And now call to searchColorFromApi should look like this
searchColorFromApi(apiCallback)
Use Live Data, declare following field in your viewmodel, if you are not using viewmodel then declare it in the class which has searchColorFromApi function.
var colors: MutableLiveData<Colors> = MutableLiveData()
and modify your searchColorFromAPI function as follows
fun searchColorFromAPI() {
val service: RetrofitService = ServiceGenerator.createService(RetrofitService::class.java)
val result: MutableList<String> = arrayListOf()
val call: Call<Colors?>? = service.unityConverter(result)
call?.enqueue(object : Callback<Colors?> {
override fun onResponse(call: Call<Colors?>?, response: Response<Colors?>) {
//switchProgressVisibility()
if (response.isSuccessful) {
val serviceResponse: Colors? = response.body()
if (serviceResponse != null) {
colors.postValue(response.body)
}
}
else {
colors.postValue(null)
val errorBody: ResponseBody = response.errorBody()
Log.e(TAG, errorBody.toString())
}
}
override fun onFailure(call: Call<Colors?>?, t: Throwable?) {
colors.postValue(null)
}
})
}
and in your activity do following
fun setupObservers(){
yourApiCallingClass.colors.observe(this, Observer {
// this code is called when ever value of color field changes
})
}
You can use live data ,that gets updated once the callback receives ,the same live data is observed by the caller fragment/activity
You can use coroutines to return a value from function which has asyn calls in it.
You can use interface callbacks to activity/ fragment to trigger the updates received from retrofit calls.