Search code examples
androidkotlinandroid-fragmentsdata-binding

Binding Adapter live data value is always null


For some reason, the second parameter value for both binding Adapters always returns null and I cannot figure out why. I am selecting a plantIndividual from a RecyclerView in the overview fragment and using it to navigate to a details page - individual fragment. Both Fragments share a viewModel.

Here are my BindingAdapters:

@BindingAdapter("listPhotoData")
fun bindPlantRecyclerView(recyclerView: RecyclerView,
                 data: List<PlantPhoto>?) {
val adapter = recyclerView.adapter as CollectionIndividualAdapter
adapter.submitList(data)
}

@BindingAdapter("singleImage")
fun loadImage(imgView: ImageView, imgUrl: File) {
imgUrl.let {
    Glide.with(imgView.context)
        .load(imgUrl)
        .apply(
            RequestOptions()
                .placeholder(R.drawable.loading_animation)
                .error(R.drawable.ic_broken_image))
        .into(imgView)
    }
}

My details fragment layout:

<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>
        <variable
            name="viewModel"
            type="com.example.collection.presentation.overview.CollectionOverviewViewModel" />
        <variable
            name="plantPhoto"
            type="com.example.storage.data.PlantPhoto" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:animateLayoutChanges="true">

        <ImageView
            android:id="@+id/collection_individual_imageview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            app:singleImage="@={viewModel.plantPhotoDisplay.plantFilePath}"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent"
            app:layout_constraintVertical_bias="0.26999998"
            tools:srcCompat="@tools:sample/avatars" />

        <androidx.recyclerview.widget.RecyclerView
            android:id="@+id/collection_individual_recyclerview"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="horizontal"
            app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintHorizontal_bias="0.0"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/collection_individual_imageview"
            app:layout_constraintVertical_bias="0.498"
            app:listPhotoData="@={viewModel.listPlantPhoto}"
            tools:listitem="@layout/image_plant_photo_view" />


    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

ViewModel:

class CollectionOverviewViewModel(application: Application) : AndroidViewModel(application) {

lateinit var mediaPlantList: MutableList<File>
private var newPhotoList = mutableListOf<PlantPhoto>()

private val context = getApplication<Application>().applicationContext

private val _navigateToSelectedPlant = MutableLiveData<PlantIndividual>()
val navigateToSelectedPlant: LiveData<PlantIndividual>
    get() = _navigateToSelectedPlant

private val _listPlantPhoto = MutableLiveData<MutableList<PlantPhoto>>()
val listPlantPhoto: LiveData<MutableList<PlantPhoto>>
    get() = _listPlantPhoto

private val _plantPhotoDisplay = MutableLiveData<PlantPhoto>()
val plantPhotoDisplay: LiveData<PlantPhoto>
    get() = _plantPhotoDisplay

fun displayPlantDetails(plantIndividual: PlantIndividual) {
    _navigateToSelectedPlant.value = plantIndividual
}

fun displayPlantDetailsComplete() {
    _navigateToSelectedPlant.value = null
}

fun retrievePlantList(plantIndividual: PlantIndividual) {
    val dataClassNum = plantIndividual.plantId
    viewModelScope.launch {
        mediaPlantList = context?.getExternalFilesDir("planio/dataclasses/$dataClassNum")
            ?.listFiles()?.sortedDescending()?.toMutableList() ?: mutableListOf()
    }
}

fun changeToPlantPhotos(plantList: MutableList<File>) {
        plantList.map {
            val file = FileInputStream(it)
            val inStream = ObjectInputStream(file)
            val item = inStream.readObject() as PlantPhoto
            newPhotoList.add(item)
        }

        _plantPhotoDisplay.value = newPhotoList.last()
        _listPlantPhoto.value = newPhotoList
}

}

OverView Fragment from which I am selecting a plantIndividual from a RecyclerView and navigating to a details page:

viewModel.navigateToSelectedPlant.observe(viewLifecycleOwner, {
        if (null != it) {

            viewModel.retrievePlantList(it)
            viewModel.changeToPlantPhotos(viewModel.mediaPlantList)

            this.findNavController().navigate(
            CollectionOverviewFragmentDirections.
            actionCollectionOverviewFragmentToCollectionIndividualFragment(it))
        }
    })

Details Fragment:

class CollectionIndividualFragment: Fragment() {

private lateinit var binding: FragmentCollectionIndividualBinding

private val viewModel: CollectionOverviewViewModel by viewModels()

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    binding = DataBindingUtil.inflate(inflater, R.layout.fragment_collection_individual, container, 
            false)
    binding.toCollectionOverview.setOnClickListener {
        this.findNavController().navigate(CollectionIndividualFragmentDirections.
        actionCollectionIndividualFragmentToCollectionOverviewFragment())
        viewModel.displayPlantDetailsComplete()
    }
    binding.viewModel = viewModel

    binding.lifecycleOwner = this

    binding.collectionIndividualRecyclerview.adapter = CollectionIndividualAdapter()

    binding.plantPhoto = viewModel.plantPhotoDisplay.value

    return binding.root
}

Solution

  • I guess you're setting the lifecycleOwner after setting the viewModel of your binding and as a result, after viewModel is set in binding, it cannot observe live data because the lifecycleOwner is null at that point. I suggest to set it after setting binding

    binding = DataBindingUtil.inflate(inflater, R.layout.fragment_collection_individual, container, 
            false)
    binding.lifecycleOwner = this
    

    Edit: Also don't forget to use by activityViewModels instead of by viewModels to share viewModel among your fragments