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

Kotlin删除Firebase文件后RecyclerView不刷新问题排查求助

问题描述

我是Kotlin新手,正在靠试错开发第一个App(之前功能都正常)。现在遇到的问题是:基于Firebase Storage文件实现的RecyclerView,每行的删除按钮能成功删除Firebase上的文件,但点击后列表不会刷新,已删除的文件还显示在列表里。之前用Room实现相同功能时列表能正常刷新,附上Fragment、ViewModel及Adapter代码,求帮忙找错。


Fragment代码

class SharingFragment : Fragment() {
    override fun onViewStateRestored(savedInstanceState: Bundle?) {
        super.onViewStateRestored(savedInstanceState)
    }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val binding: FragmentSharesBinding = DataBindingUtil.inflate(
            inflater, R.layout.fragment_shares, container, false)
        val application = requireNotNull(this.activity).application
        val viewModelFactory = SharingViewModelFactory(application)
        val sharingViewModel = ViewModelProvider(this,viewModelFactory).get(
            SharingViewModel::class.java)

        sharingViewModel.getSharedWithMe().observe(this , Observer { list->
            // set the layout manager and the adapter for the recycler view
            binding.listaRecyclerView.layoutManager = LinearLayoutManager(application)
            binding.listaRecyclerView.adapter = InsuranceAdapter(context!!,list)
        })
        binding.sharingViewModel = sharingViewModel
        val root: View = binding.root
        return root
    }
}

ViewModel(SharingViewModel)代码

class SharingViewModel(
    application: Application
) : AndroidViewModel(application)
{
    var listReturn: MutableLiveData<List<String>>

    init {
        listReturn = MutableLiveData(listOf())
    }

    fun getSharedWithMe() : MutableLiveData<List<String>> {

        var auth = Firebase.auth
        val storage = Firebase.storage
        var storageRef = storage.reference
        val databaseRef = storageRef.child("shared/${auth!!.currentUser!!.email}")

        var listFile = databaseRef.listAll()

        listFile.addOnSuccessListener { (items, prefixes) ->
                var list: MutableList<String> = mutableListOf()
                items.forEach { item ->
                    list.add(item.name)
                }
                listReturn.value = listReturn.value?.plus(list)
            }
            .addOnFailureListener {
                // Uh-oh, an error occurred!
            }
        //Wait to receive the listall
        while(listFile.isComplete==false) {Thread.sleep(1000)}
        return listReturn
    }

    fun deleteSharedWithMe(file: String) {

        var auth = Firebase.auth
        val storage = Firebase.storage
        var storageRef = storage.reference
        val fileRef =
            storageRef.child("shared/${auth!!.currentUser!!.email}/${file}")
        fileRef.delete()
        listReturn.value = listReturn.value!!.toMutableList().apply {
            remove(file)
        }.toList()
    }
}

Adapter代码

class InsuranceAdapter(val context : Context,val list : List<String>) : RecyclerView.Adapter<InsuranceAdapter.ViewHolder>() {
    // Inner ViewHolder class
    class ViewHolder(val binding : SharingListBinding) : RecyclerView.ViewHolder(binding.root){}

        // function to inflate the layout for each contact and create a new ViewHolder instance
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(
            SharingListBinding.inflate(LayoutInflater.from(parent.context),parent,false)
        )
    }

    // function to bind the data to the view elements of the ViewHolder
    @RequiresApi(Build.VERSION_CODES.O)
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.fileTextview.text = list[position]

        // delete button onClickListener to delete the
        // file from firestore and notify the
        // adapter of the change
        holder.binding.deleteButton.setOnClickListener{
            val application = requireNotNull(Application())
            val sharingViewModel = SharingViewModel(Application())

            //TODO borrar de Firebase
            sharingViewModel.deleteSharedWithMe(list[position])

           // dao.delete(list[position]) //this was working when I was using Room and deleting from the DB was refreshing the list
            notifyItemChanged(position)
            notifyDataSetChanged()
        }
    }


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

}

错误原因及修复方案

核心错误点

  1. Adapter创建独立ViewModel实例:点击删除按钮时,Adapter里新建的SharingViewModel和Fragment绑定的ViewModel完全无关,修改的是新ViewModel的listReturn,Fragment观察的旧ViewModel LiveData根本没收到更新。
  2. Firebase删除与LiveData更新不同步:当前逻辑直接修改listReturn,没有等待Firebase异步删除操作完成,且因为ViewModel实例不共享,Fragment的LiveData不会触发刷新。
  3. ViewModel线程阻塞问题:用while循环+Thread.sleep等待Firebase异步操作,会阻塞UI线程导致ANR,且listReturn.value?.plus(list)会累加旧数据,多次调用会重复添加列表项。
  4. Fragment重复初始化Adapter:每次LiveData更新都重新创建Adapter,不仅浪费资源,Adapter持有的是旧列表快照,无法响应后续更新。

修复步骤

1. 让Adapter通过回调共享ViewModel

修改Adapter,添加删除回调接口,不再自行创建ViewModel:

class InsuranceAdapter(
    val context: Context,
    val list: List<String>,
    private val onDeleteClick: (String) -> Unit // 添加删除回调
) : RecyclerView.Adapter<InsuranceAdapter.ViewHolder>() {

    class ViewHolder(val binding: SharingListBinding) : RecyclerView.ViewHolder(binding.root) {}

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(
            SharingListBinding.inflate(LayoutInflater.from(parent.context), parent, false)
        )
    }

    @RequiresApi(Build.VERSION_CODES.O)
    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        holder.binding.fileTextview.text = list[position]

        holder.binding.deleteButton.setOnClickListener {
            onDeleteClick(list[position]) // 触发回调,由Fragment处理删除逻辑
        }
    }

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

2. 修正Fragment的Adapter初始化逻辑

Fragment中只设置一次LayoutManager,初始化Adapter时传入删除回调:

class SharingFragment : Fragment() {
    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        val binding: FragmentSharesBinding = DataBindingUtil.inflate(
            inflater, R.layout.fragment_shares, container, false)
        val application = requireNotNull(this.activity).application
        val viewModelFactory = SharingViewModelFactory(application)
        val sharingViewModel = ViewModelProvider(this, viewModelFactory).get(
            SharingViewModel::class.java)

        // 仅设置一次LayoutManager
        binding.listaRecyclerView.layoutManager = LinearLayoutManager(application)

        sharingViewModel.listReturn.observe(this, Observer { list ->
            // 传入删除回调,关联Fragment的ViewModel
            binding.listaRecyclerView.adapter = InsuranceAdapter(context!!, list) { fileName ->
                sharingViewModel.deleteSharedWithMe(fileName)
            }
        })
        
        // 页面创建时主动获取一次列表
        sharingViewModel.getSharedWithMe()
        
        binding.sharingViewModel = sharingViewModel
        return binding.root
    }
}

3. 修复ViewModel逻辑问题

去掉线程阻塞,在Firebase删除成功后再更新LiveData,且每次获取列表时替换为新数据:

class SharingViewModel(application: Application) : AndroidViewModel(application) {
    private val _listReturn = MutableLiveData<List<String>>(emptyList())
    val listReturn: LiveData<List<String>> = _listReturn // 对外暴露不可变LiveData

    fun getSharedWithMe() {
        val auth = Firebase.auth.currentUser ?: return
        val storageRef = Firebase.storage.reference.child("shared/${auth.email}")

        storageRef.listAll()
            .addOnSuccessListener { result ->
                val fileNames = result.items.map { it.name }
                _listReturn.value = fileNames // 直接替换为新列表,避免累加旧数据
            }
            .addOnFailureListener {
                // 可添加错误提示,比如Toast.makeText(getApplication(), "获取列表失败", Toast.LENGTH_SHORT).show()
            }
        // 移除线程阻塞逻辑,异步操作由Firebase回调处理
    }

    fun deleteSharedWithMe(file: String) {
        val auth = Firebase.auth.currentUser ?: return
        val fileRef = Firebase.storage.reference.child("shared/${auth.email}/$file")

        fileRef.delete()
            .addOnSuccessListener {
                // 删除成功后再更新LiveData
                _listReturn.value = _listReturn.value?.toMutableList()?.apply {
                    remove(file)
                } ?: emptyList()
            }
            .addOnFailureListener {
                // 删除失败提示,比如Toast.makeText(getApplication(), "删除失败", Toast.LENGTH_SHORT).show()
            }
    }
}

4. 确保ViewModelFactory正确传递参数

确认SharingViewModelFactory能正确传递Application:

class SharingViewModelFactory(private val application: Application) : ViewModelProvider.Factory {
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        if (modelClass.isAssignableFrom(SharingViewModel::class.java)) {
            @Suppress("UNCHECKED_CAST")
            return SharingViewModel(application) as T
        }
        throw IllegalArgumentException("未知的ViewModel类")
    }
}

内容的提问来源于stack exchange,提问作者Miguel A Felipe Rodriguez

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 01:08:08