#Synchronized is not working as expected in KMM - android

I have a Ktor class where I need to do action on unauthorized exception(when token is expired), for this action I need to have synchronized action, otherwise it is not working correctly, the problem is that #Synchronized is not synchronize the action and is not waiting for action to finish for next one.
fun ktorFunction(){
HttpResponseValidator {
handleResponseException { exception ->
kermit.e { "In ${exception.message}" }
val clientException =
exception as? ClientRequestException ?: return#handleResponseException
val exceptionResponse = clientException.response
when (exceptionResponse.status) {
HttpStatusCode.Unauthorized -> {
test(){
kermit.v { "Error message" }
}
}
}
}
}
#Synchronized
fun test(messageTest: () -> Unit) {
CoroutineScope(Dispatchers.Default).launch {
delay(3000)
messageTest()
}
}
The idea is I want test function to not be called from other thread until it is finished, whatever the action is in it.

launch is a function that starts a coroutine asynchronously and immediately returns, so this is behaving as it should. If you want to synchronize coroutines, you should use Mutex.
I'll leave it alone in the example before, but IMO, it's a code smell to create a CoroutineScope if you're not going to manage its lifecycle.
private val testMutex = Mutex()
fun test(messageTest: () -> Unit) {
CoroutineScope(Dispatchers.Default).launch {
testMutex.withLock {
delay(3000)
messageTest()
}
}
}

Related

Kotlin Coroutine Flow: Limit the number of collector

Is there a way to limit the number of collector in a function that returns a Flow using flow builder?
I have this public method in a ViewModel
fun fetchAssets(limit: String) {
viewModelScope.launch {
withContext(Dispatchers.IO){
getAssetsUseCase(AppConfigs.ASSET_PARAMS, limit).onEach {
when (it) {
is RequestStatus.Loading -> {
_assetState.tryEmit(AssetState.FetchLoading)
}
is RequestStatus.Success -> {
_assetState.tryEmit(AssetState.FetchSuccess(it.data.assetDataDomain))
}
is RequestStatus.Failed -> {
_assetState.tryEmit(AssetState.FetchFailed(it.message))
}
}
}.collect()
}
}
}
This method is called on ViewModel's init block, but can also be called manually on UI.
This flow emits value every 10 seconds.
Repository
override fun fetchAssets(
query: String,
limit: String
) = flow {
while (true) {
try {
interceptor.baseUrl = AppConfigs.ASSET_BASE_URL
emit(RequestStatus.Loading())
val domainModel = mapper.mapToDomainModel(service.getAssetItems(query, limit))
emit(RequestStatus.Success(domainModel))
} catch (e: HttpException) {
emit(RequestStatus.Failed(e))
} catch (e: IOException) {
emit(RequestStatus.Failed(e))
}
delay(10_000)
}
}
Unfortunately every time fetch() was invoke from UI, I noticed that it creates another collectors thus can ended up having tons of collector which is really bad and incorrect.
The idea is having a flow that emits value every 10 seconds but can also be invoke manually via UI for immediate data update without having multiple collectors.
You seem to misunderstand what does it mean to collect the flow or you misuse the collect operation. By collecting the flow we mean we observe it for changes. But you try to use collect() to introduce changes to the flow, which can't really work. It just starts another flow in the background.
You should collect the flow only once, so keep it inside init or wherever it is appropriate for your case. Then you need to update the logic of the flow to make it possible to trigger reloading on demand. There are many ways to do it and the solution will differ depending whether you need to reset the timer on manual update or not. For example, we can use the channel to notify the flow about the need to reload:
val reloadChannel = Channel<Unit>(Channel.CONFLATED)
fun fetchAssets(
query: String,
limit: String
) = flow {
while (true) {
try {
...
}
withTimeoutOrNull(10.seconds) { reloadChannel.receive() } // replace `delay()` with this
}
}
fun reload() {
reloadChannel.trySend(Unit)
}
Whenever you need to trigger the manual reload, do not start another flow or invoke another collect() operation, but instead just invoke reload(). Then the flow that is already being collected, will start reloading and will emit state changes.
This solution resets the timer on manual reload, which I believe is better for the user experience.
I ended up moving the timer on ViewModel as I can request on demand fetch while also not having multiple collectors that runs at the same time.
private var job: Job? = null
private val _assetState = defaultMutableSharedFlow<AssetState>()
fun getAssetState() = _assetState.asSharedFlow()
init {
job = viewModelScope.launch {
while(true) {
if (lifecycleState == LifeCycleState.ON_START || lifecycleState == LifeCycleState.ON_RESUME)
fetchAssets()
delay(10_000)
}
}
}
fun fetchAssets() {
viewModelScope.launch {
withContext(Dispatchers.IO) {
getAssetsUseCase(
AppConfigs.ASSET_BASE_URL,
AppConfigs.ASSET_PARAMS,
AppConfigs.ASSET_SIZES[AppConfigs.ASSET_LIMIT_INDEX]
).onEach {
when(it){
is RequestStatus.Loading -> {
_assetState.tryEmit(AssetState.FetchLoading)
}
is RequestStatus.Success -> {
_assetState.tryEmit(AssetState.FetchSuccess(it.data.assetDataDomain))
}
is RequestStatus.Failed -> {
_assetState.tryEmit(AssetState.FetchFailed(it.message))
}
}
}.collect()
}
}
}
override fun onCleared() {
job?.cancel()
super.onCleared()
}
Please correct me if this one is a code smell.

The withContext coroutine is not working. Using Kotlin in Android

I've been mulling this over for some time now and I just can't get it to work.
So in brief, I have a Splash Activity from where I call another activity that contains my ViewModel. The ViewModel in simple terms just needs to sequentially run function A(which is getfbdata below; it is a network call.). And only after this function completes, it should run function B (which is dosavefbdata below; save info to DB.). Again, it should wait for function B to complete before running the main thread function, function C(which is confirm first below; it checks whether function B has completed by getting the result from function B (dosavefbdata below). If function C is positive, it closes the Splash activity.
Suffice to say, none of the above works. Println results show all functions were run sequentially without waiting for each to complete. Lastly, SplashActivity().killActivity() call on function C did not work.
Note: withContext does not require to await() on the suspended functions right? I also tried using viewModelScope.async instead of viewModelScope.launch.
I would really appreciate your help here. Thanks in advance.
*Under SplashActivity:
fun killActivity(){
finish()
}
*Under onCreate(SplashActivity):
CoroutingClassViewModel(myc).initialize()
**
class CoroutingClassViewModel(val myc: Context): ViewModel() {
fun initialize() {
viewModelScope.launch(Dispatchers.Main) {
try {
val fbdata = withContext(Dispatchers.IO) { getfbdata() }
val test1 = withContext(Dispatchers.IO) { test1(fbdata) }
val savedfbdata = withContext(Dispatchers.IO) { dosavefbdata(fbdata,myc) }
val confirmfirst = { confirmfunc(savedfbdata,myc) }
println("ran savedfbdata.")
} catch (exception: Exception) {
Log.d(TAG, "$exception handled !")
}
}
}
fun confirmfunc(savedfbdata: Boolean, myc: Context){
if (savedfbdata==true){
SplashActivity().killActivity()
}
}
suspend fun getfbdata(): MutableList<FirebaseClass> {
return withContext(Dispatchers.IO) {
//perform network call
return#withContext fbdata
}
}
suspend fun dosavefbdata(fbdata: MutableList<FirebaseClass>,myc: Context): Boolean{
return withContext(Dispatchers.IO) {
//save to database
return#withContext true
}
}
suspend fun test1(fbdata: MutableList<FirebaseClass>){
return withContext(Dispatchers.IO) {
println("test1: fbdata is: $fbdata")
}
}
}
Use AndroidViewModel if you want to have Context in it:
class CoroutingClassViewModel(myc: Application) : AndroidViewModel(myc) { ... }
In onCreate method of SplashActivity activity instantiate the view model like this:
val vm = ViewModelProvider(this)[CoroutingClassViewModel::class.java]
vm.initialize()
In CoroutingClassViewModel class create LiveData object to notify activity about operations completion:
val completion = MutableLiveData<Boolean>()
fun confirmfunc(savedfbdata: Boolean, myc: Context) {
if (savedfbdata) {
completion.postValue(true)
}
}
In your SplashActivity use this code to observe completion:
vm.completion.observe(this, Observer {
if (it) killActivity()
})
You use withContext(Dispatchers.IO) function two times for the same operation. Don't do that. For example in this code:
val fbdata = withContext(Dispatchers.IO) { getfbdata() }
if we look at getfbdata function we see that function withContext(Dispatchers.IO) is already called there. So get rid of repeated calls:
val fbdata = getfbdata()
I had same issue with withContext(Dispatcher.IO), I thought that switching coroutine context doesn't work, while in fact in splash screen i launched super long operation on Dispatcher.IO, then later when trying to use the same Dispatcher.IO it didn't work or in other words it waited until the first work in splash screen finished then started the new work.

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

Handling file download with gRPC on Android

I currently have a gRPC server which is sending chunks of a video file. My android application written in Kotlin uses coroutines for UI updates (on Dispatchers.MAIN) and for handling a unidirectional stream of chunks (on Dispatchers.IO). Like the following:
GlobalScope.launch(Dispatchers.Main) {
viewModel.downloadUpdated().accept(DOWNLOAD_STATE.DOWNLOADING) // MAKE PROGRESS BAR VISIBLE
GlobalScope.launch(Dispatchers.IO) {
stub.downloadVideo(request).forEach {
file.appendBytes(
it.data.toByteArray()
)
}
}.join()
viewModel.downloadUpdated().accept(DOWNLOAD_STATE.FINISHED) // MAKE PROGRESS BAR DISAPPEAR
} catch (exception: Exception) {
viewModel.downloadUpdated().accept(DOWNLOAD_STATE.ERROR) // MAKE PROGRESS BAR DISAPPEAR
screenNavigator.showError(exception) // SHOW DIALOG
}
}
This works pretty well but I wonder if there is not a 'cleaner' way to handle downloads. I already know about DownloadManager but I feel like it only accepts HTTP queries and so I can't use my gRPC stub (I might be wrong, please tell me if so). I also checked WorkManager, and here is the same problem I do not know if this is the proper way of handling that case.
So, there are two questions here:
Is there a way to handle gRPC queries in a clean way, meaning that I can now when it starts, finishes, fails and that I can cancel properly?
If not, is there a better way to use coroutines for that ?
EDIT
For those interested, I believe I came up with a dummy algorithm for downloading while updating the progress bar (open to improvments):
suspend fun downloadVideo(callback: suspend (currentBytesRead: Int) -> Unit) {
println("download")
stub.downloadVideo(request).forEach {
val data = it.data.toByteArray()
file.appendBytes(data)
callback(x) // Where x is the percentage of download
}
println("downloaded")
}
class Fragment : CoroutineScope { //NOTE: The scope is the current Fragment
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job
fun onCancel() {
if (job.isActive) {
job.cancel()
}
}
private suspend fun updateLoadingBar(currentBytesRead: Int) {
println(currentBytesRead)
}
fun onDownload() {
launch(Dispatchers.IO) {
downloadVideo { currentBytes ->
withContext(Dispatchers.Main) {
updateLoadingBar(currentBytes)
if (job.isCancelled)
println("cancelled !")
}
}
}
}
}
For more info, please check: Introduction to coroutines
EDIT 2
As proposed in comments we could actually use Flows to handle this and it would give something like:
suspend fun foo(): Flow<Int> = flow {
println("download")
stub.downloadVideo(request).forEach {
val data = it.data.toByteArray()
file.appendBytes(data)
emit(x) // Where x is the percentage of download
}
println("downloaded")
}
class Fragment : CoroutineScope {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job
fun onCancel() {
if (job.isActive) {
job.cancel()
}
}
private suspend fun updateLoadingBar(currentBytesRead: Int) {
println(currentBytesRead)
}
fun onDownload() {
launch(Dispatchers.IO) {
withContext(Dispatchers.Main) {
foo()
.onCompletion { cause -> println("Flow completed with $cause") }
.catch { e -> println("Caught $e") }
.collect { current ->
if (job.isCancelled)
return#collect
updateLoadingBar(current)
}
}
}
}
}
gRPC can be many things so in that respect your question is unclear. Most importantly, it can be fully async and callback-based, which would mean it can be turned into a Flow that you can collect on the main thread. File writing, however, is blocking.
Your code seems to send the FINISHED signal right away, as soon as it has launched the download in the background. You should probably replace launch(IO) with withContext(IO).

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

Categories

Resources