Collect Flows in Service - android

So, I'm trying to collect data from flows in my Foreground service (LifecycleService) in onCreate(), but after the first callback, it is not giving new data.
The code is :
override fun onCreate() {
super.onCreate()
lifecycleScope.launchWhenStarted {
repeatOnLifecycle(Lifecycle.State.STARTED) {
observeCoinsPrices()
}
}
}

I couldn't get lifecycleScope.launch to work in the LifecycleService.onCreate method without it freezing the app, so what I did instead was moved the collector into a method that I use to start the service, assign the Job into a property so I can cancel it when the Service is destroyed.
import kotlinx.coroutines.Job
//...
class MyService : LifecycleService() {
//...
private lateinit var myJob: Job
// my custom method for starting The Foreground service
fun startTheService() {
// call startForeground()
//...
myJob = lifecycleScope.launch {
collectFromFlow()
}
}
override fun onDestroy() {
myJob.cancel()
}
}
In my case, I was wanting to update text in the foreground notification every time a value was emitted to my Flow collector.

Because Flow used in observeCoinsPrices() not replay the latest value (replay < 1). You should change Flow logic

Related

How to properly wait for an Android Service to be bounded to an Activity

I am developing an app in which several Activitis bound to a sevice once they become visible to the user. During start up, each Activity needs to:
check some status flag of the service, based on which some UI elements are configured
check wether an adapters is enabled whose reference is inside the service
execute some functions of the Service
Since the applications should not get updates in the background, I bind to the service at onStart() and unbind at onStop(). I.e. in have something like this:
override fun onStart() {
super.onStart()
Intent(context, MyService::class.java).also { intent ->
bindService(intent, serviceCallback, Context.BIND_AUTO_CREATE)
}
}
Now I want to perform the above mentionned actions inside onResume.
override fun onResume() {
super.onResume()
// check flags
// check adapter status
// excute functions of service
}
The problem is that binding to a service is asynchronous and I do not have a valid reference to the Service's binder inside onResume(). Consequently, the app will crash with a nullpointer exception.
Approach 1: Using lateinit
I tried solving this problem using the lateinit keyword. I.e. I define the reference to the binder as
private lateinit var myBinder: MyService.LocalBinder
Problem: I cannot guarantee that the binder is initialized as it is asynchronous. Thus, the app will crash.
Approach 2: Waiting for callback in while loop
In my service callback, I set a flag as follows:
val serviceCallback = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
myBinder = service as MyService.LocalBinder
isServiceBounded = true
}
override fun onServiceDisconnected(arg0: ComponentName) {
isServiceBounded = false
myBinder = null
}
}
Then inside onResume, I block the Activity until the flag is true
override fun onResume() {
super.onResume()
while(!isServiceBounded){
// block and wait
}
}
Problem: Doesn't work either. The app will stop responding and crashes.
Approach 3: Using suspended functions and Kotlin coroutines
A suspended function will not continue unless it has received a return value. This, I can use it to wait for an event. So I tried something like this:
override fun onStart() {
super.onStart()
CoroutineScope(Dispatchers.Main).launch {
bindServiceAndWait(this#Activityname)
}
}
suspend fun bindServiceAndWait(context: Context): Boolean{
Intent(context, MyService::class.java).also { intent ->
bindService(intent, serviceCallback, Context.BIND_AUTO_CREATE)
}
return isServiceBounded // This is the flag from the callback
}
Problem: This suspended function does not actually wait for the callback. It just returns the current value of isServiceBounded.
I found a similar solution here, but I do not quite understand this solution as it has a global service callback (ServiceConnection) as well as a local one inside the suspended function. Also, I don't understand how to I could unbound in this provided example.
What is the proper way of doing this?
you simply can't ensure that service will be bounded until onResume gets called. why won't you introduce flag isResumed, set it in onResume (unset in onPause) and line below check if (isResumed && isServiceBounded).... yes, there is a chance that isServiceBounded = false in onResume, so same if check put in onServiceConnected

How to cancel collect coroutine StateFlow?

I have collect flow from shared viewmodel in fragment :
private val viewModel: MyViewModel by sharedViewModel()
private fun observeViewModelStateFlowData() {
job = lifecycleScope.launch {
viewModel.stateFlowData.collect {
when (it) {
is ViewStates.Success -> handleSuccess(it.data)
}
}
}
}
in ViewModel :
private val _stateFlowData = MutableStateFlow<ViewStates<Model>>(ViewStates.Idle)
val stateFlowData: StateFlow<ViewStates<Model>> get() = _stateFlowData
but when I go to next fragment and back to this fragment again, flow collect again.
I cancel the job in onStop() lifecycle method of fragment :
override fun onStop() {
job?.cancel()
super.onStop()
}
but not cancel and collect again!!!
This happens even when I leave the activity (when the viewmodel is cleared) and come back to activity again!!!
How can I do this so that I can prevent the collecting of flow ?
Well you have to know something about coroutine. If we just call cancel, it doesn’t mean that the coroutine work will just stop. If you’re performing some relatively heavy computation, like reading from multiple files, there’s nothing that automatically stops your code from running.
You need to make sure that all the coroutine work you’re implementing is cooperative with cancellation, therefore you need to check for cancellation periodically or before beginning any long running work. Try to add check before handling a result.
job = lifecycleScope.launch {
viewModel.stateFlowData.collect {
ensureActive()
when (it) {
is ViewStates.Success -> handleSuccess(it.data)
}
}
}
}
For more info take a look on this article https://medium.com/androiddevelopers/cancellation-in-coroutines-aa6b90163629

Cancel viewModelScope and re-use it later on

I'm using Coroutines for dealing with async jobs:
viewModelScope.launch {
val userResponse = getUsers() //suspendable function
}
What I want to do is stop all existing/ongoing coroutine jobs. Idea is that I click on different tabs. If getUsers() takes up to 5 seconds and user clicks from User tab to Job tab, I want that existing API call is stopped and response is not observed.
I tried to do viewModelScope.cancel(), but that seems not to be working.
Question is - how to cancel existing jobs on button click?
Define a reusable Job like following in the ViewModel class:
private var job = Job()
get() {
if (field.isCancelled) field = Job()
return field
}
Pass it to all of launch coroutine builders as the parent Job:
viewModelScope.launch(job) {
val userResponse = getUsers()
}
viewModelScope.launch(job) {
// some other work
}
...
On button click, just cancel the parent job:
fun cancelAll() {
job.cancel()
}
You can get its Job through its CouroutineContext like this:
viewModelScope.coroutineContext[Job]
To stop all existing/ongoing coroutine jobs you can call its cancel method:
viewModelScope.coroutineContext[Job]?.cancel()
If you need to start other coroutines eventually then call its cancelChildren method instead:
viewModelScope.coroutineContext[Job]?.cancelChildren()

How do I stop a Kotlin coroutine on Android?

I am trying to stop a coroutine if user presses a button. However, when I do:
GlobalScope.launch(Dispatchers.Main) {
//code
}
button.setonclicklistener(){
GlobalScope.cancel()
}
The app crashes. How can I fix this?
Change it like this
var job: Job? = null
job = GlobalScope.launch(Dispatchers.Main) {
//code
}
button.setonclicklistener(){
job?.cancel()
}
Here is the sample you can use and modify as per your code
val job =GlobalScope.launch(Dispatchers.Main) {
try {
//code
} finally {
println("job: I'm running finally")
}
}
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
First, I would like to point out that you should not use the GlobalScope. Instead, you should make your local scope bound to your component fragment/activity/presenter, etc. lifecycle. here's why https://elizarov.medium.com/the-reason-to-avoid-globalscope-835337445abc
Now, once that is taken care of, you can create a local scope like this (assuming you need MainScope)
class MyFragment: Fragment(), CoroutineScope by MainScope() {
var job: Job? = null
....
Next, like #Francesc suggested, you'll need to grab a Job reference. Since every coroutine launch returns a Job, you can keep the reference and cancel it whenever you need. Or in this case, if the fragment dies, the coroutine will be canceled automatically as it's bound to the fragment now (which is most of the time the desired behavior).
job = launch {
// your code
}
button.setOnClickListener() {
job?.cancel()
}
Also please note that now you don't have to mention the scope and context before launching.

How to dispose Rx in WorkManager?

I implemented an AlarmManager to send notifications when user adds a due date to a Task. However, when the user turns off the device, all the alarms are lost. Now I'm updating the BroadcastReceiver to receive an android.intent.action.BOOT_COMPLETED and reschedule all the alarms set to each task.
My first attempt was to get an Rx Single with all the tasks where the due date is higher than the current time inside the BroadcastReceiver, then reschedule all the alarms. The issue is I'm not able to dispose the Observable once the BroadcastReceiver has no lifecycle. Also, it seems that this is not a good approach.
During my researches, the IntentService was a good solution for this case, but I'm getting into the new WorkManager library and the OneTimeWorkRequest looks like a good and simple solution.
The Worker is being called and executing correctly, but I'm not able to dispose the Observable because the onStopped method is never called.
Here is the implementation, based on this snippet:
class TaskAlarmWorker(context: Context, params: WorkerParameters) :
Worker(context, params), KoinComponent {
private val daoRepository: DaoRepository by inject()
private val compositeDisposable = CompositeDisposable()
override fun doWork(): Result {
Timber.d("doWork")
val result = LinkedBlockingQueue<Result>()
val disposable =
daoRepository.getTaskDao().getAllTasks().applySchedulers().subscribe(
{ result.put(Result.SUCCESS) },
{ result.put(Result.FAILURE) }
)
compositeDisposable.add(disposable)
return try {
result.take()
} catch (e: InterruptedException) {
Result.RETRY
}
}
override fun onStopped(cancelled: Boolean) {
Timber.d("onStopped")
compositeDisposable.clear()
}
}
Is WorkManager a good solution for this case?
Is it possible to dispose the Observable correctly?
Yes WorkManager is a good solution(even could be the best one)
You should use RxWorker instead of Worker. here is an example:
To implement it. add androidx.work:work-rxjava2:$work_version to your build.gradle file as dependency.
Extend your class from RxWorker class, then override createWork() function.
class TaskAlarmWorker(context: Context, params: WorkerParameters) :
RxWorker(context, params), KoinComponent {
private val daoRepository: DaoRepository by inject()
override fun createWork(): Single<Result> {
Timber.d("doRxWork")
return daoRepository.getTaskDao().getAllTasks()
.doOnSuccess { /* process result somehow */ }
.map { Result.success() }
.onErrorReturn { Result.failure() }
}
}
Important notes about RxWorker:
The createWork() method is called on the main thread but returned single is subscribed on the background thread.
You don’t need to worry about disposing the Observer since RxWorker will dispose it automatically when the work stops.
Both returning Single with the value Result.failure() and single with an error will cause the worker to enter the failed state.
You can override onStopped function to do more.
Read more :
How to use WorkManager with RxJava
Stackoverflow answer
You can clear it in onStoped() method then compositeDisposable.dispose();
Then call super.onStoped()

Categories

Resources