Issue
In my Fragment, I'm trying to fetch data from Firebase Database using coroutines where data is retrieving properly. Here is my code
@ExperimentalCoroutinesApi //Fragment Class code
override fun onStart() {
super.onStart()
checkOutViewModel.viewModelScope.launch {
try{
if (isActive){
checkOutViewModel.getCartDataFromFirebaseNetwork().collect{
tempList.add(it)
}
}
}catch (ex : Exception){
Log.d("exception message",ex.cause?.message!!) //Fatal Exception: Main
}
orderListAdapter?.submitList(tempList)
binding.progress.visibility = View.GONE
binding.recycler.visibility = View.VISIBLE
}
}
@ExperimentalCoroutinesApi //Viewmodel class code
suspend fun getCartDataFromFirebaseNetwork()= firebaseNetwork.getCartFromFirebase()
@ExperimentalCoroutinesApi //Repository class code
suspend fun getCartFromFirebase() = callbackFlow<Cart> {
ensureActive()
val counterList = myFlow.toList()
val itemList = myFlow.mapBasketToItemsList().toList()
val pairs = myFlow.mapBasketListToQuantity().toList()
if(itemList.isNotEmpty() && pairs.isNotEmpty()){
for ((current,item) in itemList.withIndex()) {
val cart = Cart(counterList[current].basketId!!,item.id!!,item.url!!,item.name!!,pairs[current].first,pairs[current].second,counterList[current].itemCounter!!,pairs[current].second)
offer(cart)
}
channel.close()
}
}
@ExperimentalCoroutinesApi
val myFlow = callbackFlow<Basket> {
databaseReference.child("Cart").child(getCurrentUserUid())
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
if (dataSnapshot.exists()) {
for (data in dataSnapshot.children) {
val basket = Basket()
basket.basketId = data.key
basket.itemId = data.child("itemId").value as String
basket.itemCounter = data.child("itemCounter").value as String
basket.itemWeight = data.child("itemWeight").value as String
offer(basket)
}
channel.close()
}
}
})
awaitClose()
}
@ExperimentalCoroutinesApi
private fun Flow<Basket>.mapBasketToItemsList() : Flow<Items> = map{basket ->
suspendCoroutine<Items> {continuation ->
databaseReference.child("Items").child(basket.itemId!!)
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
if (dataSnapshot.exists()) {
val items = dataSnapshot.getValue(Items::class.java)!!
continuation.resume(items)
}
}
})
}
}
@ExperimentalCoroutinesApi
private fun Flow<Basket>.mapBasketListToQuantity() : Flow<Pair<String,String>> = map{basket ->
suspendCoroutine<Pair<String,String>> {continuation ->
databaseReference.child("Quantities").child(basket.itemId!!)
.child(basket.itemWeight!!)
.addListenerForSingleValueEvent(object : ValueEventListener {
override fun onCancelled(p0: DatabaseError) {
}
override fun onDataChange(dataSnapshot: DataSnapshot) {
if (dataSnapshot.exists()) {
val key = dataSnapshot.key
val value = dataSnapshot.value as String
val myPair = Pair(key!!, value)
continuation.resume(myPair)
}
}
})
}
}
Edited: This is my Navigation Flow of Fragments
OnBoarding-Authentication-MainFragment-CheckItemListFragment
override fun onStart() { //OnBoarding Fragment
super.onStart()
try {
if(viewModel.checkAuth()){
updateUI()
}
}catch (ex : Exception){
println("In onBoarding Fragment")
Log.d("exception message",ex.cause?.message!!)
}
}
override fun onStart() { //Authentication Fragment
super.onStart()
try {
if(mAuth.currentUser == null){
showShortToast("Please Login")
}else{
updateUI()
}
}catch (ex : Exception){
println("In authentication Fragment")
Log.d("exception message",ex.cause?.message!!)
}
}
override fun onStart() { //MainFragment
super.onStart()
try {
if(mainFragmentViewModel.checkSignIn() == null)
findNavController().navigateUp()
binding.toolbar.add_to_cart.setOnClickListener {
it.findNavController().navigate(R.id.action_mainFragment_to_checkoutItemsList)
}
}catch (ex : Exception){
println("In Main Fragment")
Log.d("exception",ex.message!!)
}
}
@ExperimentalCoroutinesApi
override fun onStart() { //CheckItemList Fragment
super.onStart()
try {
binding.addToCart.setOnClickListener {
checkOutViewModel.viewModelScope.launch {
val message = orderListAdapter?.getList()?.let { it1 -> checkOutViewModel.submitFinalCart(it1) }
if(message!!){
findNavController().navigate(R.id.action_checkoutItemsList_to_finalCarts)
}
}
}
viewLifecycleOwner.lifecycleScope.launch {
ensureActive()
checkOutViewModel.getCartDataFromFirebaseNetwork().collect {
tempList.add(it)
orderListAdapter?.submitList(tempList)
binding.progress.visibility = View.GONE
binding.recycler.visibility = View.VISIBLE
}
}
}catch (ex : Exception){
println("In checkItemList Fragment")
Log.d("exception message",ex.cause?.message!!)
}
}
Edited : My Logcat is :-
--------- beginning of crash
07-10 21:18:40.605 30715-30715/com.example.groceryapp E/AndroidRuntime: FATAL
EXCEPTION: main
Process: com.example.groceryapp, PID: 30715
f.d
at com.example.groceryapp.checkout.CheckoutItemsList$e.a(:73)
at f.z.k.a.a.b(:33)
at kotlinx.coroutines.u0.run(:334)
at kotlinx.coroutines.z0.k(:68)
at kotlinx.coroutines.r0.b(:354)
at f.z.i.b(:42)
at com.example.groceryapp.f.a$r$a$b.a(:262)
at com.google.firebase.database.m$a.a(:179)
at com.google.firebase.database.u.a0.a(:75)
at com.google.firebase.database.u.i0.d.a(:63)
at com.google.firebase.database.u.i0.g$a.run(:55)
at android.os.Handler.handleCallback(Handler.java:742)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:157)
at android.app.ActivityThread.main(ActivityThread.java:5603)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:774)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:652)
This is how i used my Try Catch block in every Fragments to handle this issue but it is also not working at all. I'm also using isActive method to check whether the job is still active or not before retrieving any data. I get "Fatal Exception: Main, Job was Cancelled" if i pressed back button before recyclerview shows the data. This exception only comes if i use callback flow. Is there any way to handle this issue or is it a bug in callback flow?. So far I couldn't find any possible answer that will solve my issue. Please tell me how do i fix it?
Solution
I faced with the same issue before and here is my solution with an extension of SendChannel
fun <T> SendChannel<T>.offerCatching(element: T): Boolean {
return runCatching { offer(element) }.getOrDefault(false)
}
and when emit event just call offerCatching
Answered By - Steve.P