How to restart a function, when the data it operating on it is updated

Viewed 84

Inside the onCreateView() method of a fragment, I am attaching 3 valueEventListeners to different, mutually exclusive locations at firebase databse. Whenever the data at the reference is updated, they are triggered asynchronously. My sample code looks like this:

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    listner1 = ref1.addCustomValueEventListener {
        list1.clear()
        // do some operation and populate list1
        rewriteMainData()
    }
    listner2 = ref2.addCustomValueEventListener {
        list2.clear()
        // do some operation and populate list2
        rewriteMainData()
    }
    listner3 = ref3.addCustomValueEventListener {
        list3.clear()
        // do some operation and populate list3
        rewriteMainData()
    }
}

private fun rewriteMainData() {
    mainList.clear()
    mainDbReference.addCustomListenerForSingleValueEvent {
        // compare the values present here with values in list1, list2, list3
        // populate mainList with appropriate values
        // notifyAdapterDataSetChanged
    }
    return
}

// EXTENSION FUNCTION
internal inline fun DatabaseReference.addCustomValueEventListener(crossinline onDataChange: (DataSnapshot) -> Unit) =
addValueEventListener(object : ValueEventListener {
    override fun onDataChange(snapshot: DataSnapshot) {
        onDataChange(snapshot)
    }
    override fun onCancelled(error: DatabaseError) {
        Log.e(TAG, "onCancelled: ", error.toException())
    }
})

In this example however, if one listener is triggered soon after the another, then rewriteMainData() will be invoked twice. At that time, mainList will be wrote simultaneously by two callbacks. One way to solve this is to use an instance of ReentrantLock to avoid simultaneous writes. But this makes latest invoke to wait till its previous invoke is completed, which is redundant since all data will be cleared and rewritten.
Hence is there any way to know when rewriteMainData() is invoked and if it is already running, then to just abort and restart the function with updated lists?

2 Answers

You can yield, every time before doing any intensive work to check for cancellation of the current coroutine.

// instance variable on the class
var job1: Job? = null

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
    ref1.addCustomValueEventListener {
        job1?.cancel()
        job2?.cancel()
        job3?.cancel()

        job1 = lifecycleScope.launch(Dispatchers.Default) {
            list1.clear()
    
            // do some operation and populate list1
            rewriteMainData()
        }
    }
    // ...
}


private suspend fun rewriteMainData() {
    mainList.clear()

    // before every intensive work, to check for cancellation.
    yield()
    // some work involving cpu consumption or takes significant time.
}

Note that, the impact of this is quite small or even worse if the work is just to update the list synchronously, which involves just a few CPU cycles and millisecond of time. Use this only if you are sure there is a performance impact due to the work rewriteMainData is doing in your current code.

mainList will be wrote simultaneously by two callbacks.

That's not possible with the code you have here. Database callbacks are invoked on the Android main thread by default, which means that they cannot be invoked simultaneously. They will be invoked sequentially. As such, you don't have to worry about threading or locking.

Related