Data binding with RecyclerView not rendering correclty - android

I am trying to use data binding with RecyclerView. I set up a simple application that shows the numbers 1 to 50, one on each row, in a RecyclerView. And I want to use data binding to each row.
Here is the code of my adapter (Note that I called dataBinding.executePendingBindings()):
class SimpleAdapter
: ListAdapter<Int, SimpleAdapter.SimpleAdapterViewHolder>(SimpleAdapterDiffUtil()) {
inner class SimpleAdapterViewHolder(
private val dataBinding: SimpleAdapterViewHolderBinding
) : RecyclerView.ViewHolder(dataBinding.root) {
fun bind(str: String) {
Log.e("MYTAG", "setting TextView: $str")
dataBinding.txtView.text = str
dataBinding.executePendingBindings()
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SimpleAdapterViewHolder {
val binding = SimpleAdapterViewHolderBinding.inflate(
LayoutInflater.from(parent.context), parent, false)
return SimpleAdapterViewHolder(binding)
}
override fun onBindViewHolder(holder: SimpleAdapterViewHolder, position: Int) {
val stringValue = getItem(position)
holder.bind(stringValue.toString())
}
}
private class SimpleAdapterDiffUtil: DiffUtil.ItemCallback<Int>() {
override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
return oldItem == newItem
}
}
And this is the view XML
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="text"
type="String" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="#+id/txt_view"
android:text="#{text}"
android:layout_width="match_parent"
android:layout_height="36dp" />
<View
android:layout_width="match_parent"
android:layout_height="1dp"
android:background="#color/black"
app:layout_constraintTop_toBottomOf="#id/txt_view"
/>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
And here is what it does:
As you can see, in the initial screen, none of the rows were not properly rendered. When I scrolled down, I found that it started rendering from row 23. Then if I scrolled back up, I can finally see the prior rows being filled.
If I change the code to use plain old views, rather than data bindings, it works as expected. Code here:
// This works fine!
class SimpleAdapter
: ListAdapter<Int, SimpleAdapter.SimpleAdapterViewHolder>(SimpleAdapterDiffUtil()) {
inner class SimpleAdapterViewHolder(
private val rootView: View
) : RecyclerView.ViewHolder(rootView) {
fun bind(str: String) {
Log.e("MYTAG", "setting TextView: $str")
rootView.findViewById<TextView>(R.id.txt_view).text = str
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SimpleAdapterViewHolder {
val rootView = LayoutInflater.from(parent.context)
.inflate(R.layout.simple_adapter_view_holder, parent, false)
return SimpleAdapterViewHolder(rootView)
}
override fun onBindViewHolder(holder: SimpleAdapterViewHolder, position: Int) {
val stringValue = getItem(position)
holder.bind(stringValue.toString())
}
}
private class SimpleAdapterDiffUtil: DiffUtil.ItemCallback<Int>() {
override fun areItemsTheSame(oldItem: Int, newItem: Int): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Int, newItem: Int): Boolean {
return oldItem == newItem
}
}

As I see in your xml, you're setting android: text="#{text}"
In other words, textView value is being set from binding.text
You set dataBinding.txtView.text = str and it will update the text. After that using executePendingDataBinding it's value will be reset to that dataBinding.text (aka android: text="#{text}") which is null or empty. So you shoud set
dataBinding.text = str
Instead of
dataBinding.txtView.text = str

Related

RecyclerView items not displaying

The title says, I'm trying to load items into a recyclerview but the items doesn't displaying.
I'm getting the items from the api, using retrofit and mutablelivedata, I'm getting the items right, and the adapter gets the items (there are 3 items, and the adapter gets the 3 items), but the items doesn't display on the UI.
Here is my code:
Adapter.java
class EstablecimientosAdapter : ListAdapter<EstablecimientoModel, EstablecimientosViewHolder>(
DIFF_CALLBACK
) {
companion object {
val DIFF_CALLBACK: DiffUtil.ItemCallback<EstablecimientoModel> =
object : DiffUtil.ItemCallback<EstablecimientoModel>() {
override fun areItemsTheSame(
oldItem: EstablecimientoModel,
newItem: EstablecimientoModel
): Boolean {
return oldItem.hash == newItem.hash
}
override fun areContentsTheSame(
oldItem: EstablecimientoModel,
newItem: EstablecimientoModel
): Boolean {
return oldItem.nombre == newItem.nombre
}
}
}
private val mEstablecimientos: MutableList<EstablecimientoModel> = mutableListOf()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): EstablecimientosViewHolder {
val binding = RowEstablecimientoBinding.inflate(
LayoutInflater.from(parent.context), parent, false
)
return EstablecimientosViewHolder(binding)
}
override fun onBindViewHolder(holder: EstablecimientosViewHolder, position: Int) {
val establecimiento = getItem(position)
holder.bindItem(establecimiento!!)
}
override fun getItemCount(): Int {
return mEstablecimientos.size
}
fun addMoreEstablecimientos(newEstablecimientos: List<EstablecimientoModel>) {
mEstablecimientos.addAll(newEstablecimientos)
submitList(mEstablecimientos)
}
class EstablecimientosViewHolder(val binding: RowEstablecimientoBinding) :
RecyclerView.ViewHolder(
binding.root
) {
fun bindItem(establecimiento: EstablecimientoModel) {
binding.setVariable(BR.establecimiento, establecimiento)
}
}
}
recycler_view.xml
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/rvEstablecimientos"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
tools:listitem="#layout/row_establecimiento" />
Fragment.java
val cvGoBackEstablecimientos = binding!!.cvGoBackEstablecimientos
val rvEstablecimientos = binding!!.rvEstablecimientos
val adapter = EstablecimientosAdapter()
rvEstablecimientos.setHasFixedSize(true)
rvEstablecimientos.adapter = adapter
cvGoBackEstablecimientos.setOnClickListener { requireActivity().onBackPressed() }
viewModel?.getNearFreeEstablecimientos(2.0, 2.0)
?.observe(viewLifecycleOwner) { t -> adapter.addMoreEstablecimientos(t!!) }
It seems that I had some problems with the layout, I removed a CoordinatorLayout and it works now.

Flow is not working with Binding Adapters

For a particular Recycler View Item, If I select the Checkbox (tick it) then I need the text of its corresponding TextView to formatted as Strikethrough.
I am using Binding Adapters, Flow and Live Data.
But after selecting the checkbox, its corresponding TextView is not getting formatted.
But If I navigate to some other fragment and come back to here(FruitFragmnet) then the TextView data is formatted. (i.e. the database gets updated correctly on ticking checkbox but the live data emission is delayed to UI)
Possible Root Cause: My update to Room Database is happening immeialtey, but from database the LiveData is not flown to UI immediately.
I did lot of trial and errors, read multiple similar questions but I was unable to find the missing link and solution to this issue.
Please advice. Following is the code:
BindingAdapter
#BindingAdapter("markAsCompleted")
fun markAsCompleted(textView: TextView, completed: Boolean) {
if (completed) {
textView.paintFlags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG
} else {
textView.paintFlags = textView.paintFlags or Paint.STRIKE_THRU_TEXT_FLAG.inv()
}
}
#BindingAdapter("setItems")
fun setItems(view: RecyclerView, items: List<Fruit>?) {
items?.let {
(view.adapter as SettingAdapter).submitList(items)
}
}
Fruit Fragment with Recycler View
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
<variable
name="settingViewModel"
type="com.example.ui.SettingViewModel" />
</data>
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/fruits_list"
setItems="#{settingViewModel.allList}" // This is Binding Adapter
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager" />
</layout>
Above Fruit's Fragment Item View
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.widget.CompoundButton" />
<variable
name="fruit"
type="com.example.data.Fruit" />
<variable
name="settingViewModel"
type="com.example.ui.SettingViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
...
<CheckBox
android:id="#+id/fruit_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:checked="#{fruit.completed}"
android:onClick="#{(view) -> settingViewModel.completeFruit(fruit,((CompoundButton)view).isChecked())}"
/>
<TextView
android:id="#+id/fruit_name"
markAsCompleted="#{fruit.completed}" // This is Binding Adapter
android:layout_width="0dp"
android:layout_height="wrap_content"
android:text="#{fruit.fruit}" />
....
Fruit Fragment
class FruitFragment : Fragment() {
private lateinit var binding: FragmentFruitBinding
private lateinit var fruitAdapter: FruitAdapter
private val viewModel: SettingViewModel by activityViewModels()
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
binding = FragmentFruitBinding.inflate(layoutInflater, container, false).apply {
lifecycleOwner = viewLifecycleOwner
settingViewModel = viewModel
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
fruitAdapter = FruitAdapter(viewModel)
binding.fruitslist.apply {
adapter = fruitAdapter
}
}
}
SettingViewModel
class SettingViewModel(application: Application) : AndroidViewModel(application) {
private val app = getApplication<Application>()
private val dao = Database.getDatabase(app.applicationContext).dao
val allList: LiveData<List<Fruit>> = dao.getFruits().asLiveData().distinctUntilChanged()
fun completeFruit(fruit: Fruit, completed: Boolean) {
viewModelScope.launch {
if (completed) {
dao.updateCompleted(fruit.id, completed)
} else {
dao.updateCompleted(fruit.id, completed)
}
}
}
....
}
DAO Class
#Dao
interface DatabaseDao {
#Query("SELECT * FROM fruit_table")
fun getFruits(): Flow<List<Fruit>>
#Query("UPDATE fruit_table SET completed = :completed WHERE id = :id")
suspend fun updateCompleted(id: Int, completed: Boolean)
}
Recycler View Adapter
class FruitAdapter(private val viewModel: SettingViewModel) : ListAdapter<Fruit, ViewHolder>(FruitDiffCallback()) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
return ViewHolder.from(parent)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val item = getItem(position)
holder.bind(item, viewModel)
}
class ViewHolder private constructor(val binding: ContainerFruitBinding) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: Fruit, viewModel: SettingViewModel) {
binding.apply {
settingViewModel = viewModel
fruit = item
executePendingBindings()
}
}
companion object {
fun from(parent: ViewGroup): ViewHolder {
val layoutInflater = LayoutInflater.from(parent.context)
val binding = ContainerFruitBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
}
}
}
class FruitDiffCallback : DiffUtil.ItemCallback<Fruit>() {
override fun areItemsTheSame(oldItem: Fruit, newItem: Fruit): Boolean {
return oldItem.id == newItem.id
}
override fun areContentsTheSame(oldItem: Fruit, newItem: Fruit): Boolean {
return oldItem.fruit == newItem.fruit
}
}
Data Class
#Entity(tableName = "fruit_table")
data class Fruit(
#PrimaryKey(autoGenerate = true)
var id: Int = 0,
var fruit: String,
var completed: Boolean = false
)
I guess you need to change the second parameter of setItems function to LiveData in BindingAdapter:
#BindingAdapter("setItems")
fun setItems(view: RecyclerView, data: LiveData<List<Fruit>>) {
data.value?.let {
(view.adapter as SettingAdapter).submitList(it)
}
}

Android Kotlin RecyclerView in Fragment is not working

I'm very new in Kotlin and Android programming. I tried to create a Fragment which populates a Recycler view, but somehow I get the following error: E/RecyclerView: No adapter attached; skipping layout
I don't really understand why I get this, since I binded everything. If somebody can explain what I'm doing wrong I would really appreciate it. My code is the following:
My class:
data class Movie(val id:Int, val posterPath:String, val vote:Double, val language:String,val releaseDate:String, val title:String) {}
My fragment:
class MovelistScreen : Fragment(R.layout.fragment_movelist_screen) {
#ExperimentalStdlibApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
// View created, can be accessed
// val args = arguments ?: throw IllegalArgumentException("Use new instance method")
// val argValue = args.getString(ARG_NAME)
val binding = FragmentMovelistScreenBinding.inflate(layoutInflater)
val lst : List<Movie> = buildList {
add(Movie(id=1,posterPath="/asdasd",vote=7.3,language="Eng",releaseDate="2017",title="Test1"))
add(Movie(id=2,posterPath="/asdasd",vote=6.3,language="Hun",releaseDate="2013",title="Test2"))
}
val listAdapter = MovieListAdapter()
binding.itemList.adapter=listAdapter
listAdapter.submitList(lst)
}
companion object {
private const val ARG_NAME = "test_argument"
fun newInstance(testArg: String): DetailpageFragment = DetailpageFragment().apply {
arguments = Bundle().apply { putString(ARG_NAME, testArg) }
}
}
}
My adapter
class MovieListAdapter : ListAdapter<Movie, MovieViewHolder>(diffUtil) {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
val layoutInflater: LayoutInflater = LayoutInflater.from(parent.context)
return MovieViewHolder(MovieItemBinding.inflate(layoutInflater,parent,false))
}
override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
val item:Movie=getItem(position)
val binding:MovieItemBinding = holder.binding
binding.movieTitle.text=item.title
binding.releaseYear.text=item.releaseDate
binding.language.text=item.language
binding.ratingtext.text=item.vote.toString()
binding.movieImage.load("https://i.postimg.cc/VLbN4hkz/the-hobbit-the-desolation-of-smaug.jpg")
}
}
private val diffUtil : DiffUtil.ItemCallback<Movie> = object : DiffUtil.ItemCallback<Movie>() {
override fun areItemsTheSame(oldItem: Movie, newItem: Movie): Boolean = oldItem.id == newItem.id
override fun areContentsTheSame(oldItem: Movie, newItem: Movie): Boolean = oldItem == newItem
}
class MovieViewHolder(val binding: MovieItemBinding):RecyclerView.ViewHolder(binding.root)
fragment_movelist_screen.xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_height="match_parent"
android:layout_width="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto">
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/item_list"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:orientation="vertical"
android:padding="16dp">
</androidx.recyclerview.widget.RecyclerView>
</androidx.constraintlayout.widget.ConstraintLayout>
mainactivity.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent">
<fragment
android:id="#+id/fragmentmovielist"
android:name="com.example.ubbassignment2.MovelistScreen"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:layout="#layout/fragment_detailpage" />
</FrameLayout>
You're creating a new view and binding in onViewCreated and setting adapter to it (that view will be garbage collected) you should inflate your view in onCreateView and set the adapter to that recycler view instead of your temporary one.
Fragment:
override fun onCreateView(...): View {
val binding = FragmentMovelistScreenBinding.inflate(layoutInflater)
val lst : List<Movie> = buildList {
add(Movie(id=1,posterPath="/asdasd",vote=7.3,language="Eng",releaseDate="2017",title="Test1"))
add(Movie(id=2,posterPath="/asdasd",vote=6.3,language="Hun",releaseDate="2013",title="Test2"))
}
val listAdapter = MovieListAdapter()
binding.itemList.adapter=listAdapter
listAdapter.submitList(lst)
return binding.root
}

RecyclerView doesn't display last item when multiple ViewHolders are used

I'm using androidx.recyclerview.widget.RecyclerView to display a list of items, separated by an other item as a "header" with some aggregated values.
When i put only one item in my list without adding the header, everything is ok and the item is displayed correctly. As soon as i add the header item, only the header is displayed and the one single item isn't shown.
When i add two items and the header, the header and one item are displayed. I don't know why the last item of my list is missing altough it exists in the adapters datasource.
My ListAdapter inherits from RecyclerView.Adapter<RecyclerView.ViewHolder> and uses two ViewHolders detected by a viewType property of my list items.
When loading the data, the onBindViewHolder method isn't called for the last item in my list, even tough the item is in the visible section of my screen.
Does anybody has a hint, why this happens?
class ListAdapter(val onClick: (position: Long) -> Unit,
val onLongClick: (Long) -> Unit,
val onShareClick: (id: Long?) -> Unit) : RecyclerView.Adapter<RecyclerView.ViewHolder>(),
BindableAdapter<List<ListAdapterItem<*>>> {
var items: List<ListAdapterItem<*>> = emptyList()
private var actionMode: ActionMode? = null
var tracker: SelectionTracker<Long>? = null
init {
setHasStableIds(true)
}
override fun setData(data: List<ListAdapterItem<*>>) {
this.items = data // all items are set correctly here!!
notifyDataSetChanged()
}
override fun getItemViewType(position: Int): Int {
return if (items.isEmpty()) EMPTY else items[position].viewType
}
override fun getItemCount(): Int {
return if (items.isEmpty()) 1 else items.filter { it.viewType == ITEM }.size
}
override fun getItemId(position: Int): Long = position.toLong()
fun getItem(position: Long): ListViewModel.ListItem = item[position.toInt()].value as ListViewModel.ListItem
fun setActionMode(actionMode: ActionMode?) {
this.actionMode = actionMode
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
return when (viewType) {
EMPTY -> EmptyViewHolder(parent)
HEADER -> HistoryGroupHeaderViewHolder(parent)
else -> HistoryViewHolder(parent)
}
}
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
if (holder is HistoryViewHolder) {
val item = items[position].value as ListViewModel.ListItem
tracker?.let {
holder.bind(item, it.isSelected(position.toLong()))
}
holder.itemView.setOnClickListener {
onClick(position.toLong())
}
holder.itemView.findViewById<AppCompatImageView>(R.id.history_item_share)?.setOnClickListener {
onShareClick(item.id)
}
}
else if (holder is HistoryGroupHeaderViewHolder) {
val header = items[position].value as ListViewModel.ListSectionHeader
holder.bind(header)
}
}
class HistoryViewHolder(
private val parent: ViewGroup,
private val binding: at.app.databinding.ViewHistoryListItemBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.view_history_list_item,
parent,
false
)
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: ListViewModel.ListItem, isActivated: Boolean = false) {
binding.model = item
itemView.isActivated = isActivated
val imageView = itemView.findViewById<AppCompatImageView>(R.id.history_item_image)
if(itemView.isActivated) {
val parameter = imageView?.layoutParams as ConstraintLayout.LayoutParams
parameter.setMargins(
parent.context.resources.getDimension(R.dimen.spacing_small).toInt(),
parent.context.resources.getDimension(R.dimen.spacing_small).toInt(),
parent.context.resources.getDimension(R.dimen.spacing_small).toInt(),
parent.context.resources.getDimension(R.dimen.spacing_small).toInt()
)
imageView.layoutParams = parameter
} else {
val parameter = imageView?.layoutParams as ConstraintLayout.LayoutParams
parameter.setMargins(0,0,0,0)
imageView.layoutParams = parameter
}
}
fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
object : ItemDetailsLookup.ItemDetails<Long>() {
override fun getPosition(): Int = adapterPosition
override fun getSelectionKey(): Long? = itemId
}
}
class HistoryGroupHeaderViewHolder(
private val parent: ViewGroup,
private val binding: at.app.databinding.ViewHistoryListGroupHeaderItemBinding = DataBindingUtil.inflate(
LayoutInflater.from(parent.context),
R.layout.view_history_list_group_header_item,
parent,
false
)
) : RecyclerView.ViewHolder(binding.root) {
fun bind(item: ListViewModel.ListSectionHeader) {
binding.model = item
}
}
class EmptyViewHolder(
private val parent: ViewGroup, view: View = LayoutInflater.from(parent.context).inflate(
R.layout.view_history_empty_item,
parent,
false
)
) : RecyclerView.ViewHolder(view)
companion object {
const val EMPTY = 0
const val ITEM = 1
const val HEADER = 2
}
}
class MyItemDetailsLookup(private val recyclerView: RecyclerView) : ItemDetailsLookup<Long>() {
private val log = LoggerFactory.getLogger(ListAdapter::class.java)
override fun getItemDetails(e: MotionEvent): ItemDetails<Long>? {
val view = recyclerView.findChildViewUnder(e.x, e.y)
if (view != null) {
return try {
if(recyclerView.getChildViewHolder(view) is ListAdapter.HistoryViewHolder) {
(recyclerView.getChildViewHolder(view) as ListAdapter.HistoryViewHolder)
.getItemDetails()
} else {
null
}
} catch (ex: Exception) {
log.error("Error on getItemDetails. ", ex)
null
}
}
return null
}
}
data class ListAdapterItem<out T>(val value: T, val viewType: Int)
And this is my layout:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="viewModel"
type="at.app.ui.viewmodel.ListViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<include
android:id="#+id/list_app_bar"
layout="#layout/layout_toolbar" />
<androidx.recyclerview.widget.RecyclerView
android:id="#+id/history_recycler_view"
android:layout_width="0dp"
android:layout_height="0dp"
android:background="#android:color/transparent"
android:scrollbars="vertical"
app:data="#{viewModel.items}"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="#+id/list_app_bar" />
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
When i add two items and the header, the header and one item are
displayed.
problem is in your getItemCount method.
override fun getItemCount(): Int {
return if (items.isEmpty()) 1 else items.filter { it.viewType == ITEM }.size
}
If you want to show 1 header and 2 elements that means that there are must be 3 items in recyclerview, so getItemCount must return 3. But now it looks like getItemCount will return 2, thats why recycerlview doesn't even create third element.

Why my pagedList is not updated via LiveData method .observe?

I have a Dao with this method.
#Query("SELECT * FROM expense WHERE date BETWEEN :dateStart AND :dateEnd")
fun getExpensesBetweenTheDate(dateStart: Calendar, dateEnd: Calendar):
DataSource.Factory<Int, Expense>
My repository get Dao and create LiveData> object.
fun getExpensesBetweenTheDate(startDay: Calendar, endDay: Calendar): LiveData<PagedList<Expense>> {
val factory = expenseDao.getExpensesBetweenTheDate(startDay, endDay)
val config = PagedList.Config.Builder()
.setPageSize(30)
.setMaxSize(200)
.setEnablePlaceholders(true)
.build()
return LivePagedListBuilder(factory, config)
.build()
}
My ViewModel get repository and create a variable.
val expenses = repository.getExpensesBetweenTheDate(startCalendar, endCalendar)
Finally, MainActivity observes on LiveData.
viewModel.expenses.observe(this, Observer(simpleExpenseAdapter::submitList))
All working fine, but when I try to add a new record to the database, it appears there not immediately, but after restarting the application. Similar code without a paging library works well. Maybe i do something wrong. Just in case, I give below the code of the adapter, viewHolder and layout.
Adapter.
class ExpenseAdapter : PagedListAdapter<Expense, ExpenseViewHolder>(EXPENSE_COMPARATOR) {
companion object {
private val EXPENSE_COMPARATOR = object : DiffUtil.ItemCallback<Expense>() {
override fun areItemsTheSame(oldItem: Expense, newItem: Expense): Boolean {
return oldItem.expenseId == newItem.expenseId
}
override fun areContentsTheSame(oldItem: Expense, newItem: Expense): Boolean {
return oldItem == newItem
}
}
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExpenseViewHolder {
return ExpenseViewHolder.create(parent)
}
override fun onBindViewHolder(holder: ExpenseViewHolder, position: Int) {
val expenseItem = getItem(position)
if (expenseItem != null) holder.bind(expenseItem)
}
}
ViewHolder.
class ExpenseViewHolder(binding: ExpenseElementSimpleBinding) : RecyclerView.ViewHolder(binding.root) {
private val mBinding = binding
init {
mBinding.root.setOnClickListener {
val intent = Intent(it.context, ShowExpenseActivity::class.java)
intent.putExtra("expense", mBinding.expense)
it.context.startActivity(intent)
}
}
companion object {
fun create(parent: ViewGroup): ExpenseViewHolder {
val inflater = LayoutInflater.from(parent.context)
val binding = ExpenseElementSimpleBinding.inflate(inflater, parent, false)
return ExpenseViewHolder(binding)
}
}
fun bind(item: Expense) {
mBinding.apply {
expense = item
executePendingBindings()
}
}
}
Layout.
<layout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<data>
<variable
name="expense"
type="com.example.budgetplanning.data.model.Expense"/>
</data>
<LinearLayout
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.appcompat.widget.AppCompatTextView
android:text="#{expense.description}"
tools:text="Gasoline"
android:padding="5dp"
android:layout_weight="1"
android:layout_width="0dp"
android:layout_height="wrap_content"/>
<androidx.appcompat.widget.AppCompatTextView
android:text="#{String.valueOf(expense.amount)}"
tools:text="123"
android:padding="5dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
</layout>
You have to call the simpleExpenseAdapter.notifyDataSetChanged() after the the submitList
This is happening because when you are calling simpleExpenseAdapter::submitList is equivalent to call simpleExpenseAdapter:submitList() when the list diff is not called at this time. So, you have to notify that the list has changed.
Or so, you can pass the new list as a parameter like:
viewModel.expenses.observe(this, Observer<YourObjectListened> {
simpleExpenseAdapter.submitList(it)
})
try to use toLiveData with original example from Paging library overview

Categories

Resources