I want to understand the following pdf file in 15 minutes, gimme a comprehensive description of the file :
© G. Malnati, C.Barberis 2013-23
Displaying collections
A.A. 2022-23© G. Malnati, C.Barberis 2013-23 2
Managing collections
• We often need to show lists or other kind of information
collections
o Whose size is not known a priori and may be large
• Dynamically populating a view is not, usually, a good solution
o If the number of entries to display grows significantly, the visual hierarchy
grows and consumes a lot of resources
• Originally, the Android framework offered a bunch of classes to
deal with this problem
o ListView, GridView, Gallery, … which are subclasses of a more general one,
AdapterView
o Today, these classes are considered legacy and their use is discouraged in
favour of a more modern, flexible, and performant approach to displaying
lists, based on RecyclerView© G. Malnati, C.Barberis 2013-23 3
Recycling views
• Usually, only a limited set of items can be displayed on the
screen
o Instead of creating as many views as items in the collection, only a small
number of them can be created: those that are visible on the screen plus
one extra ghost item
• The ghost item will become part of the visual hierarchy when the
list is scrolled
o At the same time, since the item at the opposite end will become invisible,
it will take the role of the ghost item© G. Malnati, C.Barberis 2013-23
Recycling views
4
Ghost
item
Nam
Addreses
Nam
Addreses
Nam
Addreses
Nam
Addreses
Nam
Addreses
Nam
Addreses© G. Malnati, C.Barberis 2013-23 5
Recycling views
• The same principle may be employed if the collection is shown
as a grid or with an other kind of layout
o Simply, more ghost items are needed
• This approach can drastically improve performance
o By skipping initial layout inflation or construction, which require a lot of
CPU cycles© G. Malnati, C.Barberis 2013-23 6
androidx.recyclerview.widget.
RecylcerView
• Class of the Jetpack library that manages the presentation of
potentially large data sets
o Presented items are managed by an Adapter, which is responsible for
providing and filling views with relevant content from the data set
o Each single item in the data set is presented in a recyclable visual hierarchy
which is managed by a ViewHolder
o The visual presentation of all the items is delegated to a LayoutManager,
possibly helped by an ItemAnimator and an ItemDecorator© G. Malnati, C.Barberis 2013-23
androidx.recyclerview.widget.
RecylcerView
7
RecyclerView
LayoutManager
ItemAnimator
ItemDecorator
MyViewHolder:
MyViewHolder: ViewHolder
MyViewHolder: ViewHolder
MyViewHolder: ViewHolder
ViewHolder
MyDataAdapter:
Adapter
MyData
Create
Bind
Item visual trees
framework class
user-defined class© G. Malnati, C.Barberis 2013-23 8
RecyclerView
fun onCreate( savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_list)
val recyclerView: RecyclerView =
findViewById(R.id.rv)
//Initialize data to be displayed
val myData: List< SomeDataClass > = …
//Show items as a simple linear list
recyclerView.layoutManager =
LinearLayoutManager(this)
//Populate recyclerView with data
recyclerView.adapter = MyDataAdapter(myData)
}© G. Malnati, C.Barberis 2013-23 9
Setting up project dependencies
• RecyclerView is part of the Jetpack extension library:
o Add a corresponding dependency in the app build.gradle file
dependencies {
…
implementation ‘androidx.recyclerview:recyclerview:1.3.0’
…
}
build.gradle (Module: app)© G. Malnati, C.Barberis 2013-23 10
LayoutManager
StaggeredGrid
LayoutManager
LinearLayoutManager GridLayoutManager© G. Malnati, C.Barberis 2013-23 11
Implementing the Adapter
• The RecyclerView.Adapter instance has three responsibilities
o It stores the data model and offers a method to retrieve the total number
of items in the model (getItemCount())
o It supports the creation of a new ViewHolder and of the corresponding
visual tree (onCreateViewHolder(…))
o It populates the created visual tree, via the ViewHolder with the data of a
specific item (onBindViewHolder(…))© G. Malnati, C.Barberis 2013-23 12
Implementing the Adapter
• Given an item data model, a corresponding layout for presenting
its content is usually created
data class User(
val name : String,
val address: String,
val iconURL: String
);
R.id.icon
TextView
TextView
R.id.name
R.id.address
R.layout.user_layout© G. Malnati, C.Barberis 2013-23
Implementing the Adapter
13
class UserAdapter(val data:List<User>):
RecyclerView.Adapter< UserAdapter.UserViewHolder >()
{
override fun getItemCount() = data.size
override fun onCreateViewHolder(parent: ViewGroup,
viewType: Int) {
val v=LayoutInflater.from(parent.context)
.inflate(R.layout.user_layout, parent, false)
return UserViewHolder(v)
}
// continues in next slide…
}© G. Malnati, C.Barberis 2013-23 14
Implementing the Adapter
class UserAdapter(val data:List<User>):
RecyclerView.Adapter< UserAdapter.UserViewHolder >()
{
// continues from previous slide…
override fun onBindViewHolder(holder:UserViewHolder,
position: Int) {
val u = data[position] //access data item
// let the holder use data as needed
holder.bind(u);
}
// continues in next slide…
}© G. Malnati, C.Barberis 2013-23 15
Implementing the Adapter
class UserAdapter(val data:List<User>):
RecyclerView.Adapter< UserAdapter.UserViewHolder >()
{// continues from previous slide…
class UserViewHolder(v:View):
RecyclerView.ViewHolder(v) {
val name: TextView = v.findViewById(R.id.name)
val address: TextView = v.findViewById(R.id.address)
val icon: ImageView = v.findViewById(R.id.icon)
fun bind(u: User) {
name.text = u.text
address.text = u.address
icon.load(u.iconURL) //this requires library COIL
}
}© G. Malnati, C.Barberis 2013-23
RecyclerView.ViewHolder
16
Name
Address
class UserViewHolder(val v:View): RecyclerView.ViewHolder(v){
val name: TextView = v.findViewById(R.id.name)
val address: TextView = v.findViewById(R.id.address)
val icon: ImageView = v.findViewById(R.id.icon)
}© G. Malnati, C.Barberis 2013-23 17
Displaying heterogeneous views
• Sometimes, items inside the collection need different
representations depending on their content
o RecyclerView can manage this, provided that the adapter cooperates
• Adapters can override method getItemViewType(pos: Int): Int
o It can return an arbitrary integer
o When a ViewHolder is created, the requested type is passed as a
parameter
o Items which have the same type can recycle the same ViewHolder© G. Malnati, C.Barberis 2013-23
Displaying heterogeneous views
18
TextItem
s:String
abstract
Item
ImageItem
url:String
TextVH
bind(i:Item)
abstract
ItemVH
abstract bind(i:Item)
ImageVH
bind(i:item)
R.layout.text_item R.layout.image_item© G. Malnati, C.Barberis 2013-23
Displaying heterogeneous views
19
abstract class Item {}
class TextItem(val s:String): Item()
class ImageItem(val url:String): Item()
class ItemAdapter(val l: List<Item>):
RecyclerView.Adapter<ItemAdapter.ItemVH>() {
override fun getItemCount() = l.size
override fun onBindViewHolder(h: ItemVH, p:Int) {
h.bind( l[position] )
}
//continues in next slide…
}© G. Malnati, C.Barberis 2013-23
Displaying heterogeneous views
20
class ItemAdapter(val l: List<Item>):
RecyclerView.Adapter<ItemAdapter.ItemVH>() {
//…continues from previous slide
override fun onCreateViewHolder(p: ViewGroup, t:Int):
ItemVH {
val inflater = LayoutInflater.from(p.context)
val v = inflater.inflate(t, parent, false)
return when (t) {
R.layout.text_item -> TextVH(v)
R.layout.image_item -> ImageVH(v)
else -> throw RuntimeException(“Illegal type”)
}
}
//continues in next slide…© G. Malnati, C.Barberis 2013-23
Displaying heterogeneous views
21
class ItemAdapter(val l: List<Item>):
RecyclerView.Adapter<ItemAdapter.ItemVH>() {
//…continues from previous slide
override fun getItemViewType(position: Int):Int {
return when (l[position]) {
is TextItem -> R.layout.text_item
is ImageItem -> R.layout.image_item
else -> throw RuntimeException(“Illegal type”)
}
}
//continues in next slide…
}© G. Malnati, C.Barberis 2013-23
Displaying heterogeneous views
22
class ItemAdapter(val l: List<Item>):
RecyclerView.Adapter<ItemAdapter.ItemVH>() {
//…continues from previous slide
abstract class ItemVH(v:View):
RecyclerView.ViewHolder(v) {
abstract fun bind(item: Item);
}
class TextVH(v: View): ItemVH(v) {
val textView: TextView = v.findViewById(R.id.tv);
override fun bind(item:Item) {
textView.text = (item as TextItem).s
}
//continues in next slide…
}© G. Malnati, C.Barberis 2013-23
Displaying heterogeneous views
23
class ItemAdapter(val l: List<Item>):
RecyclerView.Adapter<ItemAdapter.ItemVH>() {
//…continues from previous slide
class ImageVH(v: View): ItemVH(v) {
val imageView: ImageView = v.findViewById(R.id.iv);
override fun bind(item:Item) {
imageView.load((item as TextItem).url)
//this requires the COIL library
}
}© G. Malnati, C.Barberis 2013-23 24
Interacting with the RecyclerView
• If any kind of interaction is needed within the single item
presentation…
o A suitable handler should be installed when the ViewHolder is bound to a
specific data item
class SomeViewHolder(v:View):
RecyclerView.ViewHolder(v) {
val b:Button = v.findViewById(R.id.button)
//… more properties
fun bind(d:SomeData, callback: (Int) -> Unit) {
// bind data to properties, then…
b.setOnClickListener{ callback(adapterPosition) }
}
fun unbind() { b.setOnClickListener(null) }
}© G. Malnati, C.Barberis 2013-23 25
Interacting with the RecyclerView
• Callbacks can be removed when the ViewHolder is recycled
class MyAdapter (…): RecyclerView.Adapter<SomeVH>()
{
override fun onBindViewHolder(h: SomeVH, p: Int) {
h.bind(data[p]) { pos: Int -> doSomething(pos) }
}
override fun onViewRecycled(holder: SomeVH) {
holder.unbind();
}
}© G. Malnati, C.Barberis 2013-23 26
Manipulating the data model
• If any change occurs to the data list, a proper notification should
be raised
o RecyclerView.Adapter offers several useful methods that trigger an update
of the RecyclerView
o notifyItemInserted(position: Int)
o notifyItemRemoved(position:Int)
o notifyItemChanged(position:Int)
o notifyItemMoved(fromPos: Int, toPos: Int)
o notifyDataSetChanged()© G. Malnati, C.Barberis 2013-23 27
Manipulating the data model
• When a notification is received, the RecyclerView updates its
presentation, performing an animation
o This will not happen if notifyDataSetChanged() is invoked, because it has
no clues about what changes did happen
• In order to cope with this, the DiffUtil class can be used
o It calculates the difference between two lists and output a list of update
operations, based on the Myers’ algorithm
o A class implementing the DiffUtil.Callback interface must be provided© G. Malnati, C.Barberis 2013-23 28
Manipulating the data model
data class User(val id:Int, val name:String, val age: Int)
class UserAdapter(var users: List<User>):
RecyclerView<UserAdapter.UserVH>() {
//other methods…
fun setUsers(newUsers: List<User>) {
val diffs = DiffUtil.calculateDiff(
UserDiffCallback(users, newUsers) )
users = newUsers //update data
diffs.dispatchUpdatesTo(this) //animate UI
}
}© G. Malnati, C.Barberis 2013-23
Manipulating the data model
29
class UserDiffCallback(private val users: List<User>,
private val newUsers: List<User>): DiffUtil.Callback() {
RecyclerView<UserAdapter.UserVH>() {
override fun getOldListSize(): Int = users.size
override fun getNewListSize(): Int = newUsers.size
override fun areItemsTheSame(oldP: Int,
newP: Int): Boolean {
return users[oldP].id === newUsers[newP].id }
override fun areContentsTheSame(oldPosition: Int,
newPosition: Int): Boolean {
val (, name, age) = oldList[oldPosition]
val (, name1, age1) = newList[newPosition]
return name == name1 && age == age1
}
}© G. Malnati, C.Barberis 2013-23 30
Legacy data containers
• Initial Android versions did offer several classes specialized in the
presentation and interaction with data collections,
o ListView displays a vertical list of views
o GridView displays a two-dimensional grid of views
o Other specialized versions…
• These classes are now considered deprecated in favor of
RecyclerView
o Which is simpler and more efficient