永远观察实时数据的片段

Posted

技术标签:

【中文标题】永远观察实时数据的片段【英文标题】:Fragments observing livedata forever 【发布时间】:2021-07-28 16:59:47 【问题描述】:

问题:我面临片段和共享的问题 viewmodel LiveData ...问题是存在 FragmentA更新共享 viewmodel 中的数据并观察其变化,然后在 FragmentA 中为用户显示结果,FragmentA 可以启动 的新实例>FragmentA 获取新数据并显示它,以便片段自行启动并将旧实例添加到后台堆栈,直到这里没有任何问题,新实例更新 LiveData 中的 viewModel 并完美显示新数据问题是当我 popUpBackstack() 返回到 FragmentA 旧实例时,其中显示的数据是新的数据FragmentA 实例得到它,这意味着即使我移除了观察者,旧的 FragmentA 实例仍在观察数据......这是一般情况关于问题的概述,现在我将向您展示片段结构、代码以及我尝试过的解决方案。

预期行为:我想要实现的是,当 FragmentA 自行启动并添加到后端堆栈时,停止观察 viewModel 中的数据,当我返回它时仅显示旧数据。

片段结构:我使用一个活动来保存所有片段... MainActivity 里面有 FindMoviesFragment 里面有一个 viewPager FragmentMovies 启动 MovieDetailsFragment 并在其中有 ViewPager 还保存显示来自 MoviesViewModel 的数据的片段看到下面的代码就明白了。

这段代码展示了MovieDetailsFragment如何初始化MoviesViewModel并更新viewmodel中的数据:

 class MovieDetailsFragment:Fragment(R.layout.movie_details_fragment) 

    private val args: MovieDetailsFragmentArgs by navArgs()
    private val  fragmentList:ArrayList<Fragment> by lazy 
        arrayListOf(MovieDetailsOneFragment(args.movieId), MovieDetailsTwoFragment(),MovieDetailsThreeFragment())
    
    private lateinit var pagerAdapter: ViewPagerAdapter
    private lateinit var moviesViewModel: MoviesViewModel

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)

        moviesViewModel = ViewModelProvider(requireActivity()).get(MoviesViewModel::class.java)
        
        //these two lines updates the data in viewmodel
        moviesViewModel.getMovieDetails(args.movieId)
        moviesViewModel.changeMovieID(args.movieId)

    



//---------------------MoviesViewModel------------------//

class MoviesViewModel constructor(private val repo:Repository):ViewModel() 

     private val _currentMovieDetails = MutableLiveData<Resource<MovieDetails>>()
    val currentMovieDetails :LiveData<Resource<MovieDetails>>
        get() = _currentMovieDetails

    fun getMovieDetails(movieID:Int) = viewModelScope.launch 
        _currentMovieDetails.postValue(Resource.loading(null))

        val result = repo.getMovieDetails(movieID)
   
        if(result.isSuccessful)
            _currentMovieDetails.postValue(Resource.success(result.body()))
           
        
        else
            _currentMovieDetails.postValue(Resource.error(result.errorBody().toString(),null))
        

    



MovieDetailsFragment 的 viewpager 内的 MovieDetailsOne 中,我观察到这样的数据:

class MovieDetailsOneFragment(private val movieId: Int):Fragment(R.layout.movie_details_one) 

    private lateinit var moviesViewModel:MoviesViewModel

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)

        //this is how i define viewModel(global scope)
        moviesViewModel = ViewModelProvider(requireActivity()).get(MoviesViewModel::class.java)

    

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) 
        super.onViewCreated(view, savedInstanceState)
        
        //in this method i observe on changes in currentMovieDetils liveData
        subscribeToObservers()
        //i try to call this from on create and nothing changes too

    


现在我尝试的是以下内容:

-片段的本地范围

//Define viewModel like this in MovieDetilsFragment

moviesViewModel = ViewModelProvider(this).get(MoviesViewModel::class.java)

//And in MovieDetailsOne like this
moviesViewModel = ViewModelProvider(this).get(MoviesViewModel::class.java)

//and this doesn't work MovieDetailsOne don't observe any changes

-观察一次消光函数:

fun <T> LiveData<T>.observeOnce(lifecycleOwner: LifecycleOwner, observer: Observer<T>) 
   observe(lifecycleOwner, object : Observer<T> 
       override fun onChanged(t: T?) 
           observer.onChanged(t)
           removeObserver(this)
       
   )


//and this doesn't work MovieDetailsOne don't observe first time it's lanches

我知道我花了很长时间来解释:D,但我试图给你一个清晰的想法,对不起你的时间

【问题讨论】:

【参考方案1】:

最后我通过一些步骤实现了我想要的行为:

使用 childFragmentManager 附加 ViewPager 孩子,这意味着 android 会将 viewpager 片段视为 viewPager 持有者的孩子:

//instead of doing this
pagerAdapter = ViewPagerAdapter(activity?.supportragmentManager, lifecycle, fragmentList)

//do this
pagerAdapter = ViewPagerAdapter(childFragmentManager, lifecycle, fragmentList)

为父片段定义本地 viewModel,如下所示:

//instead of doing this
moviesViewModel = ViewModelProvider(requireActivity()).get(MoviesViewModel::class.java)

//do this
moviesViewModel = ViewModelProvider(this).get(MoviesViewModel::class.java)

在子片段中定义父片段范围viewModel

//instead of doing this
moviesViewModel = ViewModelProvider(requireActivity()).get(MoviesViewModel::class.java)

//do this
private val moviesViewModel:MoviesViewModel by viewModels(
        requireParentFragment()
    )

通过执行这些步骤,父片段将在数据被销毁时停止观察数据,子片段也将停止观察。

【讨论】:

以上是关于永远观察实时数据的片段的主要内容,如果未能解决你的问题,请参考以下文章

观察对象类中的实时数据

如何观察 Firebase 实时数据库的变化

如何实现一个 Firebase 监听器来观察数据库的实时变化

如何在观察实时数据的值时修复“resp”的空指针

在 Swift 中的 Firebase 实时数据库观察方法中具有异步函数的 DispatchGroup

如何通过在 swift 中实现观察者从 Firebase 实时数据库中获取嵌套数据