I need to cancel/remove my work when it meets a condition. I`ve read that there is a method onStopped() that can be overridden, but it is for simple :Worker and not for CoroutineWorker.
My worker:
class MyJob(appContext: Context, params: WorkerParameters) :
CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
val itemId = inputData.getLong("item_id",0)
val itemDao = ItemDB.getInstance(applicationContext).itemDao()
val itemRepository = ItemRepo(itemsDao)
val item = itemRepository.getItemById(itemId)
val newValue = item.a + item.b
item.a = newValue
itemRepository.updateItem(item)
if(item.a == item.c){
WorkManager.getInstance(applicationContext).cancelUniqueWork("TEST_WORKER")
}
return Result.success()
}
}
EDIT:
My mistake, i forgot to mention that this is a unique periodic work.
You can try to cancel the work by returning Result.failure(); so you can add it under your condition
if(item.a == item.c){
//Cancel work
return Result.failure()
}
Now you need to listen to the work result in your activity/fragment; as you didn't provide this part of the code, I will do some assumptions, and listen to the work result with lifecycle observation model
// instantiate periodic work
final PeriodicWorkRequest workRequest = new PeriodicWorkRequest.Builder(...).build();
// schedule the work
WorkManager.getInstance(this).enqueue(workRequest);
// observe the work
WorkManager.getInstance(this).getWorkInfoByIdLiveData(workRequest.getId())
.observe(this, new Observer<WorkInfo>() {
#Override
public void onChanged(WorkInfo workInfo) {
if (workInfo.getState() == WorkInfo.State.FAILED) {
// cancelling work
WorkManager.getInstance(MainActivity.this).cancelWorkById(workRequest.getId());
}
}
});
Related
I want to call an api multiple times using WorkManager.
where idsArrayList is a list of ids.
I send each id in the api as Path to get response and similarly for other ids.
I want the workManager to return success after it has called api for all ids.
But the problem is WorkManager only returns SUCCESS for one id from the list. This is the first time I'm using WorkManager and I tried starting work manager for every id too by iterating over idsList one by one and making instance of workManger for every id in the for loop. But I thought sending the idsList as data in the workmanager and then itering over ids from inside doWork() would be better, but it's not working like I want and I don't understand why. Here's my code:
class MyWorkManager(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
private lateinit var callGrabShifts: Call<ConfirmStatus>
override fun doWork(): Result {
val idsList = inputData.getStringArray("IDS_LIST")
val idsArrayList = idsList?.toCollection(ArrayList())
var response = ""
if (idsArrayList != null) {
try {
response = callConfirmShiftApi(idsArrayList)
if (response.contains("CONFIRM")) {
return Result.success()
}
} catch (e: Exception) {
e.printStackTrace()
return Result.failure()
}
}
return Result.retry()
}
private fun callConfirmShiftApi(idsArrayList: ArrayList<String>): String {
var response = ""
for ((index, id) in idsArrayList.withIndex()) {
response = callApiForId(id)
if(index == idsArrayList.lastIndex) {
response = "CONFIRM"
}
}
return response
}
private fun callApiForId(id: String): String {
var shiftGrabStatus = ""
callGrabShifts = BaseApp.apiInterface.confirmGrabAllShifts(BaseApp.userId, id)
callGrabShifts.enqueue(object : Callback<ConfirmStatus> {
override fun onResponse(call: Call<ConfirmStatus>, response: Response<ConfirmStatus>) {
if (response.body() != null) {
shiftGrabStatus = response.body()!!.status
if (shiftGrabStatus != null) {
if (shiftGrabStatus.contains("CONFIRM")) {
val shiftNumber = ++BaseApp.noOfShiftsGrabbed
sendNotification(applicationContext)
shiftGrabStatus = "CONFIRM"
return
} else {
shiftGrabStatus = "NOT CONFIRM"
return
}
} else {
shiftGrabStatus = "NULL"
return
}
} else {
shiftGrabStatus = "NULL"
return
}
}
override fun onFailure(call: Call<ConfirmStatus>, t: Throwable) {
shiftGrabStatus = "FAILURE"
return
}
})
return shiftGrabStatus
}
}
And this is the code where I'm starting the WorkManager:
private fun confirmShiftApi(availableShiftsIdList: ArrayList<String>) {
val data = Data.Builder()
data.putStringArray("IDS_LIST", availableShiftsIdList.toArray(arrayOfNulls<String>(availableShiftsIdList.size)))
val oneTimeWorkRequest = OneTimeWorkRequestBuilder<MyWorkManager>().setInputData(data.build())
.build()
WorkManager.getInstance(applicationContext).enqueue(oneTimeWorkRequest)
WorkManager.getInstance(this).getWorkInfoByIdLiveData(oneTimeWorkRequest.id)
.observe(this, Observer { workInfo: WorkInfo? ->
if (workInfo != null && workInfo.state.isFinished) {
val progress = workInfo.progress
}
Log.d("TESTING", "(MainActivity) : observing work manager - workInfo?.state - ${workInfo?.state}")
})
}
Any suggestions what I might be doing wrong or any other alternative to perform the same? I chose workmanager basicaly to perform this task even when app is closed and for learning purposes as I haven't used WorkManager before. But would switch to other options if this doesn't work.
I tried the following things:
removed the 'var response line in every method that I'm using to set the response, though I added it temporarily just for debugging earlier but it was causing an issue.
I removed the check for "CONFIRM" in doWork() method and just made the api calls, removed the extra return lines.
I tried adding manual delay in between api calls for each id.
I removed the code where I'm sending the ids data from my activity before calling workmanager and made the api call to fetch those ids inside workmanager and added more delay in between those calls to that keep running in background to check for data one round completes(to call api for all ids that were fetched earlier, it had to call api again to check for more ids on repeat)
I removed the extra api calls from onRestart() and from other conditons that were required to call api again.
I tested only one round of api calls for all ids with delay and removed the repeated call part just to test first. Didn't work.
None of the above worked, it just removed extra lines of code.
This is my final code that is tested and It cleared my doubt. Though it didn't fix this issue as the problem was because of backend server and Apis were returning failure in onResponse callback for most ids(when calls are made repeatedly using a for loop for each id) except first id and randomly last id from the list sometimes(with delay) for the rest of the ids it didn't return CONFIRM status message from api using Workmanager. Adding delay didn't make much difference.
Here's my Workmanager code:
class MyWorkManager(appContext: Context, workerParams: WorkerParameters):
Worker(appContext, workerParams) {
private lateinit var callGrabShifts: Call<ConfirmStatus>
override fun doWork(): Result {
val idsList = inputData.getStringArray("IDS_LIST")
val idsArrayList = idsList?.toCollection(ArrayList())
if (idsArrayList != null) {
try {
response = callConfirmShiftApi(idsArrayList)
if (response.contains("CONFIRM")) {
return Result.success()
}
} catch (e: Exception) {
e.printStackTrace()
return Result.failure()
}
}
return Result.success()
}
private fun callConfirmShiftApi(idsArrayList: ArrayList<String>): String {
for ((index, id) in idsArrayList.withIndex()) {
response = callApiForId(id)
Thread.sleep(800)
if(index == idsArrayList.lastIndex) {
response = "CONFIRM"
}
}
return response
}
private fun callApiForId(id: String): String {
callGrabShifts = BaseApp.apiInterface.confirmGrabAllShifts(BaseApp.userId, id)
callGrabShifts.enqueue(object : Callback<ConfirmStatus> {
override fun onResponse(call: Call<ConfirmStatus>, response: Response<ConfirmStatus>) {
if (response.body() != null) {
shiftGrabStatus = response.body()!!.status
if (shiftGrabStatus != null) {
if (shiftGrabStatus.contains("CONFIRM")) {
return
} else {
return
}
} else {
return
}
} else {
return
}
}
override fun onFailure(call: Call<ConfirmStatus>, t: Throwable) {
return
}
})
return shiftGrabStatus
}
Eventually this problem(when an individual call is made for an id, it always returns success but when i call the api for every id using a loop, it only returns success for first call and failure for others) was solved using Service, it didn't have a complete success rate from apis either, but for 6/11 ids the api returned success(400ms delay between each api call), so it served the purpose for now.
I try these tutorials: https://github.com/docusign/mobile-android-sdk/blob/master/README.md, especially the function getUserSignatureInfo. In this function a REST API call (userSignaturesGetUserSignature) is made.
In my code below I try to return a value (userSignatureId) I get from REST API. I understand, it's impossible this way, because onSuccess() will be invoked later as the outer function getUserSignatureInfo() returns.
I want to call getUserSignatureInfo() from a Fragments onActivityCreated() and use this value on creating a RecyclerView.Adapter.
The question is, what is the (best practice) way to do something like this: make a REST API call, wait for response, and use the response in further code.
// my Fragment
...
...
val userSignatureId = getUserSignatureInfo()
recyclerView.adapter = createMyAdapter(userSignatureId)
...
...
// function where the REST API call is made
fun getUserSignatureInfo(context: Context) : String {
val eSignApiDelegate = DocuSign.getInstance().getESignApiDelegate()
val usersApi = eSignApiDelegate.createApiService(UsersApi::class.java)
val authDelegate = DocuSign.getInstance().getAuthenticationDelegate()
val user = authDelegate.getLoggedInUser(context)
var userSignatureId = ""
eSignApiDelegate.invoke(object : DSESignApiListener {
override fun <T> onSuccess(response: T?) {
if (response is UserSignaturesInformation) {
val userSignature = (response as UserSignaturesInformation).getUserSignatures().get(0)
Log.d(TAG, "Signature Id: " + userSignature.signatureId);
// My problem: this assignment is useless
// because the outer function getUserSignatureInfo()
// returns earlier as onSuccess()
userSignatureId = userSignature.signatureId
}
}
override fun onError(exception: DSRestException) {
// TODO: Handle error
}
}) {
usersApi!!.userSignaturesGetUserSignature(user.accountId, user.userId, "signature")
}
// This is my problem: userSignatureId is empty because
// onSuccess() fires later as this function returns
return userSignatureId
}
Thank you much!
You could pass a callback into getUserSignatureInfo(), for example
fun getUserSignatureInfo(context: Context, callback: (String)->Unit) : String {
val eSignApiDelegate = DocuSign.getInstance().getESignApiDelegate()
val usersApi = eSignApiDelegate.createApiService(UsersApi::class.java)
val authDelegate = DocuSign.getInstance().getAuthenticationDelegate()
val user = authDelegate.getLoggedInUser(context)
eSignApiDelegate.invoke(object : DSESignApiListener {
override fun <T> onSuccess(response: T?) {
if (response is UserSignaturesInformation) {
val userSignature = (response as UserSignaturesInformation).getUserSignatures().get(0)
Log.d(TAG, "Signature Id: " + userSignature.signatureId);
// return the value in the callback
callback(userSignature.signatureId)
}
}
When you want to use the string value from another class,
getUserSignatureInfo(context) { id ->
Log.d("test", id)
}
Hi I have some usecases which are written in Java which uses rxJava. I have converted them to kotlin files and instead of rxJava I have made them into couroutines suspend functions.
In my rxJava code I am making an api call from the usecase and it returns the result but at the same time onNext it does something and onError it does something.
How can I do the same thing in coroutines
here is my rxjava code
#PerApp
public class StartFuellingUseCase {
#Inject
App app;
#Inject
CurrentOrderStorage orderStorage;
#Inject
FuelOrderRepository repository;
#Inject
StartFuellingUseCase() {
// empty constructor for injection usage
}
public Observable<GenericResponse> execute(Equipment equipment) {
if (orderStorage.getFuelOrder() == null) return null;
DateTime startTime = new DateTime();
TimestampedAction action = new TimestampedAction(
app.getSession().getUser().getId(), null, startTime
);
return repository.startFuelling(orderStorage.getFuelOrder().getId(), action)
.subscribeOn(Schedulers.io())
.unsubscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.doOnNext(response -> onSuccess(startTime, equipment))
.doOnError(this::onError);
}
private void onSuccess(DateTime startTime, Equipment equipment) {
if (orderStorage.getFuelOrder() == null) return;
orderStorage.getFuelOrder().setStatus(FuelOrderData.STATUS_FUELLING);
equipment.getTimes().setStart(startTime);
app.saveState();
}
private void onError(Throwable e) {
Timber.e(e, "Error calling started fuelling! %s", e.getMessage());
}
}
I have re written the code in Kotlin using coroutines usecases
#PerApp
class StartFuellingUseCaseCoroutine #Inject constructor(
private val currentOrderStorage: CurrentOrderStorage,
private val fuelOrderRepository: FuelOrderRepository,
private val app: App
): UseCaseCoroutine<GenericResponse, StartFuellingUseCaseCoroutine.Params>() {
override suspend fun run(params: Params): GenericResponse {
val startTime = DateTime()
val action = TimestampedAction(
app.session.user.id, null, startTime
)
return fuelOrderRepository.startFuelling(
currentOrderStorage.fuelOrder!!.id,
action
)
//SHOULD RETURN THE VALUE FROM THE fuelOrderRepository.startFuelling
//AND ALSO
//ON NEXT
//CALL onSuccess PASSING startTime and equipment
//ON ERROR
//CALL onError
}
private fun onSuccess(startTime: DateTime, equipment: Equipment) {
if (currentOrderStorage.getFuelOrder() == null) return
currentOrderStorage.getFuelOrder()!!.setStatus(FuelOrderData.STATUS_FUELLING)
equipment.times.start = startTime
app.saveState()
}
private fun onError(errorMessage: String) {
Timber.e(errorMessage, "Error calling started fuelling! %s", errorMessage)
}
data class Params(val equipment: Equipment)
}
Can you please suggest how can i call onSuccess and onError similar to how we have in rxjava onnext and onError.
could you please suggest how to fix this
thanks
R
You can using Kotlin Flow like converted example below:
RxJava
private fun observable(
value: Int = 1
): Observable<Int> {
return Observable.create { emitter ->
emitter.onNext(value)
emitter.onError(RuntimeException())
}
}
Flow:
private fun myFlow(
value: Int = 1
): Flow<Int> {
return flow {
emit(value)
throw RuntimeException()
}
}
For more detail : https://developer.android.com/kotlin/flow
convert startFuelling to flow using flowOf, you can do below
return flowOf(repository
.startFuelling(orderStorage.getFuelOrder().getId(), action))
.onEach{response -> onSuccess(startTime, equipment)}
.catch{e -> onError(e) }
.flowOn(Dispatchers.IO) //this will make above statements to execute on IO
if you want to collect it on main thread, you can use launchIn
.onEach{ }
.launchIn(mainScope)//could be lifeCycleScope/viewModelScope
//or
CoroutineScope(Dispatchers.Main).launch{
flow.collect{}
}
Kotlin/Android novice here :). I'm playing around with chunked uploads using a CoroutineWorker and don't see a built-in way to maintain state for my worker in case a retry happens, but I'm having sort of a hard time believing smth like that would be missing...
My use case is the following:
Create the worker request with the path to the file to upload as input data
Worker loops over the file and performs uploads in chunks. The latest uploaded chunkIndex is being tracked.
In case of an error and subsequent Retry(), the worker somehow retrieves the current chunk index and resumes rather than starting from at the beginning again.
So basically, I really just need to preserve that chunkIndex flag. I looked into setting progress, but this seems to be hit or miss on retries (worked once, wasn't available on another attempt).
override suspend fun doWork(): Result {
try {
// TODO check if we are resuming with a given chunk index
chunkIndex = ...
// do the work
performUpload(...)
return Result.success()
} catch (e: Exception) {
// TODO cache the chunk index
return Result.retry()
}
}
Did I overlook something, or would I really have to store that index outside the worker?
You have a pretty good use-case but unfortunately you cannot cache data within Worker class or pass on the data to the next Worker object on retry! As you suspected, you will have to store the index outside of the WorkManager provided constructs!
Long answer,
The Worker object can receive and return data. It can access the data from getInputData() method. If you chain tasks, the output of one worker can be input for the next-in-line worker. This can be done by returning Result.success(output) (see below code)
public Result doWork() {
int chunkIndex = upload();
//...set the output, and we're done!
Data output = new Data.Builder()
.putInt(KEY_RESULT, result)
.build();
return Result.success(output);
}
So the problem is we cannot return data for the retry case, only for failure and success case! (Result.retry(Data data) method is missing!)
Reference: official documentation and API.
As stated in GB's answer, there seems to be no way to cache data with in the worker, or do a Result.retry(data). I ended up just doing a quick hack with SharedPreferences instead.
Solution below. Take it with a grain of salt, I have a total of ~10 hours of Kotlin under my belt ;)
var latestChunkIndex = -1
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
try {
// get cached entry (simplified - no checking for fishy status or anything)
val transferId = id.toString()
var uploadInfo: UploadInfo = TransferCache.tryGetUpload(applicationContext, transferId) ?: TransferCache.registerUpload(applicationContext, transferId, TransferStatus.InProgress)
if(uploadInfo.status != TransferStatus.InProgress) {
TransferCache.setUploadStatus(applicationContext, transferId, TransferStatus.InProgress)
}
// resolve the current chunk - this will allow us to resume in case we're retrying
latestChunkIndex = uploadInfo.latestChunkIndex
// do the actual work
upload()
// update status and complete
TransferCache.setUploadStatus(applicationContext, id.toString(), TransferStatus.Success)
Result.success()
} catch (e: Exception) {
if (runAttemptCount > 20) {
// give up
TransferCache.setUploadStatus(applicationContext, id.toString(), TransferStatus.Error)
Result.failure()
}
// update status and schedule retry
TransferCache.setUploadStatus(applicationContext, id.toString(), TransferStatus.Paused)
Result.retry()
}
}
Within my upload function, I'm simply keeping track of my cache (I could also just do it in the exception handler of the doWork method, but I'll use the cache entry for status checks as well, and it's cheap):
private suspend fun upload() {
while ((latestChunkIndex + 1) * defaultChunkSize < fileSize) {
// doing the actual upload
...
// increment chunk number and store as progress
latestChunkIndex += 1
TransferCache.cacheUploadProgress(applicationContext, id.toString(), latestChunkIndex)
}
}
and the TransferCache looking like this (note that there is no housekeeping there, so without cleanup, this would just continue to grow!)
class UploadInfo() {
var transferId: String = ""
var status: TransferStatus = TransferStatus.Undefined
var latestChunkIndex: Int = -1
constructor(transferId: String) : this() {
this.transferId = transferId
}
}
object TransferCache {
private const val PREFERENCES_NAME = "${BuildConfig.APPLICATION_ID}.transfercache"
private val gson = Gson()
fun tryGetUpload(context: Context, transferId: String): UploadInfo? {
return getPreferences(context).tryGetUpload(transferId);
}
fun cacheUploadProgress(context: Context, transferId: String, transferredChunkIndex: Int): UploadInfo {
getPreferences(context).run {
// get or create entry, update and save
val uploadInfo = tryGetUpload(transferId)!!
uploadInfo.latestChunkIndex = transferredChunkIndex
return saveUpload(uploadInfo)
}
}
fun setUploadStatus(context: Context, transferId: String, status: TransferStatus): UploadInfo {
getPreferences(context).run {
val upload = tryGetUpload(transferId) ?: registerUpload(context, transferId, status)
if (upload.status != status) {
upload.status = status
saveUpload(upload)
}
return upload
}
}
/**
* Registers a new upload transfer. This would simply (and silently) override any
* existing registration.
*/
fun registerUpload(context: Context, transferId: String, status: TransferStatus): UploadInfo {
getPreferences(context).run {
val upload = UploadInfo(transferId).apply {
this.status = status
}
return saveUpload(upload)
}
}
private fun getPreferences(context: Context): SharedPreferences {
return context.getSharedPreferences(
PREFERENCES_NAME,
Context.MODE_PRIVATE
)
}
private fun SharedPreferences.tryGetUpload(transferId: String): UploadInfo? {
val data: String? = getString(transferId, null)
return if (data == null)
null
else
gson.fromJson(data, UploadInfo::class.java)
}
private fun SharedPreferences.saveUpload(uploadInfo: UploadInfo): UploadInfo {
val editor = edit()
editor.putString(uploadInfo.transferId, gson.toJson(uploadInfo))
editor.apply()
return uploadInfo;
}
}
By using LiveData's latest version "androidx.lifecycle:lifecycle-livedata-ktx:2.2.0-alpha03", I have developed a code for a feature called "Search Products" in the ViewModel using LiveData's new building block (LiveData + Coroutine) that performs a synchronous network call using Retrofit and update different flags (isLoading, isError) in ViewModel accordingly. I am using Transforamtions.switchMap on "query" LiveData so whenever there is a change in "query" from the UI, the "Search Products" code starts its executing using Transformations.switchMap. Every thing is working fine, except that i want to cancel the previous Retrofit Call whenever a change happens in "query" LiveData. Currently i can't see any way to do this. Any help would be appreciated.
class ProductSearchViewModel : ViewModel() {
val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
// Query Observable Field
val query: MutableLiveData<String> = MutableLiveData()
// IsLoading Observable Field
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
val products: LiveData<List<ProductModel>> = query.switchMap { q ->
liveData(context = coroutineScope.coroutineContext) {
emit(emptyList())
_isLoading.postValue(true)
val service = MyApplication.getRetrofitService()
val response = service?.searchProducts(q)
if (response != null && response.isSuccessful && response.body() != null) {
_isLoading.postValue(false)
val body = response.body()
if (body != null && body.results != null) {
emit(body.results)
}
} else {
_isLoading.postValue(false)
}
}
}
}
You can solve this problem in two ways:
Method # 1 ( Easy Method )
Just like Mel has explained in his answer, you can keep a referece to the job instance outside of switchMap and cancel instantance of that job right before returning your new liveData in switchMap.
class ProductSearchViewModel : ViewModel() {
// Job instance
private var job = Job()
val products = Transformations.switchMap(_query) {
job.cancel() // Cancel this job instance before returning liveData for new query
job = Job() // Create new one and assign to that same variable
// Pass that instance to CoroutineScope so that it can be cancelled for next query
liveData(CoroutineScope(job + Dispatchers.IO).coroutineContext) {
// Your code here
}
}
override fun onCleared() {
super.onCleared()
job.cancel()
}
}
Method # 2 ( Not so clean but self contained and reusable)
Since liveData {} builder block runs inside a coroutine scope, you can use a combination of CompletableDeffered and coroutine launch builder to suspend that liveData block and observe query liveData manually to launch jobs for network requests.
class ProductSearchViewModel : ViewModel() {
private val _query = MutableLiveData<String>()
val products: LiveData<List<String>> = liveData {
var job: Job? = null // Job instance to keep reference of last job
// LiveData observer for query
val queryObserver = Observer<String> {
job?.cancel() // Cancel job before launching new coroutine
job = GlobalScope.launch {
// Your code here
}
}
// Observe query liveData here manually
_query.observeForever(queryObserver)
try {
// Create CompletableDeffered instance and call await.
// Calling await will suspend this current block
// from executing anything further from here
CompletableDeferred<Unit>().await()
} finally {
// Since we have called await on CompletableDeffered above,
// this will cause an Exception on this liveData when onDestory
// event is called on a lifeCycle . By wrapping it in
// try/finally we can use this to know when that will happen and
// cleanup to avoid any leaks.
job?.cancel()
_query.removeObserver(queryObserver)
}
}
}
You can download and test run both of these methods in this demo project
Edit: Updated Method # 1 to add job cancellation on onCleared method as pointed out by yasir in comments.
Retrofit request should be cancelled when parent scope is cancelled.
class ProductSearchViewModel : ViewModel() {
val completableJob = Job()
private val coroutineScope = CoroutineScope(Dispatchers.IO + completableJob)
/**
* Adding job that will be used to cancel liveData builder.
* Be wary - after cancelling, it'll return a new one like:
*
* ongoingRequestJob.cancel() // Cancelled
* ongoingRequestJob.isActive // Will return true because getter created a new one
*/
var ongoingRequestJob = Job(coroutineScope.coroutineContext[Job])
get() = if (field.isActive) field else Job(coroutineScope.coroutineContext[Job])
// Query Observable Field
val query: MutableLiveData<String> = MutableLiveData()
// IsLoading Observable Field
private val _isLoading = MutableLiveData<Boolean>()
val isLoading: LiveData<Boolean> = _isLoading
val products: LiveData<List<ProductModel>> = query.switchMap { q ->
liveData(context = ongoingRequestJob) {
emit(emptyList())
_isLoading.postValue(true)
val service = MyApplication.getRetrofitService()
val response = service?.searchProducts(q)
if (response != null && response.isSuccessful && response.body() != null) {
_isLoading.postValue(false)
val body = response.body()
if (body != null && body.results != null) {
emit(body.results)
}
} else {
_isLoading.postValue(false)
}
}
}
}
Then you need to cancel ongoingRequestJob when you need to. Next time liveData(context = ongoingRequestJob) is triggered, since it'll return a new job, it should run without problems. All you need to left is cancel it where you need to, i.e. in query.switchMap function scope.