Close long running task in CoroutineScope Android Kotlin - android

I have outdated update system in my app, it is outdated because use AsyncTask. In ViewModel the task is called, and then start download apk file. While app download file function send to interface % how much file is downloaded. When I go back to settings and go again to update activity, the interface still remeber previous % and still download from previous task.
This is how I execute task.
fun startUpdate( listener: ApplicationUpdaterResponder) {
coroutine.launch {
try {
updater.tryUpdate(listener, AppVariant.Companion)
} catch (e: Exception) {
setMessage(e.message.toString())
Log.i(TAG, e.printStackTrace().toString())
}
}
}
I trying to stop this task by close coroutine but it dont work
fun stopJob() {
updater.stopUpdate()
coroutine.cancel("Przerwano")
}
This is how look tryUpdate
class DownloadAPK(val context: Context): AsyncTask<String, String, String>() {
override fun doInBackground(vararg params: String?): String {
//here fille download and update listener(interface)
//listener.setPercent((progress*100)/total)
}
Result must be this : Like user go out from update activity, the task stops, and if user go again to update it should start everything from zero.
Now its still remember previous task and two values are send to interface old and new.
I try to execute task with CoroutineScope and try cancel functionbut its dont work.
I also try to define DownloadAPK(val context: Context) as variable and then on variable use .cancel(true) but it still dont work
Edit//
I tried zahid proposition but this still dont work
private var job : Job? = null
fun startUpdate(context: Context, listener: ApplicationUpdaterResponder) {
job = viewModelScope.launch(IO) {
try {
val updater = ApplicationUpdater(context)
updater.tryUpdate(listener, AppVariant.Companion.TransportFP4)
} catch (e: Exception) {
FirebaseCrashlytics.getInstance().recordException(e)
setMessage(e.message.toString())
}
}
}
fun stopJob() {
job!!.cancel()
}
This is how it look on image: in viewModel I start coroutineScope and then it start AsyncTask
This is how it look on image: in viewModel I start coroutineScope and then it start AsyncTask

You can create co-routine this way and than close it.
val job= CoroutineScope(Dispatchers.Default).launch {
//write your code here
}
//write your code here
job.cancel()

Related

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.

Make part of coroutine continue past cancellation

I have a file managing class that can save a big file. The file manager class is an application singleton, so it outlives my UI classes. My Activity/Fragment can call the save suspend function of the file manager from a coroutine and then show success or failure in the UI. For example:
//In MyActivity:
private fun saveTheFile() = lifecycleScope.launch {
try {
myFileManager.saveBigFile()
myTextView.text = "Successfully saved file"
} catch (e: IOException) {
myTextView.text = "Failed to save file"
}
}
//In MyFileManager
suspend fun saveBigFile() {
//Set up the parameters
//...
withContext(Dispatchers.IO) {
//Save the file
//...
}
}
The problem with this approach is that I don't want the save operation to be aborted if the Activity is finished. If the activity is destroyed before the withContext block gets going, or if the withContext block has any suspension points in it, then saving will not be completed because the coroutine will be canceled.
What I want to happen is that the file is always saved. If the Activity is still around, then we can show UI updates on completion.
I thought one way to do it might be to start a new coroutineScope from the suspend function like this, but this scope still seems to get cancelled when its parent job is cancelled.
suspend fun saveBigFile() = coroutineScope {
//...
}
I thought another alternative might be to make this a regular function that updates some LiveData when it's finished. The Activity could observe the live data for the result, and since LiveData automatically removes lifecycle observers when they're destroyed, the Activity is not leaked to the FileManager. I'd like to avoid this pattern if the something less convoluted like the above can be done instead.
//In MyActivity:
private fun saveTheFile() {
val result = myFileManager.saveBigFile()
result.observe(this#MyActivity) {
myTextView.text = when (it) {
true -> "Successfully saved file"
else -> "Failed to save file"
}
}
}
//In MyFileManager
fun saveBigFile(): LiveData<Boolean> {
//Set up the parameters
//...
val liveData = MutableLiveData<Boolean>()
MainScope().launch {
val success = withContext(Dispatchers.IO) {
//Save the file
//...
}
liveData.value = success
}
return liveData
}
You can wrap the bit that you don't want to be cancelled with NonCancellable.
// May cancel here.
withContext(Dispatchers.IO + NonCancellable) {
// Will complete, even if cancelled.
}
// May cancel here.
If you have code whose lifetime is scoped to the lifetime of the whole application, then this is a use case for the GlobalScope. However, just saying GlobalScope.launch is not a good strategy because you could launch several concurrent file operations that may be in conflict (this depends on your app's details). The recommended way is to use a globally-scoped actor, in the role of an executor service.
Basically, you can say
#ObsoleteCoroutinesApi
val executor = GlobalScope.actor<() -> Unit>(Dispatchers.IO) {
for (task in channel) {
task()
}
}
And use it like this:
private fun saveTheFile() = lifecycleScope.launch {
executor.send {
try {
myFileManager.saveBigFile()
withContext(Main) {
myTextView.text = "Successfully saved file"
}
} catch (e: IOException) {
withContext(Main) {
myTextView.text = "Failed to save file"
}
}
}
}
Note that this is still not a great solution, it retains myTextView beyond its lifetime. Decoupling the UI notifications from the view is another topic, though.
actor is labeled as "obsolete coroutines API", but that's just an advance notice that it will be replaced with a more powerful alternative in a future version of Kotlin. It doesn't mean it's broken or unsupported.
I tried this, and it appears to do what I described that I wanted. The FileManager class has its own scope, though I suppose it could also be GlobalScope since it's a singleton class.
We launch a new job in its own scope from the coroutine. This is done from a separate function to remove any ambiguity about the scope of the job. I use async
for this other job so I can bubble up exceptions that the UI should respond to.
Then after launch, we await the async job back in the original scope. await() suspends until the job is completed and passes along any throws (in my case I want IOExceptions to bubble up for the UI to show an error message). So if the original scope is cancelled, its coroutine never waits for the result, but the launched job keeps rolling along until it completes normally. Any exceptions that we want to ensure are always handled should be handled within the async function. Otherwise, they won't bubble up if the original job is cancelled.
//In MyActivity:
private fun saveTheFile() = lifecycleScope.launch {
try {
myFileManager.saveBigFile()
myTextView.text = "Successfully saved file"
} catch (e: IOException) {
myTextView.text = "Failed to save file"
}
}
class MyFileManager private constructor(app: Application):
CoroutineScope by MainScope() {
suspend fun saveBigFile() {
//Set up the parameters
//...
val deferred = saveBigFileAsync()
deferred.await()
}
private fun saveBigFileAsync() = async(Dispatchers.IO) {
//Save the file
//...
}
}

Handling file download with gRPC on Android

I currently have a gRPC server which is sending chunks of a video file. My android application written in Kotlin uses coroutines for UI updates (on Dispatchers.MAIN) and for handling a unidirectional stream of chunks (on Dispatchers.IO). Like the following:
GlobalScope.launch(Dispatchers.Main) {
viewModel.downloadUpdated().accept(DOWNLOAD_STATE.DOWNLOADING) // MAKE PROGRESS BAR VISIBLE
GlobalScope.launch(Dispatchers.IO) {
stub.downloadVideo(request).forEach {
file.appendBytes(
it.data.toByteArray()
)
}
}.join()
viewModel.downloadUpdated().accept(DOWNLOAD_STATE.FINISHED) // MAKE PROGRESS BAR DISAPPEAR
} catch (exception: Exception) {
viewModel.downloadUpdated().accept(DOWNLOAD_STATE.ERROR) // MAKE PROGRESS BAR DISAPPEAR
screenNavigator.showError(exception) // SHOW DIALOG
}
}
This works pretty well but I wonder if there is not a 'cleaner' way to handle downloads. I already know about DownloadManager but I feel like it only accepts HTTP queries and so I can't use my gRPC stub (I might be wrong, please tell me if so). I also checked WorkManager, and here is the same problem I do not know if this is the proper way of handling that case.
So, there are two questions here:
Is there a way to handle gRPC queries in a clean way, meaning that I can now when it starts, finishes, fails and that I can cancel properly?
If not, is there a better way to use coroutines for that ?
EDIT
For those interested, I believe I came up with a dummy algorithm for downloading while updating the progress bar (open to improvments):
suspend fun downloadVideo(callback: suspend (currentBytesRead: Int) -> Unit) {
println("download")
stub.downloadVideo(request).forEach {
val data = it.data.toByteArray()
file.appendBytes(data)
callback(x) // Where x is the percentage of download
}
println("downloaded")
}
class Fragment : CoroutineScope { //NOTE: The scope is the current Fragment
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job
fun onCancel() {
if (job.isActive) {
job.cancel()
}
}
private suspend fun updateLoadingBar(currentBytesRead: Int) {
println(currentBytesRead)
}
fun onDownload() {
launch(Dispatchers.IO) {
downloadVideo { currentBytes ->
withContext(Dispatchers.Main) {
updateLoadingBar(currentBytes)
if (job.isCancelled)
println("cancelled !")
}
}
}
}
}
For more info, please check: Introduction to coroutines
EDIT 2
As proposed in comments we could actually use Flows to handle this and it would give something like:
suspend fun foo(): Flow<Int> = flow {
println("download")
stub.downloadVideo(request).forEach {
val data = it.data.toByteArray()
file.appendBytes(data)
emit(x) // Where x is the percentage of download
}
println("downloaded")
}
class Fragment : CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job
fun onCancel() {
if (job.isActive) {
job.cancel()
}
}
private suspend fun updateLoadingBar(currentBytesRead: Int) {
println(currentBytesRead)
}
fun onDownload() {
launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
foo()
.onCompletion { cause -> println("Flow completed with $cause") }
.catch { e -> println("Caught $e") }
.collect { current ->
if (job.isCancelled)
return#collect
updateLoadingBar(current)
}
}
}
}
}
gRPC can be many things so in that respect your question is unclear. Most importantly, it can be fully async and callback-based, which would mean it can be turned into a Flow that you can collect on the main thread. File writing, however, is blocking.
Your code seems to send the FINISHED signal right away, as soon as it has launched the download in the background. You should probably replace launch(IO) with withContext(IO).

Why coroutine's await function doesn't finish?

I am trying coroutines to make network request using Retrofit on background thread. First of all, I changed Retrofit's Call to Deferred. Here's how it looks like:
#GET("some_endpoint")
fun getData(#Query("id") id: Int): Deferred<JsonObject>
So, in order to use above function, I created suspend function and used await. Here's how it looks:
suspend fun getNetworkData(id: Int): Resource<JsonObject> {
try {
val data = api.getData(id).await()
return Resource.success(data)
} catch (e: Exception) {
return Resource.error()
}
}
But, when I debug my app, await never finishes. Break point never comes to return statement. So, I decided to replace Deferred to Retrofit's Call again. And, instead of await, I decided to use Retrofit's execute. Of course, I removed suspend keyword.
Here's how it looks now:
#GET("some_endpoint")
fun getData(#Query("id") id: Int): Call<JsonObject>
fun getNetworkData(id: Int): Resource<JsonObject> {
try {
val data = api.getData(id).execute()
return Resource.success(data.body())
} catch (e: Exception) {
return Resource.error()
}
}
Then, it worked like a charm. I successfully got my data. But, I want to understand why this happened. In others words, why coroutine await call didn't finish or even didn't run catch block, when Retrofit's Call and execute finished task successfully?
If it is needed, I provide how I call above getNetworkData function:
launch {
val result = withContext(Dispatchers.IO) { repo.getNetworkData(id) }
//do something
}

Kotlin corountines : Note: end time exceeds epoch:

Combine runBlocking and withContext seems to dispatch the message
Note: end time exceeds epoch:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
runBlocking {
withContext(DefaultDispatcher) {
null
}
}
}
}
I use many coroutines like this and the logcat is spammed, any idea to avoid this ? Another way to do this, for example :
var projects: List<ProjectEntity>? = runBlocking {
withContext(DefaultDispatcher) {
//Get the ProjectEntity list
}
}
projects?.let {
onResult(projects)
}
EDIT
I try something based on your comments (thank you), but I can't get a similar result as my example above :
Log.d("Coroutines", "getMostRecent start")
var localeProject: ProjectEntity? = null
launch {
withContext(CommonPool) {
Log.d("Coroutines", "getRecentLocaleProject")
localeProject = getRecentLocaleProject()
}
}
Log.d("Coroutines", "check localeProject")
if (localeProject != null) {
//Show UI
}
In Logcat :
D/Coroutines: getMostRecent start
D/Coroutines: check localeProject
D/Coroutines: getRecentLocaleProject
I want to separate async and sync stuff, there is no way like this ? I really want to avoid all the callbacks things in my repositories when possible.
Markos comment is right, you should not block the UI thread.
You should use launch or async and use withContext to switch back to the UI thread.
You find some examples here: https://github.com/Kotlin/kotlinx.coroutines/blob/master/ui/coroutines-guide-ui.md#structured-concurrency-lifecycle-and-coroutine-parent-child-hierarchy
class MainActivity : ScopedAppActivity() {
fun asyncShowData() = launch { // Is invoked in UI context with Activity's job as a parent
// actual implementation
}
suspend fun showIOData() {
val deferred = async(Dispatchers.IO) {
// impl
}
withContext(Dispatchers.Main) {
val data = deferred.await()
// Show data in UI
}
}
}
Be aware, that the example uses the new coroutine API (>0.26.0), that renamed the Dispatchers. So Dispatchers.Main corresponds to UI in older versions.
var localeProject: ProjectEntity? = null
launch {
withContext(CommonPool) {
localeProject = getRecentLocaleProject()
}
}
if (localeProject != null) {
//Show UI
}
I want to separate async and sync stuff, there is no way like this ?
When you launch a coroutine, semantically it's like you started a thread. Intuition tells you that you can't expect localeProject != null just after you've started the thread that sets it, and this is true for the coroutine as well. It's even stronger: you are guaranteed not to ever see localeProject != null because launch only adds a new event to the event loop. Until your current method completes, that event won't be handled.
So you can forget about top-level vals initialized from async code. Not even lateinit vars can work because you have no guarantee you'll see it already initialized. You must work with the loosest kind: nullable vars.

Categories

Resources