I'm trying to figure out how to use an extension function to run any method with a delay, but can't seem to figure it out.
I am trying something like below where I have a function and I want a handler to delay the execution by a certain timeInterval:
functionX().withDelay(500)
functionY().withDelay(500)
private fun Unit.withDelay(delay: Int) {
Handler().postDelayed( {this} , delay)}
private fun Handler.postDelayed(function: () -> Any, delay: Int) {
this.postDelayed(function, delay)}
Anyone?
Another approach would be to declare a top-level (i.e. global) function like this:
fun withDelay(delay : Long, block : () -> Unit) {
Handler().postDelayed(Runnable(block), delay)
}
Then you can call it, from anywhere, like this:
withDelay(500) { functionX() }
You should put the extension on the function type, not Unit:
fun functionX() {}
fun functionY() {}
private fun (() -> Any).withDelay(delay: Int) {
Handler().postDelayed(this , delay)
}
Usage:
::functionX.withDelay(500)
::functionY.withDelay(500)
Here, ::functionX is a reference to the global function called functionX.
Or I like this version too:
Wrap whatever code block you want to be executed within { ... }
{ invokeSomeMethodHere() }.withDelay()
And have an extension function that invokes the Runnable after a certain delay:
fun <R> (() -> R).withDelay(delay: Long = 250L) {
Handler().postDelayed({ this.invoke() }, delay)
}
For example, you can declare your global variables like:
private var handler: Handler = Handler()
private lateinit var runnableNewUrlBackground: Runnable
And also declare the function as global
init {
runnableNewUrlBackground = Runnable {
// Your function code here
myCustomFun();
}
}
Then, when you want to call this, just use:
handler.postDelayed(runnableNewUrlBackground, YOUR_DESIRED_TIME)
Related
I have method, which returns response from server. For example:
fun uploadVideo(link: String, completionHandler: (Result<String>) -> Unit) {
// some action
completionHandler(Result.success(""))
}
I want to call this method one by one. Wait for a response from the previous one to call the next one. For example
uploadVideo("https://stackoverflow.com/video1.mp4") {
}
// call this only when i have response from preview request
uploadVideo("https://stackoverflow.com/video2.mp4") {
}
// call this only when i have response from preview request
uploadVideo("https://stackoverflow.com/video3.mp4") {
}
I tried use suspendCancellableCoroutine, like this
suspend fun uploadVideo(link: String?): String? = suspendCancellableCoroutine { cont ->
uri?.let {
uploadVideo(link,
completionHandler = {
it.onSuccess { uri ->
cont.resume(uri.toString())
}.onFailure {
cont.resumeWithException(it)
}
}
)
} ?: kotlin.run {
cont.resume(null)
}
}
and then call like this:
uploadVideo("https://stackoverflow.com/video1.mp4")
uploadVideo("https://stackoverflow.com/video2.mp4")
uploadVideo("https://stackoverflow.com/video3.mp4")
but these methods are not called sequentially, but in parallel
Note, the contents of your example API function don't quite make sense. If the callback were simply called inside the body of the function, then that would mean the function was blocking the whole time, which would mean there would be no reason for it to even have a callback. It could just directly return the value.
The actual contents of the API function might look more like this:
fun uploadVideo(link: String, completionHandler: (Result<String>) -> Unit) {
val callbackHandler = Handler(Looper.myLooper())
someOtherHandlerOrThreadPool.run {
// some action
callbackHandler.post {
completionHandler(Result.success(""))
}
}
}
The reason I bring that up is that the alternative to nesting a bunch of callbacks is use suspend functions and coroutines, but the code to convert the above to a suspend function doesn't make sense if it were a blocking function like in your version of it.
The basic pattern to convert a callback-based function into a suspend function is to use suspendCoroutine or suspendCancellableCoroutine. If uploadVideo was a function in some api class, you can define it as an extension function:
suspend fun SomeApiClass.uploadVideo(link: String): Result<String> = withContext(Dispatchers.Main) {
suspendCoroutine { cont ->
uploadVideo(link) { cont.resume(it) }
}
}
Now you can call this suspend function repeatedly in sequence if you're inside a coroutine or another suspend function:
fun foo() {
viewModelScope.launch {
val result1 = uploadVideo("https://stackoverflow.com/video1.mp4")
val result2 = uploadVideo("https://stackoverflow.com/video2.mp4")
val result3 = uploadVideo("https://stackoverflow.com/video3.mp4")
}
}
You could try this. this waits till the previous method called its callback and then runs the next one. Only if you have many images this is not a really nice way to do this.
fun uploadVideo(link: String, completionHandler: () -> Unit) {
// some action
completionHandler()
}
uploadVideo("https://stackoverflow.com/video1.mp4") {
uploadVideo("https://stackoverflow.com/video2.mp4") {
uploadVideo("https://stackoverflow.com/video3.mp4") {}
}
}
I use Handler for creating a timer in a Widget.
I use the recommended constructor, i.e. passing a Looper to it.
private val updateHandler = Handler(Looper.getMainLooper())
#RequiresApi(Build.VERSION_CODES.Q)
private val runnable = Runnable {
updateDisplay()
}
#RequiresApi(Build.VERSION_CODES.Q)
private fun updateDisplay () {
updateHandler?.postDelayed(runnable, TIMER_MS)
// some other code
}
The TIMER MS is set to 3000 ms.
The timer runs fine for a while and execute the given code. However after a random time elapsed the timer stops working and no more execution of the given code happens.
Please advise what the problem could be ond how to fix it.
Alternatively, can I use some other timer? (The timer should go off every few second - this is the reason why I use Handler)
Thank you for any advice in advance
You could always try using a Coroutine for something like this:
class TimedRepeater(var delayMs: Long,
var worker: (() -> Unit)) {
private var timerJob: Job? = null
suspend fun start() {
if (timerJob != null) throw IllegalStateException()
timerJob = launch {
while(isActive) {
delay(delayMs)
worker()
}
}
}
suspend fun stop() {
if (timerJob == null) return
timerJob.cancelAndJoin()
timerJob = null
}
}
suspend fun myStuff() {
val timer = Timer(1000) {
// Do my work
}
timer.start()
// Some time later
timer.stop()
}
I haven't tested the above, but it should work well enough.
You can use CountDownTimer from Android framework to achieve the same. It internally uses Handler for timer
val timer = object: CountDownTimer(1000,1000){
override fun onTick(millisUntilFinished: Long) {
}
override fun onFinish() {
}
}
timer.start()
There is a fun method0:
private fun method0() {
println("method0 fun")
}
And a var method0 :
var method0 = {
println("method0")
}
It seems they are used the same:
method0()
I found that both occur at the same time, and the fun function has a higher priority when the code calls.
Other than that, is there any difference between them?
The var way of doing it results in a functional object. The lambda content is wrapped as a functional object so it can be passed around like any other instance of a class. It can directly be used as a function parameter, for instance.
var method0 = {
println("method0")
}
fun doSomethingTwice(action: ()->Unit) {
repeat(2) { action() }
}
fun main() {
doSomethingTwice(method0)
}
And since it's marked as a var you can swap it out for a different function:
fun main() {
method0 = { println("hello, world!") }
doSomethingTwice(method0)
}
Note that this way of specifying a function is a little bit heavier since it is wrapping the function in another class instance.
And you can still wrap any "regular" function into a functional object at any time by using :: to avoid doing it until it's necessary.
fun method0() {
println("method0")
}
fun main() {
doSomethingTwice(::method0)
}
What I've tried so far
fun getCPByID(ids: List<Int>): List<CheckingPointVo> {
var list : List<CheckingPointVo> = emptyList()
coroutineScope.launch {
list = someMethod()
}
return list
}
here I tried to use async and await but that cannot be run from a non suspend function. Is there a way to do this ?
Not really with the current structure, you're basically trying to combine synchronous code with async.
You have 3 possible options though to make it async:
Use a callback:
fun getCPByID(ids: List<Int>, listCallback: (List<CheckingPointVo>) -> Unit) {
coroutineScope.launch {
listCallback(someMethod())
}
}
Note: If you're using it from Java, this should work with either Java lambdas or Function. But you may create an interface for this, like :
Interface ListCallback {
fun onListReceived(list: List<CheckingPointVo>)
}
fun getCPByID(ids: List<Int>, listCallback: ListCallback) {
.... // Same implementation
}
// Call it from Java
getCPByID(ids, new ListCallback() {
void onListReceived(List<CheckingPointVo> list) {
...
}
});
Use either an observable pattern, use a Flow or LiveData. A possible example:
fun getCPByID(ids: List<Int>) = coroutineScope.launch {
flow {
emit(someMethod())
}
}
}
Make your function a suspend function and use coroutineScope.launch from the caller
I am trying to create an Extension function in android with Handlers but facing issue:
Extension fun Code:
fun delayTask(millis: Long, myFunction: (data:String) -> Unit) {
Handler().postDelayed({
myFunction(data) //why error is of data here
}, millis)
}
Calling like this:
delayTask(500, ::function)
Getting error Unresolved reference: data
data is not a parameter of your higher order function. It is a parameter of your function parameter. So it doesn't exist for you to pass to the passed function.
To be able to pass this data to your lambda, you will need to add it as another parameter:
fun delayTask(millis: Long, data: String, myFunction: (String) -> Unit) {
Handler().postDelayed({
myFunction(data)
}, millis)
}
And when you call it, you would have to also pass the data:
delayTask(500, someDataString, ::function)
Your function could be more versatile by removing the parameter from the function parameter. Then you could call any function with any amount of parameters needed just by wrapping it in a lambda:
fun delayTask(millis: Long, myFunction: () -> Unit) {
Handler().postDelayed({
myFunction()
}, millis)
}
delayTask(500) { myFunction(someData) }
For performance reasons, it would be better to make it inline. But the passed function has to be crossinline since it's wrapped in another object and called later:
inline fun delayTask(millis: Long, crossinline myFunction: () -> Unit) {
Handler().postDelayed({
myFunction()
}, millis)
}
Note this functionality is already available with the postDelayed function in Android Ktx core:
Handler().postDelayed(500L) { someFunction() }