New to Android development and trying to wrap my head around the latest Architecture Components. Using Android Studio 3.2, Room, LiveData, ViewModel, Data Binding and RecyclerView and I’ve been fighting with Data Binding for days. I have a fragment in my activity and the RecyclerView portion that should be showing data from my ViewModel/Query is blank/empty. The Log.d in getItemCount for the Adapter shows zero items even though the related Room query is valid and returns items.
Please hit me with the clue stick and let me know what I am missing. The relevant portions of my code:
Entity and Dao
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>>
Fragment 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
}
ViewModel
class MainViewModel(private val repository: DataRepository) : ViewModel() {
val actions: LiveData<List<ActionDetails>> = repository.liveStatus()
}
Adapter
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
}
}
Main Fragment
<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>
Fragment 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>
Finally resolved this issue. Not sure exactly what resolved it—I think it was due to moving the ViewModel and running executePendingBindings() prior to configuring the RecyclerView in binding.apply in the fragment, but I’m not certain—because I also made some other changes after learning about BindingAdapters.
My changes are below if it helps anyone else—I’d also appreciate any comments on how this could be improved.
Cheers.
Fragment 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
}
Adapter
import ...FragmentActionDetailBinding
class ViewAdapter(private val actions: List<ActionDetails>) : RecyclerView.Adapter<ViewAdapter.ViewHolder>() {
...
override fun setData(data: List<ActionDetails>) {
actions = data
notifyDataSetChanged()
}
Binding Adapter
@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)
}
Main Fragment
<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>
Fragment 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>