I have a method called saveAccount
fun saveAccount(id: Int, newName: String): Account {
val encryptedNames: List<String> = repository.findNamesById(id)
val decryptedNames: List<String> = encryptedNames.map { cryptographyService.decrypt(it) }
if(decryptedNames.contains(newName))
throw IllegalStateException()
return repository.save(newName)
}
I want to concurrently decrypt all names, so I did:
suspend fun saveAccount(id: Int, newName: String): Account {
val encryptedNames: List<String> = repository.findNamesById(id)
val decryptedNames: List<String> = encryptedNames.map {
CoroutineScope(Dispatchers.IO).async {
cryptographyService.decrypt(it)
}
}.awaitAll()
if(decryptedNames.contains(newName))
throw IllegalStateException()
return repository.save(newName)
}
Until now everything is fine, but the question is: I can't make saveAccount a suspend function. What should I do?
So, you want to decrypt each name in a separate coroutine, but saveAccount should only return when all decryption is done.
You can use runBlocking for that:
fun saveAccount(id: Int, newName: String): Account {
// ...
val decryptedNames = runBlocking {
encryptedNames.map {
CoroutineScope(Dispatchers.IO).async {
cryptographyService.decrypt(it)
}
}.awaitAll()
}
// ...
}
This way saveAccount does not have to be a suspend function.
Related
I am a newbie in coroutine/flow and would like to know the appropriate way to close the flow from the collect's code block when it gets the value it wanted.
The code like this:
suspend fun findService(scope:CoroutineScope, context:Context, name:String) {
val flow = getWifiDebuggingConnectDiscoveryFlow( context )
try {
flow.collect {
if(name == it.serviceName) {
/* need to exit the collection and execute the code that follows */
}
}
println("service found!")
} catch(e: Throwable) {
println("Exception from the flow: $e")
}
/* need to do something after service found */
}
private fun getWifiDebuggingConnectDiscoveryFlow(context:Context) = callbackFlow {
val nsdManager:NsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager
val listener = object : NsdManager.DiscoveryListener {
override fun onStartDiscoveryFailed(serviceType: String?, errorCode: Int) {cancel("onStartDiscoveryFailed")}
override fun onStopDiscoveryFailed(serviceType: String?, errorCode: Int) {cancel("onStopDiscoveryFailed")}
override fun onDiscoveryStarted(serviceType: String?) {}
override fun onDiscoveryStopped(serviceType: String?) {}
override fun onServiceLost(serviceInfo: NsdServiceInfo?) {}
override fun onServiceFound(serviceInfo: NsdServiceInfo?) {
if(serviceInfo==null) return
trySend(serviceInfo)
}
}
nsdManager.discoverServices(ServiceDiscovery.ADB_CONNECT_TYPE, NsdManager.PROTOCOL_DNS_SD, listener)
awaitClose { nsdManager.stopServiceDiscovery(listener) }
}
This problem has been bothering me for a long time, and I would appreciate any help I get.
You can use the first or firstOrNull operators. It will stop collecting as soon as the first element that complies the condition is received:
val service = flow.firstOrNull { name == it.serviceName }
...
You can find first official documentation here
sample code in dart:
void main() {
step1().then(step2).then(step3).then(print);
}
Future<String> step1() async {
return Future.value("setp1");
}
Future<int> step2(String input) async {
return Future.value(input.length);
}
Future<bool> step3(int input) async {
return Future.value(input > 3);
}
is there any way to write code in kotlin like this?
I use flow to write a simple code, but I won't find a way to simplify it
suspend fun step1(): String {
return "step1"
}
suspend fun step2(input: String): Int {
return input.length
}
suspend fun step3(input: Int): Boolean {
return input > 3
}
suspend fun execute() {
flowOf(step1())
.map { step2(it) }
.map { step3(it) }
.collect { print(it) }
}
I think you're saying you just want to run the suspend functions sequentially. If so, it doesn't look any different than non-suspending code.
suspend fun execute(){
val result1 = step1()
val result2 = step2(result1)
print(step3(result2))
}
If you really want to chain functions that are not extension functions, you can use run or let, although some might say this is less readable
suspend fun execute() = step1()
.run(::step2)
.run(::step3)
.run(::print)
If your functions are defined as extensions:
suspend fun String.step2() = length
suspend fun Int.step3() = this > 3
then you can chain directly:
suspend fun execute() = step1()
.step2()
.step3()
.run(::print)
kotlin coroutine does not adopt Promise like APIs (then/map/flatMap) because with suspend function, this can be done much easier
import kotlinx.coroutines.delay
import kotlinx.coroutines.yield
suspend fun main(args: Array<String>) {
val one = step1()
val two = step2(one)
val three = step3(two)
println(three)
}
suspend fun <T> resolve(value: T): T {
yield() // to simulate Future.resolve
return value
}
suspend fun step1() = resolve("100")
suspend fun step2(input: String) = resolve(input.length)
suspend fun step3(input: Int) = resolve(input > 3)
see discussions below
https://github.com/Kotlin/kotlinx.coroutines/issues/342
Using Coroutines in Kotlin
Synchronous run with three functions: here we perform three tasks in a coroutine and return the three results of the tasks in a Triple class.
suspend fun workWithThreeTimes(time1: Int, time2: Int, time3: Int): Triple<Result, Result, Result> {
return coroutineScope {
val result1 = manager.workForTime(time1) // work1
val result2 = manager.workForTime(time2) // work2 after work1
val result3 = manager.workForTime(time3) // work3 after work2
Triple(result1, result2, result3)
}
}
Parallel run with three functions: here we perform three works, in parallel, in a coroutine and return the three results of the tasks in a Triple class.
suspend fun workWithThreeTimesParallel(time1: Int, time2: Int, time3: Int): Triple<Result, Result, Result> {
return coroutineScope {
val work1 = async { manager.workForTime(time1) } // Async work1
val work2 = async { manager.workForTime(time2) } // Async work2 while work1 is working
val result3 = manager.workForTime(time3) // work3 while work1 and work2 are working
val result1 = work1.await() // non-blocking wait
val result2 = work2.await()// non-blocking wait
Triple(result1, result2, result3)
}
}
I wrote this helper function, so that I can easily process a list in parallel and only continue code execution when all the work is done. It works nicely when you don't need to return a result.
(I know it isn't the best practice to create new pools every time, it can be easily moved out, but I wanted to keep the examples simple.)
fun recursiveAction(action: () -> Unit): RecursiveAction {
return object : RecursiveAction() {
override fun compute() {
action()
}
}
}
fun <T> List<T>.parallelForEach(parallelSize: Int, action: (T) -> Unit) {
ForkJoinPool(parallelSize).invoke(recursiveAction {
this.parallelStream().forEach { action(it) }
})
}
Example use:
val myList: List<SomeClass> [...]
val parallelSize: Int = 8
myList.parallelForEach(parallelSize) { listElement ->
//Some task here
}
Is there any way to make a similar helper construct for when you want to collect the results back into a list?
I know I have to use a RecursiveTask instead of the RecursiveAction, but I couldn't manage to write a helper function like I had above to wrap it.
I'd like to use it like this:
val myList: List<SomeClass> [...]
val parallelSize: Int = 8
val result: List<SomeClass> = myList.parallelForEach(parallelSize) { listElement ->
//Some task here
}
Alternatively, is there a simpler way to do this alltogether?
Answered by JeffMurdock over on Reddit
fun <T> recursiveTask(action: () -> T): RecursiveTask<T> {
return object : RecursiveTask<T>() {
override fun compute(): T {
return action()
}
}
}
fun <T, E> List<T>.parallelForEach(parallelSize: Int, action: (T) -> E): List<E> {
val pool = ForkJoinPool(parallelSize)
val result = mutableListOf<ForkJoinTask<E>>()
for (item in this) {
result.add(pool.submit(recursiveTask {
action(item)
}))
}
return result.map { it.join() }
}
fun main(args: Array<String>) {
val list = listOf(1, 2, 3)
list.parallelForEach(3) { it + 2 }.forEach { println(it) }
}
I created a TextWatcher extension to listen to text updates with debounce. I wrapped the TextWatcher into a callbackFlow which offers the text input through Flow.
The issue is that calling collect() suspends the processing and I need to register it for multiple EditText. Is calling multiple launch inside the scope the right way correct?
private fun initListeners() = lifecycleScope.launch {
launch {
edittext_taskdetail_title.textChangedFlow()
.collect { text -> viewModel.updateTitle(text) }
}
launch {
edittext_taskdetail_description.textChangedFlow()
.collect { text -> viewModel.updateDescription(text) }
}
}
This is the extension:
fun TextView.textChangedFlow(): Flow<String> {
val flow: Flow<String> = callbackFlow {
val listener = object : TextWatcher {
override fun afterTextChanged(s: Editable?) {}
override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
offer(s.toString())
}
}
addTextChangedListener(listener)
awaitClose { removeTextChangedListener(listener) }
}
return flow.debounce(TEXT_UPDATE_DEBOUNCE)
}
What you have is acceptable I think but here's an alternative.
private fun initListeners() {
edittext_taskdetail_title.textChangedFlow()
.onEach { text -> viewModel.updateTitle(text) }
.launchIn(lifecycleScope)
edittext_taskdetail_description.textChangedFlow()
.onEach { text -> viewModel.updateDescription(text) }
.launchIn(lifecycleScope)
}
I does pretty much the same thing except, if one of them fails for some reason, the other one won't be cancelled immediately.
I want to test a method of my ViewModel that collects a Flow. Inside the collector a LiveData object is mutated, which I want to check in the end. This is roughly how the setup looks:
//Outside viewmodel
val f = flow { emit("Test") }.flowOn(Dispatchers.IO)
//Inside viewmodel
val liveData = MutableLiveData<String>()
fun action() {
viewModelScope.launch { privateAction() }
}
suspend fun privateAction() {
f.collect {
liveData.value = it
}
}
When I now call the action() method in my unit test, the test finishes before the flow is collected. This is how the test might look:
#Test
fun example() = runBlockingTest {
viewModel.action()
assertEquals(viewModel.liveData.value, "Test")
}
I am using the TestCoroutineDispatcher via this Junit5 extension and also the instant executor extension for LiveData:
class TestCoroutineDispatcherExtension : BeforeEachCallback, AfterEachCallback, ParameterResolver {
#SuppressLint("NewApi") // Only used in unit tests
override fun supportsParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Boolean {
return parameterContext?.parameter?.type === testDispatcher.javaClass
}
override fun resolveParameter(parameterContext: ParameterContext?, extensionContext: ExtensionContext?): Any {
return testDispatcher
}
private val testDispatcher = TestCoroutineDispatcher()
override fun beforeEach(context: ExtensionContext?) {
Dispatchers.setMain(testDispatcher)
}
override fun afterEach(context: ExtensionContext?) {
Dispatchers.resetMain()
testDispatcher.cleanupTestCoroutines()
}
}
class InstantExecutorExtension : BeforeEachCallback, AfterEachCallback {
override fun beforeEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance()
.setDelegate(object : TaskExecutor() {
override fun executeOnDiskIO(runnable: Runnable) = runnable.run()
override fun postToMainThread(runnable: Runnable) = runnable.run()
override fun isMainThread(): Boolean = true
})
}
override fun afterEach(context: ExtensionContext?) {
ArchTaskExecutor.getInstance().setDelegate(null)
}
}
You can try either,
fun action() = viewModelScope.launch { privateAction() }
suspend fun privateAction() {
f.collect {
liveData.value = it
}
}
#Test
fun example() = runBlockingTest {
viewModel.action().join()
assertEquals(viewModel.liveData.value, "Test")
}
or
fun action() {
viewModelScope.launch { privateAction()
}
suspend fun privateAction() {
f.collect {
liveData.value = it
}
}
#Test
fun example() = runBlockingTest {
viewModel.action()
viewModel.viewModelScope.coroutineContext[Job]!!.join()
assertEquals(viewModel.liveData.value, "Test")
}
You could also try this,
suspend fun <T> LiveData<T>.awaitValue(): T? {
return suspendCoroutine { cont ->
val observer = object : Observer<T> {
override fun onChanged(t: T?) {
removeObserver(this)
cont.resume(t)
}
}
observeForever(observer)
}
}
#Test
fun example() = runBlockingTest {
viewModel.action()
assertEquals(viewModel.liveData.awaitValue(), "Test")
}
So what I ended up doing is just passing the Dispatcher to the viewmodel constructor:
class MyViewModel(..., private val dispatcher = Dispatchers.Main)
and then using it like this:
viewModelScope.launch(dispatcher) {}
So now I can override this when I instantiate the ViewModel in my test with a TestCoroutineDispatcher and then advance the time, use testCoroutineDispatcher.runBlockingTest {}, etc.