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

时间:2019-05-30 12:43:22

标签: android android-architecture-components android-architecture-navigation android-architecture-lifecycle

我将Bottom NavigationNavigation 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)
        }
    }
}

enter image description here

5 个答案:

答案 0 :(得分:1)

onActivityCreated()中,您正在呼叫getData()。在其中,您可以:

productsViewModel.setInput(productsFilters, 2)

这反过来改变了_inputProductsViewModel的值。而且,每次_input发生变化时,都会评估getProducts lambda表达式,并调用您的存储库。

因此,每个onActivityCreated()调用都会触发对存储库的调用。

我对您的应用程序了解不足,无法告诉您需要更改的内容。这里有一些可能性:

  • onActivityCreated()切换到其他生命周期方法。 initViewModel()可以在onCreate()中调用,其余的应该在onViewCreated()中。

  • 重新考虑您的getData()实现。每次我们导航到此片段时,您是否真的需要致电setInput()?还是应该将其作为initViewModel()的一部分并在onCreate()中完成一次?或者,由于productsFilters似乎根本没有与片段绑定,因此productsFilterssetInput()调用应该成为init的{​​{1}}块的一部分,那么它只会发生一次?

答案 1 :(得分:1)

一个简单的解决方案是将以下代码行中的ViewModelProvider所有者从 this 更改为 requireActivity()

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

因此,由于活动是视图模型的所有者,而视图模型的lifcycle附加在活动上而不是片段上,因此在活动内的片段之间导航不会重新创建视图模型。

答案 2 :(得分:1)

在mainActivity中通过static定义您的ProductsViewModel,并在onCreate方法中进行初始化。 现在,只需在片段中使用这种方式即可:

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

答案 3 :(得分:0)

我有同样的问题。我的应用程序还使用带有JetPack导航体系结构组件的底部导航。我使用单个活动设计,将多个片段作为目标。每个片段都有其自己的ViewModel,MainActivity也具有SharedViewModel。

我的片段之一的ViewModel的init块从网络中获取数据。问题是,当我通过底部导航栏切换到另一个片段时,该片段与MainActivity分离,并且ViewModel被清除。当我切换回去时,重新创建了ViewModel,并且再次运行了init块以获取新的网络数据。我使用Sharedpreference和SharedViewModel解决了这个问题。由于SharedViewmodel是与MainActivity绑定的,而不是与片段之一绑定的,因此不会被清除。我的代码如下。我是Android开发的新手,因此不确定这种骇人听闻的解决方案是否行之有效,但它是否有效。确保您从AndroidViewModel而不是ViewModel扩展,因为您需要应用程序上下文才能访问SharedPreferenceManager。

我片段的ViewModel的init块:

init {
    if (preferenceManager.getBoolean("appfirstlaunched", true)) {
        viewModelScope.launch {
            photoRepository.refreshPhotos() //kotlin coroutine function
            preferenceManager.edit().putBoolean("appfirstlaunched", false).apply()
        }
   }
}

然后在SharedViewModel中重写onCleared()方法:

override fun onCleared() {
    super.onCleared()
    PreferenceManager.getDefaultSharedPreferences(getApplication()).edit().putBoolean("appfirstlaunched", true).apply()

在您的settings_prefences.xml中创建一个不可见的SwitchPreference,如下所示:

<SwitchPreferenceCompat
        android:defaultValue="true"
        app:key="appfirstlaunched"
        app:persistent="true"
        app:isPreferenceVisible="false"
        />

这样,应用程序首次启动时,值为“ true”,并且片段ViewModel的init块运行,然后将值设置为“ false”,并保持“ false”,直到清除SharedViewModel。通常是在活动被完全销毁并且用户返回到应用程序时需要新数据的时候。

答案 4 :(得分:0)

当您通过底部导航选择其他页面并返回时,片段销毁并重新创建。因此, onCreate onViewCreated onActivityCreate 将再次运行。但是viewModel仍然有效。

因此,您可以在viewModel的“ init” 中调用函数( getProducts )来运行一次。

init {
        getProducts()
    }