21

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) {

        }
    }
user12575366
  • 303
  • 1
  • 2
  • 8

6 Answers6

24

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.

RobCo
  • 6,240
  • 2
  • 19
  • 26
  • Declaring **Lifecycle.State.STARTED** get will call every activity start() invoke, define **Lifecycle.State.CREATED** if you need once on activity start – Merlin Jeyakumar Sep 18 '22 at 07:59
  • @MerlinJeyakumar Yes the given code will top repeating when the lifecycle moves to the stopped state (background) and is automatically restarted when returning to the foreground (started). If you don't need this behaviour and want to start it indefinitely then just skip the `repeatOnLifecycle` call. It will still automatically stop when the lifecycle is destroyed as the lifecycleScope will be cancelled. – RobCo Sep 21 '22 at 07:00
22

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()
vignesh
  • 492
  • 3
  • 9
  • 1
    For performing network operations, I think you should also provide Dispatchers.IO as parameter in launch method. Refer https://developer.android.com/kotlin/coroutines#main-safety, https://kotlinlang.org/docs/reference/coroutines/coroutine-context-and-dispatchers.html [Dispatchers and threads section] – Namrata Bagerwal Apr 10 '20 at 08:03
  • @Namrata Bagerwal I think you mean this one: https://developer.android.com/kotlin/coroutines/coroutines-adv#main-safety. – Sam Chen Dec 17 '21 at 13:44
7

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()
Hitesh Sahu
  • 41,955
  • 17
  • 205
  • 154
5

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()
nilsi
  • 10,351
  • 10
  • 67
  • 79
  • point to be noted here is canceling the fetchDatesTimer will not cancel the scope. So while (isActive) is always true and this code will run for an infinite time. – Roshan Kumar Dec 29 '22 at 08:20
4

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>()
}
Thiago
  • 12,778
  • 14
  • 93
  • 110
0

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
           }
        }
Dragan N
  • 21
  • 4