Kotlin coroutine doesn't wait to be done - android

I have a legacy project where I want to use coroutines when contacting the backend. The backend have been handle by a sdk delivered by Hybris. It use volley for instance, and with some callbacks. What I want is to wrap those callbacks with a coroutine. But the problem I have is that the coroutine doesn't wait to be done, it start the coroutine, and keep going to next lines, and method returns a value, and long after that the coroutine finish.
My code:
suspend fun ServiceHelper.getList(): ListOfWishes {
return suspendCancellableCoroutine { continuation ->
getAllLists(object : ResponseReceiver<ListOfWishes> {
override fun onResponse(response: Response<ListOfWishes>?) {
continuation.resume(response?.data!!)
}
override fun onError(response: Response<ErrorList>?) {
val throwable = Throwable(Util.getFirstErrorSafe(response?.data))
continuation.resumeWithException(throwable)
}
}, RequestUtils.generateUniqueRequestId(), false, null, object : OnRequestListener {
override fun beforeRequest() {}
override fun afterRequestBeforeResponse() {}
override fun afterRequest(isDataSynced: Boolean) {}
})
}
}
The helper method:
suspend fun ServiceHelper.wishLists(): Deferred<ListOfWishes> {
return async(CommonPool) {
getWishList()
}
}
And where the coroutine is called:
fun getUpdatedLists(): ListOfWishes? {
val context = Injector.getContext()
val serviceHelper = Util.getContentServiceHelper(context)
var list = ListOfWishLists()
launch(Android) {
try {
list = serviceHelper.wishLists().await()
} catch (ex: Exception){
Timber.d("Error: $ex")
}
}
return list
So instead of waiting for serviceHelper.wishLists().await() is done, it return list. I have also tried to make the method return a runBlocking{}, but that only block the UI thread and doesn't end the coroutine.

Coroutines don't work like this. getUpdatedLists() method can't wait a coroutine to finish its execution without being a suspend method itself. If method getUpdatedLists() defined in Activity or Fragment, you can launch a coroutine and do something, e.g. update UI, in it after serviceHelper.wishLists().await() is executed. It will look something like this:
fun loadUpdatedLists() {
val context = Injector.getContext()
val serviceHelper = Util.getContentServiceHelper(context)
lifecycleScope.launch {
try {
val list = serviceHelper.wishLists().await()
// use list object, for example Update UI
} catch (ex: Exception){
Timber.d("Error: $ex")
}
}
}
lifecycleScope - CoroutineScope tied to LifecycleOwner's Lifecycle. To use it add dependency:
androidx.lifecycle:lifecycle-runtime-ktx:2.4.0 or higher.

Related

Kotlin executes only one of two suspend functions

In my suspend function getContent I am calling two other suspend functions:
override suspend fun getContent(token: String) {
...
getUserPosts(token)
getRecentPosts(token)
...
}
But only the first one gets executed.
If I put getRecentPosts first then only that one gets executed.
How to solve this?
I call the getContent method from ViewModel with viewModelScope.
EDIT
Here is my getUserPosts() method, the getRecentPosts() method look pretty much the same, the "Finished 1" and "Finsihed 2" Log is getting logged but not "Finished 3:
override suspend fun getUserPosts(token: String) {
try {
myInterceptor.setAccessToken(token)
graphqlAPI.query(GetUserPostsQuery()).fetchPolicy(FetchPolicy.NetworkFirst).watch()
.collect { response ->
val newList = arrayListOf<Post>()
response.data?.getUserPosts?.let {
for (post in it) {
newList.add(
Post(
id = post.postFragment.id,
...
)
)
}
userState.editUserState(userState.userStateFlow.value?.copy(posts = newList))
Log.i("#+#", "Finished 1")
}
Log.i("#+#", "Finished 2")
}
Log.i("#+#", "Finished 3")
} catch (e: ApolloException) {
e.printStackTrace()
}
}
collect never stops, so the first function will never end.
What you have to do is launch another coroutine inside.
override suspend fun getContent(token: String) {
CoroutineContex.launch { getUserPosts(token) }
CoroutineContex.launch { getRecentPosts(token) }
}
However that is considered dirty because you can't test it. That launch there won't have the same scope than the coroutine scope from testing.
Also, it won't stop, so it is better to provide the coroutine scope.
override suspend fun getContent(token: String, coroutineScope: CoroutineScope) {
coroutineScope.launch { getUserPosts(token) }
coroutineScope.launch { getRecentPosts(token) }
}
This way in your view model or fragment you can call it "safely" by knowing the scope will cancel the collection when the owner life cycle ends.
getContent(token, viewModelScope)
getContent(token, viewLifeCycleOwner.lifecycleScope)
This is not an uncommon doubt, and there is a fairly brief mention on the documentation: https://developer.android.com/topic/libraries/architecture/coroutines#lifecycle-aware

How to run a suspend function inside another one without waiting for its result?

I have a scenario where my code has to send an api call and move on with its work (which contains another api call) without waiting for the result of the first call.
Right now I do this in my viewmodel
fun showItem(id:Int) {
launch{
repo.markItemRead(id)
}
launch {
try {
val item = repo.getItemById(id).getOrThrow
commands.postValue(ShowItemCommand(item))
} catch (t:Throwable) {
commands.postValue(ShowError(R.string.error_retrieve_item))
repo.logError(t)
}
}
}
this calls the repository which has these two functions
suspend fun markItemRead(id) {
try {
service.markItemAsRead(id)
} catch(ignored:Throwable) {
}
}
suspend fun getItemById(id) : Result<ItemData> {
return try {
val response : ItemEntity = service.getItemById(id)
val item = response.toData()
Result.Success(item)
} catch (t:Throwable) {
Result.Failure(t)
}
}
I would prefer it if the repository did all those jobs because one has to follow the other every time.
Unfortunatelly when I try to do something like this in my repository:
suspend fun getItemById(id:Int) : Result<ItemData> {
try {
service.markItemAsRead(id)
} catch(ignored:Throwable) {
}
return try {
val response : ItemEntity = service.getItemById(id)
val item = response.toData()
Result.Success(item)
} catch (t:Throwable) {
Result.Failure(t)
}
}
It waits for the markItemAsRead function to finish before moving on
Other than defining a scope for the repository and putting the markItemAsRead call inside a launch (which I have read is incorrect to do inside a suspending function) is there another way of doing this inside the repository?
You can use coroutineScope or supervisorScope in the repository, depending on your needs. Both functions are designed for parallel decomposition of work. These functions return as soon as the given block and all its children coroutines are completed.
When any child coroutine in coroutineScope fails, this scope fails and all the rest of the children are cancelled. Unlike coroutineScope, a failure of a child coroutine in supervisorScope does not cause this scope to fail and does not affect its other children, so a custom policy for handling failures of its children can be implemented.
Please choose what best suits your needs. Example of usage:
suspend fun getItemByIdAndMarkRead(id: Int) : Result<ItemData> = supervisorScope {
launch {
try {
service.markItemAsRead(id)
} catch(ignored:Throwable) { }
}
return#supervisorScope withContext(Dispatchers.Default) {
try {
val response : ItemEntity = service.getItemById(id)
val item = response.toData()
Result.Success(item)
} catch (t: Throwable) {
Result.Failure(t)
}
}
}
service.markItemAsRead(id) and service.getItemById(id) will execute in parallel.
is there another way of doing this inside the repository?
No there isn't
(which I have read is incorrect to do inside a suspending function)
It is perfectly okay to use launch in a suspend function for a fire and forget task
See if the coroutineScope function does what you want:
Creates a CoroutineScope and calls the specified suspend block with this scope. The provided scope inherits its coroutineContext from the outer scope, but overrides the context's Job.
suspend fun getItemById(id:Int) : Result<ItemData> {
coroutineScope {
launch {
try {
service.markItemAsRead(id)
} catch(ignored:Throwable) { }
}
}
return try {
val response : ItemEntity = service.getItemById(id)
val item = response.toData()
Result.Success(item)
} catch (t:Throwable) {
Result.Failure(t)
}
}
you want to do multiple tasks in parallel but return function when all tasks done. if i'm right.
you can use async/await. inside suspend function
val d1 = async { t1() }
val d2 = async { t2() }
d1.await()
d2.await()
// all tasks done
t1 and t2 will be run in parallel. when t1.await() calls it's gonna wait for result but t2 still running.
in your function you can change it like this:
suspend fun getItemById(id:Int) : Result<ItemData> = coroutineScope {
val t1 = async {
try {
service.markItemAsRead(id)
} catch(ignored:Throwable) {
null
}
}
val t2 = async {
try {
val response : ItemEntity = service.getItemById(id)
val item = response.toData()
Result.Success(item)
} catch (t:Throwable) {
Result.Failure(t)
}
}
t1.await()
return#coroutineScope t2.await()
}

The withContext coroutine is not working. Using Kotlin in Android

I've been mulling this over for some time now and I just can't get it to work.
So in brief, I have a Splash Activity from where I call another activity that contains my ViewModel. The ViewModel in simple terms just needs to sequentially run function A(which is getfbdata below; it is a network call.). And only after this function completes, it should run function B (which is dosavefbdata below; save info to DB.). Again, it should wait for function B to complete before running the main thread function, function C(which is confirm first below; it checks whether function B has completed by getting the result from function B (dosavefbdata below). If function C is positive, it closes the Splash activity.
Suffice to say, none of the above works. Println results show all functions were run sequentially without waiting for each to complete. Lastly, SplashActivity().killActivity() call on function C did not work.
Note: withContext does not require to await() on the suspended functions right? I also tried using viewModelScope.async instead of viewModelScope.launch.
I would really appreciate your help here. Thanks in advance.
*Under SplashActivity:
fun killActivity(){
finish()
}
*Under onCreate(SplashActivity):
CoroutingClassViewModel(myc).initialize()
**
class CoroutingClassViewModel(val myc: Context): ViewModel() {
fun initialize() {
viewModelScope.launch(Dispatchers.Main) {
try {
val fbdata = withContext(Dispatchers.IO) { getfbdata() }
val test1 = withContext(Dispatchers.IO) { test1(fbdata) }
val savedfbdata = withContext(Dispatchers.IO) { dosavefbdata(fbdata,myc) }
val confirmfirst = { confirmfunc(savedfbdata,myc) }
println("ran savedfbdata.")
} catch (exception: Exception) {
Log.d(TAG, "$exception handled !")
}
}
}
fun confirmfunc(savedfbdata: Boolean, myc: Context){
if (savedfbdata==true){
SplashActivity().killActivity()
}
}
suspend fun getfbdata(): MutableList<FirebaseClass> {
return withContext(Dispatchers.IO) {
//perform network call
return#withContext fbdata
}
}
suspend fun dosavefbdata(fbdata: MutableList<FirebaseClass>,myc: Context): Boolean{
return withContext(Dispatchers.IO) {
//save to database
return#withContext true
}
}
suspend fun test1(fbdata: MutableList<FirebaseClass>){
return withContext(Dispatchers.IO) {
println("test1: fbdata is: $fbdata")
}
}
}
Use AndroidViewModel if you want to have Context in it:
class CoroutingClassViewModel(myc: Application) : AndroidViewModel(myc) { ... }
In onCreate method of SplashActivity activity instantiate the view model like this:
val vm = ViewModelProvider(this)[CoroutingClassViewModel::class.java]
vm.initialize()
In CoroutingClassViewModel class create LiveData object to notify activity about operations completion:
val completion = MutableLiveData<Boolean>()
fun confirmfunc(savedfbdata: Boolean, myc: Context) {
if (savedfbdata) {
completion.postValue(true)
}
}
In your SplashActivity use this code to observe completion:
vm.completion.observe(this, Observer {
if (it) killActivity()
})
You use withContext(Dispatchers.IO) function two times for the same operation. Don't do that. For example in this code:
val fbdata = withContext(Dispatchers.IO) { getfbdata() }
if we look at getfbdata function we see that function withContext(Dispatchers.IO) is already called there. So get rid of repeated calls:
val fbdata = getfbdata()
I had same issue with withContext(Dispatcher.IO), I thought that switching coroutine context doesn't work, while in fact in splash screen i launched super long operation on Dispatcher.IO, then later when trying to use the same Dispatcher.IO it didn't work or in other words it waited until the first work in splash screen finished then started the new work.

how to use Coroutine in kotlin to call a function every second

i just created an app where my function getdata() call every second to fetch new data from server and updateui() function will update view in UI i don't use any asynctask or coroutine in my app i wants to do this please tell me how i can do that.
here's my code...
private fun getdata(){
try {
val api = RetroClient.getApiService()
call = api.myJSON
call!!.enqueue(object : Callback<ProductResponse> {
override fun onResponse(
call: Call<ProductResponse>,
response: Response<ProductResponse>
) {
if (response.isSuccessful) {
productList = response.body()!!.data
for (list in productList) {
if (list.BB.equals("AAA")) {
aProductList.add(list)
}
}
if (recyclerView.adapter != null) {
eAdapter!!.updatedata(aProductList)
}
updateui()
}
}
override fun onFailure(call: Call<ProductResponse>, t: Throwable) {
println("error")
}
})
} catch (ex: Exception) {
} catch (ex: OutOfMemoryError) {
}
Handler().postDelayed({
getdata()
}, 1000)
}
private fun updateui() {
try {
//some code to handel ui
} catch (e: NumberFormatException) {
} catch (e: ArithmeticException) {
} catch (e: NullPointerException) {
} catch (e: Exception) {
}
}
To run a function every second with coroutines:
val scope = MainScope() // could also use an other scope such as viewModelScope if available
var job: Job? = null
fun startUpdates() {
stopUpdates()
job = scope.launch {
while(true) {
getData() // the function that should be ran every second
delay(1000)
}
}
}
fun stopUpdates() {
job?.cancel()
job = null
}
However, if getData() only starts a network request and doesn't wait for its completion, this might not be a very good idea. The function will be called a second after it finished, but because the network request is done asynchronously it may be scheduled way too much.
For example if the network request takes 5 seconds, it will have been started 4 more times before the first one even finished!
To fix this, you should find a way to suspend the coroutine until the network request is done.
This could be done by using a blocking api, then pass Dispatchers.IO to the launch function to make sure it's done on a background thread.
Alternatively you could use suspendCoroutine to convert a callback-based api to a suspending one.
Update - Lifecycle scope
Inside a component with a Android Lifecycle you could use the following code to automate repeating ui updates:
fun startUpdates() {
val lifecycle = this // in Activity
val lifecycle = viewLifecycleOwner // in Fragment
lifecycle.lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
// this block is automatically executed when moving into
// the started state, and cancelled when stopping.
while (true) {
getData() // the function to repeat
delay(1000)
}
}
}
}
This code requires the current androidx.lifecycle:lifecycle-runtime-ktx dependency.
The above remark about async, blocking or suspending code inside getData() still applies.
it's not advisable to hit the server every second. if you need to get data continuously try the socket. Because some times your server takes more than a few seconds to respond to your request. Then all your requests will be in a queue..if you still need to try with this.
fun repeatFun(): Job {
return coroutineScope.launch {
while(isActive) {
//do your network request here
delay(1000)
}
}
}
//start the loop
val repeatFun = repeatRequest()
//Cancel the loop
repeatFun.cancel()
For those who are new to Coroutine
add Coroutine in Build.gradle
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2'
To create a repeating Job
/**
* start Job
* val job = startRepeatingJob()
* cancels the job and waits for its completion
* job.cancelAndJoin()
* Params
* timeInterval: time milliSeconds
*/
private fun startRepeatingJob(timeInterval: Long): Job {
return CoroutineScope(Dispatchers.Default).launch {
while (NonCancellable.isActive) {
// add your task here
doSomething()
delay(timeInterval)
}
}
}
To start:
Job myJob = startRepeatingJob(1000L)
To Stop:
myJob .cancel()
I ended up doing like this with an extension function:
fun CoroutineScope.launchPeriodicAsync(repeatMillis: Long, action: () -> Unit) = this.async {
while (isActive) {
action()
delay(repeatMillis)
}
}
then call it like:
val fetchDatesTimer = CoroutineScope(Dispatchers.IO)
.launchPeriodicAsync(TimeUnit.MINUTES.toMillis(1)) {
viewModel.fetchDeliveryDates()
}
and cancel it like:
fetchDatesTimer.cancel()
My solution in Kotlin inside MainViewModel
fun apiCall() {
viewModelScope.launch(Dispatchers.IO) {
while(isActive) {
when(val response = repository.getServerData()) {
is NetworkState.Success -> {
getAllData.postValue(response.data)
}
is NetworkState.Error -> this#MainViewModel.isActive = false
}
delay(1000)
}
}
}
sealed class NetworkState<out R> {
data class Success<out T>(val data: T): NetworkState<T>()
data class Error(val exception: String): NetworkState<Nothing>()
object Loading: NetworkState<Nothing>()
}
My solution for one time running a code after check for something is successful and checking for that periodically, function is:
fun CoroutineScope.launchPeriodic(repeatMillis: Long, action: () -> Unit) : Job {
return launch {
while (!enabled) {
action()
delay(repeatMillis)
}
}
}
and start periodic function here (in which action runs every 2 seconds), which automatically ends up when something is enabled and some code run:
CoroutineScope(Dispatchers.IO).launchPeriodic(TimeUnit.SECONDS.toMillis(2)) {
if(checkIfSomethingIsEnabledCodeIsHere) {
enabled = true
//some code here to run when it is enabled
}
}

Making synchronous calls to Cloud Firestore when running off the main thread

I am building an app based off of the Android Clean Architecture Kotlin version (https://github.com/android10/Android-CleanArchitecture-Kotlin).
Using this architecture, each time you want to invoke a use case, a Kotlin coroutine is launched and the result is posted in the main thread. This is achieved by this code:
abstract class UseCase<out Type, in Params> where Type : Any {
abstract suspend fun run(params: Params): Either<Failure, Type>
fun execute(onResult: (Either<Failure, Type>) -> Unit, params: Params) {
val job = async(CommonPool) { run(params) }
launch(UI) { onResult.invoke(job.await()) }
}
In his example architecture, Mr. Android10 uses Retrofit to make a synchronous api call inside the kotlin couroutine. For example:
override fun movies(): Either<Failure, List<Movie>> {
return when (networkHandler.isConnected) {
true -> request(service.movies(), { it.map { it.toMovie() } }, emptyList())
false, null -> Left(NetworkConnection())
}
}
private fun <T, R> request(call: Call<T>, transform: (T) -> R, default: T): Either<Failure, R> {
return try {
val response = call.execute()
when (response.isSuccessful) {
true -> Right(transform((response.body() ?: default)))
false -> Left(ServerError())
}
} catch (exception: Throwable) {
Left(ServerError())
}
}
'Either' represents a disjoint type, meaning the result will either be a Failure or the object of type T you want.
His service.movies() method is implemented like so (using retrofit)
#GET(MOVIES) fun movies(): Call<List<MovieEntity>>
Now here is my question. I am replacing retrofit with Google Cloud Firestore. I know that currently, Firebase/Firestore is an all async library. I want to know if anyone knows of a method more elegant way of making a synchronous API call to Firebase.
I implemented my own version of Call:
interface Call<T: Any> {
fun execute(): Response<T>
data class Response<T>(var isSuccessful: Boolean, var body: T?, var failure: Failure?)
}
and my API call is implemented here
override fun movieList(): Call<List<MovieEntity>> = object : Call<List<MovieEntity>> {
override fun execute(): Call.Response<List<MovieEntity>> {
return movieListResponse()
}
}
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
var response: Call.Response<List<MovieEntity>>? = null
FirebaseFirestore.getInstance().collection(DataConfig.databasePath + MOVIES_PATH).get().addOnCompleteListener { task ->
response = when {
!task.isSuccessful -> Call.Response(false, null, Failure.ServerError())
task.result.isEmpty -> Call.Response(false, null, MovieFailure.ListNotAvailable())
else -> Call.Response(true, task.result.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
}
}
while (response == null)
Thread.sleep(50)
return response as Call.Response<List<MovieEntity>>
}
Of course, the while loop at the end bothers me. Is there any other, more elegant ways, to wait for the response to be assigned before returning from the movieListResponse method?
I tried calling await() on the Task that is returned from the Firebase get() method, but the movieListResponse method would return immediately anyway. Thanks for the help!
So I found what I was looking for in the Google Tasks API: "If your program is already executing in a background thread you can block a task to get the result synchronously and avoid callbacks" https://developers.google.com/android/guides/tasks#blocking
So my previous problematic code becomes:
private fun movieListResponse(): Call.Response<List<MovieEntity>> {
return try {
val taskResult = Tasks.await(FirebaseFirestore.getInstance().
collection(DataConfig.databasePath + MOVIES_PATH).get(), 2, TimeUnit.SECONDS)
Call.Response(true, taskResult.mapTo(ArrayList()) { MovieEntity.fromSnapshot(it) }, null)
} catch (e: ExecutionException) {
Call.Response(false, null, Failure.ServerError())
} catch (e: InterruptedException) {
Call.Response(false, null, Failure.InterruptedError())
} catch (e: TimeoutException) {
Call.Response(false, null, Failure.TimeoutError())
}
}
Note I no longer need my Thread.sleep while loop.
This code should only be run in a background thread/kotlin coroutine.
This is overengineered, there are several layers trying to do the same thing. I suggest you go back a few steps, undo the abstractions and get into the mood of using coroutines directly. Implement a suspend fun according to this template. You don't need the crutches of Either, handle exceptions in the most natural way: a try-catch around a suspend fun call.
You should end up with a signature as follows:
suspend fun movieList(): List<MovieEntity>
Call site:
launch(UI) {
try {
val list = movieList()
...
} catch (e: FireException) {
// handle
}
}
That's is not the way how firebase works. Firebase is based on callback.
I recommend architecture component's livedata.
Please check the following example.
here is a link: https://android.jlelse.eu/android-architecture-components-with-firebase-907b7699f6a0

Categories

Resources