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
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.
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" />
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)
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>>
}
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>
)
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)
Adding ListView inside RecyclerView
(SELECT f2.name FROM favourites as f2 WHERE f1.category = f2.category)
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
}
}
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
To set-up the recyclerView inside home fragment, add this code
with(card_list){
layoutManager = LinearLayoutManager(activity)
adapter = ItemAdapter (requireContext())
}
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()
}
}
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()
}
Now observe the data inside items list in home fragment
viewModel.items.observe(viewLifecycleOwner, Observer{
(card_list.adapter as ItemAdapter).submitList(it)
})
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
Top comments (5)
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...
Thanks for sharing! I was wondering why we would do that... LiveData (Android dependency) in business logic, seemed weird.
Thank you so much! I'll go through the article and try to make corrections as soon as possible
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.
This is a great article. Thank you. Just to thank you I have created an account here.