5

I have a generic list that need to be show on most of the activities/fragments throughout my app. For this I have created a layout and added the recycler in it as in below code. This layout is then included in the activities/fragment where required. I am using MVVM and android bindings.My problem here is that the recyclerview`s onCreateViewHolder or even getItemCount is never called. My updateList function is called successfully from viewmodel with 2 items in the list but notifydatasetchanged has no effect as all. What i am doing wrong? Here is my code. Adapter Class

class FaqsAdapter : RecyclerView.Adapter<FaqsAdapter.MyViewHolder>() {
    lateinit var binding: FaqItemBinding
    lateinit var listFaqs: List<ModelFaqs>

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        binding = DataBindingUtil.inflate(inflater, R.layout.faq_item, parent, false)
        return MyViewHolder(binding)
    }

    override fun getItemCount(): Int {
        return if (::listFaqs.isInitialized) listFaqs.size else 0
    }

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

    fun updateList(faqs: List<ModelFaqs>) {
        this.listFaqs = faqs //this line executes
        notifyDataSetChanged() // line also executes but does nothing. 
    }

    class MyViewHolder(binding: FaqItemBinding) : RecyclerView.ViewHolder(binding.root) {
        private val mBinding = binding
        fun bind(modelFaqs: ModelFaqs) {
            val viewModel = FaqItemViewModel()
            viewModel.bind(modelFaqs)
            mBinding.mViewModel = viewModel
        }
    }
}

Fragment

class FragLoan : Fragment() {
    //other code

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        binding.viewFaqs.rcFaqs.layoutManager = LinearLayoutManager(context)
        viewModel = ViewModelProviders.of(this).get(FragLoanViewModel::class.java)

    }
}

The viewModel class

class FragLoanViewModel : BaseViewModel() {
    //other code

    private fun onLoanSuccess(response: AdvanceLoanDetails?) {
        Log.d(TAG, "LoanViewModel--On Success")
        val modelFaqs: MutableList<ModelFaqs> = mutableListOf()
        if (response?.result == true) {
            if (response.resultContent?.faqs != null) {
                for (faqs in response.resultContent.faqs) {
                    val model = ModelFaqs(faqs?.question, faqs?.answer)
                    modelFaqs.add(model)
                }
                updateFaq(modelFaqs)
            }
        }
    }

    //other code. 
}

Base ViewModel This is my baseviewModel

abstract class BaseViewModel : ViewModel() {
    val faqsAdapter = FaqsAdapter()

    private val injector: ViewModelInjector = DaggerViewModelInjector
        .builder()
        .networkModule(NetworkModule)
        .build()

    init {
        inject()
    }

    private fun inject() {
        when (this) {
            //code here
        }
    }

    fun updateFaq(listFaqs: List<ModelFaqs>) {
        faqsAdapter.updateList(listFaqs)
    }
}

The fragment Layout

 <?xml version="1.0" encoding="utf-8"?>

<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.mypackage.viewmodel.FragLoanViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@color/colorViewBackgroundGrey"
        tools:context="com.mypackage.myfragments.FragLoan">

        <ImageView
            android:id="@+id/ivLoanBanner"
            android:layout_width="0dp"
            android:layout_height="@dimen/_60sdp"
            android:layout_marginTop="16dp"
            android:contentDescription="@null"
            android:scaleType="fitXY"
            app:imageurl="@{viewModel.banner}"
            app:layout_constraintLeft_toLeftOf="@id/guideLeft"
            app:layout_constraintRight_toRightOf="@id/guideRight"
            app:layout_constraintTop_toTopOf="parent" />

        <androidx.cardview.widget.CardView
            android:id="@+id/cvLoanDetails"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            android:background="@color/colorWhite"
            app:cardCornerRadius="@dimen/card_content_radius"
            app:layout_constraintEnd_toEndOf="@id/guideRight"
            app:layout_constraintStart_toStartOf="@id/guideLeft"
            app:layout_constraintTop_toBottomOf="@id/ivLoanBanner">

            <androidx.constraintlayout.widget.ConstraintLayout
                android:layout_width="match_parent"
                android:layout_height="match_parent"
                android:layout_margin="@dimen/_16sdp">

                <TextView
                    android:id="@+id/tvAdvanceLoadTitle"
                    mutableText="@{viewModel.title}"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/_6sdp"
                    android:textColor="@color/colorBlack"
                    android:textStyle="bold"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toTopOf="parent" />

                <TextView
                    android:id="@+id/tvAdvanceLoanValidity"
                    mutableText="@{viewModel.validity}"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="3dp"
                    android:textColor="@color/lightGrey"
                    app:layout_constraintStart_toStartOf="@+id/tvAdvanceLoadTitle"
                    app:layout_constraintTop_toBottomOf="@+id/tvAdvanceLoadTitle" />


                <TextView
                    android:id="@+id/tvAdvanceLoanPrice"
                    mutableText="@{viewModel.amount}"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:textColor="@color/colorPrimary"
                    android:textStyle="bold"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintTop_toTopOf="@+id/tvAdvanceLoadTitle" />

                <TextView
                    android:id="@+id/tvAdvanceLoanTax"
                    mutableText="@{viewModel.inclusiveTax}"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="3dp"
                    android:textColor="@color/lightGrey"
                    app:layout_constraintEnd_toEndOf="@+id/tvAdvanceLoanPrice"
                    app:layout_constraintTop_toBottomOf="@+id/tvAdvanceLoanPrice" />

                <TextView
                    android:id="@+id/tvAdvanceLoanDetail"
                    mutableText="@{viewModel.description}"
                    android:layout_width="0dp"
                    android:layout_height="wrap_content"
                    android:layout_marginTop="@dimen/_8sdp"
                    android:background="@drawable/edit_text_history"
                    android:gravity="center"
                    android:minHeight="@dimen/_50sdp"
                    android:padding="@dimen/_6sdp"
                    android:textColor="@color/lightGrey"
                    android:textSize="12sp"
                    app:layout_constraintEnd_toEndOf="parent"
                    app:layout_constraintStart_toStartOf="parent"
                    app:layout_constraintTop_toBottomOf="@+id/tvAdvanceLoanValidity" />

                <androidx.constraintlayout.widget.Barrier
                    android:id="@+id/barrier"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    app:barrierDirection="left"
                    app:constraint_referenced_ids="tvAdvanceLoanTax,tvAdvanceLoanPrice"
                    tools:layout_editor_absoluteX="369dp" />
            </androidx.constraintlayout.widget.ConstraintLayout>
        </androidx.cardview.widget.CardView>

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guideLeft"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_begin="16dp" />

        <androidx.constraintlayout.widget.Guideline
            android:id="@+id/guideRight"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            app:layout_constraintGuide_end="16dp" />

        <!--        <androidx.recyclerview.widget.RecyclerView-->
        <!--            android:id="@+id/rcFaqs"-->
        <!--            app:layout_constraintEnd_toEndOf="@id/guideRight"-->
        <!--            app:layout_constraintStart_toStartOf="@id/guideLeft"-->
        <!--            app:layout_constraintTop_toBottomOf="@+id/cvLoanDetails"-->
        <!--            android:layout_width="0dp"-->
        <!--            android:layout_height="wrap_content"-->
        <!--            app:adapter="@{viewModel.faqsAdapter}" />-->
        <include
            android:id="@+id/viewFaqs"
            layout="@layout/view_faq"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            app:layout_constraintEnd_toEndOf="@id/guideRight"
            app:layout_constraintStart_toStartOf="@id/guideLeft"
            app:layout_constraintTop_toBottomOf="@+id/cvLoanDetails" />

        <include
            android:id="@+id/viewTerms"
            layout="@layout/view_terms_conditions"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="16dp"
            app:layout_constraintEnd_toEndOf="@id/guideRight"
            app:layout_constraintStart_toStartOf="@id/guideLeft"
            app:layout_constraintTop_toBottomOf="@+id/viewFaqs" />

        <com.google.android.material.button.MaterialButton
            android:id="@+id/btnGetLoan"
            android:layout_width="0dp"
            android:layout_height="@dimen/button_height"
            android:layout_marginBottom="8dp"
            android:text="@string/get_loan"
            android:textAllCaps="false"
            app:cornerRadius="@dimen/button_corner_radius"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="@id/guideRight"
            app:layout_constraintStart_toStartOf="@id/guideLeft" />

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

My FaqItemViewModel

class FaqItemViewModel : BaseViewModel() {
    private val sequenceNumber = MutableLiveData<String>()
    private val question = MutableLiveData<String>()
    private val answer = MutableLiveData<String>()
    private var sqNo = 0

    fun bind(faqs: ModelFaqs) {

        sqNo += 1
        sequenceNumber.value = sqNo.toString()
        question.value = faqs.question
        answer.value = faqs.answer

    }

    fun getSequenceNumber(): MutableLiveData<String> {
        return sequenceNumber
    }

    fun getQuestion(): MutableLiveData<String> {
        return question
    }

    fun getAnswer(): MutableLiveData<String> {
        return answer
    }

}

and Item XML

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">

    <data>

        <variable
            name="mViewModel"
            type="com.mypackage.viewmodel.FaqItemViewModel" />
    </data>

    <androidx.constraintlayout.widget.ConstraintLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="vertical">

        <TextView
            android:id="@+id/tvSerialNumber"
            mutableText="@{mViewModel.sequenceNumber}"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/colorAccent"
            android:textStyle="bold"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent" />

        <TextView
            android:id="@+id/tvQuestion"
            mutableText="@{mViewModel.question}"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:paddingLeft="6dp"
            android:paddingRight="6dp"
            android:textColor="@color/colorAccent"
            android:textStyle="bold"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toEndOf="@+id/tvSerialNumber"
            app:layout_constraintTop_toTopOf="@+id/tvSerialNumber" />

        <TextView
            android:id="@+id/tvAnswer"
            mutableText="@{mViewModel.answer}"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:paddingLeft="6dp"
            android:paddingRight="6dp"
            android:textColor="@color/lightGrey"
            android:textSize="12sp"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="@+id/tvQuestion"
            app:layout_constraintTop_toBottomOf="@+id/tvQuestion" />
    </androidx.constraintlayout.widget.ConstraintLayout>
</layout>
Imran Ali
  • 321
  • 2
  • 13

3 Answers3

3

You are not binding the viewmodel to your layout. In you fragment's onActivityCreated you be

override fun onActivityCreated(savedInstanceState: Bundle?) {
    super.onActivityCreated(savedInstanceState)
    binding.viewFaqs.rcFaqs.layoutManager = LinearLayoutManager(context)
    viewModel = ViewModelProviders.of(this).get(FragLoanViewModel::class.java)
    binding.viewModel = viewModel //this line will fix

    //Additionally, bind it to lifecycleOwner
    binding.lifecycleOwner = this
}

Next in your adapter, you shouldn't have parameter binding as it is of each item. you should pass the binding in onBindViewHolder

class FaqsAdapter : RecyclerView.Adapter<FaqsAdapter.MyViewHolder>() {
private val listFaqs = mutableListOf<ModelFaqs>()

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
    val inflater = LayoutInflater.from(parent.context)
    val binding = DataBindingUtil.inflate(inflater, R.layout.faq_item, parent, false)
    return MyViewHolder(binding)
}

override fun getItemCount() = listFaqs.size

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

fun updateList(faqs: List<ModelFaqs>) {
    this.listFaqs.clear()
    this.listFaqs.addAll(faqs)
    notifyDataSetChanged() // line also executes but does nothing.
}

class MyViewHolder(private val binding: FaqItemBinding) :
    RecyclerView.ViewHolder(binding.root) {
    fun bind(modelFaqs: ModelFaqs) {
        val viewModel = FaqItemViewModel()
        viewModel.bind(modelFaqs)
        binding.mViewModel = viewModel
    }
}
isudansh
  • 370
  • 3
  • 16
  • I am binding the viewmodel to layout. I have a method initViewModel() after viewModel = ViewModelProviders.of(this).get(FragLoanViewModel::class.java) which contains these line and other code as well binding.viewModel = viewModel binding.lifecycleOwner = this binding.viewFaqs.rcFaqs.layoutManager = LinearLayoutManager(context) binding.viewFaqs.rcFaqs.adapter = viewModel.faqsAdapter – Imran Ali Nov 11 '19 at 09:10
  • lifecycle owner is already added. Let me check by removing by binding parameter. – Imran Ali Nov 11 '19 at 09:26
  • Still the same, No effect – Imran Ali Nov 11 '19 at 09:36
  • updated the adapter. if it still doesn't work you would have to show your FaqItemViewModel – isudansh Nov 11 '19 at 09:50
  • updating adapter did not work. I have updated the question with FaqItemViewModel and its XML – Imran Ali Nov 11 '19 at 10:00
3

Ok, seems like you forget one thing. During include layouts inside you primary, you need to ensure, you are forwarding your ViewModel and/or other data to the inner layouts. Eventually, without forwarding your data with bound in one layout, it will not come to included.

1) Just make sure, you are passing your ViewModel and/or data for interaction to included layouts. You need to make sure, that your ViewModel Adapter is bound to included layout. Just for ex. something like next.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:bind="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".view.DetailActivity">

    <data>

        <variable
            name="viewModel"
            type="sample.settings.gensagames.samplejetpackmvvm.viewmodel.DetailViewModel" />
    </data>

    <androidx.coordinatorlayout.widget.CoordinatorLayout
        android:layout_width="match_parent"
        android:layout_height="match_parent">

        <com.google.android.material.appbar.AppBarLayout
            android:id="@+id/app_bar"
            android:layout_width="match_parent">
          <!-- Other code -->

        </com.google.android.material.appbar.AppBarLayout>

        <include
            android:id="@+id/detail_content"
            layout="@layout/detail_content"
            bind:viewModel="@{viewModel}" />

    </androidx.coordinatorlayout.widget.CoordinatorLayout>
</layout>

And then use parameters in your included layouts. Something like. It's not your example, but you should understand the point. You need to forward your ViewModel and/or parameters to included layout. This all it's just to setup Adapter.

<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:bind="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    tools:context=".view.DetailActivity">
    <data>
        <variable
            name="viewModel"
            type="sample.settings.gensagames.samplejetpackmvvm.viewmodel.DetailViewModel" />
    </data>
    <androidx.core.widget.NestedScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:layout_behavior="@string/appbar_scrolling_view_behavior"
        tools:showIn="@layout/detail_fragment">
        <TextView
            android:id="@+id/textViewContent"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_margin="@dimen/text_margin"
            app:mutableText="@{viewModel.getTextContent()}"
            tools:text="Sample Text Content" />
    </androidx.core.widget.NestedScrollView>
</layout>
GensaGames
  • 5,538
  • 4
  • 24
  • 53
2

The issue is now resolved. I searched for examples on the internet and found google sample app sunflowerAlpha. After looking around this code I made few changes to mine. like first i remove these lines from BaseView model

val faqsAdapter = FaqsAdapter()
fun updateFaq(listFaqs: List<ModelFaqs>) {
        faqsAdapter.updateList(listFaqs)
    }

and then I remove adapter binding "app:adapter="@{theviewmodel.myAdapter}" from xml file and bind the adapter from my Fragment like this.

                binding.viewFaqs.rcFaqs.layoutManager = LinearLayoutManager(context)
                binding.viewFaqs.rcFaqs.isNestedScrollingEnabled = false
                binding.viewFaqs.rcFaqs.adapter = faqsAdapter
                val faqs = createFaqs(loanDetails)
                faqsAdapter.updateList(faqs)

Now the recyclerview is successfully populated.

Imran Ali
  • 321
  • 2
  • 13
  • You could make it work with `"app:adapter="@{theviewmodel.myAdapter}"`. You just need to set this `Adapter` in your included layout. As I explained below. – GensaGames Nov 12 '19 at 00:06
  • BTW I have `NotSunflower` project, which basically similar to Google `Sunflower`, but contains also `Dagger` injection. https://github.com/GensaGames/Sample-NotSunflower – GensaGames Nov 12 '19 at 00:07
  • I will give it a try as well – Imran Ali Nov 12 '19 at 03:15