【问题标题】:LiveData in Nested RecyclerView嵌套 RecyclerView 中的 LiveData
【发布时间】:2021-02-16 14:03:05
【问题描述】:

我现在正在编码的片段应该为用户提供他的膳食计划时间表的日历概览。因此,通过日期选择器,他可以选择一个时间段,程序将向用户显示他为所选工作日选择了哪些食谱。 因此,我构建了一个嵌套的 RecyclerView,其中工作日作为父层,相应的食谱作为子层。工作日层的数据类如下所示:

data class Weekday (
    val weekday : String,
    val listWithRecipes : List<Recipe>?
        )

Recipe 实体的类如下所示:

@Entity(tableName = "Recipe")
@Parcelize
data class Recipe(
    @PrimaryKey var recipeName : String,
    var description : String?,
    var serving : Int,
    var preparationTime : Int?
) : Parcelable

顶部 Recycler 视图的适配器如下所示:

class MealPlanAdapter(private var mealplan: List<Weekday>) :
    RecyclerView.Adapter<MealPlanAdapter.MealPlanViewHolder>(), RecipeAdapter.OnItemClickListener {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MealPlanViewHolder {
        return MealPlanViewHolder(
            DailyMealplanItemBinding.inflate(
                LayoutInflater.from(parent.context),
                parent,
                false
            )
        )
    }

    override fun getItemCount() = mealplan.size

    override fun onBindViewHolder(holder: MealPlanViewHolder, position: Int) {
        val weekday = mealplan[position]
        val recipeAdapter = RecipeAdapter(this)
        recipeAdapter.submitList(weekday?.listWithRecipes)
        holder.dayOfWeek.text = weekday.weekday
        val recipeLayoutManager = LinearLayoutManager(holder.recyclerView.context,RecyclerView.VERTICAL, false)
        recipeLayoutManager.initialPrefetchItemCount = 4
        holder.recyclerView.apply{
            layoutManager = recipeLayoutManager
            adapter = recipeAdapter
        }

    }

    fun setSchedule(mealplan : List <Weekday>){
        this.mealplan = mealplan
        notifyDataSetChanged()
    }

    inner class MealPlanViewHolder(val binding: DailyMealplanItemBinding) :
        RecyclerView.ViewHolder(binding.root) {
        val recyclerView: RecyclerView = binding.rvRecyclerView
        val dayOfWeek: TextView = binding.tvDayOfWeek
    }

    override fun onItemClick(recipe: Recipe) {
        TODO("Not yet implemented")
    }
}

每当用户更改时间段时,都会在片段中调用适配器中的 setScheduled() 方法。

 materialDatePickerStartDate.addOnPositiveButtonClickListener(
            MaterialPickerOnPositiveButtonClickListener<Any?> { selection ->
                _binding.viewmodel!!.startDateInUTCFormat = selection as Long
                _binding.tvStartDate.setText(materialDatePickerStartDate.headerText)
                adapter.setSchedule(
                    _binding.viewmodel!!.returnListWithWeekDaysAndCorrespondingRecipes(
                        _binding.viewmodel!!.startDateInUTCFormat,
                        _binding.viewmodel!!.endDateInUTCFormat
                    )


                )
            }

        )

视图模型如下所示:

@HiltViewModel
class MealplanViewModel @Inject constructor(
    val mealPlanRepository: MealPlanRepository
) : ViewModel() {
    private lateinit var _binding: FragmentMealPlanBinding
    var startDateInUTCFormat: Long = System.currentTimeMillis()
    var endDateInUTCFormat: Long = System.currentTimeMillis()    

    fun returnListWithWeekDaysAndCorrespondingRecipes(
        startDate: Long,
        endDate: Long
    ): ArrayList<Weekday> {
        var startDate = Date(startDateInUTCFormat)
        var endDate = Date(endDateInUTCFormat)
        var startDateCalendar = dateToCalendar(startDate)
        var endDateCalendar = dateToCalendar(endDate)
        val calendarDays = createListWithCalendarDates(startDateCalendar, endDateCalendar)


        return createListWithWeekDaysAndCorrespondingRecipes(calendarDays)

    }

    fun dateToCalendar(date: Date): Calendar {
        var calInstance = Calendar.getInstance()
        calInstance.setTime(date)
        return calInstance
    }

    fun createListWithCalendarDates(
        startDateCalendar: Calendar,
        endDateCalendar: Calendar
    ): ArrayList<Calendar> {
        var listWithCalendarDates = arrayListOf<Calendar>()
        while (startDateCalendar <= endDateCalendar) {
            listWithCalendarDates.add(startDateCalendar.clone() as Calendar)
            startDateCalendar.add(Calendar.DATE, 1)
        }
        return listWithCalendarDates
    }

    fun createListWithWeekDaysAndCorrespondingRecipes(calendarDays: ArrayList<Calendar>): ArrayList<Weekday> {
        var dayOfWeekAsString: String
        var listWithDaysOfWeeksAndRecipes = arrayListOf<Weekday>()
        var flattenedListWithRecipes: List<Recipe>?
        for (i in 0 until calendarDays.size) {
            var dayOfWeekAsInt = calendarDays[i].get(Calendar.DAY_OF_WEEK)
            dayOfWeekAsString = when (dayOfWeekAsInt) {
                1 -> "Sunday"
                2 -> "Monday"
                3 -> "Tuesday"
                4 -> "Wednesday"
                5 -> "Thursday"
                6 -> "Friday"
                else -> "Saturday"
            } 
          

            var calendarDateInString =
                transformCalendarDateIntoRequiredStringFormat(calendarDays[i])
            var listWithDateAndCorrespondingRecipes: List<MealplanScheduleWithRecipes> =
                listOf() 


            var liveDatalistWithDateAndCorrespondingRecipes =
                mealPlanRepository.getMealplanScheduleWithRecipes(calendarDateInString)


            liveDatalistWithDateAndCorrespondingRecipes.observeForever() { list ->
                listWithDateAndCorrespondingRecipes = list



                var listWithRecipes = listWithDateAndCorrespondingRecipes?.map { it.recipes }
                flattenedListWithRecipes = listWithRecipes?.flatten()
                var wochentag = dayOfWeekAsString
                listWithDaysOfWeeksAndRecipes.add(Weekday(dayOfWeekAsString, flattenedListWithRecipes))

            }    


        }


        return listWithDaysOfWeeksAndRecipes
    }

    fun transformCalendarDateIntoRequiredStringFormat(calendarDate: Calendar): String {
        var year = calendarDate.get(Calendar.YEAR)
        var month = transformCalendarMonthFormatToCorrectMonth(calendarDate)
        var day = calendarDate.get(Calendar.DAY_OF_MONTH)
        return "$day" + "$month" + "$year"
    }

    fun transformCalendarMonthFormatToCorrectMonth(calendarDate: Calendar): String {
        var monthCalendarFormat = calendarDate.get(Calendar.MONTH)
        var monthCorrectFormat = when (monthCalendarFormat) {
            0 -> "1"
            1 -> "2"
            2 -> "3"
            3 -> "4"
            4 -> "5"
            5 -> "6"
            6 -> "7"
            7 -> "8"
            8 -> "9"
            9 -> "10"
            10 -> "11"
            else -> "12"
        }
        return monthCorrectFormat
    }

    fun datesAreReasonable(startDate: Long, endDate: Long): Boolean {
        return (startDate <= endDate)
    }


}

我的问题是传递给 RecyclerView 适配器的列表由 Weekday 对象组成,这些对象由工作日的名称和相应的食谱组成(请参阅顶部的数据类“weekday”)。 在视图模型中的“createListWithWeekDaysAndCorrespondingRecipes”方法中,我在一个 for 循环中创建了这个列表,该循环获取给定日期及其相应食谱之间的所有工作日。但是,食谱是通过 Room 数据库查询异步获取的 LiveData,而工作日的名称是在主线程中同步派生的。然而,最后当我创建 Weekday 对象时 (请参阅 for 循环末尾的 listWithDaysOfWeeksAndRecipes.add(Weekday(dayOfWeekAsString, flattenedListWithRecipes) )我同时需要它们。我还没有找到一种方法如何成功地协调它。目前添加的逻辑列表中的对象位于异步“observeForever”块中。 见这里:

liveDatalistWithDateAndCorrespondingRecipes.observeForever() { list ->
                listWithDateAndCorrespondingRecipes = list



                var listWithRecipes = listWithDateAndCorrespondingRecipes?.map { it.recipes }
                flattenedListWithRecipes = listWithRecipes?.flatten()
                var wochentag = dayOfWeekAsString
                listWithDaysOfWeeksAndRecipes.add(Weekday(dayOfWeekAsString, flattenedListWithRecipes))

            }    

这会产生错误的结果,可能是因为主线程和观察者线程之间的协调不起作用。 但是,如果我采用从观察者块中添加的逻辑,则由于查询的异步特性,带有配方的列表将给我 null。

我知道我对问题的描述非常糟糕。也许还有人掌握了它并可以提供帮助?

【问题讨论】:

    标签: android kotlin android-recyclerview android-room android-livedata


    【解决方案1】:

    你应该尽量避免使用observeForever,我希望你在一个片段或一个活动中使用它,它实际上有一个你的观察者可以使用的生命周期范围。

    你的观察者应该看起来像这样

    liveDataList.observe(viewLifecycleOwner, { list ->
        // The way I do it at the moment I just set the recyclerViews adapter and layoutManager here
        // This is not the best way to do it, so please keep that in mind
        recyclerView.apply {
            adapter = MyAdapter(list)
            layoutManager = LinearLayoutManager(requireContext())
        }
    })
    // Or if used inside an activity
    liveDataList.observe(this, {})
    

    这样,您的观察者将附加到您的生命周期并与您的视图一起“消亡”。每当该列表更改时,您将在 recyclerView 中显示所有实体。但是,当您将 Array 与 LiveData 一起使用时,当您向该值添加一些内容时,LiveData 对象永远不会“更新”,因为该数组只是对数组开头的内存引用。

    要在向数组中添加内容时解决此问题,您需要刷新 LiveData 对象以触发更新和所有观察者。

    myLiveDataObject.value.add(someOtherObject)
    myLiveDataObject.value = myLiveDataObject.value
    

    myLiveDataObject.value = myLiveDataObject.value触发所有观察者说有变化,烦我知道

    如果您在 viewHolder 或适配器中使用它,只需将生命周期与列表一起传递

    我对 kotlin 也很陌生,请记住这一点,我向您保证有更好的方法来做到这一点,但希望对您有所帮助

    【讨论】:

      猜你喜欢
      • 2020-05-31
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-03-17
      相关资源
      最近更新 更多