RecyclerView、数据绑定和 ViewModel 没有数据
No Data with RecyclerView, Data Binding and ViewModel
Android 开发的新手,正在尝试了解最新的架构组件。使用 Android Studio 3.2、Room、LiveData、ViewModel、Data Binding 和 RecyclerView,几天来我一直在与 Data Binding 作斗争。我的 activity 中有一个片段,应该显示来自 ViewModel/Query 的数据的 RecyclerView 部分是 blank/empty。适配器的 getItemCount 中的 Log.d 显示零项,即使相关的 Room 查询有效且 returns 项。
请用线索棒打我,让我知道我错过了什么。我的代码的相关部分:
实体与道
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
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
}
视图模型
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>
终于解决了这个问题。不确定到底是什么解决了它——我认为这是由于在片段中的 binding.apply 中配置 RecyclerView 之前移动了 ViewModel 和 运行 executePendingBindings(),但我不确定——因为我也在学习了 BindingAdapters 之后做了一些其他的改变。
如果对其他人有帮助,我的更改如下 - 我也非常感谢任何关于如何改进的评论。
干杯。
片段Class
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>
Android 开发的新手,正在尝试了解最新的架构组件。使用 Android Studio 3.2、Room、LiveData、ViewModel、Data Binding 和 RecyclerView,几天来我一直在与 Data Binding 作斗争。我的 activity 中有一个片段,应该显示来自 ViewModel/Query 的数据的 RecyclerView 部分是 blank/empty。适配器的 getItemCount 中的 Log.d 显示零项,即使相关的 Room 查询有效且 returns 项。
请用线索棒打我,让我知道我错过了什么。我的代码的相关部分:
实体与道
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
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
}
视图模型
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>
终于解决了这个问题。不确定到底是什么解决了它——我认为这是由于在片段中的 binding.apply 中配置 RecyclerView 之前移动了 ViewModel 和 运行 executePendingBindings(),但我不确定——因为我也在学习了 BindingAdapters 之后做了一些其他的改变。
如果对其他人有帮助,我的更改如下 - 我也非常感谢任何关于如何改进的评论。
干杯。
片段Class
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>