DEV Community

Cover image for Implementing ListView inside RecyclerView and observing LiveData
Anureet Kaur
Anureet Kaur

Posted on

Implementing ListView inside RecyclerView and observing LiveData

Recently I wanted to show cards in my application which gets updated according to the month I have added in my list. Inside the card, I wanted to show the list of items that I have added for that month. Being a beginner in android, it took me a long time to figure this out so I decided to write a "how to" post on the same. I will be explaining the main files, complete project is available here.

Let's get started 💁

I have created a simple application as an example

Alt Text

In the above screen, there is an option to add name and a category to which it belongs. For selection of a single category, I have implemented a spinner inside this fragment. On the home page, a card will be generated according to the category added and it will show the name of the category and the respective list of items belonging to that category. Floating action button can be used to add more items to the list.

Alt Text

I have used MVVM architecture and single activity multiple fragments pattern to implement this app. There are two parts to this. One is on adding cards according to the category added to the database and the other one is on how to insert list inside an existing list.

Adding card dynamically

So firstly add recyclerview to your fragment

<androidx.recyclerview.widget.RecyclerView
        android:id="@+id/card_list"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:listitem="@layout/card_item" />
Enter fullscreen mode Exit fullscreen mode

I have created a database to store items which uses data class Favourites.kt

Favourites.kt

import androidx.room.Entity
import androidx.room.PrimaryKey

enum class Categories{
    Movies, Sports, Fruits, Vegetables
}

@Entity(tableName = "favourites")
data class Favourites(@PrimaryKey(autoGenerate = true) val id: Long,
                      val name : String,
                      val category: Int)
Enter fullscreen mode Exit fullscreen mode

FavouritesListDao.kt

import androidx.lifecycle.LiveData
import androidx.room.Dao
import androidx.room.Query

@Dao
interface FavouritesListDao{
    @Query("SELECT f1.category, (SELECT f2.name FROM favourites as f2 WHERE f1.category = f2.category) FROM favourites as f1 GROUP BY category")
    fun getData(): LiveData<List<Category>>

}
Enter fullscreen mode Exit fullscreen mode

The above sqlite query is responsible for displaying cards according to the category inserted. The GROUP BY clause will group the data with respect to category.
This query will fetch data from the database. Following data class is being used to store the results obtained by the above query.

import androidx.room.ColumnInfo
import androidx.room.Relation


data class Category(
    @ColumnInfo(name = "category")
    var category: Int,

    @Relation(parentColumn = "category", entityColumn = "category", entity = Favourites::class)
    val children : List<itemName>
)
Enter fullscreen mode Exit fullscreen mode

The code below denotes that the result stored inside children list will be obtained by a self-join .

@Relation(parentColumn = "category", entityColumn = "category", entity = Favourites::class)
Enter fullscreen mode Exit fullscreen mode

Adding ListView inside RecyclerView

(SELECT f2.name FROM favourites as f2 WHERE f1.category = f2.category)
Enter fullscreen mode Exit fullscreen mode

Results obtained from this query present inside the above sqlite query will be stored inside 'children' list. This will be added to the listView inside cardView.

Here is the itemAdapter class:

class ItemAdapter(val context: Context):
    ListAdapter<Category, ItemAdapter.ViewHolder>(
        DiffCallback()
    ){

    override fun onCreateViewHolder(parent: ViewGroup,
                                    viewType: Int): ViewHolder {
        val itemLayout = LayoutInflater.from(parent.context)
            .inflate(R.layout.card_item, parent, false)

        return ViewHolder(itemLayout)
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.bind(getItem(position))
    }

    inner class ViewHolder (override val containerView: View) : RecyclerView.ViewHolder(containerView),
        LayoutContainer {

        fun bind(item: Category){
            with(item){
                when(item.category){
                    Categories.Movies.ordinal ->{
                        category_name.text = Categories.Movies.toString()
                    }
                    Categories.Sports.ordinal ->{
                        category_name.text = Categories.Sports.toString()
                    }
                    Categories.Fruits.ordinal ->{
                        category_name.text = Categories.Fruits.toString()
                    }
                    else ->  category_name.text = Categories.Vegetables.toString()
                }

                // Setting up list view
                val list: List<String> = item.children.map { it.name }
                val adapter = ArrayAdapter(context, android.R.layout.simple_list_item_1,list)
                item_list.adapter = adapter

            }
        }
    }
}

class DiffCallback : DiffUtil.ItemCallback<Category>() {
    override fun areItemsTheSame(oldItem: Category, newItem: Category): Boolean {
        return oldItem.category == newItem.category
    }

    override fun areContentsTheSame(oldItem: Category, newItem: Category): Boolean {
        return oldItem == newItem
    }
}
Enter fullscreen mode Exit fullscreen mode

This code segment inside the above code is used to set up listView inside card_item (cardView)

// Setting up list view
val list: List<String> = item.children.map { it.name }
val adapter = ArrayAdapter(context,android.R.layout.simple_list_item_1,list)
                item_list.adapter = adapter
Enter fullscreen mode Exit fullscreen mode

To set-up the recyclerView inside home fragment, add this code

with(card_list){
     layoutManager = LinearLayoutManager(activity)
     adapter = ItemAdapter (requireContext())
}
Enter fullscreen mode Exit fullscreen mode

Observing Live Data

Create a repository class for communication between viewModel and DAO

import android.app.Application
import androidx.lifecycle.LiveData

class FavouritesListRepository(context:Application) {
    private val favouritesListDao: FavouritesListDao = FavouritesDatabase.getDatabase(context).favouritesListDao()

    fun getData(): LiveData<List<Category>> {
        return favouritesListDao.getData()
    }

}
Enter fullscreen mode Exit fullscreen mode

To observe live data, create a viewModel class for home fragment.

class FavouriteListViewModel(application: Application): AndroidViewModel(application) {
    private val repo : FavouritesListRepository = FavouritesListRepository(application)

    val items: LiveData<List<Category>>
        get() = repo.getData()

}
Enter fullscreen mode Exit fullscreen mode

Now observe the data inside items list in home fragment

viewModel.items.observe(viewLifecycleOwner, Observer{
            (card_list.adapter as ItemAdapter).submitList(it)
        })
Enter fullscreen mode Exit fullscreen mode

To checkout how items are getting inserted in the database and the structure of the app, here is the github link from where you can download this project

This is my first post. I hope this is helpful.
Alt Text

Top comments (5)

Collapse
 
ike__jr profile image
Isaac

Hey, great article! I observed that you used a LiveData inside your repository; probably inspired by Google's Architecture Guide. But this is an anti-pattern and you should replace the livedata with Kotlin's Flow.

You can read this article for a detailed explanation: proandroiddev.com/no-more-livedata...

Collapse
 
mx profile image
Maxime Moreau • Edited

Thanks for sharing! I was wondering why we would do that... LiveData (Android dependency) in business logic, seemed weird.

Collapse
 
anureet19 profile image
Anureet Kaur

Thank you so much! I'll go through the article and try to make corrections as soon as possible

Collapse
 
mx profile image
Maxime Moreau

Hi! I've just started to learn Android with Kotlin. I have a strong programming background no 0 experience with Android. Nicely written, thank you for sharing this! It's interesting, right to the point I like it! Good luck for the rest.

Collapse
 
nerviantone profile image
nerviantone

This is a great article. Thank you. Just to thank you I have created an account here.