kotlin 回收者视图不使用实时数据更新项目

k75qkfdt  于 2022-12-19  发布在  Kotlin
关注(0)|答案(2)|浏览(132)

bounty已结束。回答此问题可获得+50声望奖励。奖励宽限期将在15小时后结束。jane希望引起更多人关注此问题。

我有一个片段,其中使用创建一个特定类别的预算如下:

下面是它的工作原理:用户在NewBudgetFragment中添加新预算项。该项将显示在recyclerview的BudgetFragment中。预算项具有amountSpent变量,用户每次添加新事务时都应更新该变量(这发生在另一个片段中)。但是在创建预算项目之后,如果用户在该特定类别上花钱,amountSpent在recyclerview项中没有得到更新。我在BudgetAdapter中同时使用了LiveData和DiffUtil,但我不知道为什么它没有得到更新。
下面是预算适配器:

class BudgetAdapter() : ListAdapter<Budget, BudgetAdapter.BudgetViewHolder>(DiffCallback()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): BudgetViewHolder {
        val binding =
            BudgetItemLayoutBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        return BudgetViewHolder(binding)
    }

    override fun onBindViewHolder(holder: BudgetViewHolder, position: Int) {
        val currentItem = getItem(position)
        holder.bind(currentItem, position)
    }

    class BudgetViewHolder(val binding: BudgetItemLayoutBinding) :
        RecyclerView.ViewHolder(binding.root) {
        fun bind(budget: Budget, position: Int) {
            binding.apply {
                tvBudgetName.text = budget.name
                tvBudgetLimit.text = budget.limit.toString()
                tvAmountSpent.text = budget.amountSpent.toString()
                tvPercentageSpent.text = ((budget.amountSpent/budget.limit)*100).toInt().toString() + "%"
            }
        }
    }

    class DiffCallback : DiffUtil.ItemCallback<Budget>() {
        override fun areItemsTheSame(oldItem: Budget, newItem: Budget): Boolean {
            return oldItem.id == newItem.id
        }

        override fun areContentsTheSame(oldItem: Budget, newItem: Budget): Boolean {
            return oldItem == newItem
        }

    }
}

以下是创建新预算项目的方法:新预算片段:

...
viewModel.transactions.observe(viewLifecycleOwner) { it ->
                            transactionList = it.filter { it.category == listCategory[selectedCategoryIndex].name }
                            amountSpent = transactionList.sumOf { it.amount }
                        }
...

if (budgetName.isNotEmpty() && budgetLimit.isNotEmpty() && budgetCategory != null) {
                            viewModel.addBudget(
                                name = budgetName,
                                limit = budgetLimit.toDouble(),
                                amountSpent=amountSpent,
                                category = budgetCategory.name)

这是BudgetFragment.kt,其中适配器是:

class BudgetFragment : Fragment(R.layout.fragment_budget),BudgetAdapter.OnItemClickListener {
    private lateinit var binding: FragmentBudgetBinding
    private val viewModel: SharedViewModel by activityViewModels()

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        binding = FragmentBudgetBinding.bind(view)
        val budgetAdapter = BudgetAdapter(this)

        val toolbar = binding.toolbar.root
        toolbar.title = "Budget"
        (requireActivity() as MainActivity).setSupportActionBar(toolbar)

        binding.apply {
            rvBudget.apply {
                adapter = budgetAdapter
                setHasFixedSize(true)
            }
        }
        viewModel.budgets.observe(viewLifecycleOwner){
            if(it.isNotEmpty()){
                binding.rvBudget.visibility = View.VISIBLE
                binding.tvNoBudget.visibility = View.INVISIBLE
            }else{
                binding.rvBudget.visibility = View.INVISIBLE
                binding.tvNoBudget.visibility = View.VISIBLE
            }
            budgetAdapter.submitList(it)

        }

        binding.btAddBudget.setOnClickListener {
            val action = BudgetFragmentDirections.actionBudgetFragmentToNewBudgetFragment()
            findNavController().navigate(action)
        }
    }
arknldoa

arknldoa1#

我试着在互联网上找到一些示例解决方案。肯定有一个,但是找不到。我发现其中很多都太短或太详细。所以我将在这里提供您所需要的。有一个涉及RecyclerView + LiveData的标准化模式,所以在所有RecyclerView + LiveData的使用中都遵循这个模式。

片段:

override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
): View? {

    ...

    val recyclerView = view.findViewById(R.id.recyclerView)
    val myAdapter = MyAdapter()
    recyclerView.adapter = myAdapter
    // recyclerView.layoutManager = LinearLayoutManager(context) // You need this only if you haven't set it in xml

    db.task().getAll().observe(viewLifecycleOwner, Observer {
        myAdapter.submitList(it)
    })
}

此处进行了一些重要更改:
必须在onCreateView()中使用.observe()。不仅仅是在这个特定的情况下,但通常情况下,如果正确使用LiveData,则永远不需要在任何其他位置调用.observe()viewLifecycleOwner是用于观察LiveData的正确LifecycleOwner。(除非您创建了自定义适配器。)取决于用例,但通常每个RecyclerView示例化一个适配器,即使您的数据随时间而更改。您应该交换数据,而不是整个适配器。

我的适配器:

MyAdapter应实现视图将使用的.submitList()函数

class MyAdapter: RecyclerView.Adapter<ViewHolder>() {

    val myData = mutableListOf<Data>()

    fun submitList(newData: List<Data>) {
        myData.clear()
        myData.addAll(newData)
        notifyDataSetChanged()
    }
}

请注意,notifyDataSetChanged()实际上是向适配器发出更新视图的信号。

wqsoz72f

wqsoz72f2#

假设Budget类是一个数据类,那么内容比较应该可以工作,所以我们应该不会遇到适配器和DiffUtil使用的对象本身的任何问题。
但是:
我看不到ViewModel代码-您是否使用submitList将相同的列表示例提交给适配器?例如,您是否每次都在ViewModel中修改私人列表中的项目,并在相同的动态数据上发布相同的列表?
如果是,则这可能是RecyclerView中的项目未被刷新的原因。您需要使用旧列表的内容创建新的列表示例,然后将其发布到LiveData上。

示例ViewModel,如果您不想覆盖“submitList”的行为,即清除以前的数据并添加新数据,然后自己调用notifiyDatasetChanged()

class MyBudgetViewModel : ViewModel() {
    // Important that its a data class, to actually have a content sensistive equals comparison

    // if you don't have an ID, you have to work with list indexes when finding
    // and updating this item
    data class Budget(val id: Int, val amount: Double)

    private val _budgets = MutableLiveData<List<Budget>>()
    val budgets: LiveData<List<Budget>>
        get() = _budgets

    init {
        _budgets.value = listOf(Budget(1, 20.0))
    }

    fun onBudgetChanged(id: Int, newBudget: Double) {
        // depends on your setup and how you fill the initial list, this maybe should never be null
        // by the time you call onBudgetChanged or something similar
        val oldList = _budgets.value ?: emptyList()

        // unused example variable - if you want to copy the list 1 by 1, ArrayList takes another list as Constructor 
        val newListUnchanged = ArrayList(oldList)

        // map returns a new instance of the list.
        val newList = oldList.map { oldItem ->
            if (oldItem.id == id) {
                oldItem.copy(amount = newBudget)
            } else oldItem
        }
        _budgets.value = newList
    }
}

相关问题