ViewModel 在重新创建片段时重新获取数据
ViewModel refetches data when fragment is recreated
-
android
-
android-architecture-lifecycle
-
android-architecture-components
-
android-architecture-navigation
我正在使用 Bottom Navigation with Navigation Architecture Component。当用户从一个项目导航到另一个项目(通过底部导航)并再次返回时,视图模型调用存储库函数再次获取数据。因此,如果用户来回移动 10 次,相同的数据将被提取 10 次。重新创建片段时如何避免重新获取数据已经存在?。
片段
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)
存储库
@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)
}
}
导航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)
}
}
}
在onActivityCreated()
,您正在呼叫getData()
。在那里,你有:
productsViewModel.setInput(productsFilters, 2)
这反过来会改变 ProductsViewModel
中 _input
的值。而且,每次 _input
更改时,都会计算 getProducts
lambda 表达式,调用您的存储库。
因此,每个 onActivityCreated()
调用都会触发对您的存储库的调用。
我对您的应用了解不多,无法告诉您需要更改的内容。这里有一些可能性:
从 onActivityCreated()
切换到其他生命周期方法。 initViewModel()
可以在onCreate()
中调用,其余的应该在onViewCreated()
.
中
重新考虑您的 getData()
实施。每次我们导航到这个片段时,你真的需要调用 setInput()
吗?或者,它应该是 initViewModel()
的一部分并在 onCreate()
中完成一次吗?或者,由于 productsFilters
似乎根本与片段无关,因此 productsFilters
和 setInput()
调用是否应该成为 ProductsViewModel
的 init
块的一部分, 所以它只发生一次?
当您 select 其他页面通过底部导航返回时,片段销毁并重新创建。所以 onCreate、onViewCreated 和 onActivityCreate
将再次运行。但是 viewModel 还活着。
所以你可以调用你的函数 (getProducts) inside the "init" in viewModel to 运行 一次。
init {
getProducts()
}
一个简单的解决方案是在这行代码中将 ViewModelProvider 所有者从 this 更改为 requireActivity():
ViewModelProviders.of(this, viewModelFactory).get(ProductsViewModel::class.java)
因此,由于 activity 是视图模型的所有者,并且视图模型的生命周期附加到 activity 而不是片段,因此在 activity 内的片段之间导航不会' t 重新创建了视图模型。
在 mainActivity 中通过静态定义 ProductsViewModel 并在 onCreate 方法中初始化。
现在只需在片段中以这种方式使用它:
MainActivity.productsViewModel.products.observe(viewLifecycleOwner, Observer {
it.products()?.let { products -> productsAdapter.setData(products) }
})
android
android-architecture-lifecycle
android-architecture-components
android-architecture-navigation
我正在使用 Bottom Navigation with Navigation Architecture Component。当用户从一个项目导航到另一个项目(通过底部导航)并再次返回时,视图模型调用存储库函数再次获取数据。因此,如果用户来回移动 10 次,相同的数据将被提取 10 次。重新创建片段时如何避免重新获取数据已经存在?。
片段
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)
存储库
@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)
}
}
导航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)
}
}
}
在onActivityCreated()
,您正在呼叫getData()
。在那里,你有:
productsViewModel.setInput(productsFilters, 2)
这反过来会改变 ProductsViewModel
中 _input
的值。而且,每次 _input
更改时,都会计算 getProducts
lambda 表达式,调用您的存储库。
因此,每个 onActivityCreated()
调用都会触发对您的存储库的调用。
我对您的应用了解不多,无法告诉您需要更改的内容。这里有一些可能性:
从
onActivityCreated()
切换到其他生命周期方法。initViewModel()
可以在onCreate()
中调用,其余的应该在onViewCreated()
. 中
重新考虑您的
getData()
实施。每次我们导航到这个片段时,你真的需要调用setInput()
吗?或者,它应该是initViewModel()
的一部分并在onCreate()
中完成一次吗?或者,由于productsFilters
似乎根本与片段无关,因此productsFilters
和setInput()
调用是否应该成为ProductsViewModel
的init
块的一部分, 所以它只发生一次?
当您 select 其他页面通过底部导航返回时,片段销毁并重新创建。所以 onCreate、onViewCreated 和 onActivityCreate 将再次运行。但是 viewModel 还活着。
所以你可以调用你的函数 (getProducts) inside the "init" in viewModel to 运行 一次。
init {
getProducts()
}
一个简单的解决方案是在这行代码中将 ViewModelProvider 所有者从 this 更改为 requireActivity():
ViewModelProviders.of(this, viewModelFactory).get(ProductsViewModel::class.java)
因此,由于 activity 是视图模型的所有者,并且视图模型的生命周期附加到 activity 而不是片段,因此在 activity 内的片段之间导航不会' t 重新创建了视图模型。
在 mainActivity 中通过静态定义 ProductsViewModel 并在 onCreate 方法中初始化。 现在只需在片段中以这种方式使用它:
MainActivity.productsViewModel.products.observe(viewLifecycleOwner, Observer {
it.products()?.let { products -> productsAdapter.setData(products) }
})