You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用DeepLink在Fragment间传数据遇空值错误的无ViewModel解决方案

问题描述

点击任务标题打开详情页时提示“所需值为空”,需传递任务标题、日期、描述数据,采用data class实现,不使用ViewModel,寻求解决方案。


相关代码

TodoAdapter

package com.bignerdranch.android.taskmaster

import android.graphics.Paint.STRIKE_THRU_TEXT_FLAG
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.CheckBox
import android.widget.TextView
import androidx.core.net.toUri
import androidx.navigation.NavDeepLinkRequest
import androidx.navigation.findNavController
import androidx.navigation.fragment.NavHostFragment

import androidx.recyclerview.widget.RecyclerView
import com.bignerdranch.android.taskmaster.Constants.TASK_BUNDLE_KEY
import com.bignerdranch.android.taskmaster.Constants.TASK_DATE_BUNDLE_KEY
import com.bignerdranch.android.taskmaster.TaskListFragment.Companion.TASK_DETAIL_URI
import com.bignerdranch.android.taskmaster.databinding.ItemTodoBinding
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch


class TodoAdapter(
    private var todos: MutableList<Todo>, private val todoDao: TodoDao
):RecyclerView.Adapter<TodoAdapter.TodoViewHolder>() {
    class TodoViewHolder( ItemTodoBinding: ItemTodoBinding): RecyclerView.ViewHolder(ItemTodoBinding.root)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {

        val layoutInflater = LayoutInflater.from(parent.context)
        val ItemTodoBinding = ItemTodoBinding.inflate(layoutInflater, parent, false)

        return TodoViewHolder(
            return TodoViewHolder(ItemTodoBinding)
        )
    }
    private fun toggleStrikeThrough(tvTodoTitle: TextView, isChecked:Boolean){
        if(isChecked){
            tvTodoTitle.paintFlags = tvTodoTitle.paintFlags or STRIKE_THRU_TEXT_FLAG
        }else{
            tvTodoTitle.paintFlags = tvTodoTitle.paintFlags and STRIKE_THRU_TEXT_FLAG.inv()
        }
    }

    override fun onBindViewHolder(holder: TodoViewHolder, position: Int) {
          val curTodo = todos[position]
          holder.itemView.apply{
              val tvTodoTitle = findViewById(R.id.tvTodoTitle) as TextView
              tvTodoTitle.text = curTodo.title
              val cbDone = findViewById(R.id.cbDone) as CheckBox
              val deleteBtn = findViewById(R.id.deleteBtn) as Button
              cbDone.isChecked = curTodo.isChecked
              toggleStrikeThrough(tvTodoTitle, curTodo.isChecked)
              cbDone.setOnCheckedChangeListener { _, isChecked ->
                  toggleStrikeThrough(tvTodoTitle, isChecked)
                  curTodo.isChecked = !curTodo.isChecked
              }
              cbDone.setOnClickListener(View.OnClickListener{
                  GlobalScope.launch {
                      todoDao.update(curTodo)
                      todoDao.delete(curTodo)
                  }
                      todos.removeAll { todo ->
                          todo.isChecked
                      }
                      notifyDataSetChanged()
              })

              deleteBtn.setOnClickListener{
                  GlobalScope.launch {
                      todoDao.delete(curTodo)
                  }
                  notifyDataSetChanged()
              }
              tvTodoTitle.setOnClickListener {
                  val request = NavDeepLinkRequest.Builder
                      .fromUri("${TASK_DETAIL_URI}?${TASK_BUNDLE_KEY}=${curTodo.title}&${TASK_DATE_BUNDLE_KEY}=${curTodo.date}&${TASK_DESCR_BUNDLE_KEY}=${curTodo.description}".toUri())
                      .build()
                  findNavController().navigate(request)
              }
        }
    }

    override fun getItemCount(): Int {
          return todos.size
    }
}

TaskListFragment

class TaskListFragment : Fragment(R.layout.fragment_task_list) {
    private lateinit var todoAdapter: TodoAdapter
    private var _binding: FragmentTaskListBinding? = null
    private lateinit var todoDao: TodoDao
    private val binding: FragmentTaskListBinding
        get() = checkNotNull(_binding)
    private var todos: MutableList<Todo> = mutableListOf()
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val appDatabase = AppDatabase.getInstance(this.requireActivity())
        todoDao = appDatabase.todoDao()
        todoDao.getAllTasks().observe(this, Observer { todoList ->
            todos.clear()
            todos.addAll(todoList)
            todoAdapter.notifyDataSetChanged()

        })

    }
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentTaskListBinding.inflate(inflater, container, false)
        return binding.root
    }
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        todoAdapter = TodoAdapter(todos, todoDao)
        binding.rvTodoItems.adapter = todoAdapter
        binding.rvTodoItems.layoutManager = LinearLayoutManager(this.activity)
        binding.btnAddTodo.setOnClickListener {
            NewTaskSheet().show(requireActivity().supportFragmentManager, "NewTaskTag")
        }
        requireActivity().supportFragmentManager.setFragmentResultListener(
            "requestKey",
            this
        ) { requestKey, bundle ->
            val newTodo = bundle.getSerializable("bundleKey") as Todo
            GlobalScope.launch {
                todoDao.insert(newTodo)
            }
            todoAdapter.notifyDataSetChanged()
        }
        binding.rvTodoItems.addItemDecoration(
            DividerItemDecoration(
                binding.rvTodoItems.getContext(),
                DividerItemDecoration.VERTICAL
            )
        )
    }
}

TaskDetailFragment

class TaskDetailFragment : Fragment() {
    private var _binding: FragmentTaskDetailBinding? = null
    private val binding: FragmentTaskDetailBinding
        get() = checkNotNull(_binding)
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val title = arguments?.getString(TASK_BUNDLE_KEY)
        val date = arguments?.getString(TASK_DATE_BUNDLE_KEY)
        val descr = arguments?.getString(TASK_DESCR_BUNDLE_KEY)
        binding.taskTitle.setText(title.toString())
        binding.selectedDate.setText(date.toString())
        binding.selectedDescription.setText(descr.toString())
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentTaskDetailBinding.inflate(inflater, container, false)
        return binding.root

    }
}

Constants

object Constants {
    const val TASK_BUNDLE_KEY = "taskBundleKey"
    const val TASK_DATE_BUNDLE_KEY = "taskDateBundleKey"
    const val TASK_DESCR_BUNDLE_KEY = "taskDescrBundleKey"
}

task_graph.xml

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/task_graph"
    app:startDestination="@id/taskListFragment">

    <fragment
        android:id="@+id/taskListFragment"
        android:name="com.bignerdranch.android.taskmaster.TaskListFragment"
        android:label="TaskListFragment" />
    <fragment
        android:id="@+id/taskDetailFragment"
        android:name="com.bignerdranch.android.taskmaster.TaskDetailFragment"
        android:label="TaskDetailFragment">
        <deepLink
            android:id="@+id/deepLink"
            app:uri="app://taskmaster.taskDetailFragment?taskBundleKey={taskBundleKey}&amp;taskDateBundleKey={taskDateBundleKey}" />
        <argument
            android:name="taskBundleKey"
            app:argType="string" />
        <argument
            android:name="taskDateBundleKey"
            app:argType="string" />
    </fragment>
    <deepLink
        android:id="@+id/deepLink2"
        app:uri="app://taskmaster.taskDetailFragment" />
</navigation>

解决方案

1. 修复导航图参数配置

导航图中缺少描述参数,且DeepLink配置不完整,修改task_graph.xml:

<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:id="@+id/task_graph"
    app:startDestination="@id/taskListFragment">

    <fragment
        android:id="@+id/taskListFragment"
        android:name="com.bignerdranch.android.taskmaster.TaskListFragment"
        android:label="TaskListFragment" />
    <fragment
        android:id="@+id/taskDetailFragment"
        android:name="com.bignerdranch.android.taskmaster.TaskDetailFragment"
        android:label="TaskDetailFragment">
        <deepLink
            android:id="@+id/deepLink"
            app:uri="app://taskmaster.taskDetailFragment?taskBundleKey={taskBundleKey}&taskDateBundleKey={taskDateBundleKey}&taskDescrBundleKey={taskDescrBundleKey}" />
        <argument
            android:name="taskBundleKey"
            app:argType="string" />
        <argument
            android:name="taskDateBundleKey"
            app:argType="string" />
        <argument
            android:name="taskDescrBundleKey"
            app:argType="string" />
    </fragment>
</navigation>

删除无参数的deepLink2,避免触发无参数的导航路径。

2. 修复TodoAdapter语法错误

onCreateViewHolder中嵌套return是语法错误,修改为:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): TodoViewHolder {
    val layoutInflater = LayoutInflater.from(parent.context)
    val ItemTodoBinding = ItemTodoBinding.inflate(layoutInflater, parent, false)
    return TodoViewHolder(ItemTodoBinding)
}

3. 替换DeepLink为Bundle传参(避免URI编码问题)

在TodoAdapter的标题点击事件中,直接用Bundle传递参数,代替DeepLink:

tvTodoTitle.setOnClickListener {
    val bundle = Bundle().apply {
        putString(TASK_BUNDLE_KEY, curTodo.title)
        putString(TASK_DATE_BUNDLE_KEY, curTodo.date)
        putString(TASK_DESCR_BUNDLE_KEY, curTodo.description)
    }
    findNavController().navigate(R.id.taskDetailFragment, bundle)
}

4. 修复TaskDetailFragment初始化时机问题

onCreate方法中binding尚未初始化,将数据赋值逻辑移到onViewCreated,同时处理参数为空的情况:

class TaskDetailFragment : Fragment() {
    private var _binding: FragmentTaskDetailBinding? = null
    private val binding: FragmentTaskDetailBinding
        get() = checkNotNull(_binding)

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentTaskDetailBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        val title = arguments?.getString(TASK_BUNDLE_KEY) ?: "无标题"
        val date = arguments?.getString(TASK_DATE_BUNDLE_KEY) ?: "无日期"
        val descr = arguments?.getString(TASK_DESCR_BUNDLE_KEY) ?: "无描述"
        
        binding.taskTitle.text = title
        binding.selectedDate.text = date
        binding.selectedDescription.text = descr
    }
}

5. 修复其他潜在问题

  • 合并CheckBox的setOnCheckedChangeListener和setOnClickListener逻辑,避免重复操作数据库,并在主线程更新UI:
cbDone.setOnCheckedChangeListener { _, isChecked ->
    toggleStrikeThrough(tvTodoTitle, isChecked)
    curTodo.isChecked = isChecked
    GlobalScope.launch {
        todoDao.update(curTodo)
        if (isChecked) {
            todoDao.delete(curTodo)
            todos.remove(curTodo)
            GlobalScope.launch(Dispatchers.Main) {
                notifyDataSetChanged()
            }
        }
    }
}
  • 在TaskListFragment的FragmentResultListener中,确保UI更新在主线程执行:
requireActivity().supportFragmentManager.setFragmentResultListener(
    "requestKey",
    this
) { requestKey, bundle ->
    val newTodo = bundle.getSerializable("bundleKey") as Todo
    GlobalScope.launch {
        todoDao.insert(newTodo)
        GlobalScope.launch(Dispatchers.Main) {
            todoAdapter.notifyDataSetChanged()
        }
    }
}

内容的提问来源于stack exchange,提问作者Владислава Черкасова

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.13 10:52:34