Adding a yield to a non-suspending function - kotlin

I'm trying to build an interruptible version of CharSequence. Something along these lines:
class InterruptableCharSequence(private val delegate: CharSequence) : CharSequence {
override fun get(index: Int): Char = runBlocking {
yield() // Suspend here, in case we have cancelled
delegate[index]
}
override fun subSequence(start: Int, end: Int): CharSequence {
return delegate.subSequence(start, end)
}
override fun toString(): String {
return delegate.toString()
}
}
Since this is implementing an existing interface I can't make get a suspending function, but I'd like to call it from a co-routine (several layers up) and be able to interrupt/cancel it. How would I go about making this possible? Is there any way to do so without blocking like this or creating a new coroutine for each Char?

Related

Kotlin Unit and coroutine body

Assume there are some chained suspend functions like this.
suspend fun getData(): Boolean {
return request().also {
delay(1000)
}
}
suspend fun request(): Boolean {
return call()
}
suspend fun call(): Boolean {
return run {
delay(1000)
true
}
}
The above works allright. But if we convert the also block into a Unit param we will get an error: Suspension functions can be called only within coroutine body
suspend fun getData(): Boolean {
return request {
delay(1000) //the error
}
}
suspend fun request(action: (Boolean) -> Unit): Boolean {
return call().also(action)
}
Why is it so and is it possible to make the Unit inherit the coroutine body?
You should make the lambda parameter suspend:
suspend fun request(action: suspend (Boolean) -> Unit): Boolean {
Now the lambda parameter has a suspending function type, but also doesn't take that, so you can't pass it in directly like also(action), you need to change it to:
return call().also { action(it) }
See also: how to pass suspend function as parameter to another function? Kotlin Coroutines

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?

I have no way to get a return value of suspend function directly in Kotlin?

I use Room in my Android Studio project.
I hope to get the ID of added record quickly, but the following Code A can't work, how can I fix it? or I have to use these code just like Code B?
I have no way to get a return value of suspend function directly in Kotlin ?
Code A
#Dao
interface RecordDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun addRecord(aRecordEntity: RecordEntity): Long
}
class RecordRepository #Inject constructor(private val mRecordDao:RecordDao): IRecordRepository {
override suspend fun addRecord(aMRecord: MRecord): Long = withContext(Dispatchers.Default) {
mRecordDao.addRecord(ModelMapper.modelToEntity(aMRecord))
}
}
#HiltViewModel
class SoundViewModel #Inject constructor(
private val aRecordRepository: IRecordRepository
): ViewModel()
{
fun addRecord(aMRecord: MRecord):Long= viewModelScope.async {
aSoundMeter.addRecord(aMRecord)
}.await()
}
Code B
//The same
#HiltViewModel
class SoundViewModel #Inject constructor(
private val aRecordRepository: IRecordRepository
): ViewModel()
{
var id = 0L;
fun addRecord(aMRecord: MRecord) {
viewModelScope.launch {
id = aSoundMeter.addRecord(aMRecord)
}
}
}
You can only return the value of a suspend function from another suspend function. So make your ViewModel's function a suspend function:
#HiltViewModel
class SoundViewModel #Inject constructor(
private val aRecordRepository: IRecordRepository
): ViewModel()
{
suspend fun addRecord(aMRecord: MRecord): Long =
aSoundMeter.addRecord(aMRecord)
}
And launch a coroutine to call it and work with the results in your Fragment:
viewLifecycleOwner.lifecycleScope.launch {
// ...
val id = viewModel.addRecord(record)
// do something with id inside same launched coroutine
}
Note, if you're doing something critical to repository state, you should be using the results of addRecord inside a coroutine launched in the ViewModel instead. In that case, the Fragment should just "fire and forget" by calling some regular function in the ViewModel that has no return value.
Also, there are two issues with your code that I think show a misunderstanding:
Your repo code wraps the call to the DAO's suspend function using withContext. There is no reason to wrap a suspend function call in withContext() because the DAO suspend function already internally handles calling things on the right threads or dispatchers. It would be incorrect for a suspend function to ever block such that a specific IO or Default Dispatcher would be needed to call it. withContext(Dispatchers.IO) is for when you are calling blocking IO functions, like directly working with InputStreams and OutputStreams.
Never use async { }.await(). That's pointless and no different than directly calling the functions that you are wrapping in async since you are waiting for them immediately anyway.

Kotlin: Apply a suspend function on a list "in parallel"?

If I have a List<A> and a function suspend (A) -> B, how can I apply this function on the list in parallel?
coroutineScope {
list.map {
async {
process(it)
}
} // List<Deferred<B>>
.awaitAll() // List<B>
}
suspend fun process(a: A): B {
...
}
This assumes you are already in a suspend context. Otherwise, you need to launch a new coroutine on the appropriate scope instead of using the coroutineScope scoping function.
You can create an extension function on CoroutineScope, go through each element of the list and launch a coroutine for each element. In this way elements of the list will be processed in parallel. Some code snippet:
fun CoroutineScope.processListInParallel(list: List<A>): List<Deferred<B>> = list.map {
async { // launch a coroutine
processA(it)
}
}
GlobalScope.launch {
val list = listOf(A("name1"), A("name2"), A("name3"))
val deferredList = processListInParallel(list)
val results: List<B> = deferredList.awaitAll() // wait for all items to be processed
}
suspend fun processA(a: A): B {
delay(1000) // emulate suspension
return B("Result ${a.name}")
}
data class A(val name: String) {}
data class B(val name: String) {}
Note: GlobalScope is used here as an example, using it is highly discouraged, application code usually should use an application-defined CoroutineScope.

How to inherit MutableList in Kotlin?

I am trying to inherit MutableList, and add my own function to it. For example:
class CompositeJob : MutableList<Job> {
fun cancelAllJobs() {
for (job in this) {
job.cancel()
}
}
}
But I got the following error:
Class 'CompositeJob' is not abstract and does not implement abstract member
public abstract val size: Int defined in kotlin.collections.MutableList
How can I inherit MutableList, so I can use its original methods like add() and isEmpty(), and add my own one?
Thanks.
One option other answers don't mention is delegation:
class CompositeJob : MutableList<Job> by mutableListOf() {
fun cancelAllJobs() {
for (job in this) {
job.cancel()
}
}
}
is basically equivalent to
class CompositeJob : MutableList<Job> {
private val impl: MutableList<Job> = mutableListOf()
override fun size() = impl.size()
override fun add(x: Job) { impl.add(x) }
// etc for all other MutableList methods
fun cancelAllJobs() {
for (job in this) {
job.cancel()
}
}
}
MutableList is an interface - it doesn't implement any of its methods, just declares them. If you want to implement MutableList from scratch, you'll have to implement all 20 of its methods plus the size property, as your error already told you.
You can, however, subclass actual implementations of this interface, for example ArrayList or LinkedList:
class CompositeJob : ArrayList<Job>() {
fun cancelAllJobs() {
for (job in this) {
job.cancel()
}
}
}
Edit: If you're just looking to group coroutine Job instances, you should use a parent Job, a SupervisorJob, and CoroutineScope at this point, instead of collecting jobs like this manually.
Implementation of all the members is not the only way to achieve what you need.
An easier way is create an extension function like this:
fun MutableList<Job>.cancelAllJobs() {
this.forEach { it.cancel() }
}
Now you can call myList.cancelAllJobs() for every MutableList<Job> object.
As I have tested you need to make CompositeJob an abstract as error also suggest. After that, you can able to override add() and isEmpty() from MutableList
So your code looks like as below:
abstract class CompositeJob : MutableList<Job> {
override fun add(element:Job): Boolean
{
}
override fun isEmpty(): Boolean
{
}
}
If you want to avoid inheritance, or if the concrete class methods are marked final, then you can use composition and redirect all method calls which are not overridden. Here's a template:
class MyMutableList<T> : MutableList<T> {
private val list = mutableListOf<T>()
override val size get() = list.size
// MutableList<T>
override fun add(element: T): Boolean = list.add(element)
override fun add(index: Int, element: T) = list.add(index, element)
override fun addAll(elements: Collection<T>): Boolean = list.addAll(elements)
override fun addAll(index: Int, elements: Collection<T>): Boolean = list.addAll(index, elements)
override fun clear() = list.clear()
override fun remove(element: T): Boolean = list.remove(element)
override fun removeAll(elements: Collection<T>): Boolean = list.removeAll(elements)
override fun removeAt(index: Int): T = list.removeAt(index)
override fun retainAll(elements: Collection<T>): Boolean = list.retainAll(elements)
override fun set(index: Int, element: T): T = list.set(index, element)
// List<T>
override fun contains(element: T): Boolean = list.contains(element)
override fun containsAll(elements: Collection<T>): Boolean = list.containsAll(elements)
override fun get(index: Int): T = list.get(index)
override fun indexOf(element: T): Int = list.indexOf(element)
override fun isEmpty(): Boolean = list.isEmpty()
override fun iterator(): MutableIterator<T> = list.iterator()
override fun lastIndexOf(element: T): Int = list.lastIndexOf(element)
override fun listIterator(): MutableListIterator<T> = list.listIterator()
override fun listIterator(index: Int): MutableListIterator<T> = list.listIterator(index)
override fun subList(fromIndex: Int, toIndex: Int): MutableList<T> =
list.subList(fromIndex, toIndex)
}
...of course, this might add an additional layer of indirection, possibly affecting list performance with a slight overhead.