简体   繁体   English

ViewModel 在重新创建片段时重新获取数据

[英]ViewModel refetches data when fragment is recreated

I am using Bottom Navigation with Navigation Architecture Component .我正在使用带有导航架构组件的底部导航 When the user navigates from one item to another(via Bottom navigation) and back again view model call repository function to fetch data again.当用户从一个项目导航到另一个项目(通过底部导航)并再次返回时,查看模型调用存储库函数再次获取数据。 So if the user goes back and forth 10 times the same data will be fetched 10 times.因此,如果用户来回 10 次,则将获取 10 次相同的数据。 How to avoid re-fetching when the fragment is recreated data is already there?.当片段被重新创建时,如何避免重新获取数据已经存在?。

Fragment分段

class HomeFragment : Fragment() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    private lateinit var productsViewModel: ProductsViewModel
    private lateinit var productsAdapter: ProductsAdapter

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_home, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        initViewModel()
        initAdapters()
        initLayouts()
        getData()
    }

    private fun initViewModel() {
        (activity!!.application as App).component.inject(this)

        productsViewModel = activity?.run {
            ViewModelProviders.of(this, viewModelFactory).get(ProductsViewModel::class.java)
        }!!
    }

    private fun initAdapters() {
        productsAdapter = ProductsAdapter(this.context!!, From.HOME_FRAGMENT)
    }

    private fun initLayouts() {
        productsRecyclerView.layoutManager = LinearLayoutManager(this.activity)
        productsRecyclerView.adapter = productsAdapter
    }

    private fun getData() {
        val productsFilters = ProductsFilters.builder().sortBy(SortProductsBy.NEWEST).build()

        //Products filters
        productsViewModel.setInput(productsFilters, 2)

        //Observing products data
        productsViewModel.products.observe(viewLifecycleOwner, Observer {
            it.products()?.let { products -> productsAdapter.setData(products) }
        })

        //Observing loading
        productsViewModel.networkState.observe(viewLifecycleOwner, Observer {
            //Todo showing progress bar
        })
    }
}

ViewModel视图模型

class ProductsViewModel
@Inject constructor(private val repository: ProductsRepository) : ViewModel() {

    private val _input = MutableLiveData<PInput>()

    fun setInput(filters: ProductsFilters, limit: Int) {
        _input.value = PInput(filters, limit)
    }

    private val getProducts = map(_input) {
        repository.getProducts(it.filters, it.limit)
    }

    val products = switchMap(getProducts) { it.data }
    val networkState = switchMap(getProducts) { it.networkState }
}

data class PInput(val filters: ProductsFilters, val limit: Int)

Repository存储库

@Singleton
class ProductsRepository @Inject constructor(private val api: ApolloClient) {

    val networkState = MutableLiveData<NetworkState>()

    fun getProducts(filters: ProductsFilters, limit: Int): ApiResponse<ProductsQuery.Data> {
        val products = MutableLiveData<ProductsQuery.Data>()

        networkState.postValue(NetworkState.LOADING)

        val request = api.query(ProductsQuery
                .builder()
                .filters(filters)
                .limit(limit)
                .build())

        request.enqueue(object : ApolloCall.Callback<ProductsQuery.Data>() {
            override fun onFailure(e: ApolloException) {
                networkState.postValue(NetworkState.error(e.localizedMessage))
            }

            override fun onResponse(response: Response<ProductsQuery.Data>) = when {
                response.hasErrors() -> networkState.postValue(NetworkState.error(response.errors()[0].message()))
                else -> {
                    networkState.postValue(NetworkState.LOADED)
                    products.postValue(response.data())
                }
            }
        })

        return ApiResponse(data = products, networkState = networkState)
    }
}

Navigation main.xml导航main.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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation.xml"
    app:startDestination="@id/home">

    <fragment
        android:id="@+id/home"
        android:name="com.nux.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home"/>
    <fragment
        android:id="@+id/search"
        android:name="com.nux.ui.search.SearchFragment"
        android:label="@string/title_search"
        tools:layout="@layout/fragment_search" />
    <fragment
        android:id="@+id/my_profile"
        android:name="com.nux.ui.user.MyProfileFragment"
        android:label="@string/title_profile"
        tools:layout="@layout/fragment_profile" />
</navigation>

ViewModelFactory视图模型工厂

@Singleton
class ViewModelFactory @Inject
constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        val creator = viewModels[modelClass]
                ?: viewModels.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
                ?: throw IllegalArgumentException("unknown model class $modelClass")
        return try {
            creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}

在此处输入图片说明

In onActivityCreated() , you are calling getData() .onActivityCreated() ,您正在调用getData() In there, you have:在那里,你有:

productsViewModel.setInput(productsFilters, 2)

This, in turn, changes the value of _input in your ProductsViewModel .这反过来会更改ProductsViewModel_input的值。 And, every time that _input changes, the getProducts lambda expression will be evaluated, calling your repository.而且,每次_input更改时,都会评估getProducts lambda 表达式,调用您的存储库。

So, every onActivityCreated() call triggers a call to your repository.因此,每个onActivityCreated()调用都会触发对您的存储库的调用。

I do not know enough about your app to tell you what you need to change.我对您的应用程序了解不够,无法告诉您需要更改的内容。 Here are some possibilities:这里有一些可能性:

  • Switch from onActivityCreated() to other lifecycle methods.onActivityCreated()切换到其他生命周期方法。 initViewModel() could be called in onCreate() , while the rest should be in onViewCreated() . initViewModel()可以在onCreate()调用,而其余的应该在onViewCreated()

  • Reconsider your getData() implementation.重新考虑您的getData()实现。 Do you really need to call setInput() every time we navigate to this fragment?每次我们导航到这个片段时,你真的需要调用setInput()吗? Or, should that be part of initViewModel() and done once in onCreate() ?或者,这应该是initViewModel()一部分并在onCreate()完成一次吗? Or, since productsFilters does not seem to be tied to the fragment at all, should productsFilters and the setInput() call be part of the init block of ProductsViewModel , so it only happens once?或者,由于productsFilters似乎根本没有绑定到片段, productsFilterssetInput()调用是否应该成为ProductsViewModel init块的一部分,所以它只发生一次?

One simple solution would be to change the ViewModelProvider owner from this to requireActivity() in this line of code:一个简单的解决方案是在这行代码中将 ViewModelProvider 所有者从this更改为requireActivity()

ViewModelProviders.of(this, viewModelFactory).get(ProductsViewModel::class.java)

Therefore, as the activity is the owner of the viewmodel and the lifcycle of viewmodel attached to the activity not to the fragment, navigating between fragments within the activity won't recreated the viewmodel.因此,由于活动是视图模型的所有者,并且视图模型的生命周期附加到活动而不是片段,因此在活动中的片段之间导航不会重新创建视图模型。

define your ProductsViewModel by static in mainActivity and initialize in onCreate method.在 mainActivity 中通过 static 定义您的 ProductsViewModel 并在 onCreate 方法中初始化。 Now just use it this way in fragment:现在只需在片段中以这种方式使用它:

MainActivity.productsViewModel.products.observe(viewLifecycleOwner, Observer {
            it.products()?.let { products -> productsAdapter.setData(products) }
        })

When you select other pages via bottom navigation and come back, fragment destroy and recreate.当您通过底部导航选择其他页面并返回时,片段销毁并重新创建。 So the onCreate , onViewCreated and onActivityCreate will run again.所以onCreateonViewCreatedonActivityCreate将再次运行。 But viewModel is still alive.但是 viewModel 还活着。

So you can call your function ( getProducts ) inside the "init" in viewModel to run it once.因此,您可以在 viewModel 的“init”中调用您的函数 ( getProducts ) 来运行它一次。

init {
        getProducts()
    }

I am using Bottom Navigation with Navigation Architecture Component .我正在将Bottom NavigationNavigation Architecture Component一起使用 When the user navigates from one item to another(via Bottom navigation) and back again view model call repository function to fetch data again.当用户从一个项目导航到另一个项目(通过底部导航)并再次返回时,查看模型调用存储库功能以再次获取数据。 So if the user goes back and forth 10 times the same data will be fetched 10 times.因此,如果用户来回移动10次,则相同的数据将被提取10次。 How to avoid re-fetching when the fragment is recreated data is already there?.如何在重新创建片段时避免重新获取数据?

Fragment分段

class HomeFragment : Fragment() {

    @Inject
    lateinit var viewModelFactory: ViewModelProvider.Factory

    private lateinit var productsViewModel: ProductsViewModel
    private lateinit var productsAdapter: ProductsAdapter

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        // Inflate the layout for this fragment
        return inflater.inflate(R.layout.fragment_home, container, false)
    }

    override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)
        initViewModel()
        initAdapters()
        initLayouts()
        getData()
    }

    private fun initViewModel() {
        (activity!!.application as App).component.inject(this)

        productsViewModel = activity?.run {
            ViewModelProviders.of(this, viewModelFactory).get(ProductsViewModel::class.java)
        }!!
    }

    private fun initAdapters() {
        productsAdapter = ProductsAdapter(this.context!!, From.HOME_FRAGMENT)
    }

    private fun initLayouts() {
        productsRecyclerView.layoutManager = LinearLayoutManager(this.activity)
        productsRecyclerView.adapter = productsAdapter
    }

    private fun getData() {
        val productsFilters = ProductsFilters.builder().sortBy(SortProductsBy.NEWEST).build()

        //Products filters
        productsViewModel.setInput(productsFilters, 2)

        //Observing products data
        productsViewModel.products.observe(viewLifecycleOwner, Observer {
            it.products()?.let { products -> productsAdapter.setData(products) }
        })

        //Observing loading
        productsViewModel.networkState.observe(viewLifecycleOwner, Observer {
            //Todo showing progress bar
        })
    }
}

ViewModel视图模型

class ProductsViewModel
@Inject constructor(private val repository: ProductsRepository) : ViewModel() {

    private val _input = MutableLiveData<PInput>()

    fun setInput(filters: ProductsFilters, limit: Int) {
        _input.value = PInput(filters, limit)
    }

    private val getProducts = map(_input) {
        repository.getProducts(it.filters, it.limit)
    }

    val products = switchMap(getProducts) { it.data }
    val networkState = switchMap(getProducts) { it.networkState }
}

data class PInput(val filters: ProductsFilters, val limit: Int)

Repository资料库

@Singleton
class ProductsRepository @Inject constructor(private val api: ApolloClient) {

    val networkState = MutableLiveData<NetworkState>()

    fun getProducts(filters: ProductsFilters, limit: Int): ApiResponse<ProductsQuery.Data> {
        val products = MutableLiveData<ProductsQuery.Data>()

        networkState.postValue(NetworkState.LOADING)

        val request = api.query(ProductsQuery
                .builder()
                .filters(filters)
                .limit(limit)
                .build())

        request.enqueue(object : ApolloCall.Callback<ProductsQuery.Data>() {
            override fun onFailure(e: ApolloException) {
                networkState.postValue(NetworkState.error(e.localizedMessage))
            }

            override fun onResponse(response: Response<ProductsQuery.Data>) = when {
                response.hasErrors() -> networkState.postValue(NetworkState.error(response.errors()[0].message()))
                else -> {
                    networkState.postValue(NetworkState.LOADED)
                    products.postValue(response.data())
                }
            }
        })

        return ApiResponse(data = products, networkState = networkState)
    }
}

Navigation main.xml导航main.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"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/mobile_navigation.xml"
    app:startDestination="@id/home">

    <fragment
        android:id="@+id/home"
        android:name="com.nux.ui.home.HomeFragment"
        android:label="@string/title_home"
        tools:layout="@layout/fragment_home"/>
    <fragment
        android:id="@+id/search"
        android:name="com.nux.ui.search.SearchFragment"
        android:label="@string/title_search"
        tools:layout="@layout/fragment_search" />
    <fragment
        android:id="@+id/my_profile"
        android:name="com.nux.ui.user.MyProfileFragment"
        android:label="@string/title_profile"
        tools:layout="@layout/fragment_profile" />
</navigation>

ViewModelFactory ViewModelFactory

@Singleton
class ViewModelFactory @Inject
constructor(private val viewModels: MutableMap<Class<out ViewModel>, Provider<ViewModel>>) : ViewModelProvider.Factory {

    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel> create(modelClass: Class<T>): T {
        val creator = viewModels[modelClass]
                ?: viewModels.asIterable().firstOrNull { modelClass.isAssignableFrom(it.key) }?.value
                ?: throw IllegalArgumentException("unknown model class $modelClass")
        return try {
            creator.get() as T
        } catch (e: Exception) {
            throw RuntimeException(e)
        }
    }
}

在此处输入图片说明

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 在可见但未重新创建时执行片段 - to execute fragment when visible but not recreated 显示新活动时重新创建活动/视图模型 - Activity/ViewModel recreated when new Activity is shown 当片段被重新创建到前台时,Recyclerview 被重新创建 - Recyclerview gets recreated when fragment is recreated coming to foreground 当方向改变时,不需要时重新创建片段 - Fragment is Recreated when orientation change, when not needed 为什么片段重新创建时onChange()调用了两次 - Why onChange() called twice when fragment recreated 重新创建活动时,分离的片段会被附着吗? - When activity is recreated, detached fragment gets attached? 重新创建包含Fragment时,ViewPager中的碎片未加载 - Fragments in ViewPager not loaded when the containing Fragment is recreated 重新创建活动后片段不可见 - Fragment not visible after when activity is recreated 内存不足时会重新创建Android活动/片段 - Android activity/fragment recreated when low memory 使用 ViewModel 重新创建 Fragments 时如何执行一次提取操作 - How to do one time fetch operations when Fragments are recreated with ViewModel
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM