How to close the channel after all producer coroutines are done? - kotlin

Consider the following code:
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
val channel = Channel<String>()
launch {
channel.send("A1")
channel.send("A2")
log("A done")
}
launch {
channel.send("B1")
log("B done")
}
launch {
for (x in channel) {
log(x)
}
}
}
fun log(message: Any?) {
println("[${Thread.currentThread().name}] $message")
}
The original version has the receiver coroutine like that:
launch {
repeat(3) {
val x = channel.receive()
log(x)
}
}
It expects only 3 messages in the channel. If I change it to the first version then I need to close the channel after all producer coroutines are done. How can I do that?

A possible solution is to create a job that will wait for all channel.send() to finish, and call channel.close() in the invokeOnCompletion of this job:
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
val channel = Channel<String>()
launch {
launch {
channel.send("A1")
channel.send("A2")
log("A done")
}
launch {
channel.send("B1")
log("B done")
}
}.invokeOnCompletion {
channel.close()
}
launch {
for (x in channel) {
log(x)
}
}
}
fun log(message: Any?) {
println("[${Thread.currentThread().name}] $message")
}

Related

Where should i place the code to observe internet connection so that the user is notified if the device is online or offline?

I have the code to monitor if internet is available. It returns a LiveData and it is observed in the MainActivity . The code is given below.
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding=DataBindingUtil.setContentView(this,R.layout.activity_main)
NetworkStatusHelper(this#MainActivity).observe(this, Observer {
when(it){
NetworkStatus.Available-> Snackbar.make(binding.root, "Back online", Snackbar.LENGTH_LONG).show()
NetworkStatus.Unavailable-> Snackbar.make(binding.root, "No Internet connection", Snackbar.LENGTH_LONG).show()
}
})
}
NetworkHelper
package com.todo.utils.networkhelper
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
import android.util.Log
import androidx.lifecycle.LiveData
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.net.InetAddress
import java.net.InetSocketAddress
import java.net.Socket
class NetworkStatusHelper(private val context: Context): LiveData<NetworkStatus>() {
var connectivityManager: ConnectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private lateinit var connectivityManagerCallback: ConnectivityManager.NetworkCallback
val validNetworkConnections: ArrayList<Network> = ArrayList()
fun getConnectivityCallbacks() = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
val networkCapability =
connectivityManager.getNetworkCapabilities(network)
val hasNetworkConnection =
networkCapability?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
?: false
if (hasNetworkConnection) {
determineInternetAccess(network)
}
}
override fun onLost(network: Network) {
super.onLost(network)
validNetworkConnections.remove(network)
announceNetworkStatus()
}
// override fun onCapabilitiesChanged(
// network: Network,
// networkCapabilities: NetworkCapabilities
// ) {
// super.onCapabilitiesChanged(network, networkCapabilities)
//
// Log.d("validNetworkConnection","onCapabilitiesChanged size "+validNetworkConnections.size)
//
//
// if (networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)) {
// determineInternetAccess(network)
// } else {
// validNetworkConnections.remove(network)
// }
// announceNetworkStatus()
// }
private fun determineInternetAccess(network: Network) {
CoroutineScope(Dispatchers.IO).launch {
if (InternetAvailability.check()) {
withContext(Dispatchers.Main) {
validNetworkConnections.add(network)
announceNetworkStatus()
}
}
}
}
fun announceNetworkStatus() {
if (validNetworkConnections.isNotEmpty()) {
postValue(NetworkStatus.Available)
} else {
postValue(NetworkStatus.Unavailable)
}
}
}
} else {
TODO("VERSION.SDK_INT < LOLLIPOP")
}
override fun onActive() {
super.onActive()
connectivityManagerCallback = getConnectivityCallbacks()
val networkRequest = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
NetworkRequest
.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
} else {
TODO("VERSION.SDK_INT < LOLLIPOP")
}
connectivityManager.registerNetworkCallback(networkRequest, connectivityManagerCallback)
}
override fun onInactive() {
super.onInactive()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
connectivityManager.unregisterNetworkCallback(connectivityManagerCallback)
}
}
object InternetAvailability {
fun check() : Boolean {
return try {
val socket = Socket()
socket.connect(InetSocketAddress("8.8.8.8",53))
socket.close()
true
} catch ( e: Exception){
e.printStackTrace()
false
}
}
}
}
The problem is here is , the Snackbar is displayed even when the app is opened for the first time .I don't want the Snackbar to be displayed when the app is opened for the first time when network is available. If network is unavailable, then the Snackbar should be displayed even when the app is opened for the first time.
Can someone help to improve the code with correct logic to implement the same.
If your helper class is a Flow, then you can use Flow operators to easily customize its behavior. You should keep the instance of your helper class in a ViewModel so it can maintain its state when there are configuration changes.
Here's a Flow version of your class's functionality. I actually just made it into a function, because I think that's simpler.
I removed the List<Network> but you can add it back in if you think it's necessary. I don't think it makes sense to keep a List that can only ever hold at most one item. A device cannot have multiple simultaneous network connections. If you do need it, it won't work for pre-Lollipop, so you will have to juggle differing functionality and probably do need a class instead of just a function.
I think you can probably remove the checkAvailability() function as it is redundant, but I put it in because you have it.
I added a pre-Lollipop version based on a broadcast receiver, since you seem to want to add support for that.
#get:RequiresPermission("android.permission.ACCESS_NETWORK_STATE")
val Context.networkStatus: Flow<NetworkStatus> get() = when {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP -> getNetworkStatusLollipop(this)
else -> getNetworkStatusPreLollipop(this)
}
#RequiresApi(Build.VERSION_CODES.LOLLIPOP)
#RequiresPermission("android.permission.ACCESS_NETWORK_STATE")
private fun getNetworkStatusLollipop(context: Context): Flow<NetworkStatus> = callbackFlow {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val callback = object : ConnectivityManager.NetworkCallback() {
private var availabilityCheckJob: Job? = null
override fun onUnavailable() {
availabilityCheckJob?.cancel()
trySend(NetworkStatus.Unavailable)
}
override fun onAvailable(network: Network) {
availabilityCheckJob = launch {
send(if(checkAvailability()) NetworkStatus.Available else NetworkStatus.Unavailable)
}
}
override fun onLost(network: Network) {
availabilityCheckJob?.cancel()
trySend(NetworkStatus.Unavailable)
}
}
val request = NetworkRequest.Builder()
.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
.build()
connectivityManager.registerNetworkCallback(request, callback)
awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
}
#RequiresPermission("android.permission.ACCESS_NETWORK_STATE")
private fun getNetworkStatusPreLollipop(context: Context): Flow<NetworkStatus> = callbackFlow {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val receiver = object: BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
launch {
if (connectivityManager.activeNetworkInfo?.isConnectedOrConnecting == true) {
send(if(checkAvailability()) NetworkStatus.Available else NetworkStatus.Unavailable)
} else {
send(NetworkStatus.Unavailable)
}
}
}
}
context.registerReceiver(receiver, IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION))
awaitClose { context.unregisterReceiver(receiver) }
}
private suspend fun checkAvailability() : Boolean = withContext(Dispatchers.IO) {
try {
Socket().use {
it.connect(InetSocketAddress("8.8.8.8", 53))
}
true
} catch (e: Exception){
e.printStackTrace()
false
}
}
Then in your ViewModel, you can use Flow operators to easily expose a Flow that skips initial NetworkStatus.Available values:
class MyViewModel(application: Application): AndroidViewModel(application) {
val changedNetworkStatus = application.context.networkStatus
.dropWhile { it == NetworkStatus.Available } // ignore initial available status
.shareIn(viewModelScope, SharingStarted.Eagerly, 1) // or .asLiveData() if preferred
}

Is there a way to catch exceptions thrown within flattenMerge()?

Playground sample
Given this code, the exception thrown in getRecords() is not caught in testFlattenMerge() - should it not be catchable though? Also, in getPeople(), the exception can be caught which causes flattenMerge() to work as expected, but it prints out "Caught in people" before any numbers are printed, and not after 64, as I expected. Is this the correct behavior? I can't quite fit my mental model of flattenMerge() around this.
import kotlin.reflect.KProperty
import kotlin.system.measureTimeMillis
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.*
fun getRecords(id: Int) = flow {
repeat(5) { emit("A record for $id") }
if (id == 6) throw RuntimeException("Anything but #6!")
repeat(5) { emit("A record for $id") }
}
fun getPeople() = flow {
repeat(10) { emit(getRecords(it)) }
// repeat(10) { emit(getRecords(it).catch{ println("Caught in getPeople()")}) } // This works, but it prints /before/ any cnt lines...?
}
suspend fun testFlattenMerge() {
println ("Merge with flattenMerge()")
var cnt = 0
val flowOfFlows = getPeople()
flowOfFlows.catch{ println("Caught before flattenMerge")}
.flattenMerge()
.catch{ println("Caught after flattenMerge")}
.collect {
println("${cnt++}") // Without catching inside getPeople() this stops at 64
}
}
suspend fun testManualMerge() {
println("Merge manually")
var cnt = 0
repeat(10) {
getRecords(it).catch{ println("Caught in manual merge") }
.collect {
println("${cnt++}") // This goes up to 94, as expected
}
}
}
fun main() = runBlocking {
testFlattenMerge()
testManualMerge()
}

Kotlin coruntines wont execute when in a launch and callback

I thought I was familiar enough with Kotlin's coroutines, until I got this code.
1 to 8 are all printed except 2:
import kotlinx.coroutines.*
import java.lang.Runnable
import java.lang.Thread.sleep
import kotlin.concurrent.thread
fun main() {
runBlocking {
Client.createAccount()
delay(1000)
}
}
object Client: CoroutineScope {
override val coroutineContext = newSingleThreadContext("Client")
fun createAccount() = launch {
Client2.init(Runnable {
println('1')
launch {
println('2')
}
ok()
ok2()
})
println('7')
launch {
println('8')
}
}
fun ok() {
println('3')
launch {
println('4')
}
}
fun ok2() = launch {
println('5')
launch {
println('6')
}
}
}
object Client2 {
fun init(runnable: Runnable) = thread {
sleep(100)
runnable.run()
}
}
The result is:
7
8
1
3
4
5
6
The coroutine in callback will never be called. Why?
And if I remove the launch in createAccount() the 1 to 8 will be all printed.
Also if I use GlobalScope.launch { println('2') } instead of launch { println('2') }, I can also get the 2 printed.
the reason is that anonymous class uses its wrapper scope as a parent.
launch { println('2') } in Runnable { } will be cancelled when parent job createAccount() launched is completed.
Therefore, it can't be invoked because it would be cancelled right after launch { println('8') }.
So, If you change Client like below, it would print '2' correctly.
object Client: CoroutineScope {
override val coroutineContext = Dispatchers.Main
fun createAccount() = launch {
Client2.init(Run())
println("7")
launch {
println("8")
}
}
fun ok() {
println("3")
launch {
println("4")
}
}
fun ok2() = launch {
println("5")
launch {
println("6")
}
}
class Run: Runnable {
override fun run() {
println("1")
launch {
println("2")
}
ok()
ok2()
}
}
}
launch posts a Runnable in a Handler, so its code execution is not immediate.
launch(Dispatchers.Main, CoroutineStart.UNDISPATCHED) will immediately execute its lambda expression in the current thread.
change the dispatcher to the current one you are using
change the lunch from inside the thread to
launch (coroutineContext, CoroutineStart.UNDISPATCHED)
.
fun createAccount() = launch {
Client2.init(Runnable {
println('1')
launch (coroutineContext, CoroutineStart.UNDISPATCHED){
println('2')
}
ok()
ok2()
})
println('7')
launch {
println('8')
}
}
output:
7
8
1
2
3
4
5
6

Cancel kotlin flow collection on signal

I'm struggling to create a 'takeUntilSignal' operator for a Flow - an extension method that will cancel a flow when another flow generates an output.
fun <T> Flow<T>.takeUntilSignal(signal: Flow<Unit>): Flow<T>
My initial effort was to try to launch collection of the signal flow in the same coroutine scope as the primary flow collection, and cancel the coroutine scope:
fun <T> Flow<T>.takeUntilSignal(signal: Flow<Unit>): Flow<T> = flow {
kotlinx.coroutines.withContext(coroutineContext) {
launch {
signal.take(1).collect()
println("signalled")
cancel()
}
collect {
emit(it)
}
}
}
But this isn't working (and uses the forbidden "withContext" method that is expressly stubbed out by Flow to prevent usage).
edit
I've kludged together the following abomination, which doesn't quite fit the definition (resulting flow will only cancel after first emission from primary flow), and I get the feeling there's a far better way out there:
fun <T> Flow<T>.takeUntilSignal(signal: Flow<Unit>): Flow<T> =
combine(
signal.map { it as Any? }.onStart { emit(null) }
) { x, y -> x to y }
.takeWhile { it.second == null }
.map { it.first }
edit2
another try, using channelFlow:
fun <T> Flow<T>.takeUntilSignal(signal: Flow<Unit>): Flow<T> =
channelFlow {
launch {
signal.take(1).collect()
println("hello!")
close()
}
collect { send(it) }
close()
}
Use couroutineScope and start the new coroutine inside:
fun <T> Flow<T>.takeUntilSignal(signal: Flow<Unit>): Flow<T> = flow {
try {
coroutineScope {
launch {
signal.take(1).collect()
println("signalled")
this#coroutineScope.cancel()
}
collect {
emit(it)
}
}
} catch (e: CancellationException) {
//ignore
}
}
Check it https://github.com/hoc081098/FlowExt
package com.hoc081098.flowext
import com.hoc081098.flowext.internal.ClosedException
import com.hoc081098.flowext.internal.checkOwnership
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.take
import kotlinx.coroutines.launch
/**
* Emits the values emitted by the source [Flow] until a [notifier] [Flow] emits a value or completes.
*
* #param notifier The [Flow] whose first emitted value or complete event
* will cause the output [Flow] of [takeUntil] to stop emitting values from the source [Flow].
*/
public fun <T, R> Flow<T>.takeUntil(notifier: Flow<R>): Flow<T> = flow {
try {
coroutineScope {
val job = launch(start = CoroutineStart.UNDISPATCHED) {
notifier.take(1).collect()
throw ClosedException(this#flow)
}
collect { emit(it) }
job.cancel()
}
} catch (e: ClosedException) {
e.checkOwnership(this#flow)
}
}

Vertx plus Kotlin coroutines hangs forever

I am rewriting some Java Vertx asynch code using Kotlin coroutines for learning purposes. However, when I try to test a simple HTTP call, the coroutine based test hangs forever and I really don't understand where is the issue. Here a reproducer:
#RunWith(VertxUnitRunner::class)
class HelloWorldTest {
private val vertx: Vertx = Vertx.vertx()
#Before
fun setUp(context: TestContext) {
// HelloWorldVerticle is a simple http server that replies "Hello, World!" to whatever call
vertx.deployVerticle(HelloWorldVerticle::class.java!!.getName(), context.asyncAssertSuccess())
}
// ORIGINAL ASYNC TEST HERE. IT WORKS AS EXPECTED
#Test
fun testAsync(context: TestContext) {
val atc = context.async()
vertx.createHttpClient().getNow(8080, "localhost", "/") { response ->
response.handler { body ->
context.assertTrue(body.toString().equals("Hello, World!"))
atc.complete()
}
}
}
// First attempt, it hangs forever, the response is never called
#Test
fun testSync1(context: TestContext) = runBlocking<Unit> {
val atc = context.async()
val body = await<HttpClientResponse> {
vertx.createHttpClient().getNow(8080, "localhost", "/", { response -> response.handler {it}} )
}
context.assertTrue(body.toString().equals("Hello, World!"))
atc.complete()
}
// Second attempt, it hangs forever, the response is never called
#Test
fun testSync2(context: TestContext) = runBlocking<Unit> {
val atc = context.async()
val response = await<HttpClientResponse> {
vertx.createHttpClient().getNow(8080, "localhost", "/", it )
}
response.handler { body ->
context.assertTrue(body.toString().equals("Hello, World!"))
atc.complete()
}
}
suspend fun <T> await(callback: (Handler<T>) -> Unit) =
suspendCoroutine<T> { cont ->
callback(Handler { result: T ->
cont.resume(result)
})
}
}
Is everyone able to figure out the issue?
It seems to me that your code have several problems:
you may running the test before the http-server got deployed
I believe that since you execute your code inside runBlocking you are blocking the event loop from completing the request.
Finally, I will advise you to use the HttpClienctResponse::bodyHandler method instead of HttpClientResponse::handler as the handler may receive partial data.
Here is an alternative solution that works fine:
import io.vertx.core.AbstractVerticle
import io.vertx.core.Future
import io.vertx.core.Handler
import io.vertx.core.Vertx
import io.vertx.core.buffer.Buffer
import io.vertx.core.http.HttpClientResponse
import kotlin.coroutines.experimental.Continuation
import kotlin.coroutines.experimental.EmptyCoroutineContext
import kotlin.coroutines.experimental.startCoroutine
import kotlin.coroutines.experimental.suspendCoroutine
inline suspend fun <T> await(crossinline callback: (Handler<T>) -> Unit) =
suspendCoroutine<T> { cont ->
callback(Handler { result: T ->
cont.resume(result)
})
}
fun <T : Any> async(code: suspend () -> T) = Future.future<T>().apply {
code.startCoroutine(object : Continuation<T> {
override val context = EmptyCoroutineContext
override fun resume(value: T) = complete()
override fun resumeWithException(exception: Throwable) = fail(exception)
})
}
fun main(args: Array<String>) {
async {
val vertx: Vertx = Vertx.vertx()
//0. take the current context
val ctx = vertx.getOrCreateContext()
//1. deploy the http server
await<Unit> { cont ->
vertx.deployVerticle(object : AbstractVerticle() {
override fun start() {
vertx.createHttpServer()
.requestHandler { it.response().end("Hello World") }
.listen(7777) { ctx.runOnContext { cont.handle(Unit) } }
//note that it is important tp complete the handler in the correct context
}
})
}
//2. send request
val response: HttpClientResponse = await { vertx.createHttpClient().getNow(7777, "localhost", "/", it) }
//3. await response
val body = await<Buffer> { response.bodyHandler(it) }
println("received $body")
}
}