-
Notifications
You must be signed in to change notification settings - Fork 0
Lab 11
Letβs update our app to display a hardcoded list of data in MyNotesFragment
Β
-
Remove
textView
andnoteDetailButton
fromfragment_my_notes.xml
-
Add a
RecyclerView
tofragment_my_note.xml
and constrain it to fill the screen.- The
FloatingActionButton
should still be visible on top of theRecyclerView
- The
-
Create
item_note.xml
to represent individualNote
list items in the UI. The layout should include:- An
ImageView
with idnoteImage
- A
TextView
with idtitleTextView
- A
TextView
with idcategoryText
- A
TextView
with idcontentText
- An
-
Create a file named
MyNotesListAdapter.kt
within themynotes
package -
Within
MyNotesListAdapter.kt
, create aNoteViewholder
class that binds view references fromitem_note.xml
and bindNote
s into the view-
NoteViewHolder
should extendRecyclerView.ViewHolder
- Pass an instance of
ItemNoteBinding
as a constructor property toNoteViewHolder
and passbinding.root
to theRecyclerView.ViewHolder
constructor - Create a
bindNote(note: Note)
method that takes a note - Within
bindNote(note: Note)
, update the fields from the binding class using the passedNote
-
-
Create an object class
NoteDiffUtil
that extendsDiffUtil.ItemCallback<Note>
- Override both
areItemsTheSame()
andareContentsTheSame()
to perform equality comparison of twoNote
s (see hints) - When we create an instance of our
RecyclerView.Adapter
class, we will pass this callback to it - The
DiffUtil.ItemCallback
makes ourRecyclerView
update more efficient by calculating whichViews
need updated
- Override both
-
Create a
MyNotesListAdapter
class to convert aList<Note>
into views for theRecyclerView
-
MyNotesListAdapter
should extendListAdapter<Note, NoteViewHolder>
- Pass
NoteDiffUtil
to theListAdapter
constructor so we get the efficient diffing we are after - Override
onCreateViewHolder()
which should inflateItemNoteBinding
and create an instance ofNoteViewHolder
- Override
onBindViewHolder()
to bind the new note data to an instance ofNoteViewHolder
- Get the
Note
for the current position by callinggetItem(position)
- Bind the data by passing the
Note
to ourholder.bindNote(note)
method
- Get the
-
-
Within
MyNotesFragment
, set aLinearLayoutManager
to yourRecyclerView
- ex
binding.notesList.layoutManager = LinearLayoutManager(requireContext())
- Without a
LayoutManager
being set, aRecyclerView
will throw an exception when it tries to draw its content - A
LayoutManager
is in charge of determining how child views should be laid out within theRecyclerView
. -
LinearLayoutManager
will draw elements in a vertically scrolling list
- ex
-
Set an instance of
MyNotesListAdapter
to ourRecyclerView
- Within
MyNotesFragment
, create an instance ofMyNotesListAdapter
and store it as a class property namednotesAdapter
: exprivate val notesAdapter = MyNotesListAdapter()
- Set
notesAdapter
to theRecyclerView
by addingbinding.notesList.adapter = notesAdapter
withinonCreateView()
- Within
-
Refactor
MyNotesViewModel.UiState.notes
to be aList<Note>
rather thanList<String>
-
Call
notesAdapter.submitList(uiState.notes)
within the collection ofMyNotesViewModel.state
- This will update our
RecyclerView.Adapter
, and by extension ourRecyclerView
, anytime our state changes
- This will update our
-
Add an
init{}
block withinMyNotesViewModel
and update theUiState
to include the data fromSAMPLE_NOTES
- ex
state.update { currentState -> currentState.copy(notes = SAMPLE_NOTES) }
- ex
-
Re-deploy your app and observe the list of
Note
s -
Respond to list item selections by showing
NoteDetailFragment
- Add a function parameter to your
MyNotesListAdapter
to respond to list item clicks. The function should take aNote
as a parameter and returnUnit
- In
MyNotesListAdapter,onBindViewHolder()
add a click listener to the item view by callingsetOnClickListener{}
- Within the
View
's click listener, call back into the click listener passed to the adapter - Update your adapter initialization within
MyNotesFragment
by passing{}
- we will respond to clicks in the next steps
- Add a function parameter to your
-
Pass the selected
Note
toNoteDetailFragment
when item is selected- Add
id 'kotlin-parcelize'
to theplugins{}
block ofapp/build.gradle
- Update
Note
to implementParcelable
and add@Parcelize
annotation to the class -
Parcelable
is an Android-specific version ofSerializable
designed to be more efficient. Making ourNote
Parcelable
allows us to pass it in aBundle
and forward it as anArgument
to a navigation destination. - Open
main_navigation.xml
and add an argument toNoteDetailsFragment
namedselectedNote
of typeNote
- Rebuild the project to generate a navigation action class
- Within the click handler passed to
MyNotesAdapter
navigate toNoteDetailsFragment
usingfindNavController().navigate(MyNotesFragmentDirections.actionMyNotesFragmentToNoteDetailsFragment(note))
- Add
-
Display the displayed
Note
data inNoteDetailsFragment
- Within
NoteDetailsFragment
, we can access the passed arguments usingprivate val args: NoteDetailsFragmentArgs by navArgs()
- We then will create a class named
NoteDetailsViewModelFactory
that takes the selected note as a constructor property and implementsViewModelProvider.Factory
- Add a constructor property named
note
toNoteDetailsViewModel
with typeNote
- Override
create()
withinNoteDetailsViewModelFactory
to return an instance ofNoteDetailsViewModel
; passing the selectedNote
- Within
NoteDetailsFragment
update the call toby viewModels()
by passingfactoryProducer = { NoteDetailsViewModelFactory(args.selectedNote) }
. This will pass the selectedNote
to our view model when it's accessed for the first time. - In
NoteDetailsViewModel
update thestate
to pull its data from the passedNote
- Within
Β
Β
- Create dynamic lists with RecyclerView
- Efficient RecyclerView Updates with ListAdapter
- Customizing list item touch feedback
- GridLayoutManager
- Efficient data serialization with Parcelable
- Simplify Parcelable implementation with @Parcelize Plugin
- Type-safe arguments with Android Navigation component
Β
RecyclerView.ViewHolder
takes the view for a list item as its constructor parameter.
How we provide that View
is up to us. In this example, we are using the root property from a ViewBinding
class.
We could also choose to pass a raw view into our ViewHolder
instead of a binding class, and cache view references ourselves using findViewById()
.
class NoteViewHolder(val binding: ItemNoteBinding) : RecyclerView.ViewHolder(binding.root) {
// this method is a convention, not a requirement
// it encapsulates the binding of new data to the view within the ViewHolder
// without this, we'd need to bind the data within RecyclerView.Adapter.onBindViewHolder()
fun bindNode(note: Note) {
binding.titleTextView.text = note.title
binding.categoryText.text = note.category
binding.contentText.text = note.content
}
}
Β
The idea behind this ItemCallback
class is that it helps ListAdapter
perform efficient diffing of items in the case where the contents of a ListAdapter
are updated. Rather than completely rebinding every element, it will attempt to determine the smallest number of elements that must be updated based on what is on the screen.
object NoteDiffUtil : DiffUtil.ItemCallback<Note>() {
override fun areItemsTheSame(oldItem: Note, newItem: Note): Boolean {
return oldItem == newItem // if your data class has a unique id, you should compare those ids here instead of the full object
}
override fun areContentsTheSame(oldItem: Note, newItem: Note): Boolean {
return oldItem == newItem
}
}
Β
class MyNotesListAdapter(private val noteClickHandler: (Note) -> Unit) : ListAdapter<Note, NoteViewHolder>(NoteDiffUtil) {
// creates a binding class that caches references to our views
// this caching makes list scrolling more efficient by avoiding view lookups as elements enter and leave the screen
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): NoteViewHolder {
val binding = ItemNoteBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return NoteViewHolder(binding)
}
// rebinding our data allows us to update the content of an existing ViewHolder/View
// this makes scrolling more efficient by avoiding re-inflation of views
override fun onBindViewHolder(holder: NoteViewHolder, position: Int) {
val note = getItem(position) // getItem() pulls data from the internal collection maintained by `ListAdapter`
holder.bindNode(note)
holder.itemView.setOnClickListener { noteClickHandler(note) }
}
}
Β
// app/build.gradle
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-parcelize'
...
}
Β
Try adding android:foreground="?attr/selectableItemBackground"
to the root view of your list item layout
Β
RecyclerView
supports drawing different configurations by using LayoutManagers
.
Take a look at GridLayoutManager
to draw items in a grid