ViewModel восстанавливает данные при воссоздании фрагмента - PullRequest
2 голосов
/ 30 мая 2019

Я использую Нижняя навигация с Компонент архитектуры навигации .Когда пользователь перемещается от одного элемента к другому (через нижнюю навигацию) и снова просматривает функцию хранилища вызовов модели, чтобы снова получить данные.Таким образом, если пользователь переходит туда-сюда 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

1 Ответ

1 голос
/ 30 мая 2019

В onActivityCreated() вы звоните getData(). Там у вас есть:

productsViewModel.setInput(productsFilters, 2)

Это, в свою очередь, меняет значение _input в вашем ProductsViewModel. И каждый раз, когда меняется _input, лямбда-выражение getProducts будет оцениваться, вызывая ваш репозиторий.

Итак, каждый onActivityCreated() вызов запускает вызов в ваш репозиторий.

Я недостаточно знаю о вашем приложении, чтобы сказать вам, что вам нужно изменить. Вот некоторые возможности:

  • Переключение с onActivityCreated() на другие методы жизненного цикла. initViewModel() можно вызвать в onCreate(), а остальные должны быть в onViewCreated().

  • Пересмотрите вашу реализацию getData(). Вам действительно нужно звонить setInput() каждый раз, когда мы переходим к этому фрагменту? Или это должно быть частью initViewModel() и выполнено один раз в onCreate()? Или, поскольку productsFilters, кажется, вообще не привязан к фрагменту, должен ли productsFilters и вызов setInput() быть частью блока init ProductsViewModel, так что это происходит только один раз?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...