In Google's official codelab about advanced-coroutines-codelab sample, they've used ConflatedBroadcastChannel
to watch a variable/object change.
I've used the same technique in one of my side projects, and when resuming the listening activity, sometimes ConflatedBroadcastChannel
fires it's recent value, causing the execution of flatMapLatest
body without any change.
I think this is happening while the system collects the garbage since I can reproduce this issue by calling System.gc()
from another activity.
Here's the code
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
val tvCount = findViewById<TextView>(R.id.tv_count)
viewModel.count.observe(this, Observer {
tvCount.text = it
Toast.makeText(this, "Incremented", Toast.LENGTH_LONG).show();
})
findViewById<Button>(R.id.b_inc).setOnClickListener {
viewModel.increment()
}
findViewById<Button>(R.id.b_detail).setOnClickListener {
startActivity(Intent(this, DetailActivity::class.java))
}
}
}
MainViewModel.kt
class MainViewModel : ViewModel() {
companion object {
val TAG = MainViewModel::class.java.simpleName
}
class IncrementRequest
private var tempCount = 0
private val requestChannel = ConflatedBroadcastChannel<IncrementRequest>()
val count = requestChannel
.asFlow()
.flatMapLatest {
tempCount++
Log.d(TAG, "Incrementing number to $tempCount")
flowOf("Number is $tempCount")
}
.asLiveData()
fun increment() {
requestChannel.offer(IncrementRequest())
}
}
DetailActivity.kt
class DetailActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_detail)
val button = findViewById<Button>(R.id.b_gc)
val timer = object : CountDownTimer(5000, 1000) {
override fun onFinish() {
button.isEnabled = true
button.text = "CALL SYSTEM.GC() AND CLOSE ACTIVITY"
}
override fun onTick(millisUntilFinished: Long) {
button.text = "${TimeUnit.MILLISECONDS.toSeconds(millisUntilFinished)} second(s)"
}
}
button.setOnClickListener {
System.gc()
finish()
}
timer.start()
}
}
Here's the full source code : CoroutinesFlowTest.zip
Quoting from the official response, (The simple and straightforward solution)
The problem here is that you are trying to use
ConflatedBroadcastChannel
for events, while it is designed to represent current state as shown in the codelab. Every time the downstreamLiveData
is reactivated it receives the most recent state and performs the incrementing action. Don't useConflatedBroadcastChannel
for events.To fix it, you can replace
ConflatedBroadcastChannel
withBroadcastChannel<IncrementRequest>(1)
(non-conflated channel, which is Ok for events to use) and it'll work as you expect it too.