1

I wanted to get a list of data from the realtime database using coroutines and MVVM and put them to recyclerview. It runs but the data from the realtime database are added after the recyclerview.adapter initialization, thus returning list.size to 0

ViewModel.kt

class DasarhukumdetailsViewModel : ViewModel() {
val database = FirebaseDatabase.getInstance().reference
var dasarHukumList = ArrayList<DasarHukum>()

fun getDHData(KEYVALUE: String?) = liveData(Dispatchers.Main.immediate) {
    val postListener = object : ValueEventListener {
        override fun onDataChange(snapshot: DataSnapshot) {
            for (snapshot in snapshot.children) {
                val res = snapshot.getValue(DasarHukum::class.java)
                Log.d("dataAdd", "Adding: ${res?.filename}")
                dasarHukumList.add(res!!)
            }
        }

        override fun onCancelled(databaseError: DatabaseError) {
            // Getting Post failed, log a message
            Log.w("readDHList", "loadPost:onCancelled", databaseError.toException())
            throw databaseError.toException()
        }
    }
    try {
        if (KEYVALUE != null) {
            database.child("dasarhukum").child(KEYVALUE).addValueEventListener(postListener)
        }
        emit(Resource.success(dasarHukumList))
    } catch (e: Exception) {
        emit(Resource.error(
            null,
            e.message ?: "Unknown Error"
        ))
    }
}

Fragment.kt

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
   [...]
    observerSetup(KEYVALUE)
    rvSetup()
    return binding.root
}
fun observerSetup(keyvalue: String?) {
    viewModel.getDHData(keyvalue).observe(viewLifecycleOwner, {
        when (it.status) {
            Status.SUCCESS -> {
                it?.data.let { dhList ->
                    dasarHukumAdapter.dasarhukumList = dhList
                    dasarHukumAdapter.notifyDataSetChanged()
                }
            }
            Status.ERROR -> {
                Toast.makeText(context, "Error getting documents: ${it.message}", Toast.LENGTH_LONG)
                Log.e("realDB", it.message!!)
            }
        }
    })
}
fun rvSetup() {
    with(binding.rvDasarHukum) {
        layoutManager = LinearLayoutManager(context)
        setHasFixedSize(true)
        adapter = dasarHukumAdapter
    }
}

RVAdapter.kt

class DasarHukumAdapter : RecyclerView.Adapter<DasarHukumAdapter.DasarHukumViewHolder>() {
var dasarhukumList: List<DasarHukum>? = null
    set(value) {
        notifyDataSetChanged()
        field = value
    }

class DasarHukumViewHolder(private val binding: ItemDasarhukumBinding) :
    RecyclerView.ViewHolder(binding.root) {
    fun bind(dasarHukum: DasarHukum?) {
        binding.dasarhukum = dasarHukum
        binding.executePendingBindings()
    }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DasarHukumViewHolder {
    val layoutInflater = LayoutInflater.from(parent.context)
    val binding = ItemDasarhukumBinding.inflate(layoutInflater, parent, false)
    return DasarHukumViewHolder(binding)
}

override fun onBindViewHolder(holder: DasarHukumViewHolder, position: Int) {
    val dasarHukum = dasarhukumList?.get(position)
    Log.d("dhVH", "Adding: ${dasarHukum?.name}")
    holder.bind(dasarHukum)
}

override fun getItemCount(): Int {
    Log.d("dhCount", "List size: ${dasarhukumList?.size}")
    return dasarhukumList?.size ?: 0
}

How can the recyclerview waits for the viewmodel.getDHData() to returns the arraylist first then initialize so it can be displayed to the recyclerview?

rizkyputrapb
  • 65
  • 1
  • 9

1 Answers1

1

By the time you are trying to emit the result using the following line of code:

emit(Resource.success(dasarHukumList))

The data hasn't finished loading yet, hence the zero size of the list. Firebase API is asynchronous, so you need to wait for the data in order to use it in another operation. So any code that needs data from the Realtime Database needs to be inside the "onDataChange()" method, or be called from there. So the simplest solution, in this case, would be to move the logic regarding the emitting the result, inside the callback:

fun getDHData(KEYVALUE: String?) = liveData(Dispatchers.Main.immediate) {
    val postListener = object : ValueEventListener {
        override fun onDataChange(snapshot: DataSnapshot) {
            for (snapshot in snapshot.children) {
                val res = snapshot.getValue(DasarHukum::class.java)
                Log.d("dataAdd", "Adding: ${res?.filename}")
                dasarHukumList.add(res!!)
            }
            try {
                if (KEYVALUE != null) {
                    database.child("dasarhukum").child(KEYVALUE).addValueEventListener(postListener)
                }
                emit(Resource.success(dasarHukumList))
            } catch (e: Exception) {
            emit(Resource.error(
                null,
                e.message ?: "Unknown Error"))
            }
        }

        override fun onCancelled(databaseError: DatabaseError) {
            // Getting Post failed, log a message
            Log.w("readDHList", "loadPost:onCancelled", databaseError.toException())
            throw databaseError.toException()
        }
    }
}
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • but emitting the status cannot be done inside the onDataChange() function, thus it has to be outside the callback – rizkyputrapb May 04 '21 at 11:33
  • Sure it can, as there is the **only** place where the data is available. You cannot simply use that dasarHukumList outside that method unless you are using another [callback](https://stackoverflow.com/questions/47847694/how-to-return-datasnapshot-value-as-a-result-of-a-method/47853774), but that's not the point as you are using LiveData objects.. – Alex Mamo May 04 '21 at 11:44