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

Room预打包数据库Fragment中SearchView搜索功能失效问题排查

药品词典APP搜索功能故障排查

我正在开发一款药品词典APP,用于存储药品名称及详细信息。APP包含1个主Activity和3个Fragment,主XML中的Fragment Host承载其他Fragment。点击首页Fragment的按钮可进入MedicineListFragment,该Fragment的RecyclerView展示assets文件夹中预打包Room数据库的药品名称。我希望实现搜索功能,用户输入查询词后能显示对应结果。已在ActionBar中实现可点击的SearchView,但点击输入后无任何反应。本项目参考Android官方Room and Flow教程,仅需在含RecyclerView的MedicineListFragment中实现搜索功能。尝试多种方法均未成功,怀疑问题出在ViewModel或MedicineNameAdapter的初始化环节。


主Activity代码

class MainActivity : AppCompatActivity( ) {
 lateinit var binding: ActivityMainBinding

 private lateinit var navController: NavController

 private lateinit var toolbar : Toolbar

 private lateinit var navigationView: NavigationView

 private lateinit var appBarConfiguration: AppBarConfiguration

 private lateinit var drawerLayout: DrawerLayout
 private lateinit var listener : 
NavController.OnDestinationChangedListener

 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 binding = ActivityMainBinding.inflate(layoutInflater)
 setContentView(binding.root)

 toolbar = findViewById(R.id.my_Toolbar)
 setSupportActionBar(toolbar)

 navigationView = findViewById(R.id.navigation_v)

 val bNavigationView = findViewById<BottomNavigationView>(R.id.bnv)

 val novController = findNavController(R.id.fragmentContainerView)
 bNavigationView.setupWithNavController(novController)

 drawerLayout = findViewById(R.id.drawer_layout)
  appBarConfiguration =
AppBarConfiguration(setOf(R.id.settingFragment,R.id.aboutFragment),drawerLayout)
     setupActionBarWithNavController(novController,drawerLayout)
 navigationView.setupWithNavController(novController)

 val navHomeFragment = 
supportFragmentManager.findFragmentById(R.id.fragmentContainerView) as 
NavHostFragment

 var navController = navHomeFragment.navController

 setupActionBarWithNavController(novController)

 }
 override fun onSupportNavigateUp(): Boolean {
 val navController = findNavController(R.id.fragmentContainerView)

 return navController.navigateUp(appBarConfiguration)
 || super.onSupportNavigateUp()
 }
}

MedicineListFragment代码

class MedicinwListFragment : 
Fragment(R.layout.fragment_medicinw_list),MenuProvider,SearchView.OnQueryTextListener {

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

 private lateinit var medicineNameAdapter: MedicineNameAdapter

 private val viewModel: MedicineTableVeiwmodel by activityViewModels {
 MedicineTableVeiwmodel.MedicineNameViewModelFactory(
 (activity?.application as 
MedicineTableApplication).database.medicineDao())
 }

 override fun onCreate(savedInstanceState: Bundle?) {
 super.onCreate(savedInstanceState)
 }
 override fun onCreateView(
 inflater: LayoutInflater, container: ViewGroup?,
 savedInstanceState: Bundle?,
 ): View {
 // Inflate the layout for this fragment
 _binding = FragmentMedicinwListBinding.inflate(inflater, container, 
false)
 val view = binding.root

 val menuHost : MenuHost = requireActivity()
 
menuHost.addMenuProvider(this,viewLifecycleOwner,Lifecycle.State.RESUMED)
 medicineNameAdapter = MedicineNameAdapter { }
 setHasOptionsMenu(true)
 return view
 }

 override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

 val recyclerview = binding.recyclerView
 recyclerview.layoutManager = LinearLayoutManager(requireContext())

 val medicineNameAdapter = MedicineNameAdapter {
 val action = MedicinwListFragmentDirections
 .actionMedicinwListFragmentToMedinformationf(
 medicineName = it.medicineName
 )

 view.findNavController().navigate(action)

 }
 recyclerview.adapter = medicineNameAdapter
 lifecycle.coroutineScope.launch {
 viewModel.fulMedicine().collect() {
 medicineNameAdapter.submitList(it)
 }
 }
 }

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

 override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {
 menuInflater.inflate(R.menu.medicine_list_f_menu,menu)
 val mSearch = menu.findItem(R.id.menu_search)
 val msearchView = mSearch.actionView as SearchView
 msearchView.isSubmitButtonEnabled = true
 msearchView.setOnQueryTextListener(this)
 }

 override fun onMenuItemSelected(menuItem: MenuItem): Boolean {
 return true
 }

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

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

 fun searchDatabase(query: String?){
 val searchQuery = "%$query%"
 viewModel.searcheMedicine(searchQuery).observe(this, { list ->
 medicineNameAdapter.submitList(list)
 })
 }
}

Entity代码

@Entity(tableName = "medicine")
data class Medicine(
@PrimaryKey (autoGenerate = true) var id: Int ,
@NotNull @ColumnInfo(name = "medicine_Name") var medicineName: String ,
@NotNull @ColumnInfo(name = "medicine_Info") var medicineInfo: String ,
)

Dao代码

@Dao
interface MedicineDao {

 @Query("SELECT * FROM Medicine ORDER BY medicine_Name ASC")
 fun getAll() :kotlinx.coroutines.flow.Flow <List<Medicine>>

 @Query("SELECT * FROM medicine WHERE medicine_Name = :medicineName ORDER 
BY medicine_Info ASC")
 fun getByMedicinname( medicineName: String) : 
kotlinx.coroutines.flow.Flow <List<Medicine>>

 @Query("SELECT * FROM medicine WHERE medicine_Name LIKE :searchQuery")
 fun searchMedicine(searchQuery : String?) : LiveData <List<Medicine>>
}

ViewModel代码

class MedicineTableVeiwmodel (var medicineDao: MedicineDao): ViewModel() {

 fun fulMedicine(): Flow <List<Medicine>> = medicineDao.getAll()

 fun medicineForMedicinname(name: String) :Flow <List<Medicine>> = 
medicineDao.getByMedicinname(name)

 fun searcheMedicine(searchQuery:String) : LiveData<List<Medicine>> = 
medicineDao.searchMedicine(searchQuery)

 class MedicineNameViewModelFactory(var medicineDao: MedicineDao): 
ViewModelProvider.Factory{

 override fun <T : ViewModel> create(modelClass: Class<T>): T {
 if 
(modelClass.isAssignableFrom(MedicineTableVeiwmodel::class.java)){

 @Suppress("UNCHECKED_CAST")
 return MedicineTableVeiwmodel(medicineDao) as T
 }
 throw IllegalArgumentException("UNKNOWN viewModel class")
 }
 }
}

搜索菜单代码

<menu xmlns:tools="http://schemas.android.com/tools"
 xmlns:android="http://schemas.android.com/apk/res/android"
 xmlns:app="http://schemas.android.com/apk/res-auto">
 <item
 android:id="@+id/menu_search"
 android:title="@string/search"
 android:icon="@drawable/ic_baseline_search_24"
 app:showAsAction="ifRoom"
 android:iconTint="@color/white"
 app:actionViewClass="androidx.appcompat.widget.SearchView"
 tools:targetApi="o" />
</menu>

问题排查与修复方案

1. Adapter实例重复初始化问题

在MedicineListFragment的onCreateView中初始化了全局的medicineNameAdapter,但在onViewCreated里又重新创建了一个局部的medicineNameAdapter并设置给RecyclerView。后续搜索时调用的是全局的Adapter实例,而RecyclerView绑定的是局部实例,导致搜索结果无法更新到界面。

修复:
删除onViewCreated中的局部medicineNameAdapter初始化,直接使用全局实例:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    val recyclerview = binding.recyclerView
    recyclerview.layoutManager = LinearLayoutManager(requireContext())
    // 使用全局Adapter实例
    medicineNameAdapter = MedicineNameAdapter {
        val action = MedicinwListFragmentDirections
            .actionMedicinwListFragmentToMedinformationf(
                medicineName = it.medicineName
            )
        view.findNavController().navigate(action)
    }
    recyclerview.adapter = medicineNameAdapter
    lifecycle.coroutineScope.launch {
        viewModel.fulMedicine().collect() {
            medicineNameAdapter.submitList(it)
        }
    }
}

2. LiveData与Flow混用问题

项目中主要使用Flow处理数据,但搜索接口返回的是LiveData,且每次调用searchDatabase都会重复观察LiveData,可能导致多次订阅或内存泄漏。统一使用Flow保持风格一致。

修复:

  • 修改Dao中的searchMedicine方法返回Flow:
@Query("SELECT * FROM medicine WHERE medicine_Name LIKE :searchQuery")
fun searchMedicine(searchQuery : String) : Flow<List<Medicine>>
  • 修改ViewModel中的对应方法:
fun searcheMedicine(searchQuery:String) : Flow<List<Medicine>> = 
    medicineDao.searchMedicine(searchQuery)
  • 修改searchDatabase方法,使用Flow收集数据:
fun searchDatabase(query: String){
    val searchQuery = "%$query%"
    lifecycle.coroutineScope.launch {
        viewModel.searcheMedicine(searchQuery).collect { list ->
            medicineNameAdapter.submitList(list)
        }
    }
}

3. 搜索查询空值处理

原searchDatabase方法中query可能为空,需添加空值判断,输入为空时显示全部数据:

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

override fun onQueryTextChange(newText: String?): Boolean {
    searchDatabase(newText ?: "")
    return true
}

fun searchDatabase(query: String){
    lifecycle.coroutineScope.launch {
        val flow = if(query.isBlank()){
            viewModel.fulMedicine()
        } else {
            val searchQuery = "%$query%"
            viewModel.searcheMedicine(searchQuery)
        }
        flow.collect { list ->
            medicineNameAdapter.submitList(list)
        }
    }
}

4. MenuProvider与setHasOptionsMenu重复设置

在onCreateView中同时调用了menuHost.addMenuProvider和setHasOptionsMenu(true),会导致菜单冲突,保留MenuProvider方式即可:

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

 val menuHost : MenuHost = requireActivity()
 menuHost.addMenuProvider(this,viewLifecycleOwner,Lifecycle.State.RESUMED)
 medicineNameAdapter = MedicineNameAdapter { }
 // 删除setHasOptionsMenu(true)
 return view
 }

内容的提问来源于stack exchange,提问作者Tamim

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.14 04:10:30