My app uses "Jetpack Navigation, DataBinding, ViewModel".
A Fragment has RecyclerView.
If I touch one of the items, it is navigated to B Fragment.
And when I come back to A fragment (by back button), the crash occurs.
IndexOutOfBoundsException: Inconsistency detected. Invalid view holder adapter
A Fragment is:
#AndroidEntryPoint
class AFragment : Fragment() {
private var _binding: FragmentABinding? = null
private val binding: FragmentABinding
get() = _binding!!
private val viewModel: AViewModel by viewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentABinding.inflate(inflater, container, false)
binding.vm = viewModel
binding.lifecycleOwner = viewLifecycleOwner
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
binding.rvItems.adapter = ItemsAdapter()
viewModel.getItems()
}
}
A ViewModel is:
#HiltViewModel
class AViewModel #Inject constructor(
private val itemRepo: ItemRepo,
private val disposable: CompositeDisposable
) : ViewModel() {
private var _items: MutableLiveData<List<Item>> = MutableLiveData()
val items: LiveData<List<Item>>
get() = _items
override fun onCleared() {
super.onCleared()
disposable.clear()
}
fun getItems() {
itemRepo.getAll()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({
_items.postValue(it)
}, { t ->
})
.addTo(disposable)
}
}
XML Layout is:
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rv_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:bind_items="#{vm.items}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
Binding Adapter is:
#BindingAdapter("bind_items")
fun setItems(view: RecyclerView, list: List<Item>?) {
if (!list.isNullOrEmpty()) {
(view.adapter as? ItemsAdapter)?.update(list)
}
}
ItemsAdapter is:
class ItemsAdapter : RecyclerView.Adapter<ItemsAdapter.ItemHolder>() {
private val items: MutableList<Item> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemHolder {
return ItemHolder (ItemItemBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: ItemHolder, position: Int) {
holder.bind(items[position])
}
fun update(newItems: List<Item>) {
items.clear()
items.addAll(newItems)
notifyItemRangeInserted(0, newItems.size)
}
class ItemHolder(
private val binding: ItemItemBinding
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: Item) {
with(binding) {
tvDate.text = item.date
}
}
}
}
I think this issue is related to the "Jetpack Navigation".
But I don't know exactly what should I do...
From the android documentation of RecyclerView.Adapter of notifyItemRangeInserted:
Notify any registered observers that the currently reflected itemCount items >starting at positionStart have been newly inserted. The items previously located at >positionStart and beyond can now be found starting at position positionStart + >itemCount.
This means that after you call that function the adapter expects the list to be long oldList.size + newList.size, but finds only the new elements and so goes out of bounds.
The simplest solution is to use notifyDataSetChanged instead, though you may want to use other solutions for performance.
You're not telling the adapter when you remove the items, you'd have to do something like this:
fun update(newItems: List<Item>) {
// remove the old items
val originalSize = items.size
items.clear()
notifyItemRangeRemoved(0, originalSize)
// add the new items
items.addAll(newItems)
notifyItemRangeInserted(0, newItems.size)
}
But this is going to look a bit weird, I doubt that's what you want (it will animate the old items out, and then animate the new items in). Like the previous answer suggested, the easiest way is to use notifyDataSetChanged instead (the list will just change visually immediately with no animations)
fun update(newItems: List<Item>) {
items.clear()
items.addAll(newItems)
notifyDataSetChanged()
}
If you want nice change animations, you should probably look into DiffUtil (that will calculate the difference between your two lists - but it is an expensive call for large lists). You can get away with running on the UI thread for small lists, but you shouldn't really (using DiffUtil off the UI thread is quite a bit more complicated if you want to guarantee robust code). You could get the DiffResult like this:
fun createDiffResult(oldList: List<Thing>, newList: List<Thing>): DiffUtil.DiffResult {
return DiffUtil.calculateDiff(object : DiffUtil.Callback() {
override fun getOldListSize(): Int {
return oldList.size
}
override fun getNewListSize(): Int {
return newList.size
}
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
// are the items conceptually the same item?
// (you might compare a unique id here)
return true
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
// do the items _look_ the same when rendered in your recycler view?
// (you only need to compare the values for the things that are visible in your implementation of the item view)
return true
}
})
}
then use it like this:
diffResult.dispatchUpdatesTo(adapter);
Related
I have a problem with recycler view. In my previous app, when i get the list for recycler view adapter from database and observe it in my fragment, i used the notifyDataSetChanged() and when i tried to delete a item , view updated successfully. But in this app this does not work and i don't understand why. When i click the delete button the item deleted in database successfully but i can't see it immediatly. When i go to any other fragment and back to this Favourites fragment i see the items deleted.
I tried all the options in stackoverflow but still i can't fix it.
My Adapter:
class FavouritesAdapter(owner: ViewModelStoreOwner, val favouritesList : ArrayList<Vocabulary>) : RecyclerView.Adapter<FavouritesAdapter.FavouritesViewHolder>() {
val viewModel = ViewModelProvider(owner).get(FavouritesViewModel::class.java)
class FavouritesViewHolder(val binding: FavouritesItemRowBinding) : RecyclerView.ViewHolder(binding.root)
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FavouritesViewHolder {
return FavouritesViewHolder(FavouritesItemRowBinding.inflate(LayoutInflater.from(parent.context), parent, false))
}
override fun onBindViewHolder(holder: FavouritesViewHolder, position: Int) {
holder.binding.englishWordTV.text = favouritesList[position].word
holder.binding.turkishWordTV.text = favouritesList[position].translation
holder.binding.deleteButtonRV.setOnClickListener {
viewModel.deleteVocabulary(favouritesList[position])
notifyDataSetChanged()
}
}
override fun getItemCount(): Int {
return favouritesList.size
}
fun updateList(myList : List<Vocabulary>) {
favouritesList.clear()
favouritesList.addAll(myList)
notifyDataSetChanged()
}
}
My problem is in delete button in my recycler row;
holder.binding.deleteButtonRV.setOnClickListener {
viewModel.deleteVocabulary(favouritesList[position])
notifyDataSetChanged()
}
And here is my fragment ;
class FavouritesFragment : Fragment() {
private var _binding: FragmentFavouritesBinding? = null
private val binding get() = _binding!!
private lateinit var favouritesAdapter : FavouritesAdapter
private lateinit var viewModel : FavouritesViewModel
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
_binding = FragmentFavouritesBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
viewModel = ViewModelProvider(this).get(FavouritesViewModel::class.java)
favouritesAdapter = FavouritesAdapter(this, arrayListOf())
viewModel.getAllVocabulariesFromDB()
prepareRecyclerView()
observeFavouritesLiveData()
}
fun prepareRecyclerView(){
binding.favouritesRecyclerView.apply {
layoutManager = LinearLayoutManager(context)
adapter = favouritesAdapter
}
}
fun observeFavouritesLiveData(){
viewModel.favouritesListLiveData.observe(viewLifecycleOwner, Observer {
it?.let {
favouritesAdapter.updateList(it)
}
})
}
override fun onDestroyView() {
super.onDestroyView()
_binding = null
}
}
Try with notifyItemRemoved(position) instead of notifyDataSetChanged().
It all looks fine to me - you observe the favourites LiveData, that passes the new data to an update function in your Adapter, and that modifies the internal data set and calls notifyDataSetChanged() (which works for any kind of update).
So, are you sure your ViewModel is updating favouritesListLiveData properly when you call deleteVocabulary? Check if your observer is actually firing with a new value when you hit delete, and check if its contents are what you expect (the previous list minus the thing you want removed)
You could check it with some logging, but setting some breakpoints and debugging the app might be more helpful if you're not sure where it's going wrong
(also your button doesn't need to call notifyDataSetChanged() - that only needs to happen when the data is updated, which happens through the update function, in there is the right place for it!)
i tried to use the path "button click -> UI sends delete event to VM -> VM updates data -> observer sees new data -> observer calls update with new data" as #cactuctictacs mentioned. I added this lines to my adapter,
lateinit var onDeleteItemClick : ((Vocabulary) -> Unit)
holder.binding.deleteButtonRV.setOnClickListener {
onDeleteItemClick.invoke(favouritesList[position])
notifyItemRemoved(position)
}
and added to my fragment,
fun deleteButtonClicked(){
favouritesAdapter.onDeleteItemClick = {
viewModel.deleteVocabulary(it)
viewModel.getAllVocabulariesFromDB()
observeFavouritesLiveData()
favouritesAdapter.notifyDataSetChanged()
}
}
I hope this is the proper way to do this.
Scenario
Recently moved from synthetics to view binding and I'm still struggling (haven't done any coding in months, so I'm rusty as it gets).
MainActivity has two recyclerviews, one displaying totals, one displaying a list of transactions. Each transaction has a "OnLongClickListener" attached to it (attached in the adapter class). This listener calls for a MaterialAlertDialog with two options: edit or delete.
There's also a separate fragment for adding transactions.
Requirement
Upon addition, deletion or modification of these transactions, both recycleradapters need to refresh the data in order to reflect the correct information on screen.
Problem
I'm not able to get the adapters to receive the "notifydatasetchanged" as I am not sure how to get the adapters' reference.
Code being used
MainActivity
private val db = FirebaseFirestore.getInstance()
private val dbSettings = firestoreSettings { isPersistenceEnabled = true }
CoroutineScope(Dispatchers.Default).launch {
val adapterRV1 = Adapter_RV1(FSDB.Get_calculations, FSDB.Get_transactions(db))
val adapterRV2 = Adapter_RV2(FSDB.Get_transactions(db))
runOnUiThread {
binding.rvCalculations.adapter = adapterRV1
binding.rvTransactions.adapter = adapterRV2
}
}
Adapter_RV1
class Adapter_RV1(val calculations_list: List<calculation>,val transactions_list: ArrayList<Transactions>) : RecyclerView.Adapter<Adapter_RV1.CalculationViewHolder>() {
private lateinit var binding: RvCalculationsLayoutBinding
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Adapter_RV1.CalculationViewHolder {
val binding = RvCalculationsLayoutBinding
.inflate(LayoutInflater.from(parent.context), parent, false)
return CalculationViewHolder(binding)
}
override fun onBindViewHolder(holder: KPIViewHolder, position: Int) {
with(holder) {
with(calculations_list[position]) {
...
}
}
}
override fun getItemCount() = calculations_list.size
inner class CalculationViewHolder(val binding: RvCalculationsLayoutBinding) :
RecyclerView.ViewHolder(binding.root)
}
Adapter_RV2
class Adapter_RV2(var transactions_list: List<Transaction>):
RecyclerView.Adapter<Adapter_RV2.TransactionsViewHolder>() {
private lateinit var binding: RvTransactionsLayoutBinding
inner class TransactionsViewHolder(val binding: RvTransactionsLayoutBinding) : RecyclerView.ViewHolder(binding.root){
init {
binding.root.setOnLongClickListener {
val position = absoluteAdapterPosition
val item = transactions_list[position]
CoroutineScope(Dispatchers.Main).launch {
CreateDialog(item,position,binding)
}
true
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Adapter_RV2.TransactionsViewHolder {
val binding = RvTransactionsLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return TransactionsViewHolder(binding)
}
override fun onBindViewHolder(holder: TransactionsViewHolder, position: Int) {
...
}
override fun getItemCount() = transactions_list.size
}
CreateDialog
class CreateDialog (transaction: Transaccion, position: Int, binding: RvVistaTransaccionesBinding){
private suspend fun DeleteTransaction(position: Int) {
...
traRef.update("transactions", FieldValue.arrayRemove(transaction)).await()
}
private val puBinding : PopupLayoutBinding = PopupLayoutBinding.inflate(LayoutInflater.from(binding.root.context))
init {
with(puBinding){
...
CoroutineScope(Dispatchers.Main).launch {
supervisorScope {
val task = async {
DeleteTransaction(position)
}
try{
task.await()
/// This is where, I guess, adapters should be notified of dataset changes
popup.dismiss()
}
catch (e: Throwable){
crashy.recordException(e)
Log.d("Transaction",e.message!!)
popup.dismiss()
}
}
}
}
}
popup.setView(puBinding.root)
popup.show()
}
What I've tested so far
I honestly have no clue how to proceed. I've tried a few things but none work and considering I'm super green in Dev in general, View Binding is a bit more confusing than usual.
I have replaced the line below
notifyDataSetChanged();
with
binding.recycler.getAdapter().notifyDataSetChanged();
in my own code.
Here, "recyler" is my RecyclerView, and "binding" is ActivityListBinding.
I didnt read your code, maybe my tip will help you, it worked for me.
I am building an app in which user can favourite some items.I am showing this in a recyclerview inside a fragment. User can also delete the item from favourite item list by clicking on delete button. The problem I am facing is that the if user delete and item at the end of the list the recylerview reloads and show from the start. I am using MVVM and using Livedata. Here is my code
FragmentFavorite.kt
class FavoriteFragment :Fragment() {
lateinit var favoriteViewModel: FavoriteViewModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
override fun onAttach(context: Context) {
super.onAttach(context)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val v=inflater.inflate(R.layout.fragment_favorite,container,false)
favoriteViewModel=ViewModelProvider(requireActivity()).get(FavoriteViewModel::class.java)
val recylerview=v.findViewById<RecyclerView>(R.id.recylerviewFav)
recylerview.layoutManager=LinearLayoutManager(requireContext(),RecyclerView.VERTICAL,false)
favoriteViewModel.getAll(requireContext()).observe(requireActivity(), Observer {
val adapter=FavoriteAdapter(it,requireContext(),object:FavoriteDeleteListener{
override fun OnFavDelete(id: Int) {
favoriteViewModel.deleteFav(requireContext(),id)
}
})
recylerview.adapter=adapter
})
return v
}
}
RecylerviewAdapter.kt
class FavoriteAdapter(val list:List<Favorite>,context: Context,val listener:FavoriteDeleteListener) :RecyclerView.Adapter<FavoriteAdapter.MyViewholder> (){
val listofFav=list.reversed()
class MyViewholder(itemview: View):RecyclerView.ViewHolder(itemview) {
val textview_src=itemview.findViewById<TextView>(R.id.textview_src)
val textView_tar=itemView.findViewById<TextView>(R.id.textview_tar)
val delte_fav=itemview.findViewById<ImageView>(R.id.delete_fav)
}
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): FavoriteAdapter.MyViewholder {
val v=LayoutInflater.from(parent.context).inflate(R.layout.fav_list,parent,false)
return MyViewholder(v)
}
override fun onBindViewHolder(holder: FavoriteAdapter.MyViewholder, position: Int) {
holder.textView_tar.text=listofFav[position].translated_text
holder.textview_src.text=listofFav[position].text_to_translate
holder.delte_fav.setOnClickListener {
listofFav[position].fid?.let { it1 -> delete(it1,listener)
notifyItemRemoved(position)
}
}
}
override fun getItemCount(): Int {
return listofFav.size
}
fun delete(id:Int,listene: FavoriteDeleteListener) {
listene.OnFavDelete(id)
}
}
I suspect the problem is here:
favoriteViewModel.getAll(requireContext()).observe(requireActivity(), Observer {
val adapter=...
recylerview.adapter=adapter
})
If deleting an item from your dataset causes the observer to emit a new dataset, this will wind up re-assigning your RecyclerView's adapter, which will completely reset the view position.
You could use the ListAdapter component in order to compute a diff between the lists instead of completely overwriting the adapter everyt ime.
I've been trying to delete an item from my list so that it updates without the removed item, but the list seems to redraw itself and keeps displaying all the original items as before. For a short bit of time it's possible to see the item as if it's being removed, however, due to this redrawing everything gets back to what it was before the removal.
I've tried several combinations of the following methods but none of them seem to work in this case.
adapter.notifyItemRangeChanged(position, adapter.itemCount)
adapter.notifyItemRemoved(position)
adapter.notifyItemChanged(position)
adapter.notifyDataSetChanged()
These are my files. Please notice I'm using the Groupie library as a replacement for the default RecyclerView.
class RecyclerProductItem(
private val activity: MainActivity,
private val product: Product,
private val onItemClickListener: OnItemClickListener?
) : Item<GroupieViewHolder>() {
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
viewHolder.apply {
with(viewHolder.itemView) {
ivTrash.setOnClickListener {
if (onItemClickListener != null) {
Toast.makeText(context, "delete method to be added here", Toast.LENGTH_SHORT).show()
onItemClickListener.onClick(viewHolder.adapterPosition)
// deleteProduct(product.id)
}
}
}
}
}
interface OnItemClickListener {
fun onClick(position: Int) //pass your object types.
}
override fun getLayout() = R.layout.recyclerview_item_row
}
And here my fragment:
class ProductsListFragment : Fragment() {
private lateinit var adapter: GroupAdapter<GroupieViewHolder>
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return inflater.inflate(R.layout.fragment_products_list, container, false)
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val linearLayoutManager = LinearLayoutManager(activity)
recyclerView.layoutManager = linearLayoutManager
adapter = GroupAdapter()
recyclerView.adapter = adapter
loadProducts()
}
/**
* API calls
*/
private fun loadProducts() {
GetProductsAPI.postData(object : GetProductsAPI.ThisCallback,
RecyclerProductItem.OnItemClickListener {
override fun onSuccess(productList: List<JsonObject>) {
Log.i(LOG_TAG, "successful network call")
for (jo in productList) {
val gson = GsonBuilder().setPrettyPrinting().create()
val product: Product =
gson.fromJson(jo, Product::class.java)
adapter.add(
RecyclerProductItem(
activity as MainActivity,
Product(
product.id,
product.title,
product.description,
product.price
), this
)
)
}
}
override fun onClick(position: Int) {
Log.i(LOG_TAG, position.toString())
adapter.notifyItemRangeChanged(position,
adapter.itemCount)
adapter.notifyItemRemoved(position)
}
})
}
}
Many thanks.
Simple sample
class GroupAdapter(private val items: MutableList<Any>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
fun removeByPosition(position: Int) {
items.removeAt(position)
notifyItemRemoved(position)
}
I've done this successfully with a normal ViewAdapter but I can't seem to get it working with a ListAdapter.
Here is my Fragment that does most of the work:
class CrimeListFragment: Fragment() {
//Required interface for hosting activities
interface Callbacks {
fun onCrimeSelected(crimeId: UUID)
}
private var callbacks: Callbacks? = null
private lateinit var crimeRecyclerView: RecyclerView
private val crimeListViewModel: CrimeListViewModel by lazy {
ViewModelProviders.of(this).get(CrimeListViewModel::class.java)
}
override fun onAttach(context: Context) {
super.onAttach(context)
callbacks = context as Callbacks?
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
val view = inflater.inflate(R.layout.fragment_crime_list, container, false)
crimeRecyclerView =
view.findViewById(R.id.crime_recycler_view) as RecyclerView
crimeRecyclerView.layoutManager = LinearLayoutManager(context)
crimeRecyclerView.adapter = CrimeListAdapter(emptyList())
return view
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
crimeListViewModel.crimeListLiveData.observe(
viewLifecycleOwner,
Observer { crimes ->
crimes?.let {
Log.i(TAG, "Got crimes ${crimes.size}")
updateUI(crimes)
}
}
)
}
override fun onDetach() {
super.onDetach()
callbacks = null
}
private fun updateUI(crimes: List<Crime>) {
crimeRecyclerView.adapter = CrimeListAdapter(crimes)
}
companion object {
fun newInstance(): CrimeListFragment {
return CrimeListFragment()
}
}
private inner class CrimeHolder(view: View)
: RecyclerView.ViewHolder(view), View.OnClickListener {
private lateinit var crime: Crime
private val titleTextView = itemView.findViewById<TextView>(R.id.crime_title)
private val dateTextView = itemView.findViewById<TextView>(R.id.crime_date)
private val solvedImageView = itemView.findViewById<ImageView>(R.id.crime_solved)
init {
itemView.setOnClickListener(this)
}
fun bind(crime: Crime) {
this.crime = crime
titleTextView.text = crime.title
dateTextView.text = crime.date.toString()
solvedImageView.visibility = if(crime.isSolved) {
View.VISIBLE
} else {
View.GONE
}
}
override fun onClick(v: View) {
callbacks?.onCrimeSelected(crime.id)
}
}
private inner class CrimeListAdapter(var crimes: List<Crime>)
: ListAdapter<Crime, CrimeHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
val view =
layoutInflater.inflate(R.layout.list_item_crime, parent, false)
return CrimeHolder(view)
}
override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
holder.bind(crimes[position])
}
}
private inner class DiffCallback: DiffUtil.ItemCallback<Crime>() {
override fun areItemsTheSame(oldItem: Crime, newItem: Crime): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Crime, newItem: Crime): Boolean {
return oldItem == newItem
}
}
}
And here is the fragment's viewmodel:
class CrimeListViewModel: ViewModel() {
private val crimeRepository = CrimeRepository.get()
val crimeListLiveData = crimeRepository.getCrimes() //returns LiveData<List<Crime>>
}
Android documentation has this regarding ListAdapter:
While using a LiveData is an easy way to provide data to the adapter, it isn't required - you can use submitList(List) when new lists are available.
I'm supposed to submit a new list instead of creating a new ListAdapter object each time I update the UI. But crimeRecyclerView.adapter has no .submitList() function. So how do I pass on the new list?
LiveData is still new to me so I'm not quite clear on this. I already observe a LiveData stored in my viewmodel. So what do I observe this time? Or do I just add code to my existing Observer?
Finally when I run the code in this state, phone shows an empty RecyclerView. Only UpdateUI() gets called, none of CrimeListAdapter's functions get called. I'm not sure if this is a real problem or just the consequence of the above.
1.I'm supposed to submit a new list instead of creating a new ListAdapter object each time I update the UI. But
crimeRecyclerView.adapter has no .submitList() function. So how do I
pass on the new list?
crimeRecyclerView.adapter return RecyclerView.Adapter type
submitList() is a method of ListAdapter, a sub-class of RecyclerView.Adapter
You need to cast from super to sub class before calling that method, like this.
(crimeRecyclerView.adapter as CrimeListAdapter).submitList(crimes)
2.LiveData is still new to me so I'm not quite clear on this. I already observe a LiveData stored in my viewmodel. So what do I
observe this time? Or do I just add code to my existing Observer?
Your code for this part is good, no need to do more.
3.Finally when I run the code in this state, phone shows an empty RecyclerView. Only UpdateUI() gets called, none of CrimeListAdapter's
functions get called. I'm not sure if this is a real problem or just
the consequence of the above.
The best part of using ListAdapter is you do not need to provide a list of data (crimes in your case) to constructor.
Back to your code, you need to change 3 things.
// crimeRecyclerView.adapter = CrimeListAdapter(emptyList())
crimeRecyclerView.adapter = CrimeListAdapter()
and
// crimeRecyclerView.adapter = CrimeListAdapter(crimes)
(crimeRecyclerView.adapter as CrimeListAdapter).submitList(crimes)
and
//private inner class CrimeListAdapter(var crimes: List<Crime>) :
// ListAdapter<Crime, CrimeHolder>(DiffCallback()) {
//
// override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
// val view = layoutInflater.inflate(R.layout.list_item_crime, parent, false)
// return CrimeHolder(view)
// }
//
// override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
// holder.bind(crimes[position])
// }
//}
private inner class CrimeListAdapter : ListAdapter<Crime, CrimeHolder>(DiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CrimeHolder {
val view = layoutInflater.inflate(R.layout.list_item_crime, parent, false)
return CrimeHolder(view)
}
override fun onBindViewHolder(holder: CrimeHolder, position: Int) {
holder.bind(getItem(position))
}
}