Неправильное отображение в recycler view с databinding и livedata
Приложение для работы с api простое. Recyclerview содержит card view. Он отображает элементы, но делает это хаотично. Не правильно. Я использую databinding вместе с viewmodel. Для наблюдения livadata. Я знаю, что нужно указать жизненный цикл для livedata и я это сделал. Я не понимаю почему отображается неправильно. То-есть для всех элементов в экране отображается данные одного объекта. Листая recyclerview Они сменяются на самого нижнего на экране. Я думаю проблема в наблюдении, но в чём конкретно не понимаю. Работа с api правильна. Отображение картинок через Picasso правильная. То есть он правильно отображает то, что не связанно databinding. Проблема именно в нём
Код xml файла
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:android="http://schemas.android.com/apk/res/android">
<data>
<variable
name="viewModel"
type="com.fraime.android.rm.presentation.ui.list.ListViewModel" />
</data>
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<androidx.cardview.widget.CardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardBackgroundColor="#FFE502"
app:cardCornerRadius="20dp"
app:cardPreventCornerOverlap="true"
app:cardUseCompatPadding="true"
app:contentPadding="5dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:id="@+id/iconCharacter"
android:layout_width="78dp"
android:layout_height="82dp"
android:layout_marginStart="16dp"
android:layout_marginTop="16dp"
android:layout_marginBottom="16dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:srcCompat="@tools:sample/avatars" />
<TextView
android:id="@+id/nameCharacter"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="16dp"
android:layout_marginEnd="16dp"
android:text="@{viewModel.name}"
android:textColor="#000000"
android:textSize="20sp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/iconCharacter"
app:layout_constraintTop_toTopOf="parent"
tools:text="Rick Sanchez" />
<TextView
android:id="@+id/speciesCharacter"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:text="@{viewModel.species}"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/iconCharacter"
app:layout_constraintTop_toBottomOf="@+id/nameCharacter"
tools:text="Human" />
<TextView
android:id="@+id/genderCharacter"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:text="@{viewModel.gender}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toEndOf="@+id/iconCharacter"
app:layout_constraintTop_toBottomOf="@+id/speciesCharacter"
tools:text="Male" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView>
</androidx.constraintlayout.widget.ConstraintLayout>
</layout>
код viewmodel
package com.fraime.android.rm.presentation.ui.list
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import com.fraime.android.rm.domain.model.Character
import com.fraime.android.rm.domain.usecase.GetCharactersUseCase
class ListViewModel(getCharactersUseCase: GetCharactersUseCase) : ViewModel() {
val listCharacter : LiveData<List<Character>>
init {
listCharacter = getCharactersUseCase.execute()
}
var _name: MutableLiveData<String?> = MutableLiveData()
val name : LiveData<String?> = _name
var _species: MutableLiveData<String?> = MutableLiveData()
val species: LiveData<String?> = _species
var _gender: MutableLiveData<String?> = MutableLiveData()
val gender: LiveData<String?> = _gender
var character: Character? = null
set(character) {
field = character
_name.postValue(character?.name)
_species.postValue(character?.species)
_gender.postValue(character?.gender)
}
}
код фрагмента (я специально сделал адаптер во фрагменте потому что у же не понимаю в чем проблема и совсем отчаился)
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.fragment.app.Fragment
import androidx.databinding.DataBindingUtil
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.fraime.android.rm.R
import com.fraime.android.rm.databinding.FragmentListBinding
import com.fraime.android.rm.databinding.FragmentListItemBinding
import com.fraime.android.rm.domain.model.Character
import com.squareup.picasso.Picasso
import org.koin.androidx.viewmodel.ext.android.viewModel
class ListFragment : Fragment() {
private lateinit var binding: FragmentListBinding
private val listViewModel by viewModel<ListViewModel>()
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
binding = DataBindingUtil.inflate(inflater, R.layout.fragment_list, container, false)
binding.recyclerView.apply {
layoutManager = LinearLayoutManager(context)
}
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
listViewModel.listCharacter.observe(viewLifecycleOwner){
binding.recyclerView.adapter = RcAdapter(it)
}
}
private inner class CharacterViewHolder(private val binding: FragmentListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.viewModel = listViewModel
}
fun bind(character: Character) {
Picasso.get()
.load(character.image)
.into(binding.iconCharacter)
binding.apply {
viewModel?.character = character
executePendingBindings()
}
}
}
private inner class RcAdapter(private val characters: List<Character>) :
RecyclerView.Adapter<CharacterViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CharacterViewHolder {
val binding = DataBindingUtil.inflate<FragmentListItemBinding>(
layoutInflater,
R.layout.fragment_list_item,
parent,
false
)
binding.lifecycleOwner = this@ListFragment
return CharacterViewHolder(binding)
}
override fun onBindViewHolder(holder: CharacterViewHolder, position: Int) {
holder.bind(characters[position])
}
override fun getItemCount() = characters.size
}
}
Ответы (1 шт):
Разобрался с данной проблемой. Всё как всегда оказалось супер легко и просто Сначала я писал binding.viewModel = MyViewModel в блоке init View Holder
private inner class RcAdapter(private val characters: List<Character>) :
RecyclerView.Adapter<RcAdapter.CharacterViewHolder>() {
private inner class CharacterViewHolder(private val binding: FragmentListItemBinding) :
RecyclerView.ViewHolder(binding.root) {
init {
binding.viewModel = ListItemViewModel() //Вот это место
}
fun bind(character: Character) {
binding.apply {
Picasso.get()
.load(character.image)
.into(binding.iconCharacter)
viewModel?.character = character
executePendingBindings()
}
}
}
Но это связывание с вашей ViewModel нужно делать в функции адаптера onCreateViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CharacterViewHolder {
val binding = DataBindingUtil.inflate<FragmentListItemBinding>(
layoutInflater,
R.layout.fragment_list_item,
parent,
false
)
binding.lifecycleOwner = this@ListFragment //Владелец жизненного цикла
binding.viewModel = ListItemViewModel() //Ваша ViewModel
return CharacterViewHolder(binding)
}