findConistingViewHolder возвращает ноль - PullRequest
1 голос
/ 28 февраля 2020

В следующем приложении у вас есть в основном 2 фрагмента: 1) база данных продуктов питания, 2) список потребляемых продуктов питания

1) пользователь может вручную добавлять продукты с соответствующей информацией о питательных микроэлементах (ккал, углеводы, белок, et c.). Данные хранятся в базе данных SQL через Room и отслеживаются с помощью LiveData

2) фрагмента (FoodDiaryFragment.kt), который показывает «потребленную» пищу с помощью recyclerView. Пользователь может добавлять продукты, нажимая FAB, который отправляет пользователя к другому фрагменту (AddConsumedFoodFragment.kt). это показывает счетчик и форму. Вращатель показывает список продуктов из базы данных продуктов (из 1). В форме пользователь может просто ввести одно значение (сколько граммов выбранной пищи было потреблено). Эти данные также добавляются в ту же базу данных SQL в таблице с: id (автоматически сгенерированным), количеством (введенным пользователем), потребляемым продуктом питания (выбранным из счетчика и подключенным к другой таблице через внешний ключ) и потребляемой датой ( автоматически сгенерированная дата в момент добавления элемента).

Представление переработчика (2) показывает элементы с дополнительной информацией, которая рассчитывается в RecyclerViewAdapter (в основном, умножая количество и соответствующую информацию о питательных микроэлементах [ккал, углеводы) , et c.], см. getDailyValues ​​() в адаптере), а также «группирует» элементы по дате. Это означает, что для каждого дня в представление recyclerView добавляется отдельный viewHolder (DateViewHolder), который отображает день (и сумму каждого макроса) (см. SortAndGroupFood () в FoodDiaryFragment.kt)

DateViewHolder and ConsumedFoodViewHolder

Поскольку я использую RecyclerView Selection, setStableIds () (см. Блок init) имеет значение true. StableIdKeyProvider. java добавляет объект ChildAttachStateChangeListener с функцией onChilViewDetachedFrom Windows ().

Я не уверен точно, когда вызывается эта функция (я хотел бы получить объяснение), но иногда это происходит, когда либо Элемент удален (элементы могут быть удалены через RecyclerView.Selection) или элемент добавлен. Приложение вылетает с показанным ниже исключением NullPointerException. Когда приложение перезапускается, действие, которое в конечном итоге закончилось в cra sh, было успешно выполнено.

Я действительно в отчаянии и пытался выяснить, почему нулевое представление передается этой функции в течение нескольких часов , но, очевидно, я не добился успеха.

В соответствии с docs findConistingViewHolder возвращает ноль, если предоставленное представление не является потомком этого RecyclerView

Вопрос: Какие проблемы вызывает возвращение нуля и как я могу превратить его? GitHub Depo Link

FoodDiaryFragment.kt


import android.os.Bundle
import android.view.*
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.selection.*
import androidx.recyclerview.widget.RecyclerView
import com.hooni.macrotracker.R
import com.hooni.macrotracker.adapter.ConsumedFoodRecyclerViewAdapter
import com.hooni.macrotracker.data.ConsumedFood
import com.hooni.macrotracker.recyclerviewselector.ConsumedFoodItemDetailsLookup
import com.hooni.macrotracker.viewmodels.FoodViewModel
import kotlinx.android.synthetic.main.fragment_food_diary.view.*
import java.text.DateFormat
import java.text.SimpleDateFormat



class FoodDiaryFragment : Fragment() {
    private lateinit var foodViewModel: FoodViewModel
    private lateinit var recyclerView: RecyclerView
    private lateinit var adapter: ConsumedFoodRecyclerViewAdapter

    private val dateFormat = DateFormat.getDateInstance()

    var tracker: SelectionTracker<Long>? = null
    var actionMode: ActionMode? = null


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val v = inflater.inflate(R.layout.fragment_food_diary, container, false)
        initRecyclerView(v)
        initViewModel()
        initButtons(v)
        // this makes sure, that in case the last destination was addConsumedFoodFragment
        // the 'back' button doesn't bring you back to the addConsumedFoodFragment, but to the one that
        // has been visited before
        findNavController().popBackStack(R.id.addConsumedFoodFragment, true)
        return v
    }

    private fun initButtons(v: View) {
        val addNewFood = v.addNewConsumedFood
        addNewFood.setOnClickListener {
            findNavController().navigate(R.id.action_diaryFragment_to_addConsumedFoodFragment)
        }
    }

    private fun initRecyclerView(view: View) {
        recyclerView = view.food_diary_recyclerView
        adapter = ConsumedFoodRecyclerViewAdapter()
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(activity)

        tracker = SelectionTracker.Builder<Long>(
            "selectedItemsConsumedFoo",
            recyclerView,
            StableIdKeyProvider(recyclerView),
            ConsumedFoodItemDetailsLookup(recyclerView),
            StorageStrategy.createLongStorage()
        ).withSelectionPredicate(
            SelectionPredicates.createSelectAnything()
        ).build()

        val actionModeCallBack = object : ActionMode.Callback {
            override fun onCreateActionMode(mode: ActionMode?, menu: Menu?): Boolean {
                val inflater = mode?.menuInflater
                actionMode = mode
                actionMode?.title = getString(R.string.delete)
                inflater?.inflate(R.menu.action_menu, menu)
                return true
            }

            override fun onActionItemClicked(mode: ActionMode?, item: MenuItem?): Boolean {
                return when (item?.itemId) {
                    R.id.action_menu_delete -> {
                        removeItems(tracker?.selection!!)
                        tracker?.clearSelection()
                        actionMode?.finish()
                        actionMode = null
                        return true
                    }
                    else -> false
                }
            }

            override fun onPrepareActionMode(mode: ActionMode?, menu: Menu?): Boolean {
                return false
            }

            override fun onDestroyActionMode(mode: ActionMode?) {
                tracker?.clearSelection()
                actionMode?.finish()
                actionMode = null
            }
        }

        tracker?.addObserver(
            object : SelectionTracker.SelectionObserver<Long>() {

                override fun onSelectionChanged() {
                    if (tracker?.selection!!.size() > 0) {
                        if (actionMode == null) activity?.startActionMode(actionModeCallBack)
                    } else {
                        actionMode?.finish()
                        actionMode = null
                    }
                }
            }
        )
        adapter.tracker = tracker
    }

    private fun removeItems(selection: Selection<Long>) {
        val foodsToDelete = adapter.getFood(selection)
        foodsToDelete.forEach {
            foodViewModel.deleteConsumedFood(it)
        }
    }


    private fun initViewModel() {
        foodViewModel = ViewModelProvider(this).get(FoodViewModel::class.java)
        foodViewModel.allConsumedFood.observe(viewLifecycleOwner, Observer { consumedFoodList ->
            consumedFoodList?.let { adapter.setConsumedFood(sortAndGroupFood(consumedFoodList)) }
        })
        foodViewModel.allFood.observe(viewLifecycleOwner, Observer { food ->
            food?.let { adapter.setFood(food.sortedBy {it.foodName}) }
        })
    }

    private fun sortAndGroupFood(consumedFoodList: List<ConsumedFood>): List<ConsumedFood> {
        val sortedList = consumedFoodList.sortedBy{it.consumedDate}
        val groupedMap: Map<String,List<ConsumedFood>> = sortedList.groupBy { dateFormat.format(it.consumedDate)}
        val finalizedList = mutableListOf<ConsumedFood>()

        groupedMap.forEach {
            finalizedList.add(createDateHeader(it.key))
            finalizedList.addAll(it.value)
        }
        return finalizedList.toList()
    }

    private fun createDateHeader(dateString: String): ConsumedFood {
        val date = dateFormat.parse(dateString)
        return ConsumedFood(null,-1,"DATE_HEADER",date!!)
    }

}

AddFoodFragment.kt

package com.hooni.macrotracker.fragments

import android.annotation.SuppressLint
import android.os.Build
import android.os.Bundle
import android.text.InputType
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.findNavController
import com.google.android.material.textfield.TextInputLayout
import com.hooni.macrotracker.R
import com.hooni.macrotracker.data.Food
import com.hooni.macrotracker.util.Tools
import com.hooni.macrotracker.viewmodels.FoodViewModel
import kotlinx.android.synthetic.main.fragment_add_food.view.*
import java.text.DecimalFormatSymbols
import java.util.*

class AddFoodFragment : Fragment() {
    private lateinit var foodViewModel: FoodViewModel

    private lateinit var enterTextFoodName: TextInputLayout
    private lateinit var enterTextKcal: TextInputLayout
    private lateinit var enterTextCarbs: TextInputLayout
    private lateinit var enterTextProtein: TextInputLayout
    private lateinit var enterTextFat: TextInputLayout



    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        val v = inflater.inflate(R.layout.fragment_add_food, container, false)
        initButtons(v)
        initViewModel()
        initTextInPutLayouts(v)
        return v
    }

    private fun initTextInPutLayouts(v: View) {

        enterTextFoodName = v.enter_food_name
        enterTextKcal = v.enter_kcal
        enterTextCarbs = v.enter_carbs
        enterTextProtein = v.enter_protein
        enterTextFat = v.enter_fat

        enterTextCarbs.setNumberDecimalInputOnly()
        enterTextProtein.setNumberDecimalInputOnly()
        enterTextFat.setNumberDecimalInputOnly()


    }

    private fun initButtons(view: View) {
        view.cancel_add_food.setOnClickListener {
            findNavController().navigate(R.id.action_addFoodFragment_to_foodListFragment)
            Tools.hideSoftKeyboard(view, context)
        }

        view.add_food.setOnClickListener {
            if (validateValues()) {
                val newFood = createNewFood()
                foodViewModel.insertFood(newFood)
                showSuccessfulAdd()
                view.cancel_add_food.performClick()
            }
        }
    }

    private fun validateValues(): Boolean {

        val isFoodNameValid = enterTextFoodName.validateInput { it != null }
        val isKcalValid = enterTextKcal.validateInput { it?.toIntOrNull() != null }
        val isCarbsValid = enterTextCarbs.validateInput { it?.toDoubleOrNull() != null }
        val isProteinValid = enterTextProtein.validateInput { it?.toDoubleOrNull() != null }
        val isFatValid = enterTextFat.validateInput { it?.toDoubleOrNull() != null }


        return (isFoodNameValid
                && isKcalValid
                && isCarbsValid
                && isProteinValid
                && isFatValid)

    }

    private fun initViewModel() {
        foodViewModel = ViewModelProvider(this).get(FoodViewModel::class.java)
    }


    private fun createNewFood(): Food {
        val foodName =
            enterTextFoodName.editText?.text.toString().trim()
        val kcal =
            enterTextKcal.editText?.text.toString().trim().toInt()
        val carbs =
            enterTextCarbs.editText?.text.toString().replaceDecimalSeparator().toDouble()
        val protein =
            enterTextProtein.editText?.text.toString().replaceDecimalSeparator().toDouble()
        val fat = enterTextFat.editText?.text.toString().replaceDecimalSeparator().toDouble()
        return Food(foodName, kcal, carbs.round(), fat.round(), protein.round())
    }


    private fun showSuccessfulAdd() {
        Toast.makeText(
            activity,
            "${enterTextFoodName.editText?.text.toString().trim()} added to database",
            Toast.LENGTH_SHORT
        ).show()
    }



    private inline fun TextInputLayout.validateInput(validate: (String?) -> Boolean): Boolean {

        val textToValidate = this.editText?.text.toString().replaceDecimalSeparator().trim()
        when {
            textToValidate.isEmpty() -> {
                error = getString(R.string.cant_be_empty)

            }
            !validate(textToValidate) -> {
                error = getString(R.string.invalid_value)
            }
            else -> {
                error = null
                return true
            }
        }
        return false
    }


    private fun TextInputLayout.setNumberDecimalInputOnly() {
        this.editText?.apply {
            inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL
        }
    }

    private fun Double.round(decimals: Int = 1): Double = "%.${decimals}f".format(Locale.US,this).toDouble()


    @SuppressLint("NewApi")
    private fun String.replaceDecimalSeparator(): String {
        val decimalSeparator = when(Build.VERSION.SDK_INT) {
            in Int.MIN_VALUE..Build.VERSION_CODES.M -> {DecimalFormatSymbols(resources.configuration.locale).decimalSeparator}
            else -> DecimalFormatSymbols(resources.configuration.locales[0]).decimalSeparator
        }
        return this.replace(decimalSeparator, '.')
    }

}

ConsumedFoodRecyclerViewAdapter.kt

package com.hooni.macrotracker.adapter

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.recyclerview.selection.ItemDetailsLookup
import androidx.recyclerview.selection.Selection
import androidx.recyclerview.selection.SelectionTracker
import androidx.recyclerview.widget.RecyclerView
import com.hooni.macrotracker.R
import com.hooni.macrotracker.data.ConsumedFood
import com.hooni.macrotracker.data.Food
import kotlinx.android.synthetic.main.fragment_food_diary_date_list_item.view.*
import kotlinx.android.synthetic.main.fragment_food_diary_list_item.view.*
import java.text.DateFormat
import java.util.*


private const val DATE_VIEW_HOLDER = 0
private const val CONSUMED_FOOD_VIEW_HOLDER = 1

class ConsumedFoodRecyclerViewAdapter : RecyclerView.Adapter<ConsumedFoodRecyclerViewAdapter.BaseViewHolder<*>>() {

    private var mConsumedFoodList = emptyList<ConsumedFood>()
    private var mFoodList = emptyList<Food>()
    var tracker: SelectionTracker<Long>? = null

    init {
        setHasStableIds(true)
    }

    abstract class BaseViewHolder<T>(itemView: View): RecyclerView.ViewHolder(itemView) {

        abstract fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long>
    }


    inner class ConsumedFoodViewHolder(mView: View) :BaseViewHolder<ConsumedFood>(mView) {
        private val mConsumedFoodName: TextView = mView.consumed_food_name
        private val mConsumedKcal: TextView = mView.consumed_kcal
        private val mConsumedAmount: TextView = mView.consumed_amount
        private val mConsumedCarbs: TextView = mView.consumed_carb_amount
        private val mConsumedProtein: TextView = mView.consumed_protein_amount
        private val mConsumedFat: TextView = mView.consumed_fat_amount


        override fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
            object : ItemDetailsLookup.ItemDetails<Long>() {
                override fun getSelectionKey(): Long? = itemId
                override fun getPosition(): Int = adapterPosition
            }

        fun bind(isActivated: Boolean = false, item: ConsumedFood) {
            val consumedMacroOfFood = calculateConsumedMacro(item.amount,item.consumedFood)
            itemView.isActivated = isActivated
            mConsumedFoodName.text = item.consumedFood
            mConsumedKcal.text =  itemView.context.getString(R.string.list_item_kcal,consumedMacroOfFood[0].toInt())
            mConsumedAmount.text = itemView.context.getString(R.string.list_item_amount,item.amount)
            mConsumedCarbs.text = itemView.context.getString(R.string.list_item_macro,consumedMacroOfFood[1])
            mConsumedProtein.text = itemView.context.getString(R.string.list_item_macro,consumedMacroOfFood[2])
            mConsumedFat.text = itemView.context.getString(R.string.list_item_macro,consumedMacroOfFood[3])
        }

    }

    inner class DateViewHolder(mView: View): BaseViewHolder<ConsumedFood>(mView) {
        private val mConsumedDate: TextView = mView.food_diary_date
        private val mTotalConsumedDayKcal: TextView = mView.food_diary_date_sum_kcal
        private val mTotalConsumedDayCarbs: TextView = mView.food_diary_date_sum_carbs
        private val mTotalConsumedDayProtein: TextView = mView.food_diary_date_sum_protein
        private val mTotalConsumedDayFat: TextView = mView.food_diary_date_sum_fat

        override fun getItemDetails(): ItemDetailsLookup.ItemDetails<Long> =
            object : ItemDetailsLookup.ItemDetails<Long>() {
                override fun getSelectionKey(): Long? = itemId
                override fun getPosition(): Int = adapterPosition
            }

        fun bind(item: ConsumedFood) {
            mConsumedDate.text = itemView.context
                .getString(R.string.list_item_date,DateFormat.getDateInstance(DateFormat.MEDIUM,DateFormat.getAvailableLocales()[0]).format(item.consumedDate))
            mTotalConsumedDayKcal.text = itemView.context.getString(R.string.list_item_kcal,getDailyValues(item.consumedDate)[0].toInt())
            mTotalConsumedDayCarbs.text = itemView.context.getString(R.string.list_item_macro,getDailyValues(item.consumedDate)[1])
            mTotalConsumedDayProtein.text = itemView.context.getString(R.string.list_item_macro,getDailyValues(item.consumedDate)[2])
            mTotalConsumedDayFat.text = itemView.context.getString(R.string.list_item_macro,getDailyValues(item.consumedDate)[3])
        }
    }

    override fun getItemViewType(position: Int): Int {
        return if(mConsumedFoodList[position].amount < 0) {
            DATE_VIEW_HOLDER
        } else CONSUMED_FOOD_VIEW_HOLDER
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BaseViewHolder<ConsumedFood> {
        return when(viewType) {
            DATE_VIEW_HOLDER -> {
                val view = LayoutInflater.from(parent.context)
                    .inflate(R.layout.fragment_food_diary_date_list_item,parent,false)
                DateViewHolder(view)
            }
            //CONSUMED_FOOD_VIEW_HOLDER
            else -> {
                val view = LayoutInflater.from(parent.context)
                    .inflate(R.layout.fragment_food_diary_list_item,parent,false)
                ConsumedFoodViewHolder(view)
            }

        }
    }

    override fun getItemCount(): Int = mConsumedFoodList.size

    override fun onBindViewHolder(holder: BaseViewHolder<*>, position: Int) {

        when(holder) {
            is DateViewHolder -> {
                tracker?.let {
                    holder.bind(mConsumedFoodList[position])
                }
            }
            is ConsumedFoodViewHolder -> {
                tracker?.let {
                    holder.bind(it.isSelected(position.toLong()),mConsumedFoodList[position])
                }
            }
        }

    }

    override fun getItemId(position: Int): Long = position.toLong()

    internal fun getFood(ids: Selection<Long>): List<ConsumedFood> {
        val foodsToDelete: MutableList<ConsumedFood> = mutableListOf()
        ids.forEach {
            foodsToDelete.add(mConsumedFoodList[it.toInt()])
        }
        return foodsToDelete.toList()
    }

    internal fun setConsumedFood(consumedFood: List<ConsumedFood>) {
        mConsumedFoodList = consumedFood
        notifyDataSetChanged()
    }

    internal fun setFood(food: List<Food>) {
        mFoodList = food
        notifyDataSetChanged()
    }


    private fun calculateConsumedMacro(amount: Int, food: String): List<Double>{
        val kcalOfTheFood = mFoodList.firstOrNull{ it.foodName == food}?.kcal?.toDouble() ?: 0.0
        val carbsOfTheFood = mFoodList.firstOrNull{ it.foodName == food}?.carbs ?: 0.0
        val proteinOfTheFood = mFoodList.firstOrNull{ it.foodName == food}?.protein ?: 0.0
        val fatOfTheFood = mFoodList.firstOrNull{ it.foodName == food}?.fat ?: 0.0
        return listOf(kcalOfTheFood.getAmountOfMacro(amount),
            carbsOfTheFood.getAmountOfMacro(amount),
            proteinOfTheFood.getAmountOfMacro(amount),
            fatOfTheFood.getAmountOfMacro(amount))
    }


    private fun Double.getAmountOfMacro(amount: Int): Double {
        return this * amount / 100
    }

    private fun getDailyValues(day: Date): List<Double> {
        val foodsOfSameDay = mConsumedFoodList.filter{ DateFormat.getDateInstance().format(it.consumedDate) == DateFormat.getDateInstance().format(day)}
        val macrosOfSameDay = mutableListOf<List<Double>>()
        foodsOfSameDay.forEach {macrosOfSameDay.add(calculateConsumedMacro(it.amount,it.consumedFood))}

        val sumKcal = macrosOfSameDay.sumByDouble { it[0] }
        val sumCarbs = macrosOfSameDay.sumByDouble { it[1] }
        val sumProtein = macrosOfSameDay.sumByDouble { it[2] }
        val sumFat = macrosOfSameDay.sumByDouble { it[3] }

        return listOf(sumKcal,sumCarbs,sumProtein,sumFat)
    }

}

Ошибка

2020-02-18 18:34:07.287 28614-28614/com.hooni.macrotracker E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.hooni.macrotracker, PID: 28614
    java.lang.NullPointerException: Attempt to invoke virtual method 'int androidx.recyclerview.widget.RecyclerView$ViewHolder.getAdapterPosition()' on a null object reference
        at androidx.recyclerview.selection.StableIdKeyProvider.onDetached(StableIdKeyProvider.java:90)
        at androidx.recyclerview.selection.StableIdKeyProvider$1.onChildViewDetachedFromWindow(StableIdKeyProvider.java:69)
        at androidx.recyclerview.widget.RecyclerView.dispatchChildDetached(RecyclerView.java:7546)
        at androidx.recyclerview.widget.RecyclerView.removeDetachedView(RecyclerView.java:4349)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getScrapOrCachedViewForId(RecyclerView.java:6738)
        at androidx.recyclerview.widget.RecyclerView$Recycler.tryGetViewHolderForPositionByDeadline(RecyclerView.java:6189)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6118)
        at androidx.recyclerview.widget.RecyclerView$Recycler.getViewForPosition(RecyclerView.java:6114)
        at androidx.recyclerview.widget.LinearLayoutManager$LayoutState.next(LinearLayoutManager.java:2303)
        at androidx.recyclerview.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1627)
        at androidx.recyclerview.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1587)
        at androidx.recyclerview.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:665)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayoutStep2(RecyclerView.java:4134)
        at androidx.recyclerview.widget.RecyclerView.dispatchLayout(RecyclerView.java:3851)
        at androidx.recyclerview.widget.RecyclerView.onLayout(RecyclerView.java:4404)
        at android.view.View.layout(View.java:20672)
        at android.view.ViewGroup.layout(ViewGroup.java:6194)
        at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
        at android.view.View.layout(View.java:20672)
        at android.view.ViewGroup.layout(ViewGroup.java:6194)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
        at android.view.View.layout(View.java:20672)
        at android.view.ViewGroup.layout(ViewGroup.java:6194)
        at androidx.constraintlayout.widget.ConstraintLayout.onLayout(ConstraintLayout.java:1915)
        at android.view.View.layout(View.java:20672)
        at android.view.ViewGroup.layout(ViewGroup.java:6194)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
        at android.view.View.layout(View.java:20672)
        at android.view.ViewGroup.layout(ViewGroup.java:6194)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
        at android.view.View.layout(View.java:20672)
        at android.view.ViewGroup.layout(ViewGroup.java:6194)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
        at android.view.View.layout(View.java:20672)
        at android.view.ViewGroup.layout(ViewGroup.java:6194)
        at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1812)
        at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1656)
        at android.widget.LinearLayout.onLayout(LinearLayout.java:1565)
        at android.view.View.layout(View.java:20672)
        at android.view.ViewGroup.layout(ViewGroup.java:6194)
        at android.widget.FrameLayout.layoutChildren(FrameLayout.java:323)
        at android.widget.FrameLayout.onLayout(FrameLayout.java:261)
        at com.android.internal.policy.DecorView.onLayout(DecorView.java:753)
        at android.view.View.layout(View.java:20672)
        at android.view.ViewGroup.layout(ViewGroup.java:6194)
        at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2792)
        at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2319)
        at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1460)
        at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7183)
2020-02-18 18:34:07.288 28614-28614/com.hooni.macrotracker E/AndroidRuntime:     at android.view.Choreographer$CallbackRecord.run(Choreographer.java:949)
        at android.view.Choreographer.doCallbacks(Choreographer.java:761)
        at android.view.Choreographer.doFrame(Choreographer.java:696)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:935)
        at android.os.Handler.handleCallback(Handler.java:873)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:193)
        at android.app.ActivityThread.main(ActivityThread.java:6669)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

при добавлении нового элемента в recyclerView.

1 Ответ

0 голосов
/ 29 февраля 2020

Проблема связана с SelectrionTracker (RecyclerView Selection, который был только что кратко упомянут) и его StableIdKeyProvider.

Итак ... очевидно, что этот cra sh происходит, когда StableIdKeyProvider пытается удалить / заменил стабильный идентификатор, в то время как элемент (вид карты) больше не присоединен к ViewHolder (DateViewHolder или ConsumedFoodViewHolder).

Честно говоря, я все еще не понимаю логи c за этим, но как только я это сделаю, я вернусь и обновлю этот пост.

Решение состоит в том, чтобы предоставить пользовательский KeyProvider, такой как следующий:

class ConsumedFoodListItemKeyProvider(private val recyclerView: RecyclerView) : ItemKeyProvider<Long>(ItemKeyProvider.SCOPE_MAPPED) {
    override fun getKey(position: Int): Long? {
        return recyclerView.adapter?.getItemId(position)
    }

    override fun getPosition(key: Long): Int {
        val viewHolder = recyclerView.findViewHolderForItemId(key)
        return viewHolder?.layoutPosition ?: RecyclerView.NO_POSITION
    }
}

и использовать его в SelectionTracker.Builder

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...