For some reasom my CoroutineScope just stopped working, though, nothing crucial has been changed and errors don't show up. The program skips it and my progress bar remains unchanged. Do you have any idea what could have possibly caused this issue?
class HomeFragment : BaseFragment(R.layout.fragment_home) {
private var todayEventsCount: Int = 0
private var todayCheckedEventsCount: Int = 0
private val mHandler = Handler()
private var mAdapter: FirebaseRecyclerAdapter<EventModel, EventsHolder>? = null
private lateinit var mRunnable: Runnable
private lateinit var barList: ArrayList<BarEntry>
private lateinit var barDataSet: BarDataSet
private lateinit var barData: BarData
override fun onResume() {
super.onResume()
initFields()
}
override fun onPause() {
super.onPause()
home_events_list.removeAllViewsInLayout()
todayCheckedEventsCount = 0
}
#SuppressLint("SetTextI18n")
private fun initFields() {
val progress: ProgressBar = ev_progress_bar
val text: TextView = count
getTodayEvents()
CoroutineScope(Dispatchers.IO).launch {
mRunnable = Runnable {
if (mAdapter?.itemCount != 0) {
todayEventsCount = mAdapter?.itemCount!!
for (i in 0 until todayEventsCount) {
if (mAdapter?.getItem(i)!!.checked == "1") {
todayCheckedEventsCount++
progress.progress = todayCheckedEventsCount
text.text = progress.progress.toString() + "/" + progress.max.toString()
}
}
todayCheckedEventsCount = 0
}
mHandler.postDelayed(mRunnable, 30)
}
mHandler.post(mRunnable)
}
initChart()
}
There's no point in posting runnables if you're using a coroutine. Half the reason coroutines exist is to avoid this messy nested juggling of callbacks. There are also a few errors I see in your coroutine:
It runs on Dispatchers.IO, even though it never does anything blocking.
It runs on a throwaway CoroutineScope that is never cancelled. You should never use the CoroutineScope() constructor if you're not immediately assigning it to a variable or property through which you can cancel it at the appropriate time. You should be using viewLifeCycleOwner.lifecycleScope instead, since it's already set up to automatically cancel at the appropriate time to avoid crashes.
Your runnables you're posting are not canceled at the appropriate time, so even if you use the appropriate scope, they can still cause crashes. But as mentioned above, you don't need them in a coroutine.
(mAdapter?.itemCount != 0) will evaluate to true even if mAdapter is null, which will promptly cause a crash when you use mAdapter!!.
Fixed code looks like this. If this still doesn't do anything, you'll want to check to make sure your adapter is not null at the time this is called.
private fun initFields() {
val progress: ProgressBar = ev_progress_bar
val text: TextView = count
getTodayEvents()
viewLifeCycleOwner.lifecycleScope.launch {
val adapter = mAdapter ?: return
while (true) {
if (adapter.itemCount != 0) {
todayEventsCount = adapter.itemCount
for (i in 0 until todayEventsCount) {
if (adapter.getItem(i).checked == "1") {
todayCheckedEventsCount++
progress.progress = todayCheckedEventsCount
text.text = progress.progress.toString() + "/" + progress.max.toString()
}
}
todayCheckedEventsCount = 0
}
delay(30)
}
}
initChart()
}
I didn't try to follow your logic of what these todayEventsCount and todayCheckedEventsCount properties are. They probably should be locally defined variables in the coroutine.
You're also going to need some condition in the while loop that breaks you out of it. I didn't look closely enough to see what that condition should be. Your original code doesn't break the loop of reposting the runnable forever.
Related
I have a basic function that displays the elapsed time every time the button is pressed. I cannot get the logic in MainActivity to transfer to the recyclerview adapter. I simply want the text output color to change to red after the time passes 5 seconds. I have tried to research how to do this for the past week and I cannot find the exact answer. I'm hoping someone can help.
I have tried it with and without the boolean in the data class. I wasn't sure if that was required.
Here is my code:
Main Activity:`
class MainActivity : AppCompatActivity() {
var startTime = SystemClock.elapsedRealtime()
var displaySeconds = 0
private lateinit var binding: ActivityMainBinding
private val secondsList = generateSecondsList()
private val secondsAdapter = Adapter(secondsList)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
recyclerView.adapter = secondsAdapter
recyclerView.layoutManager = LinearLayoutManager(this)
recyclerView.setHasFixedSize(false)
binding.button.setOnClickListener {
getDuration()
addSecondsToRecyclerView()
}
}
fun getDuration(): Int {
val endTime = SystemClock.elapsedRealtime()
val elapsedMilliSeconds: Long = endTime - startTime
val elapsedSeconds = elapsedMilliSeconds / 1000.0
displaySeconds = elapsedSeconds.toInt()
return displaySeconds
}
private fun generateSecondsList(): ArrayList<Seconds> {
return ArrayList()
}
fun addSecondsToRecyclerView() {
val addSeconds =
Seconds(getDuration(), true)
secondsList.add(addSeconds)
secondsAdapter.notifyItemInserted(secondsList.size - 1)
}
}
Adapter:
var adapterSeconds = MainActivity().getDuration()
class Adapter(
private val rvDisplay: MutableList<Seconds>
) : RecyclerView.Adapter<Adapter.AdapterViewHolder>() {
class AdapterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val textView1: TextView = itemView.tv_seconds
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AdapterViewHolder {
val myItemView = LayoutInflater.from(parent.context).inflate(
R.layout.rv_item,
parent, false
)
return AdapterViewHolder(myItemView)
}
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
val currentDisplay = rvDisplay[position]
currentDisplay.isRed = adapterSeconds > 5
holder.itemView.apply {
val redColor = ContextCompat.getColor(context, R.color.red).toString()
val blackColor = ContextCompat.getColor(context, R.color.black).toString()
if (currentDisplay.isRed) {
holder.textView1.setTextColor(redColor.toInt())
holder.textView1.text = currentDisplay.rvSeconds.toString()
} else {
holder.textView1.setTextColor(blackColor.toInt())
holder.textView1.text = currentDisplay.rvSeconds.toString()
}
}
}
override fun getItemCount() = rvDisplay.size
}
Data Class:
data class Seconds(
var rvSeconds: Int,
var isRed: Boolean
)
when you call secondsList.add(addSeconds) then the data that is already inside secondsList should be updated too.
you could do something like
private var secondsList = generateSecondsList() // make this var
fun addSecondsToRecyclerView() {
val addSeconds =
Seconds(getDuration(), true)
secondsList.add(addSeconds)
if ( /* TODO check if time has passed */) {
secondsList = secondsList.map { it.isRed = true }
secondsAdapter.rvDisplay = secondsList // TODO also make rvDisplay a var
secondsAdapter.notifyDatasetChanged() // also need to tell rv to redraw the all views
} else {
secondsAdapter.notifyItemInserted(secondsList.size - 1)
}
}
that might work, but to be honest it looks bad... There is already a lot of logic inside Activity. Read about MVVM architecture and LiveData, there should be another class called ViewModel that would keep track of time and the data. Activity should be as simple as possible, because it has lifecycle, so if you rotate the screen, all your state will be lost.
Your code isn't really working because of this:
var adapterSeconds = MainActivity().getDuration()
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
...
currentDisplay.isRed = adapterSeconds > 5
...
}
You're only setting adapterSeconds right there, so it never updates as time passes. I assume you want to know the moment 5 seconds has elapsed, and then update the RecyclerView at that moment - in that case you'll need some kind of timer task that will fire after 5 seconds, and can tell the adapter to display things as red. Let's deal with that first:
class Adapter( private val rvDisplay: MutableList ) : RecyclerView.Adapter<Adapter.AdapterViewHolder>() {
private var displayRed = false
set(value) {
field = value
// Refresh the display - the ItemChanged methods mean something about the items
// has changed, rather than a structural change in the list
// But you can use notifyDataSetChanged if you want (better to be specific though)
notifyItemRangeChanged(0, itemCount)
}
override fun onBindViewHolder(holder: Adapter.AdapterViewHolder, position: Int) {
if (displayRed) {
// show things as red - you shouldn't need to store that state in the items
// themselves, it's not about them - it's an overall display state, right?
} else {
// display as not red
}
}
So with that setter function, every time you update displayRed it'll refresh the display, which calls onBindViewHolder, which checks displayRed to see how to style things. It's better to put all this internal refreshing stuff inside the adapter - just pass it data and events, let it worry about what needs to happen internally and to the RecyclerView it's managing, y'know?
Now we have a thing we can set to control how the list looks, you just need a timer to change it. Lots of ways to do this - a CountdownTimer, a coroutine, but let's keep things simple for this example and just post a task to the thread's Looper. We can do that through any View instead of creating a Handler:
// in MainActivity
recyclerView.postDelayed({ secondsAdapter.displayRed = true }, 5000)
That's it! Using any view, post a delayed function that tells the adapter to display as red.
It might be more helpful to store that runnable as an object:
private val showRedTask = Runnable { secondsAdapter.displayRed = true }
...
recyclerView.postDelayed(showRedTask, 5000)
because then you can easily cancel it
recyclerView.removeCallbacks(showRedTask)
Hopefully that's enough for you to put some logic together to get what you want. Set displayRed = false to reset the styling, use removeCallbacks to cancel any running task, and postDelayed to start a new countdown. Not the only way to do it, but it's pretty neat!
I finally figured it out using a companion object in Main Activity with a boolean set to false. If the time exceeded 5 seconds, then it set to true.
The adapter was able to recognize the companion object and change the color of seconds to red if they exceeded 5.
Looking for a natural Kotlin way to let startTime be initialized only in a particular place and exactly once.
The following naive implementation have two problems:
it is not thread safe
it does not express the fact "the variable was or will be assigned exactly once in the lifetime of an Item instance"
class Item {
var startTime: Instant?
fun start(){
if (startTime == null){
startTime = Instant.now()
}
// do stuff
}
}
I believe some kind of a delegate could be applicable here. In other words this code needs something similar to a lazy variable, but without initialization on first read, instead it happens only after explicit call of "touching" method. Maybe the Wrap calls could give an idea of possible implementation.
class Wrap<T>(
supp: () -> T
){
private var value: T? = null
private val lock = ReentrantLock()
fun get(){
return value
}
fun touch(){
lock.lock()
try{
if (value == null){
value = supp()
} else {
throw IllegalStateExecption("Duplicate init")
}
} finally{
lock.unlock()
}
}
}
How about combining AtomicReference.compareAndSet with a custom backing field?
You can use a private setter and make sure that the only place the class sets the value is from the start() method.
class Item(val value: Int) {
private val _startTime = AtomicReference(Instant.EPOCH)
var startTime: Instant?
get() = _startTime.get().takeIf { it != Instant.EPOCH }
private set(value) = check(_startTime.compareAndSet(Instant.EPOCH, value)) { "Duplicate set" }
fun start() {
startTime = Instant.now()
}
override fun toString() = "$value: $startTime"
}
fun main() = runBlocking {
val item1 = Item(1)
val item2 = Item(2)
println(Instant.now())
launch { println(item1); item1.start(); println(item1) }
launch { println(item1) }
delay(1000)
println(item2)
item2.start()
println(item2)
println(item2)
item2.start()
}
Example output:
2021-07-14T08:20:27.546821Z
1: null
1: 2021-07-14T08:20:27.607365Z
1: 2021-07-14T08:20:27.607365Z
2: null
2: 2021-07-14T08:20:28.584114Z
2: 2021-07-14T08:20:28.584114Z
Exception in thread "main" java.lang.IllegalStateException: Duplicate set
I think your Wrap class is a good starting point to implement this. I would definitely make it a property delegate and touch() could be much simplified:
fun touch() {
synchronized(this) {
check(value == null) { "Duplicate init" }
value = supp()
}
}
Then you can remove lock. But generally, this is a good approach.
If you would like to reuse lazy util from stdlib then you can do this by wrapping it with another object which does not read its value until asked:
class ManualLazy<T : Any>(private val lazy: Lazy<T>) {
operator fun getValue(thisRef: Any?, property: KProperty<*>): T? {
return if (lazy.isInitialized()) lazy.value else null
}
fun touch() {
lazy.value
}
}
class Item {
private val _startTime = ManualLazy(lazy { Instant.now() })
val startTime: Instant? by _startTime
fun start(){
_startTime.touch()
}
}
Of course, depending on your needs you can implement it in a much different way, using a similar technique.
This may be considered exploiting or hacking lazy util. I agree and I think Wrap approach is a better one.
In the new project that I'm currently working on I have no RxJava dependency at all, because until now I didn't need that - coroutines solve threading problem pretty gracefully.
At this point I stumbled upon on a requirement to have a BehaviorSubject-alike behavior, where one can subscribe to a stream of data and receive the latest value upon subscription. As I've learned, Channels provide very similar behavior in Kotlin, so I decided to give them a try.
From this article I've learned, that ConflatedBroadcastChannel is the type of channel that mimics BehaviorSubject, so I declared following:
class ChannelSender {
val channel = ConflatedBroadcastChannel<String>()
fun sendToChannel(someString: String) {
GlobalScope.launch(Dispatchers.Main) { channel.send(someString) }
}
}
For listening to the channel I do this:
class ChannelListener(val channelSender: ChannelSender) {
fun listenToChannel() {
channelSender.channel.consumeEach { someString ->
if (someString == "A") foo.perform()
else bar.perform()
}
}
}
This works as expected, but at this point I'm having difficulties understanding how to unit test ChannelListener.
I've tried to find something related here, but none of example-channel-**.kt classes were helpful.
Any help, suggestion or correction related to my incorrect assumptions is appreciated. Thanks.
With the help of Alexey I could manage to end up having following code, which answers the question:
class ChannelListenerTest {
private val val channelSender: ChannelSender = mock()
private val sut = ChannelListener(channelSender)
private val broadcastChannel = ConflatedBroadcastChannel<String>()
private val timeLimit = 1_000L
private val endMarker = "end"
#Test
fun `some description here`() = runBlocking {
whenever(channelSender.channel).thenReturn(broadcastChannel)
val sender = launch(Dispatchers.Default) {
broadcastChannel.offer("A")
yield()
}
val receiver = launch(Dispatchers.Default) {
while (isActive) {
val i = waitForEvent()
if (i == endMarker) break
yield()
}
}
try {
withTimeout(timeLimit) {
sut.listenToChannel()
sender.join()
broadcastChannel.offer(endMarker) // last event to signal receivers termination
receiver.join()
}
verify(foo).perform()
} catch (e: CancellationException) {
println("Test timed out $e")
}
}
private suspend fun waitForEvent(): String =
with(broadcastChannel.openSubscription()) {
val value = receive()
cancel()
value
}
}
I would like to suspend a kotlin coroutine until a method is called from outside, just like the old Java object.wait() and object.notify() methods. How do I do that?
Here: Correctly implementing wait and notify in Kotlin is an answer how to implement this with Kotlin threads (blocking). And here: Suspend coroutine until condition is true is an answer how to do this with CompleteableDeferreds but I do not want to have to create a new instance of CompleteableDeferred every time.
I am doing this currently:
var nextIndex = 0
fun handleNext(): Boolean {
if (nextIndex < apps.size) {
//Do the actual work on apps[nextIndex]
nextIndex++
}
//only execute again if nextIndex is a valid index
return nextIndex < apps.size
}
handleNext()
// The returned function will be called multiple times, which I would like to replace with something like notify()
return ::handleNext
From: https://gitlab.com/SuperFreezZ/SuperFreezZ/blob/master/src/superfreeze/tool/android/backend/Freezer.kt#L69
Channels can be used for this (though they are more general):
When capacity is 0 – it creates RendezvousChannel. This channel does not have any buffer at all. An element is transferred from sender to receiver only when send and receive invocations meet in time (rendezvous), so send suspends until another coroutine invokes receive and receive suspends until another coroutine invokes send.
So create
val channel = Channel<Unit>(0)
And use channel.receive() for object.wait(), and channel.offer(Unit) for object.notify() (or send if you want to wait until the other coroutine receives).
For notifyAll, you can use BroadcastChannel instead.
You can of course easily encapsulate it:
inline class Waiter(private val channel: Channel<Unit> = Channel<Unit>(0)) {
suspend fun doWait() { channel.receive() }
fun doNotify() { channel.offer(Unit) }
}
It is possible to use the basic suspendCoroutine{..} function for that, e.g.
class SuspendWait() {
private lateinit var myCont: Continuation<Unit>
suspend fun sleepAndWait() = suspendCoroutine<Unit>{ cont ->
myCont = cont
}
fun resume() {
val cont = myCont
myCont = null
cont.resume(Unit)
}
}
It is clear, the code have issues, e.g. myCont field is not synchonized, it is expected that sleepAndWait is called before the resume and so on, hope the idea is clear now.
There is another solution with the Mutex class from the kotlinx.coroutines library.
class SuspendWait2 {
private val mutex = Mutex(locaked = true)
suspend fun sleepAndWait() = mutex.withLock{}
fun resume() {
mutex.unlock()
}
}
I suggest using a CompletableJob for that.
My use case:
suspend fun onLoad() {
var job1: CompletableJob? = Job()
var job2: CompletableJob? = Job()
lifecycleScope.launch {
someList.collect {
doSomething(it)
job1?.complete()
}
}
lifecycleScope.launch {
otherList.collect {
doSomethingElse(it)
job2?.complete()
}
}
joinAll(job1!!, job2!!) // suspends until both jobs are done
job1 = null
job2 = null
// Do something one time
}
I'm trying to create a simple program, which is model of Brownian motion using concurrency (impurities randomly move left and right in cells). I have Impurity and Cells classes. Cell class contains cell array which mean how many impurities in each cell at the moment. Each Impurity object changes cell array in Cells in own thread. I'm starting threads and they are running in infinite loop for 1 seconds. But before and after this I print sum of impurities in cells and these values not equal, which means I do something wrong with synchronisation. Here is code:
Cells class:
object Cells {
var cell = Array(N) { 0 }
fun addImpurity(impurity: Impurity) {
cell[impurity.currentCell]++
}
#Synchronized
fun move(impurity: Impurity, direction: Direction) {
if (direction == Direction.LEFT && impurity.currentCell > 0) {
cell[impurity.currentCell]--
cell[impurity.currentCell - 1]++
impurity.currentCell--
} else if (direction == Direction.RIGHT && impurity.currentCell < N - 1) {
cell[impurity.currentCell]--
cell[impurity.currentCell + 1]++
impurity.currentCell++
}
Unit
}
fun printCells() {
for (c in cell)
print("$c ")
}
}
enum class Direction {
LEFT, RIGHT
}
Impurity class:
class Impurity(var currentCell: Int) {
private lateinit var thread: Thread
init {
Cells.addImpurity(this)
}
fun startMoving() {
thread = Thread {
while (true) {
if (random() > P)
Cells.move(this, Direction.RIGHT)
else
Cells.move(this, Direction.LEFT)
}
}
thread.start()
}
fun stopMoving() = thread.interrupt()
}
and Main:
const val N = 10
const val K = 15
const val P = 0.5
fun main(args: Array<String>) {
val impurities = ArrayList<Impurity>()
for (i in 1..K)
impurities.add(Impurity(0))
println(Cells.cell.sum())
startMoving(impurities)
Thread.sleep(1000)
stopMoving(impurities)
Cells.printCells()
println(Cells.cell.sum())
}
private fun startMoving(impurities: ArrayList<Impurity>) {
for (impurity in impurities)
impurity.startMoving()
}
private fun stopMoving(impurities: ArrayList<Impurity>) {
for (impurity in impurities)
impurity.stopMoving()
}
Thanks in advance!
I think it might be better to manually signal to the thread that it should finish its work by having it contain some flag that it refers to in order to know when to quit the loop. For example:
class Impurity(var currentCell: Int) {
...
private var _continue = true
fun startMoving() {
thread = Thread {
while (_continue) {
}
}
...
fun stopMoving() {
_continue = false
}
}
Additionally, you might also want to wait till the actual thread itself dies as part of the call to stopMoving. This will ensure that all the threads have definitely received the signal and quit their loops, before you call Cells.printCells. For example you could add this method to the Impurity class:
fun waitForEnded() = thread.join()
And you could update stopMoving in the main class to call this method after signaling to each thread to stop:
private fun stopMoving(impurities: ArrayList<Impurity>) {
for (impurity in impurities)
impurity.stopMoving()
impurities.forEach(Impurity::waitForEnded)
}