[英]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
似乎根本没有绑定到片段, productsFilters
和setInput()
调用是否应该成为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.所以onCreate 、 onViewCreated和onActivityCreate将再次运行。 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 Navigation与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 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.