WorkManager alpha10 ListenableFuture usage - android

I´m having some issues trying to implement the latest WorkManager-alpha10 ListenableFuture for handling process completion.
Right now I´ve the following on my JobOrganizer class
private fun enqueueDownloadWork(): ListenableFuture<Void> {
val work = WorkManager.getInstance()
.beginWith(dwdTypologiesJob)
.then(dwdElementsJob)
.then(dwdAnomaliesJob)
return work.enqueue()
}
private fun createDownloadWorkRequests() {
dwdTypologiesJob = OneTimeWorkRequestBuilder<DWDAnomalyTypesJob>()
.addTag("download_typologies_work")
.build()
dwdElementsJob = OneTimeWorkRequestBuilder<DWDElementsJob>()
.addTag("download_elements_work")
.build()
dwdAnomaliesJob = OneTimeWorkRequestBuilder<DWDAnomaliesJob>()
.addTag("download_anomalies_work")
.build()
}
fun downloadData(): ListenableFuture<Void> {
createDownloadWorkRequests()
return enqueueDownloadWork()
}
And this is my call who should listen for the completion event.
val listenable = JobOrganizer.downloadData()
listenable.addListener({
Log.d("Listenable", "Did something 1");
}, {
Log.d("Listenable", "Did something 2");
})
I´m still missing how the Runnable and Executor works on this function. Can someone explain it?
Thanks

You need to instantiate both Runnable and Executor, for instance, when you want to execute the Runnable directly and on the current thread:
.addListener(
object:Runnable {
override fun run() {
Log.d("Listenable", "Did something 1");
}
},
object:Executor {
override fun execute(command: Runnable?) {
command?.run()
}
}
You can find more examples on Executors on https://developer.android.com/reference/java/util/concurrent/Executor

For ListenableFuture, Runnable is the code that you want to run on completion and Executor tells it how exactly to run that code (which thread to run it on, for instance).
This should work on Kotlin:
listenableFuture.addListener(
{ /* Runnable: Code to run */ },
{ /* Executor: How to run */ }
)
Some simple executors could be as follows:
// Run on same thread (likely to be background thread):
{ it?.run }
// Run on main thread in android:
{ Handler(Looper.getMainLooper()).post(it) }
// Run with delay on main thread in android:
{ Handler(Looper.getMainLooper()).postDelayed(it, delayMillis) }
For instance, a usage in a ViewModel could look like this:
val dataDownloaded = MutableLiveData<Boolean>()
fun beginDownload() {
downloadData.result.addListener(
{ dataDownloaded.postValue(true) },
{ it?.run() }
)
}

Related

Kotlin: Handler is causing my UI Thread to freeze

I want to call my function indefinitely every 1 second in a specific situation. I'm using AsyncTask to execute my API calls.
I'm using this type of call for some time but this is the first time when it actually blocked my UI Thread and I don't know why.
The handler in the code below is called inside onPostExecute.
protected fun purchaseCheck(transactionId: String){
app.sysLog("Wait for purchase...")
task = asyncTask({
api.checkPaymentStatus(transactionId)
}, taskName = "Purchase Status") { r ->
r.js?.let {
when(r.httpCode){
HTTP_PAYMENT_CHECK_PENDING -> {
App.log("purchaseCheck: response pending purchase - try again")
MainActivity.afterDelay(1000){
purchaseCheck(transactionId)
}
}
else -> {
App.log("purchaseCheck: response purchase success")
onPurchaseSuccessfullyCompleted()
}
}
}?:kotlin.run {
when(r.httpCode){
HTTP_PAYMENT_CARD_EXPIRED -> {
App.log("purchaseCheck: response card expired")
showApiErrorAndRetry(r, App.getString("err_purchase_card_expired"))
}
else -> {
App.log("purchaseCheck: response error (retry)")
MainActivity.afterDelay(1000){
purchaseCheck(transactionId)
}
}
}
}
}
}
Basically
MainActivity.afterDelay(1000){
purchaseCheck(transactionId)
}
is causing my ProgressBar animation to freeze. When I remove that delay it is working as intended.
Here is afterDelay function:
fun afterDelay(delay: Int, body: () -> Unit): Cancellable {
class DelayRun : Runnable, Cancellable {
override fun run() = body()
override fun cancel() {
removePost(this)
}
}
return DelayRun().also {
post(delay, it)
}
}
fun removePost(runnable: Runnable) {
App.handler.removeCallbacks(runnable)
}
fun post(delay: Int, runnable: Runnable){
App.handler.postDelayed(runnable, delay.toLong())
}
Handler in Application class:
class App : Application(), Application.ActivityLifecycleCallbacks{
companion object {
val handler = Handler()
}
...
}
Edit:
After suggestion from post below I implemented Handler like this:
class App : Application(), Application.ActivityLifecycleCallbacks{
companion object {
val handler: Handler by lazy {
HandlerThread("MyHandlerThread").let {
it.start()
Handler(it.looper)
}
}
}
...
}
but it is still freezing my UI Thread. (ProgressBar is lagging)
By default, Handler posts tasks on Main (UI) thread. Therefore any job/task you send to your handler will be executed on UI thread - that is the reason why UI freezes - it waits for job to finish before redrawing.
You want to make your handler using another thread. The most simple way is to create HandlerThread.
val handlerThread = new HandlerThread("MyHandlerThread")
handlerThread.start()
val looper = handlerThread.getLooper()
val handler = new Handler(looper)
After these four lines of code, handler will execute it jobs on another thread. But let's take a problem further - you are using AsyncTask, which is deprecated. You also do not want your delay to be counted by afterDelay function, handler can do it for you.
In your case you can just do something like this:
handler.postDelayed(1000, { ... your job ... }).
Getting it together:
protected fun purchaseCheck(transactionId: String){
app.sysLog("Wait for purchase...")
val runnable = {
val status = api.checkPaymentStatus(transactionId)
status.js?.let { ... }
}
handler.postDelayed(1000, runnable)
}
I also recommend you to declare handler on Activity level rather than Application. Since you usually don't want it to be global.

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
}
}

Android Mockito Kotlin coroutine test cancel

I have a retrofit service
interface Service {
#PUT("path")
suspend fun dostuff(#Body body: String)
}
It is used in android view model.
class VM : ViewModel(private val service: Service){
private val viewModelJob = Job()
private val uiScope = CoroutineScope(Dispatchers.Main + viewModelJob)
val state = MutableLiveData<String()
init {
uiScope.launch {
service.doStuff()
state.value = "lol"
}
}
override fun onCleared(){
viewModelJob.cancel()
}
}
I would like to write a test for the cancelling of the view model. This will be done mocking service and delaying so that the co routine does not complete. Whilst blocking, we invoke onCleared to cancel the co routine. This should prevent state getting set...
#Test
fun `on cleared - cancels request`() = runBlocking {
//given
`when`(service.doStuff()).thenAnswer { launch { delay(1000) } }
val vm = ViewModel(service)
// when
vm.cleared()
//then
assertThat(vm.state, nullValue())
}
However it seems that vm.state always gets set??? What is the best way to test when clearing a scope that a co routine gets cancelled?
The problem here is in thenAnswer { launch { delay(1000) } }, which effectively makes your doStuff method look like that:
suspend fun doStuff() {
launch { delay(1000) }
}
As you can see, this function does not actually suspend, it launches a coroutine and returns immediately. What would actually work here is thenAnswer { delay(1000) }, which does not work, because there is no suspend version of thenAnswer in Mockito (as far as I know at least).
I would recommend to switch to Mokk mocking library, which supports kotlin natively. Then you can write coEvery { doStuff() } coAnswers { delay(1000) } and it will make your test pass (after fixing all the syntax errors ofc).

java.lang.IllegalStateException: Cannot invoke observeForever on a background thread

Can someone help me find where I am going wrong here. I need to continously observer network data and update the UI whenever there is a data change from the Worker. Please note that this was working before upgrading to androidx.
Here is a Worker class.
class TestWorker(val context: Context, val params: WorkerParameters): Worker(context, params){
override fun doWork(): Result {
Log.d(TAG, "doWork called")
val networkDataSource = Injector.provideNetworkDataSource(context)
networkDataSource.fetchData(false)
return Worker.Result.SUCCESS
}
companion object {
private const val TAG = "MY_WORKER"
}
}
Which is called as follows:
fun scheduleRecurringFetchDataSync() {
Log.d("FETCH_SCHEDULER", "Scheduling started")
val fetchWork = PeriodicWorkRequest.Builder(TestWorker::class.java, 1, TimeUnit.MINUTES)
.setConstraints(constraints())
.build()
WorkManager.getInstance().enqueue(fetchWork)
}
private fun constraints(): Constraints{
return Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.setRequiresBatteryNotLow(true)
.build()
}
I also have a UserDao and UserRepository to fetch and store data. I am observing the network data in the UserRepository as follows:
class UserRepository (
private val userDao: UserDao,
private val networkDataSource: NetworkDataSource,
private val appExecutors: AppExecutors){
init {
val networkData= networkDataSource.downloadedData
networkData.observeForever { newData->
appExecutors.diskIO().execute {
userDao.insert(newData.user)
}
}}
Can someone help me locate where I am going wrong. This is giving me error as follows:
java.lang.IllegalStateException: Cannot invoke observeForever on a background thread
at androidx.lifecycle.LiveData.assertMainThread(LiveData.java:443)
at androidx.lifecycle.LiveData.observeForever(LiveData.java:204)
at com.example.app.data.repo.UserRepository.<init>(UserRepository.kt:17)
at com.example.app.data.repo.UserRepository$Companion.getInstance(UserRepository.kt:79)
Change this:
networkData.observeForever { newData->
appExecutors.diskIO().execute {
userDao.insert(newData.user)
}
}
To:
Variant B (with coroutines):
GlobalScope.launch(Dispatchers.Main) { networkData.observerForever { /*..*/ } }
But be aware, the usage of GlobalScope is not recommended: https://stackoverflow.com/a/54351785/1185087
Variant A (without coroutines):
Handler(Looper.getMainLooper()).post { networkData.observeForever{ /*..*/ } }
Explanation
Normally observe(..) and observeForever(..) should be called from the main thread because their callbacks (Observer<T>.onChanged(T t)) often change the UI which is only possible in the main thread. That's the reason why android checks if the call of the observe functions is done by the main thread.
In your case UserRepository.init{} is called by a background thread, so the exception is thrown. To switch back to the main thread you can use one of the above variants. But be aware the code inside of your observe callback is executed by the main thread, too. Any expensive processing inside this callback will freeze your UI!
In another solution, you can call it from main dispatcher as
GlobalScope.launch(Dispatchers.Main) {
// your code here...
}
Additionally to the nice and detailled answer from #user1185087 here is a solution if you're using RxJava in your project. It's maybe not that short, but if you already use RxJava in your project, it's an elegant way to switch to the required thread (in this case the Android's UI thread via .observeOn(AndroidSchedulers.mainThread())).
Observable.just(workManager.getStatusById(workRequest.getId()))
.observeOn(AndroidSchedulers.mainThread())
.subscribe(status -> status.observeForever(workStatus -> {
// Handling result on UI thread
}), err -> Log.e(TAG, err.getMessage(), err));
In my case I was testing liveData and I forgot to add the InstantTaskExecutorRule().
#RunWith(AndroidJUnit4::class)
class UserDaoTest {
#get:Rule // <----
var instantExecutorRule = InstantTaskExecutorRule() // <----
....
}
Don't forget to add the library to the project.
testImplementation"androidx.arch.core:core-testing:2.1.0" // unit tests
androidTestImplementation "androidx.arch.core:core-testing:2.1.0"//instrumentation tests
Here is what I did in my Java code to make it work
// the LiveData query
LiveData<List<Calf>> calfLiveDataList = getCalfDao().getAllCalves();
Handler handler = new Handler(Looper.getMainLooper()); //This is the main thread
handler.post(new Runnable() { //task to run on main thread
#Override
public void run() {
calfLiveDataList.observeForever(observer);
}
}
);
Ignore my naming conventions, my app is for cow farmers.

Kotlin Coroutines the right way in Android

I'm trying to update a list inside the adapter using async, I can see there is too much boilerplate.
Is it the right way to use Kotlin Coroutines?
can this be optimized more?
fun loadListOfMediaInAsync() = async(CommonPool) {
try {
//Long running task
adapter.listOfMediaItems.addAll(resources.getAllTracks())
runOnUiThread {
adapter.notifyDataSetChanged()
progress.dismiss()
}
} catch (e: Exception) {
e.printStackTrace()
runOnUiThread {progress.dismiss()}
} catch (o: OutOfMemoryError) {
o.printStackTrace()
runOnUiThread {progress.dismiss()}
}
}
After struggling with this question for days, I think the most simple and clear async-await pattern for Android activities using Kotlin is:
override fun onCreate(savedInstanceState: Bundle?) {
//...
loadDataAsync(); //"Fire-and-forget"
}
fun loadDataAsync() = async(UI) {
try {
//Turn on busy indicator.
val job = async(CommonPool) {
//We're on a background thread here.
//Execute blocking calls, such as retrofit call.execute().body() + caching.
}
job.await();
//We're back on the main thread here.
//Update UI controls such as RecyclerView adapter data.
}
catch (e: Exception) {
}
finally {
//Turn off busy indicator.
}
}
The only Gradle dependencies for coroutines are: kotlin-stdlib-jre7, kotlinx-coroutines-android.
Note: Use job.await() instead of job.join() because await() rethrows exceptions, but join() does not. If you use join() you will need to check job.isCompletedExceptionally after the job completes.
To start concurrent retrofit calls, you can do this:
val jobA = async(CommonPool) { /* Blocking call A */ };
val jobB = async(CommonPool) { /* Blocking call B */ };
jobA.await();
jobB.await();
Or:
val jobs = arrayListOf<Deferred<Unit>>();
jobs += async(CommonPool) { /* Blocking call A */ };
jobs += async(CommonPool) { /* Blocking call B */ };
jobs.forEach { it.await(); };
How to launch a coroutine
In the kotlinx.coroutines library you can start new coroutine using either launch or async function.
Conceptually, async is just like launch. It starts a separate coroutine which is a light-weight thread that works concurrently with all the other coroutines.
The difference is that launch returns a Job and does not carry any resulting value, while async returns a Deferred - a light-weight non-blocking future that represents a promise to provide a result later. You can use .await() on a deferred value to get its eventual result, but Deferred is also a Job, so you can cancel it if needed.
Coroutine context
In Android we usually use two context:
uiContext to dispatch execution onto the Android main UI thread (for the parent coroutine).
bgContext to dispatch execution in background thread (for the child coroutines).
Example
//dispatches execution onto the Android main UI thread
private val uiContext: CoroutineContext = UI
//represents a common pool of shared threads as the coroutine dispatcher
private val bgContext: CoroutineContext = CommonPool
In following example we are going to use CommonPool for bgContext which limit the number of threads running in parallel to the value of Runtime.getRuntime.availableProcessors()-1. So if the coroutine task is scheduled, but all cores are occupied, it will be queued.
You may want to consider using newFixedThreadPoolContext or your own implementation of cached thread pool.
launch + async (execute task)
private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread
val task = async(bgContext) { dataProvider.loadData("Task") }
val result = task.await() // non ui thread, suspend until finished
view.showData(result) // ui thread
}
launch + async + async (execute two tasks sequentially)
Note: task1 and task2 are executed sequentially.
private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread
// non ui thread, suspend until task is finished
val result1 = async(bgContext) { dataProvider.loadData("Task 1") }.await()
// non ui thread, suspend until task is finished
val result2 = async(bgContext) { dataProvider.loadData("Task 2") }.await()
val result = "$result1 $result2" // ui thread
view.showData(result) // ui thread
}
launch + async + async (execute two tasks parallel)
Note: task1 and task2 are executed in parallel.
private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread
val task1 = async(bgContext) { dataProvider.loadData("Task 1") }
val task2 = async(bgContext) { dataProvider.loadData("Task 2") }
val result = "${task1.await()} ${task2.await()}" // non ui thread, suspend until finished
view.showData(result) // ui thread
}
How to cancel a coroutine
The function loadData returns a Job object which may be cancelled. When the parent coroutine is cancelled, all its children are recursively cancelled, too.
If the stopPresenting function was called while dataProvider.loadData was still in progress, the function view.showData will never be called.
var job: Job? = null
fun startPresenting() {
job = loadData()
}
fun stopPresenting() {
job?.cancel()
}
private fun loadData() = launch(uiContext) {
view.showLoading() // ui thread
val task = async(bgContext) { dataProvider.loadData("Task") }
val result = task.await() // non ui thread, suspend until finished
view.showData(result) // ui thread
}
The complete answer is available in my article Android Coroutine Recipes
I think you can get rid of runOnUiThread { ... } by using UI context for Android applications instead of CommonPool.
The UI context is provided by the kotlinx-coroutines-android module.
We also have another option. if we use Anko library , then it looks like this
doAsync {
// Call all operation related to network or other ui blocking operations here.
uiThread {
// perform all ui related operation here
}
}
Add dependency for Anko in your app gradle like this.
implementation "org.jetbrains.anko:anko:0.10.5"
Like sdeff said, if you use the UI context, the code inside that coroutine will run on UI thread by default. And, if you need to run an instruction on another thread you can use run(CommonPool) {}
Furthermore, if you don't need to return nothing from the method, you can use the function launch(UI) instead of async(UI) (the former will return a Job and the latter a Deferred<Unit>).
An example could be:
fun loadListOfMediaInAsync() = launch(UI) {
try {
withContext(CommonPool) { //The coroutine is suspended until run() ends
adapter.listOfMediaItems.addAll(resources.getAllTracks())
}
adapter.notifyDataSetChanged()
} catch(e: Exception) {
e.printStackTrace()
} catch(o: OutOfMemoryError) {
o.printStackTrace()
} finally {
progress.dismiss()
}
}
If you need more help I recommend you to read the main guide of kotlinx.coroutines and, in addition, the guide of coroutines + UI
If you want to return some thing from background thread use async
launch(UI) {
val result = async(CommonPool) {
//do long running operation
}.await()
//do stuff on UI thread
view.setText(result)
}
If background thread is not returning anything
launch(UI) {
launch(CommonPool) {
//do long running operation
}.await()
//do stuff on UI thread
}
All the above answers are right, but I was having a hard time finding the right import for the UI from kotlinx.coroutines, it was conflicting with UI from Anko.
Its
import kotlinx.coroutines.experimental.android.UI
Here's the right way to use Kotlin Coroutines. Coroutine scope simply suspends the current coroutine until all child coroutines have finished their execution. This example explicitly shows us how child coroutine works within parent coroutine.
An example with explanations:
fun main() = blockingMethod { // coroutine scope
launch {
delay(2000L) // suspends the current coroutine for 2 seconds
println("Tasks from some blockingMethod")
}
coroutineScope { // creates a new coroutine scope
launch {
delay(3000L) // suspends this coroutine for 3 seconds
println("Task from nested launch")
}
delay(1000L)
println("Task from coroutine scope") // this line will be printed before nested launch
}
println("Coroutine scope is over") // but this line isn't printed until nested launch completes
}
Hope this helps.
Please find attached the implementation for a remote API call with Kotlin Coroutines & Retrofit library.
import android.view.View
import android.util.Log
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.test.nyt_most_viewed.NYTApp
import com.test.nyt_most_viewed.data.local.PreferenceHelper
import com.test.nyt_most_viewed.data.model.NytAPI
import com.test.nyt_most_viewed.data.model.response.reviews.ResultsItem
import kotlinx.coroutines.*
import javax.inject.Inject
class MoviesReviewViewModel #Inject constructor(
private val nytAPI: NytAPI,
private val nytApp: NYTApp,
appPreference: PreferenceHelper
) : ViewModel() {
val moviesReviewsResponse: MutableLiveData<List<ResultsItem>> = MutableLiveData()
val message: MutableLiveData<String> = MutableLiveData()
val loaderProgressVisibility: MutableLiveData<Int> = MutableLiveData()
val coroutineJobs = mutableListOf<Job>()
override fun onCleared() {
super.onCleared()
coroutineJobs.forEach {
it.cancel()
}
}
// You will call this method from your activity/Fragment
fun getMoviesReviewWithCoroutine() {
viewModelScope.launch(Dispatchers.Main + handler) {
// Update your UI
showLoadingUI()
val deferredResult = async(Dispatchers.IO) {
return#async nytAPI.getMoviesReviewWithCoroutine("full-time")
}
val moviesReviewsResponse = deferredResult.await()
this#MoviesReviewViewModel.moviesReviewsResponse.value = moviesReviewsResponse.results
// Update your UI
resetLoadingUI()
}
}
val handler = CoroutineExceptionHandler { _, exception ->
onMoviesReviewFailure(exception)
}
/*Handle failure case*/
private fun onMoviesReviewFailure(throwable: Throwable) {
resetLoadingUI()
Log.d("MOVIES-REVIEWS-ERROR", throwable.toString())
}
private fun showLoadingUI() {
setLoaderVisibility(View.VISIBLE)
setMessage(STATES.INITIALIZED)
}
private fun resetLoadingUI() {
setMessage(STATES.DONE)
setLoaderVisibility(View.GONE)
}
private fun setMessage(states: STATES) {
message.value = states.name
}
private fun setLoaderVisibility(visibility: Int) {
loaderProgressVisibility.value = visibility
}
enum class STATES {
INITIALIZED,
DONE
}
}

Categories

Resources