Cannot access fixedRateTimer in Kotlin Multiplatform - kotlin

I'm working on a Kotlin Multiplatform project. And I'm trying to use a timer and countdown timer but I cannot access kotlin.concurrent.fixedRateTimer or import kotlin.concurrent.timer in commonMain module.
However the kotlin.concurrent is available:
This is root build.gradle :
plugins {
kotlin("multiplatform")
id("com.android.library")
id("kotlin-android-extensions")
}
// ...
kotlin {
//...
sourceSets {
val commonMain by getting {
dependencies {
implementation("org.jetbrains.kotlin:kotlin-stdlib:1.4.10")
implementation("org.jetbrains.kotlin:kotlin-reflect:1.4.10")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9")
//...
}
}
//...
}
}
I wonder if it's even possible to use these methods there. If not, how can I write a timer and countdown timer in commonMain module?
I have tried to use Coroutines to achieve the same functionality but failed because they are not precise:
fun doAfter(delay: Long, action: () -> (Unit)) = launch {
delay(delay)
action.invoke()
}
fun countdown(time: Long, tick: Long, onTick: () -> (Unit), onFinish: () -> (Unit)) = launch {
val ticks = (time / tick).toInt()
repeat(ticks) {
onTick()
delay(tick)
}
onFinish()
}

As said by Qaz, the function you are trying to use in common code is JVM only.
Usually in KMP, when you still don't have a common functionality already built in by the framework you could follow different approaches:
Use someone else library (e.g. moko-time) - Best place for searching libraries is here.
Use the native framworks classes by the expect/actual mechanism
Just to give you and example of what could be done (not sure if that is right for you or could fit your needs. It's just to put you in the right direction and above all what I've wrote could not be production ready at all [-;)
commonMain:Timer.kt
expect class KMMTimer(
name: String? = null,
interval: Long,
delay: Long,
action: () -> Unit
) {
val name: String?
val interval: Long
val delay: Long
fun start()
fun cancel()
fun isRunning(): Boolean
}
androidMain:Timer.kt
import java.util.*
import kotlin.concurrent.fixedRateTimer
actual class KMMTimer actual constructor(
actual val name: String?,
actual val interval: Long,
actual val delay: Long,
action: () -> Unit
) {
private var timer: Timer? = null
private val action = action
actual fun start() {
if (!isRunning()) {
timer = fixedRateTimer(
name = name,
initialDelay = delay,
period = interval
) {
action()
}
}
}
actual fun cancel() {
timer?.cancel()
timer = null
}
actual fun isRunning(): Boolean {
return timer != null
}
}
iosMain:Timer.kt
import platform.Foundation.NSDate
import platform.Foundation.NSRunLoop
import platform.Foundation.NSRunLoopCommonModes
import platform.Foundation.NSTimer
actual class KMMTimer actual constructor(
actual val name: String?,
actual val interval: Long,
actual val delay: Long,
action: () -> Unit
) {
private var timer: NSTimer? = null
private var action = action
actual fun start() {
if (!isRunning()) {
timer = NSTimer(
fireDate = NSDate(
NSDate().timeIntervalSinceReferenceDate + (delay.toDouble() / 1000)
),
interval = (interval.toDouble() / 1000),
repeats = true,
block = {
action()
}
)
timer?.let {
NSRunLoop.currentRunLoop().addTimer(it, NSRunLoopCommonModes)
}
}
}
actual fun cancel() {
timer?.invalidate()
timer = null
}
actual fun isRunning(): Boolean {
return timer != null
}
}

The function you are trying to use is JVM only. See
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.concurrent/fixed-rate-timer.html

Related

what is Best practices for designing asynchronous task in this case( in kotlin, coroutine or thread)

my android app need to call more than 10 APIs at the same time.
this call api's is use other library it made in other teams
and result receive by listener in JsonString format.
this multiple calling api is need to call at same time.
Because it takes a lot of time to call one API
i made it by callback structure like this.
but i hope refactor this code covert to coroutine.
private val library : OtherLibrary = OtherLibrary()
private val retryCount: HashMap<String?, Int> = HashMap()
private val listener = object : ApiListener {
override fun onSucceeded(apiName: String, result: String?) {
when (apiName) {
"UserInfo" -> handleResultUserInfo(result)
"ProductInfo" -> handleResultProductInfo(result)
"....Info" -> handleResult___Info(result)
// ... and Others
}
}
override fun onUpdate(apiName: String, version: String) = library.callApi(apiName, this)
override fun onFailed(apiName: String) = retry(apiName, this)
}
fun start() {
callAPI("UserInfo")
callAPI("ProductInfo")
// ... and Others
}
fun callAPI(apiName: String, listener: ApiListener? = null) {
val listener = listener ?: this.listener
retryCount[apiName] = 0
library.callApi(apiName, listener)
}
fun retry(apiName: String, listener: ApiListener) {
if (retryCount[apiName]!! < 3) {
retryCount[apiName]!!.plus(1)
library.callApi(apiName, listener)
}else{
throw RuntimeException("API Call Failed: $apiName")
}
}
fun handleResultUserInfo(result: String?) {
// TODO parse & do something
}
fun handleResultProductInfo(result: String?) {
// TODO parse & do something
}
fun handleResult___Info(result: String?) {
// TODO parse & do something
}
// ... and Others
i want use coroutine for readability not callback structure.
callback structure is not good method for readability i think.
so, i applied suspendCoroutine to library's listener for look like synchronous readability.
but, suspendCoroutine is suspend it functions when to until call it.resume
what is best practice in this case?
private val library : OtherLibrary = OtherLibrary()
private val retryCount: HashMap<String?, Int> = HashMap()
fun start(){
CoroutineScope(Dispatchers.IO).launch{
handleResultUserInfo(callAPI("UserInfo"))
handleResultProductInfo(callAPI("ProductInfo"))
handleResult___Info(callAPI("___Info"))
}
}
suspend fun callAPI(apiName: String, listener:ApiListener? = null) : String? = suspendCoroutine{
val listener = listener ?: object : ApiListener {
override fun onSucceeded(apiName: String, result: String?) = it.resume(result)
override fun onUpdate(apiName: String, version: String) = library.callApi(apiName, this)
override fun onFailed(apiName: String) = retry(apiName, this)
}
retryCount[apiName] = 0
library.callApi(apiName, listener)
}
↑ it waiting complete of previous work. it's not call api at same time
so i try to like this.
fun start(){
val callDataArr = arrayOf(
CallData("UserInfo", ::handleResultUserInfo),
CallData("ProductInfo", ::handleResultProductInfo),
CallData("___Info", ::handleResult___Info),
// ... and others
)
callDataArr.forEach {
CoroutineScope(Dispatchers.IO).launch{
it.handler(callAPI(it.apiName))
}
}
}
but... it doesn't look good.
because, CoroutineScope(Dispatchers.IO).launch called a lot of times
is not good for performance or have other problems?

Testing Kotlin Coroutines (Flow vs Publisher)

I have a reasonably simple bit of code - every second ping some handler with the current timestamp:
private val clock = Clock.systemDefaultZone()
private fun now() = clock.instant()
suspend fun ping(pinger: suspend (Instant) -> Unit) {
repeat(5) {
pinger(now())
if (it < 4) {
delay(1.seconds)
}
}
}
Which I want to expose as a reactive Producer:
fun publishPing() = publish { ping(::send) }
In practice, it works - but when testing:
#OptIn(ExperimentalCoroutinesApi::class)
#Test
fun `test publishPing`() = runTest {
var count = 0
publishPing().collect {
count++
assertTrue { EPOCH.isBefore(it) }
assertTrue { it.isBefore(now()) }
}
assertEquals(5, count) // Pass
assertEquals(4000, currentTime) // AssertionFailedError: Expected:4000; Actual:0
}
The virtual time doesn't get incremented, and he test takes 4+ seconds to run (eg the calls to delay() aren't being handled by the test dispatcher).
Doing the same with a flow all works as expected (and runs in milliseconds).
fun flowPing() = flow { ping(::emit) }
#OptIn(ExperimentalCoroutinesApi::class)
#Test
fun `test flowPing`() = runTest {
var count = 0
flowPing().collect {
count++
assertTrue { EPOCH.isBefore(it) }
assertTrue { it.isBefore(now()) }
}
assertEquals(5, count) // Pass
assertEquals(4000, currentTime) // Pass
}
I have a vague idea of what is [not] going on (the coroutines-test support isn't Hooked into the coroutines/reactive support?) - but what do I need to do to sort it?
FWIW the same behaviour happens with coroutines 1.5.x and runBlockingTest (code above from coroutines 1.6 and runTest)

While loop doesn't seem to work with .putFile when uploading multiple images to Firebase storage in Kotlin

I have been trying to upload multiple images to Firebase Storage. But, I am not able to do it successfully. I could successfully upload the image (single) to the storage and add the URL of the image to the Firestore, now that I revised my code to upload up to five images, it could be any number of images from 1 to 5.
R.id.btn_submit -> {
if (validateDetails()) {
uploadImage()
}
}
The above code, calls the following function after validating the fields, which then calls the function uploadImageToCloudStorage. mSelectedImageFileUriList is private var mSelectedImageFileUriList: MutableList<Uri?>? = null. It all seems to work correctly.
private fun uploadImage() {
showProgressDialog(resources.getString(R.string.please_wait))
FirestoreClass().uploadImageToCloudStorage(
this#AddProductActivity,
mSelectedImageFileUriList,
Constants.PRODUCT_IMAGE,
Constants.PRODUCT_IMAGE_DIRECTORY_NAME,
et_product_title.text.toString().trim { it <= ' ' }
)
}
Following code is where I guess is a mistake.
fun uploadImageToCloudStorage(
activity: AddProductActivity,
imageFileURI: MutableList<Uri?>?,
imageType: String,
directoryName: String,
title: String
) {
var i = 0
val imageURLList = ArrayList<String>()
val itr = imageFileURI?.iterator()
if (itr != null) {
while (itr.hasNext()) {
val sRef: StorageReference = FirebaseStorage.getInstance().getReference(
"/$directoryName/" + imageType + "." + Constants.getFileExtension(
activity,
imageFileURI[i]
)
)
sRef.putFile(imageFileURI[i]!!)
.addOnSuccessListener { taskSnapshot ->
taskSnapshot.metadata!!.reference!!.downloadUrl
.addOnSuccessListener { uri ->
if (i < imageFileURI.size) {
i += 1
imageURLList.add(uri.toString())
} else {
activity.imageUploadSuccess(imageURLList)
}
}
}
.addOnFailureListener { exception ->
activity.hideProgressDialog()
Log.e(
activity.javaClass.simpleName,
exception.message,
exception
)
}
}
} else {
Toast.makeText(
activity,
"There is no images in the ArrayList of URI",
Toast.LENGTH_SHORT
).show()
}
}
EDIT: After receiving the first answer.
I have created a QueueSyn.kt file and added the code in the Answer. The activity where the images and the button are changed to
class AddProductActivity : BaseActivity(), View.OnClickListener, QueueSyncCallback {
The following function is called when the button is hit.
private fun uploadProductImage() {
showProgressDialog(resources.getString(R.string.please_wait))
QueueSync(
mSelectedImageFileUriList,
Constants.PRODUCT_IMAGE,
Constants.PRODUCT_IMAGE_DIRECTORY_NAME,
et_product_title.text.toString().trim { it <= ' ' },
this
).startUploading()
}
I have also implemented these two methods in the class AddProductActivity, but I don't know what should go inside this.
override fun completed(successList: MutableList<Uri>, failureList: MutableList<Uri>) {
TODO("Not yet implemented")
}
override fun getFileExtension(uri: Uri): String {
TODO("Not yet implemented")
}
Error:
This should work
import android.net.Uri
import com.google.firebase.storage.FirebaseStorage
import com.google.firebase.storage.StorageReference
import java.util.*
import kotlin.collections.ArrayList
interface QueueSyncCallback {
fun completed(successList: MutableList<Uri>, failureList: MutableList<Uri>)
fun getFileExtension(uri: Uri): String
}
class QueueSync(
imageFileURI: MutableList<Uri?>?,
private val imageType: String,
private val directoryName: String,
private val title: String,
private val callback: QueueSyncCallback,
private val maxActive: Int = 5
) {
private val queue: LinkedList<Uri> = LinkedList()
private val runningQueue: MutableList<Uri> = Collections.synchronizedList(
object : ArrayList<Uri>() {
override fun remove(element: Uri): Boolean {
val removed = super.remove(element)
if (isEmpty() && queue.isEmpty()) {
callback.completed(successList, failureList)
} else if (queue.isNotEmpty()) {
addToRunningQueue()
}
return removed
}
}
)
private val successList: MutableList<Uri> = Collections.synchronizedList(ArrayList())
private val failureList: MutableList<Uri> = Collections.synchronizedList(ArrayList())
init {
if (imageFileURI != null)
for (uri in imageFileURI) {
if (uri != null)
queue.add(uri)
}
}
private fun getLocation(uri: Uri) = "/$directoryName/$imageType.${callback.getFileExtension(uri)}"
fun startUploading() {
var i = 0
if (queue.isEmpty()) {
callback.completed(successList, failureList)
return
}
while (i < maxActive && queue.isNotEmpty()) {
addToRunningQueue()
i++
}
}
private fun addToRunningQueue() {
val uri = queue.poll()!!
runningQueue.add(uri)
uploadImageToCloudStorage(uri)
}
private fun uploadImageToCloudStorage(locationUri: Uri) {
val sRef: StorageReference = FirebaseStorage.getInstance().getReference(getLocation(locationUri))
sRef.putFile(locationUri)
.addOnSuccessListener { taskSnapshot ->
taskSnapshot.metadata!!.reference!!.downloadUrl
.addOnSuccessListener { uri ->
successList.add(uri)
runningQueue.remove(locationUri)
}
}
.addOnFailureListener {
failureList.add(locationUri)
runningQueue.remove(locationUri)
}
}
}
Since your need requires usage of threads so to prevent race conditions I had to use Collections.synchronizedList. To use this you need to implement QueueSyncCallback in your activity and pass it as a reference to QueueSync. Make sure that any piece of code written inside completed is wrapped inside runOnMainThread if it is going to access views in any way since completed will not run on main thread as far as I know. This should work however I am not able to test it since it is based on your current code.
Edit:- Answering after edit
override fun completed(successList: MutableList<Uri>, failureList: MutableList<Uri>) {
imageUploadSuccess(successList)
hideProgressDialog()
}
override fun getFileExtension(uri: Uri): String {
Constants.getFileExtension(this, imageFileURI[i])
}

How to subscribe to StateFlow in kotlin-react useEffect

I'm trying to create a small counter example for kotlin-react with functionalComponent with kotlin 1.4-M2.
The example should use kotlinx.coroutines.flow. I'm struggling at collecting the values from the store in reacts useEffect hook.
Store:
object CounterModel { // Modified sample from kotlin StateFlow doc
private val _counter = MutableStateFlow(0) // private mutable state flow
val counter: StateFlow<Int> get() = _counter // publicly exposed as read-only state flow
fun inc() { _counter.value++ }
}
Component:
val counter = functionalComponent<RProps> {
val (counterState, setCounter) = useState(CounterModel.counter.value)
useEffect(listOf()) {
// This does not work
GlobalScope.launch { CounterModel.counter.collect { setCounter(it) } }
}
div {
h1 {
+"Counter: $counterState"
}
button {
attrs.onClickFunction = { CounterModel.inc() }
}
}
}
When I directly call CounterModel.counter.collect { setCounter(it) } it complains about Suspend function 'collect' should be called only from a coroutine or another suspend function.
How would you implement this useEffect hook?
And once the subscription works, how would you unsubscribe from it (use useEffectWithCleanup instead of useEffect)?
Finally found a solution. We can use onEach to do an action for every new value and then 'subscribe' with launchIn. This returns a job that can be canceled for cleanup:
object CounterStore {
private val _counter = MutableStateFlow(0)
val counter: StateFlow<Int> get() = _counter
fun inc() { _counter.value++ }
}
val welcome = functionalComponent<RProps> {
val (counter, setCounter) = useState(CounterStore.counter.value)
useEffectWithCleanup(listOf()) {
val job = CounterStore.counter.onEach { setCounter(it) }.launchIn(GlobalScope)
return#useEffectWithCleanup { job.cancel() }
}
div {
+"Counter: $counter"
}
button {
attrs.onClickFunction = { CounterStore.inc() }
+"Increment"
}
}
We can extract this StateFlow logic to a custom react hook:
fun <T> useStateFlow(flow: StateFlow<T>): T {
val (state, setState) = useState(flow.value)
useEffectWithCleanup(listOf()) {
val job = flow.onEach { setState(it) }.launchIn(GlobalScope)
return#useEffectWithCleanup { job.cancel() }
}
return state
}
And use it like this in our component:
val counter = useStateFlow(CounterStore.counter)
The complete project can be found here.
The Flow-Api is very experimental so this might not be the final solution :)
if's very important to check that the value hasn't changed,
before calling setState, otherwise the rendering happens twice
external interface ViewModelProps : RProps {
var viewModel : MyViewModel
}
val App = functionalComponent<ViewModelProps> { props ->
val model = props.viewModel
val (state, setState) = useState(model.stateFlow.value)
useEffectWithCleanup {
val job = model.stateFlow.onEach {
if (it != state) {
setState(it)
}
}.launchIn(GlobalScope)
return#useEffectWithCleanup { job.cancel() }
}
}

Cannot override apply function

I came across the following code:
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import java.util.concurrent.TimeUnit
class ExpBackoff(
private val jitter: Jitter,
private val delay: Long,
private val unit: TimeUnit,
private val retries: Int = 0
) : Function<Observable<out Throwable>, Observable<Long>> {
#Throws(Exception::class)
override fun apply(observable: Observable<out Throwable>): Observable<Long> {
return observable
.zipWith(Observable.range(1, retries), BiFunction<Throwable, Int, Int> { _, retryCount ->
retryCount
})
.flatMap { attemptNumber -> Observable.timer(getNewInterval(attemptNumber), unit) }
}
private fun getNewInterval(retryCount: Int): Long {
var newInterval = (delay * Math.pow(retryCount.toDouble(), 2.0) * jitter.get()).toLong()
if (newInterval < 0) {
newInterval = Long.MAX_VALUE
}
return newInterval
}
}
located at:
https://leandrofavarin.com/exponential-backoff-rxjava-operator-with-jitter
This code doesn't compile. This line is wrong:
Function<Observable<out Throwable>, Observable<Long>>
Function only takes a single argument. I am really confused here. The person who wrote the article clearly indicates that he wrote this code and I assume it works or at least worked at the time he wrote it. But I doubt that Kotlin changed the interface for Function. Even if I remove the second parameter, the code will not compile because the apply function cannot be overridden as this is not part of the Function interface.
How can I fix this issue?
Looks like you are missing correct Function import. The following code works in my IDE.
import io.reactivex.Observable
import io.reactivex.functions.BiFunction
import io.reactivex.functions.Function
import java.util.concurrent.TimeUnit
class ExpBackoff(
private val jitter: Jitter,
private val delay: Long,
private val unit: TimeUnit,
private val retries: Int = 0
) : Function<Observable<out Throwable>, Observable<Long>> {
#Throws(Exception::class)
override fun apply(observable: Observable<out Throwable>): Observable<Long> {
return observable
.zipWith(Observable.range(1, retries), BiFunction<Throwable, Int, Int> { _, retryCount ->
retryCount
})
.flatMap { attemptNumber -> Observable.timer(getNewInterval(attemptNumber), unit) }
}
private fun getNewInterval(retryCount: Int): Long {
var newInterval = (delay * Math.pow(retryCount.toDouble(), 2.0) * jitter.get()).toLong()
if (newInterval < 0) {
newInterval = Long.MAX_VALUE
}
return newInterval
}
}