MVVM - Recyclerview внутри включенного макета никогда не инициализируется - PullRequest
2 голосов
/ 06 ноября 2019

У меня есть общий список, который должен отображаться в большинстве действий / фрагментов моего приложения. Для этого я создал макет и добавил в него переработчик, как показано ниже. Этот макет затем включается в действия / фрагмент, где это необходимо. Я использую привязки MVVM и android. Моя проблема здесь в том, что onCreateViewHolder или даже getItemCount в программе recyclerview никогда не вызывается. Моя функция updateList успешно вызывается из viewmodel с двумя элементами в списке, но notifydatasetchanged не действует как все. Что я делаю не так? Вот мой код. Класс адаптера

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
        }
    }
}

Фрагмент

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)

    }
}

Класс viewModel

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 Это мой 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)
    }
}

ФрагментМакет

 <?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>

Мой 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
    }

}

и элемент 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>

Ответы [ 3 ]

1 голос
/ 11 ноября 2019

Хорошо, кажется, вы забыли одну вещь. Во время include макетов внутри вашей первичной системы вы должны убедиться, что вы перенаправляете свои ViewModel и / или другие данные во внутренние макеты. В конце концов, без пересылки ваших данных с привязкой в ​​одном макете, они не будут включены.

1) Просто убедитесь, что вы передаете свои ViewModel и / или данные для взаимодействия с включенными макетами. Вы должны убедиться, что ваш ViewModel 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.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>

А затем используйте параметры во включенных макетах. Что-то вроде. Это не ваш пример, но вы должны понять суть. Вам необходимо переслать ViewModel и / или параметры во включенный макет. Это все, что нужно просто настроить 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>
1 голос
/ 11 ноября 2019

Вы не привязываете viewmodel к своему макету. В вашем фрагменте onActivityCreated вы будете

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
}

Следующим в вашем адаптере у вас не должно быть параметра binding, как у каждого элемента. Вы должны передать binding в 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
    }
}
0 голосов
/ 11 ноября 2019

Теперь проблема решена. Я искал примеры в интернете и нашел пример приложения Google sunflowerAlpha . Посмотрев вокруг этого кода, я внес несколько изменений в свой. как сначала я удаляю эти строки из модели BaseView

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

, а затем удаляю привязку адаптера "app: adapter =" @ {theviewmodel.myAdapter} "из файла XML и привязываю адаптер из моего фрагмента следующим образом.

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

Теперь просмотрщик успешно заполнен.

...