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

Kotlin Room MVVM架构下如何筛选展示当前队伍的LiveData球员列表

问题描述
  • 在第一个Fragment中创建了对应team_table表的队伍名称
  • 跳转至球员列表Fragment时,需要页面默认仅展示当前所选队伍对应的球员列表
  • 当前异常表现:仅当更新球员名称后返回球员列表页时筛选逻辑可正常生效,其余场景下页面会展示所有队伍的全部球员

原有实现代码

UserViewModel

class UserViewModel(application: Application): AndroidViewModel(application) {
    val readAllDataTeam: LiveData<List<Team>>?
    val readAllDataUser: LiveData<List<User>>?
    val readAllDataEval: LiveData<List<Eval>>?
    val readAllDataUserWithEval: LiveData<List<UserWithEval>>?
    val readAllDataTeamWithUser: LiveData<List<TeamWithUser>>?

    private val repositoryTeam: UserRepository
    private val repositoryUser: UserRepository
    private val repositoryEval: UserRepository
    private val repositoryUserWithEval: UserRepository
    private val repositoryTeamWithUser: UserRepository

    init {
        val teamDao = UserDatabase.getDatabase(application)?.userDao()
        repositoryTeam = UserRepository(teamDao!!)
        readAllDataTeam = repositoryTeam.readAllDataTeam

        val userDao = UserDatabase.getDatabase(application)?.userDao()
        repositoryUser = UserRepository(userDao!!)
        readAllDataUser = repositoryUser.readAllDataUser

        val evalDao = UserDatabase.getDatabase(application)?.userDao()
        repositoryEval = UserRepository(evalDao!!)
        readAllDataEval = repositoryEval.readAllDataEval

        val userEvalDao = UserDatabase.getDatabase(application)?.userDao()
        repositoryUserWithEval = UserRepository(userEvalDao!!)
        readAllDataUserWithEval= repositoryUserWithEval.readAllDataUserEval

        val teamUserDao = UserDatabase.getDatabase(application)?.userDao()
        repositoryTeamWithUser = UserRepository(teamUserDao!!)
        readAllDataTeamWithUser = repositoryTeamWithUser.readAllDataTeamWithUser
    }

    //Team
    fun addTeam(team: Team) {
        viewModelScope.launch(Dispatchers.IO) {
            repositoryTeam.addTeam(team)
        }
    }

    fun updateTeam(team: Team) {
        viewModelScope.launch(Dispatchers.IO) {
            repositoryTeam.updateTeam(team)
        }
    }

    fun deleteTeam(team: Team) {
        viewModelScope.launch(Dispatchers.IO) {
            repositoryTeam.deleteTeam(team)
        }
    }

    fun deleteAllTeams() {
        viewModelScope.launch(Dispatchers.IO) {
            repositoryTeam.deleteAllTeams()
        }
    }


    //Eval
    fun addEval(eval: Eval) {
        viewModelScope.launch(Dispatchers.IO) {
            repositoryEval.addEval(eval)
        }
    }

    fun updateEval(eval: Eval) {
        viewModelScope.launch(Dispatchers.IO) {
            repositoryEval.updateEval(eval)
        }
    }

    fun deleteEval(eval: Eval) {
        viewModelScope.launch(Dispatchers.IO) {
            repositoryEval.deleteEval(eval)
        }
    }

    fun deleteAllEvals() {
        viewModelScope.launch(Dispatchers.IO) {
            repositoryEval.deleteAllEvals()
        }
    }

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

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

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

    fun deleteAllUsers() {
        viewModelScope.launch(Dispatchers.IO) {
            repositoryUser.deleteAllUsers()
        }
    }

    fun searchDatabase(searchQuery: String): LiveData<List<User>>? {
        return repositoryUser.searchDatabase(searchQuery)
    }
    fun searchDatabaseTeam(searchQuery: String): LiveData<List<Team>>? {
        return repositoryUser.searchDatabaseTeam(searchQuery)
    }

    fun getTeam(searchTeam: String): LiveData<List<User>>? {
        return repositoryUser.getTeam(searchTeam)
    }
}

UserRepository

class UserRepository (private val userDao: UserDao) {
    val readAllDataUser: LiveData<List<User>>? = userDao.getUserData()

    val readAllDataEval: LiveData<List<Eval>>? = userDao.getEvalData()

    val readAllDataTeam: LiveData<List<Team>>? = userDao.getTeamData()

    val readAllDataUserEval: LiveData<List<UserWithEval>>? = userDao.getAllUserWithEval()

    val readAllDataTeamWithUser: LiveData<List<TeamWithUser>>? = userDao.getAllTeamWithUser()

    //User
    suspend fun addUser(user: User) {
        userDao.addUser(user)
    }

    suspend fun updateUser(user: User) {
        userDao.updateUser(user)
    }

    suspend fun deleteUser(user: User) {
        userDao.deleteUser(user)
    }

    suspend fun deleteAllUsers() {
        userDao.deleteAllUsers()
    }

    //Eval
    suspend fun addEval(eval: Eval) {
        userDao.addEval(eval)
    }

    suspend fun updateEval(eval: Eval) {
        userDao.updateEval(eval)
    }

    suspend fun deleteEval(eval: Eval) {
        userDao.deleteEval(eval)
    }

    suspend fun deleteAllEvals() {
        userDao.deleteAllEvals()
    }

    //Team
    suspend fun addTeam(team: Team) {
        userDao.addTeam(team)
    }

    suspend fun updateTeam(team: Team) {
        userDao.updateTeam(team)
    }

    suspend fun deleteTeam(team: Team) {
        userDao.deleteTeam(team)
    }

    suspend fun deleteAllTeams() {
        userDao.deleteAllTeams()
    }

    fun searchDatabase(searchQuery: String): LiveData<List<User>>? {
        return userDao.searchDatabase(searchQuery)
    }

    fun searchDatabaseTeam(searchQuery: String): LiveData<List<Team>>? {
        return userDao.searchDatabaseTeam(searchQuery)
    }

    fun getTeam(searchTeam: String): LiveData<List<User>>? {
        return userDao.getTeam(searchTeam)
    }
}

UserDao

@Dao
interface UserDao {

    @Query("SELECT * FROM user_table ORDER BY id ASC")
    fun getUserData(): LiveData<List<User>>?

    @Insert(onConflict = OnConflictStrategy.IGNORE)
    fun addUser(user: User)

    @Update
    fun insertEval(eval: Eval)

    @Update
    fun updateUser(user: User)

    @Delete
    fun deleteUser(user: User)

    @Query("DELETE FROM user_table")
    fun deleteAllUsers()

    @Query("SELECT * FROM user_table WHERE id=:id")
    fun getOneUser(id: Int): User

    @Transaction
    @Query("SELECT * FROM user_table JOIN eval_table  on user_table.id = eval_table.idEval ")
    fun getAllUserWithEval(): LiveData<List<UserWithEval>>

    @Query("SELECT * FROM user_table WHERE firstName LIKE :searchQuery OR lastName LIKE :searchQuery OR nbTeam LIKE :searchQuery OR nameTeam LIKE :searchQuery")
    fun searchDatabase(searchQuery: String): LiveData<List<User>>?

    @Query("SELECT * FROM team_table WHERE  nameTeam LIKE :searchQuery")
    fun searchDatabaseTeam(searchQuery: String): LiveData<List<Team>>?

    @Transaction
    @Query("SELECT * FROM user_table WHERE nameTeam LIKE :searchTeam")
    fun getTeam(searchTeam: String): LiveData<List<User>>?
}

ListFragment

class ListFragment : Fragment(), SearchView.OnQueryTextListener {

    private var _binding: FragmentListBinding? = null
    private val binding get() = _binding!!
    private lateinit var mUserViewModel: UserViewModel
    private val args by navArgs<ListFragmentArgs>()


    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        _binding = FragmentListBinding.inflate(inflater, container,false)
        val view = binding.root
        val adapter = ListAdapter()
        val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerview)
        recyclerView?.adapter = adapter
        recyclerView?.layoutManager = LinearLayoutManager(requireContext())

        val searchTeam = args.currentTeam.nameTeam

        binding.tvNameTeam.text = searchTeam

        mUserViewModel = ViewModelProvider(this).get(UserViewModel::class.java)


        mUserViewModel.getTeam(searchTeam)?.observe(viewLifecycleOwner, Observer {
            userT -> adapter.setData(userT)
        })

        binding.floatingActionButton.setOnClickListener{
            findNavController().navigate(R.id.action_listFragment_to_addFragment)
        }

        setHasOptionsMenu(true)
        return view
    }

    override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
        inflater.inflate(R.menu.main_menu, menu)
        inflater.inflate(R.menu.delete_menu, menu)
        val search = menu?.findItem(R.id.menu_search)
        val searchView = search?.actionView as? SearchView
        searchView?.isSubmitButtonEnabled = true
        searchView?.setOnQueryTextListener(this)
        return
    }

    override fun onOptionsItemSelected(item: MenuItem): Boolean {
        if (item.itemId == R.id.menu_delete){
            deleteAllUsers()
        }
        return super.onOptionsItemSelected(item)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }

    private fun deleteAllUsers() {
        val builder = AlertDialog.Builder(requireContext())
        builder.setPositiveButton("Yes"){ _, _ ->
            mUserViewModel.deleteAllUsers()
            Toast.makeText(
                requireContext(),
                "Suppression complète réussie",
                Toast.LENGTH_SHORT).show()
        }
        builder.setNegativeButton("No"){_, _ -> }
        builder.setTitle("Tout Supprimer?")
        builder.setMessage("Êtes-vous sûr de vouloir supprimer l'ensemble des données?")
        builder.create().show()
    }

    override fun onQueryTextSubmit(query: String?): Boolean {
        return true
    }

    override fun onQueryTextChange(query: String?): Boolean {
        if(query != null){
            searchDatabase(query)
        }
        return true
    }

    private fun searchDatabase(query: String) {
        val searchQuery = "%$query%"

        val adapter = ListAdapter()
        val recyclerView = view?.findViewById<RecyclerView>(R.id.recyclerview)
        recyclerView?.adapter = adapter
        recyclerView?.layoutManager = LinearLayoutManager(requireContext())
        mUserViewModel.searchDatabase(searchQuery)?.observe(this, { userT->
            userT.let {
                adapter.setData(it)
            }
        })
    }
}

ListAdapter

class ListAdapter : RecyclerView.Adapter<ListAdapter.MyViewHolder>() {

    var userTList = emptyList<User>()

    class MyViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
        return MyViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.custom_row, parent, false))
    }

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

    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
        val currentItemUT = userTList[position]

        holder.itemView.findViewById<TextView>(R.id.id_txt).text = currentItemUT.id.toString()
        holder.itemView.findViewById<TextView>(R.id.firstName_txt).text = currentItemUT.firstName
        holder.itemView.findViewById<TextView>(R.id.lastName_txt).text = currentItemUT.lastName
        holder.itemView.findViewById<TextView>(R.id.numeroTeam_txt).text = currentItemUT.nbTeam.toString()

        holder.itemView.findViewById<View>(R.id.rowLayout).setOnClickListener {
            val action = ListFragmentDirections.actionListFragmentToUpdateFragment(currentItemUT)
            holder.itemView.findNavController().navigate(action)
        }

        holder.itemView.findViewById<Button>(R.id.btn_start_eval).setOnClickListener {
            val action = ListFragmentDirections.actionListFragmentToEvalFragment(currentItemUT)
            holder.itemView.findNavController().navigate(action)
        }

    }

    fun setData(user: List<User>){
        this.userTList = user
        notifyDataSetChanged()
    }

}

问题根因
  1. 参数传递缺失:点击悬浮按钮跳转添加球员页时,没有把当前选中的队伍名称传递过去,新增球员时无法给User实体的nameTeam字段赋值,导致新球员没有关联所属队伍
  2. 动态查询未托管:带参数的getTeam()查询每次调用都会返回新的LiveData实例,ViewModel没有统一托管当前选中队伍对应的数据源,首次进入页面时新创建的LiveData没有触发初始数据加载,只有数据变更(比如更新球员)时Room才会推送数据,导致首次进入显示全量数据
  3. Adapter重复创建:搜索方法里每次触发搜索都会新建Adapter替换RecyclerView原有实例,导致原有观察队伍筛选的观察者绑定到旧Adapter上,数据更新无法同步到当前显示的列表
  4. LIKE查询语法问题:getTeam方法的SQL用了LIKE匹配,但传入的参数没有加%通配符,只有完全匹配队伍名的球员才会被查出,容错性差

修复步骤

1. 修改ViewModel,用switchMap托管动态查询

把当前选中的队伍名作为可观察的MutableLiveData,用switchMap自动切换对应的球员列表数据源,保证整个页面生命周期内只观察同一个LiveData实例:

// 在UserViewModel里增加以下代码
// 当前选中的队伍名,默认空
private val _currentTeamName = MutableLiveData("")
// 当前队伍对应的球员列表,switchMap会自动根据_currentTeamName的值切换查询结果
val currentTeamUsers: LiveData<List<User>> = _currentTeamName.switchMap { teamName ->
    if (teamName.isNullOrBlank()) {
        // 没选队伍时返回空列表
        MutableLiveData(emptyList())
    } else {
        // 选了队伍就查对应球员,加通配符做模糊匹配
        repositoryUser.getTeam("%$teamName%")
    }
}

// 新增方法:设置当前选中的队伍
fun setCurrentTeam(teamName: String) {
    _currentTeamName.value = teamName
}

2. 修正跳转逻辑,传递队伍参数到添加页

修改ListFragment里悬浮按钮的点击事件,把当前队伍名通过Safe Args传给添加Fragment:

binding.floatingActionButton.setOnClickListener{
    val action = ListFragmentDirections.actionListFragmentToAddFragment(searchTeam)
    findNavController().navigate(action)
}

在AddFragment里接收传递过来的队伍名参数,新增User对象时把nameTeam字段设置为这个参数值,保证新球员关联到当前队伍。

3. 调整ListFragment的观察逻辑,复用同一个Adapter

把Adapter初始化和观察者绑定放到合适的位置,不要在搜索方法里重复创建Adapter:

// 把adapter提为成员变量
private val adapter = ListAdapter()

override fun onCreateView(
    inflater: LayoutInflater, container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    _binding = FragmentListBinding.inflate(inflater, container,false)
    val view = binding.root
    // 初始化RecyclerView,只设置一次adapter
    val recyclerView = view.findViewById<RecyclerView>(R.id.recyclerview)
    recyclerView?.adapter = adapter
    recyclerView?.layoutManager = LinearLayoutManager(requireContext())

    val searchTeam = args.currentTeam.nameTeam
    binding.tvNameTeam.text = searchTeam

    mUserViewModel = ViewModelProvider(this).get(UserViewModel::class.java)
    // 设置当前选中队伍,自动触发查询
    mUserViewModel.setCurrentTeam(searchTeam)
    // 只观察一次currentTeamUsers,不需要每次调用getTeam拿新的LiveData
    mUserViewModel.currentTeamUsers.observe(viewLifecycleOwner) { userList ->
        adapter.setData(userList)
    }

    binding.floatingActionButton.setOnClickListener{
        val action = ListFragmentDirections.actionListFragmentToAddFragment(searchTeam)
        findNavController().navigate(action)
    }

    setHasOptionsMenu(true)
    return view
}

// 修改searchDatabase方法,不要新建adapter
private fun searchDatabase(query: String) {
    val searchQuery = "%$query%"
    // 直接给现有adapter设置搜索结果,不要替换adapter
    mUserViewModel.searchDatabase(searchQuery)?.observe(viewLifecycleOwner) { userT->
        adapter.setData(userT)
    }
}

4. 修正搜索逻辑的范围

如果需要搜索时只在当前队伍内搜索,可以给Dao层的搜索方法增加队伍名参数,SQL里增加nameTeam = :currentTeam的过滤条件,避免搜索出其他队伍的球员

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.27 03:49:12