Search code examples
androidkotlinmvvmandroid-recyclerviewviewmodel

Remove UI element checking if table is empty from the viewmodel


Ok, i'll try to keep it simple. I'm writing an app, and one of its fragments contains a recyclerview which displays the elements inside a Room table. When the table is empty, i should display a placeholder.

I'm quite new to MVVM, but the approach i tried to follow was to add the placeholder to the layout (a simple textview) and removing it from the layout if the table is not empty. The problem is, every attempt to determine if the table is empty didn't work. What i tried was:

  • Having a simple query in the DAO (code below), and calling it in viewmodel. Then from viewmodel i use the function in the fragment itself. This approach doesn't work due to the execution times i think, since the viewmodel function uses a coroutine to work properly
  • Trying to use the recyclerview adapter and its getItemCount method, but it always return 0

So basically i'm struggling a lot to simply remove a textview from a layout when a db is empty while avoiding to break the MVVM principles. Here's some useful code

DAO function

@Query("SELECT * FROM Event LIMIT 1")
fun getAnyEvent(): EventResult?

ViewModel Function

fun getAnyEvent() = viewModelScope.launch(Dispatchers.Default) {
    eventDao.getAnyEvent()
}

The first non-working approach

if (homeViewModel.getAnyEvent() != null) {
    val homeMain: LinearLayout = v.findViewById(R.id.homeMain)
    val placeholder: TextView = v.findViewById(R.id.noEvents)
    homeMain.removeView(placeholder)
}

The adapter

class EventAdapter internal constructor(context: Context) : RecyclerView.Adapter<EventAdapter.EventViewHolder>() {
    private var events = emptyList<EventResult>() // Cached copy of events
    private val appContext = context

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EventViewHolder {
        return EventViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.event_row, parent, false))
    }

    override fun onBindViewHolder(holder: EventViewHolder, position: Int) {
        val current = events[position]
        holder.setUpView(event = current)
    }

    inner class EventViewHolder (view: View) : RecyclerView.ViewHolder(view), View.OnClickListener {
        private val favoriteButton: ImageView = view.favoriteButton
        private val eventPerson: TextView = view.eventPerson
        private val eventDate: TextView = view.eventDate
        private val eventYears: TextView = view.eventYears

        init {
            view.setOnClickListener(this)
        }

        // Set every necessary text and click action in each row
        fun setUpView(event: EventResult?) {
            val personName = event?.name + " " + event?.surname
            val formatter: DateTimeFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.FULL)
            val nextDate = event?.nextDate?.format(formatter)
            val nextAge = appContext.getString(R.string.next_age_years) + ": " + (event?.nextDate?.year?.minus(event.originalDate.year)).toString()
            eventPerson.text = personName
            eventDate.text = nextDate
            eventYears.text = nextAge

        override fun onClick(v: View?) {
            println("test")
        }
    }

    internal fun setEvents(events: List<EventResult>) {
        this.events = events
        notifyDataSetChanged()
    }

    override fun getItemCount() = events.size

}

What approach should i use? And what am i doing wrong using this pattern? As a side note, the recyclerview works and i'm already using livedata to properly observe changes.


Solution

  • It seemed you have just one need - to know in your fragment whether your db-query return 0 or more records. Have you tried to use LiveData for this (with no coroutines)? (you mention that, but it's not clear)

    1. Your DAO should return LiveData-value. By this Room "out-of-the-box" will fetch to your ViewModel actual query-result in online-mode (of course, in background thread).

    fun getAnyEvent(): LiveData<List<EventResult>>

    1. In your ViewModel you can declare field with LiveData as well

    val eventResult: LiveData<List<EventResult>> = eventDao.getAnyEvent()

    1. In your fragment you simply observe your eventResult and you can do whatever you want with your UI according to the list size (if it's 0, then remove your View stub)

      homeViewModel.eventResult.observe(viewLifecycleOwner, Observer { eventList -> yourAdapter.setEvents(eventList)})

    Here is Google's Codelab with all this stuff, for example