【问题标题】:Initialize viewHolder in Fragment在 Fragment 中初始化 viewHolder
【发布时间】:2023-02-03 07:08:42
【问题描述】:

我想通过我的应用程序中的对话框添加编辑任务。问题是我无法初始化 viewHolder 以访问 bindingAdapterPosition。我需要它来传递数据并更新 viewModel。尝试将它添加到构造函数中 - 没有用。我知道我必须初始化 viewHolder,但不知道如何初始化。

RecyclerviewFragment.kt:

class RecyclerviewFragment : Fragment() {

    private lateinit var mUserViewModel: UserViewModel
    private lateinit var viewHolder: ViewHolder
    private lateinit var adapter: ListAdapter

    private var _binding: FragmentRecyclerviewBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        _binding = FragmentRecyclerviewBinding.inflate(inflater, container, false)
        mUserViewModel = ViewModelProvider(this)[UserViewModel::class.java]

        adapter = ListAdapter{showUpdateDialog()}
        val adapter = ListAdapter{showUpdateDialog()}
        val recyclerView = binding.recyclerView
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(requireContext())

        // Creates a controller responsible for swiping and moving the views in recyclerview
        val itemTouchController = ItemTouchHelper(
            object : ItemTouchHelper.SimpleCallback(
                ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT
            ) {
                override fun onMove(
                    recyclerView: RecyclerView,
                    viewHolder: ViewHolder, target: ViewHolder,
                ): Boolean {
                    // Move specific item from "fromPos" to "toPos" in recyclerview adapter
                    val fromPos = viewHolder.bindingAdapterPosition
                    val toPos = target.bindingAdapterPosition
                    adapter.notifyItemMoved(fromPos, toPos)
                    return true // true if moved, false otherwise

                }

                override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
                    mUserViewModel.deleteUser(adapter.getTaskPosition(viewHolder.bindingAdapterPosition))
                    Toast.makeText(context, "Task deleted", Toast.LENGTH_SHORT).show()
                    adapter.notifyItemRemoved(viewHolder.bindingAdapterPosition)
                }
            })

        itemTouchController.attachToRecyclerView(binding.recyclerView)

        mUserViewModel.readAllData.observe(viewLifecycleOwner) { user ->
            adapter.setData(user)
        }
        return binding.root
    }

    private fun updateItemInDatabase(dialog: DialogInterface) {
        val editText = (dialog as AlertDialog).findViewById<EditText>(R.id.editTextDialog)
        val task = editText?.text.toString()

        if(inputCheck(task)) {
            // Update an entity
            mUserViewModel.updateUser(adapter.getTaskPosition(viewHolder.bindingAdapterPosition))
            Toast.makeText(context, "Task updated", Toast.LENGTH_SHORT).show()
        }
        else {
            Toast.makeText(context, "Please fill out required fields", Toast.LENGTH_SHORT).show()
        }
    }

    private fun inputCheck(task: String): Boolean {
        return !(TextUtils.isEmpty(task))
    }

    private fun showUpdateDialog() {
            MaterialAlertDialogBuilder(requireContext())
                .setView(R.layout.fragment_add)
                .setNegativeButton(getString(R.string.cancel)) { _, _ ->
                    // Respond to negative button press
                    Toast.makeText(context, getString(R.string.cancelled), Toast.LENGTH_SHORT).show()
                }
                .setPositiveButton(getString(R.string.ok)) { dialogInterface, _ ->
                    // Respond to positive button press
                    updateItemInDatabase(dialogInterface)
                }
                .show()
    }
}

编辑:

class RecyclerviewFragment : Fragment() {

    private lateinit var mUserViewModel: UserViewModel
    private lateinit var adapter: ListAdapter

    private var _binding: FragmentRecyclerviewBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        _binding = FragmentRecyclerviewBinding.inflate(inflater, container, false)
        mUserViewModel = ViewModelProvider(this)[UserViewModel::class.java]

        adapter = ListAdapter{ user ->  showUpdateDialog(user)}
        val recyclerView = binding.recyclerView
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(requireContext())

        // Creates a controller responsible for swiping and moving the views in recyclerview
        val itemTouchController = ItemTouchHelper(
            object : ItemTouchHelper.SimpleCallback(
                ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT
            ) {
                override fun onMove(
                    recyclerView: RecyclerView,
                    viewHolder: ViewHolder, target: ViewHolder,
                ): Boolean {
                    // Move specific item from "fromPos" to "toPos" in recyclerview adapter
                    val fromPos = viewHolder.bindingAdapterPosition
                    val toPos = target.bindingAdapterPosition
                    adapter.notifyItemMoved(fromPos, toPos)
                    return true // true if moved, false otherwise

                }

                override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
                    mUserViewModel.deleteUser(adapter.getTaskPosition(viewHolder.bindingAdapterPosition))
                    Toast.makeText(context, "Task deleted", Toast.LENGTH_SHORT).show()
                    adapter.notifyItemRemoved(viewHolder.bindingAdapterPosition)
                }
            })

        itemTouchController.attachToRecyclerView(binding.recyclerView)

        mUserViewModel.readAllData.observe(viewLifecycleOwner) { user ->
            adapter.setData(user)
        }
        return binding.root
    }

    private fun updateItemInDatabase(user: User) {
        val editText = view?.findViewById<EditText>(R.id.editTextDialog)
        val task = editText?.text.toString()

        if(inputCheck(task)) {
            // Update an entity
            mUserViewModel.updateUser(user)
            Toast.makeText(context, "Task updated", Toast.LENGTH_SHORT).show()
        }
        else {
            Toast.makeText(context, "Please fill out required fields", Toast.LENGTH_SHORT).show()
        }
    }

    private fun inputCheck(task: String): Boolean {
        return !(TextUtils.isEmpty(task))
    }

    private fun showUpdateDialog(user: User) {
            MaterialAlertDialogBuilder(requireContext())
                .setView(R.layout.fragment_add)
                .setNegativeButton(getString(R.string.cancel)) { _, _ ->
                    // Respond to negative button press
                    Toast.makeText(context, getString(R.string.cancelled), Toast.LENGTH_SHORT).show()
                }
                .setPositiveButton(getString(R.string.ok)) { _, _ ->
                    // Respond to positive button press
                    val taskText = view
                        ?.findViewById<EditText>(R.id.editTextDialog)
                        ?.text?.toString()
                    updateItemInDatabase(user)
                }
                .show()
    }
}

当您在 updateDialog 中按 ok 时,应用程序不会崩溃,但它不会真正更新数据库或 recyclerview 项目。原因是我不知道如何更新它,因为我让列表适配器返回整个用户(id,任务)并且不知道如何只更新任务。添加一些适配器代码让它自己解释。

class ListAdapter(var imageListener:(user: User)->Unit) : RecyclerView.Adapter<ListAdapter.MyViewHolder>() {
...
}

override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val currentItem = dataSet[position]
        holder.taskTitle.text = currentItem.task

        holder.editImage.setOnClickListener {
            imageListener(getTaskPosition(position))
        }

        holder.notificationImage.setOnClickListener {
            val action = RecyclerviewFragmentDirections.actionRecyclerFragmentToNotificationFragment()
            holder.itemView.findNavController().navigate(action)
        }
    }

    fun getTaskPosition(position: Int): User {
        return dataSet[position]
    }

我明白了,似乎更了解这个问题。现在我发现我并没有充分利用从适配器传递数据的全部潜力,但仍然存在一个问题,如果你能指导我完成它,我将很荣幸 :))

编辑 2:

class RecyclerviewFragment : Fragment() {

    private lateinit var mUserViewModel: UserViewModel
    private lateinit var adapter: ListAdapter

    private var _binding: FragmentRecyclerviewBinding? = null
    private val binding get() = _binding!!

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?,
    ): View {
        _binding = FragmentRecyclerviewBinding.inflate(inflater, container, false)
        mUserViewModel = ViewModelProvider(this)[UserViewModel::class.java]

        adapter = ListAdapter{ user ->  showUpdateDialog(user)}
        val recyclerView = binding.recyclerView
        recyclerView.adapter = adapter
        recyclerView.layoutManager = LinearLayoutManager(requireContext())

        // Creates a controller responsible for swiping and moving the views in recyclerview
        val itemTouchController = ItemTouchHelper(
            object : ItemTouchHelper.SimpleCallback(
                ItemTouchHelper.UP or ItemTouchHelper.DOWN, ItemTouchHelper.LEFT
            ) {
                override fun onMove(
                    recyclerView: RecyclerView,
                    viewHolder: ViewHolder, target: ViewHolder,
                ): Boolean {
                    // Move specific item from "fromPos" to "toPos" in recyclerview adapter
                    val fromPos = viewHolder.bindingAdapterPosition
                    val toPos = target.bindingAdapterPosition
                    adapter.notifyItemMoved(fromPos, toPos)
                    return true // true if moved, false otherwise

                }

                override fun onSwiped(viewHolder: ViewHolder, direction: Int) {
                    mUserViewModel.deleteUser(adapter.getTaskPosition(viewHolder.bindingAdapterPosition))
                    Toast.makeText(context, "Task deleted", Toast.LENGTH_SHORT).show()
                    adapter.notifyItemRemoved(viewHolder.bindingAdapterPosition)
                }
            })

        itemTouchController.attachToRecyclerView(binding.recyclerView)

        mUserViewModel.readAllData.observe(viewLifecycleOwner) { user ->
            adapter.setData(user)
        }
        return binding.root
    }

    @SuppressLint("NotifyDataSetChanged")
    private fun updateItemInDatabase(user: User) {
        val editText = view?.findViewById<EditText>(R.id.editTextDialog)
        val task = editText?.text.toString()

        if(inputCheck(task)) {
            // Update an entity
            mUserViewModel.updateUser(user)
            Toast.makeText(context, "Task updated", Toast.LENGTH_SHORT).show()
            adapter.notifyDataSetChanged()
        }
        else {
            Toast.makeText(context, "Please fill out required fields", Toast.LENGTH_SHORT).show()
        }
    }

    private fun inputCheck(task: String): Boolean {
        return !(TextUtils.isEmpty(task))
    }

    private fun showUpdateDialog(user: User) {
            MaterialAlertDialogBuilder(requireContext())
                .setView(R.layout.fragment_add)
                .setNegativeButton(getString(R.string.cancel)) { _, _ ->
                    // Respond to negative button press
                    Toast.makeText(context, getString(R.string.cancelled), Toast.LENGTH_SHORT).show()
                }
                .setPositiveButton(getString(R.string.ok)) { _, _ ->
                    // Respond to positive button press
                    val taskText = view
                        ?.findViewById<EditText>(R.id.editTextDialog)
                        ?.text?.toString()
                    updateItemInDatabase(User(user.id, taskText.toString()))
                }
                .show()
    }
}

使用此代码,该功能似乎可以正常工作,但是,它无法访问 taskText 值?如果我尝试编辑模拟器中的任何任务,它会更新为“空”提供 ViewModel,但我认为那里没有问题。它可能植根于价值本身的某个地方。

用户视图模型.kt:

class UserViewModel(application: Application) : AndroidViewModel(application) {

    val readAllData: LiveData<List<User>>
    private val repository: UserRepository

    init {
        val userDao = UserDatabase.getDatabase(application).userDao()
        repository = UserRepository(userDao)
        readAllData = repository.readAllData
    }

    fun addUser(user: User) {
        viewModelScope.launch(Dispatchers.IO) {
            repository.addUser(user)
        }
    }

    fun updateUser(user: User) {
        viewModelScope.launch(Dispatchers.IO) {
            repository.updateUser(user)
        }
    }

    fun deleteUser(user: User) {
        viewModelScope.launch(Dispatchers.IO) {
            repository.deleteUser(user)
        }
    }
}

【问题讨论】:

  • ViewHolder 是你的Adapter 处理的内部事务——Fragment 根本不应该直接与他们交互。你的Adapter 应该通过了事件对听众来说,比如删除此项管他呢。如果您的Fragment 处理了这些事件,它会显示一个确认对话框,然后调用ViewModel 上的deleteItem(item) 方法。把它想象成流动的事件适配器 -> 片段 -> VM -> 存储库等等,你不需要“回去”问适配器它又在看哪个项目
  • 我不认为你理解这个问题。删除方法非常有效,但是,如果我尝试在编辑对话框中按下“确定”按钮,一切都会崩溃,导致 viewHolder 未初始化。
  • 为什么你的updateItemInDatabase函数需要访问ViewHolder?它没有被初始化,因为它是Fragment 中的局部变量,而且您还没有将它设置为任何值。但是你无论如何不应该那样做因为 Fragment 不应该像那样在 Adapter 的内部闲逛。如果您要保留对任何内容的引用,只需首先从适配器存储您想要的内容 - 在本例中,是 getTaskPosition 的结果。当用户单击删除或其他任何操作时(从适配器)将其传递出去,并在确认对话框时使用它

标签: android


【解决方案1】:

就像一个快速草图,所以你明白我在说什么:

// your adapter already takes a callback function - make it send some data
// about the item being clicked. I'm assuming it's an ID here but you could
// pass back a specific object too
class ListAdapter(
    private val onDeleteListener: (itemId: Int) -> Unit
) ...

Fragment

// onCreate
adapter = ListAdapter { itemId -> showUpdateDialog(itemId) }

// dialog function should take the ID as a parameter
private fun showUpdateDialog(itemId: Int) {
    ...
    .setPositiveButton(getString(R.string.ok)) { dialogInterface, _ ->
        // Don't send the dialog interface - pass the actual data you want to use
        val taskText = (dialogInterface as AlertDialog)
            .findViewById<EditText>(R.id.editTextDialog)
            ?.text?.toString()
        updateItemInDatabase(itemId, taskText)
    }
...
}


// Your update function acts on specific data - it has no knowledge of how the rest
// of the app is implemented, it's not hardwired into other components etc
private fun updateItemInDatabase(itemId: Int, task: String?) {
    if(inputCheck(task)) {
        mUserViewModel.updateUser(itemId)
    }
}

看看这有多简单?您有一个明确的数据流方向,其中适配器将特定数据片段交给其回调函数,回调函数将其传递给确认对话框,确认对话框将其传递给需要该特定数据片段的更新函数。 Adapter 唯一参与的是说“嘿,点击了一个项目,这是信息”。您不需要稍后再询问更多详细信息,例如“嘿,您当前正在显示什么”——这是作为事件数据的一部分传递的。


总的来说这更干净,但是尤其对于 RecyclerViews,您不想戳穿它们的内部结构,保留对 ViewHolders 等的引用,因为该状态是易变的。它们的工作方式是重复使用这些对象来显示不同的数据,因此假设它们正在显示特定项目而保持对它们的长期运行引用是自找麻烦。它大概在这里没那么重要(如果你点击一个项目来获得一个对话框,用户可能无法让它滚动到另一个位置)但最好不要做那件事。


顺便说一句,这是一个错误:

// top-level variable
adapter = ListAdapter{showUpdateDialog()}
// local variable
val adapter = ListAdapter{showUpdateDialog()}
// local variable
recyclerView.adapter = adapter

您正在创建 ListAdapter 的两个独立实例 - 一个是长期存储的,另一个是您在 RecyclerView 上实际设置的实例。长期的是你在updateItemInDatabase中访问的,RecyclerView实际上没有使用的那个,所以它不是实际被点击的东西(它不会有任何ViewHolder还是)。这就是为什么最好只在一个方向上传递数据,如果可以的话,引入并发症的机会更少!

【讨论】:

  • 你能看看我的编辑吗?认为我们正在取得进展:))
  • @多雾路段它并没有真正更新数据库或 recyclerview 项目- 这取决于你在mUserViewModel.updateUser 中所做的事情。您已经在观察 mUserViewModel.readAllData 并在该值更改时调用 adapter.setData,因此当您在该 LiveData 上设置新值时,您的 RecyclerView 将更新(只要 setData 也刷新为 notifyDatasetChanged 或某物)。因此,您的 ViewModel 需要更新 readAllData - 直接(设置其 value)或通过公开数据库查询返回的 LiveData,它会在您更新数据库时更新
  • 你现在怎么看?你能想到我可以去的任何方向吗?
猜你喜欢
  • 1970-01-01
  • 2020-07-12
  • 2013-05-23
  • 2022-01-14
  • 2021-12-14
  • 2020-01-18
  • 2012-11-14
  • 2018-10-12
  • 1970-01-01
相关资源
最近更新 更多