Нет данных с RecyclerView, привязкой данных и ViewModel

Я новичок в разработке Android и пытаюсь осмыслить последние компоненты архитектуры. Используя Android Studio 3.2, Room, LiveData, ViewModel, Data Binding и RecyclerView, я уже несколько дней борюсь с Data Binding. У меня есть фрагмент в моей деятельности, а часть RecyclerView, которая должна отображать данные из моей модели ViewModel / Query, пуста / пуста. Log.d в getItemCount для адаптера показывает нулевые элементы, даже если связанный запрос Room действителен и возвращает элементы.

Пожалуйста, ударь меня подсказкой и дай мне знать, что мне не хватает. Соответствующие части моего кода:

Сущность и Дао

import org.threeten.bp.Instant

data class ActionDetails(val time: Instant,
                     val firstName: String,
                     ... )

@Query("SELECT time, first_name as firstName...")
fun liveStatus(): LiveData<List<ActionDetails>>

Класс фрагмента

class MainFragment : Fragment() {
...
private lateinit var viewModel: MainViewModel
private lateinit var adapter: ViewAdapter

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View {
    val binding = MainFragmentBinding.inflate(inflater, container, false)
    val context = context ?: return binding.root

    val factory = Utilities.provideMainViewModelFactory(context)
    viewModel = ViewModelProviders.of(this, factory).get(MainViewModel::class.java)

    adapter = ViewAdapter(listOf())

    binding.apply {
        rvActionDetails.setHasFixedSize(true)
        rvActionDetails.layoutManager = LinearLayoutManager(context)
        rvActionDetails.adapter = adapter
        vm = viewModel
        setLifecycleOwner(this@MainFragment)
    }

    return binding.root
}

ViewModel

class MainViewModel(private val repository: DataRepository) : ViewModel() {
    val actions: LiveData<List<ActionDetails>> = repository.liveStatus()
}

Адаптер

import ...FragmentActionDetailBinding

class ViewAdapter(private val actions: List<ActionDetails>) : RecyclerView.Adapter<ViewAdapter.ViewHolder>() {
private val TAG = this::class.java.simpleName

class ViewHolder(val binding: FragmentActionDetailBinding) : RecyclerView.ViewHolder(binding.root) {
    fun bind(action: ActionDetails) {
            binding.apply {
            vm = action
            executePendingBindings()
            }
    }
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
    val inflater = LayoutInflater.from(parent.context)
    val binding = FragmentActionDetailBinding.inflate(inflater, parent, false)
    return ViewHolder(binding)
}

override fun onBindViewHolder(holder: ViewHolder, position: Int) {
    holder.bind(actions[position])
}

override fun getItemCount(): Int {
    Log.d(TAG, "Adapter has ${actions.size} items!")
    return actions.size
}
}

Основной фрагмент

<layout xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:tools = "http://schemas.android.com/tools">

<data>
    <variable
        name = "vm" type = ".MainViewModel" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:app = "http://schemas.android.com/apk/res-auto"
    android:id = "@+id/main"
    android:layout_width = "match_parent"
    android:layout_height = "match_parent"
    tools:context = ".MainFragment" >
    ...
    <androidx.recyclerview.widget.RecyclerView
        android:id = "@+id/rv_action_details"
        android:layout_width = "match_parent"
        android:layout_height = "wrap_content"
        app:layout_constraintBottom_toBottomOf = "parent"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layoutManager = "androidx.recyclerview.widget.LinearLayoutManager"
        tools:listitem = "@layout/fragment_action_detail" />

</androidx.constraintlayout.widget.ConstraintLayout>

Фрагмент XML

<layout xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:tools = "http://schemas.android.com/tools">

<data>
    <variable
        name = "vm" type = ".ActionDetails" />
</data>

<LinearLayout
    android:layout_width = "match_parent"
    android:layout_height = "wrap_content"
    tools:context = ".ActionDetailFragment" >

    <TextView
        android:id = "@+id/first_name"
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        android:text = "@{vm.firstName}"
        tools:text = "John"/>
    ...
</LinearLayout>

2
0
3 214
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Наконец решил эту проблему. Не уверен, что именно разрешило это - я думаю, это было связано с перемещением ViewModel и запуском executePendingBindings () до настройки RecyclerView в binding.apply во фрагменте, но я не уверен, потому что я также внес некоторые другие изменения после изучения о BindingAdapters.

Ниже приведены мои изменения, если они помогут кому-то еще. Я также буду признателен за любые комментарии о том, как это можно улучшить.

Ваше здоровье.

Класс фрагмента

class MainFragment : Fragment() {
...

override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                          savedInstanceState: Bundle?): View {
...
    binding.apply {
        //Need to do this before configuring the RecyclerView
        vm = viewModel
        setLifecycleOwner(this@MainFragment)
        executePendingBindings()

        rvActionDetails.setHasFixedSize(true)
        rvActionDetails.layoutManager = LinearLayoutManager(context)
        rvActionDetails.adapter = adapter
    }

    return binding.root
}

Адаптер

import ...FragmentActionDetailBinding

class ViewAdapter(private val actions: List<ActionDetails>) : RecyclerView.Adapter<ViewAdapter.ViewHolder>() {
...
    override fun setData(data: List<ActionDetails>) {
        actions = data
        notifyDataSetChanged()
}

Адаптер для привязки

@BindingAdapter("listData")
fun <T> setRecyclerViewList (recyclerView: RecyclerView, data: T) {
    if (recyclerView.adapter is BindableListAdapter<*>) {
        (recyclerView.adapter as BindableListAdapter<T>).setData(data)
    }
}

interface BindableListAdapter<T> {
    fun setData(data: T)
}

Основной фрагмент

<layout xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:tools = "http://schemas.android.com/tools">

<data>
    <variable
        name = "vm" type = ".MainViewModel" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
    ...
    <androidx.recyclerview.widget.RecyclerView
        android:id = "@+id/rv_action_details"
        android:layout_width = "match_parent"
        android:layout_height = "wrap_content"
        app:layout_constraintBottom_toBottomOf = "parent"
        app:layout_constraintEnd_toEndOf = "parent"
        app:layoutManager = "androidx.recyclerview.widget.LinearLayoutManager"
        app:listData = "@{vm.actions}"
        tools:listitem = "@layout/fragment_action_detail" />

</androidx.constraintlayout.widget.ConstraintLayout>

Фрагмент XML

<layout xmlns:android = "http://schemas.android.com/apk/res/android"
xmlns:tools = "http://schemas.android.com/tools">

<data>
    <variable
        name = "vm" type = ".ActionDetails" />
</data>

<LinearLayout
    android:layout_width = "match_parent"
    android:layout_height = "wrap_content"
    tools:context = ".ActionDetailFragment" >

    <TextView
        android:id = "@+id/first_name"
        android:layout_width = "wrap_content"
        android:layout_height = "wrap_content"
        android:text = "@{vm.firstName}"
        tools:text = "John"/>
    ...
</LinearLayout>

Другие вопросы по теме