Я использую Нижняя навигация с Компонент архитектуры навигации. Когда пользователь переходит от одного элемента к другому (через нижнюю навигацию) и обратно, просмотрите функцию репозитория вызова модели, чтобы снова получить данные. Таким образом, если пользователь переходит туда и обратно 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)
}
}
}
@JeelVankhede Я добавил это.
Поставьте точку останова на repository.getProducts(it.filters, it.limit)
и посмотрите, когда она будет вызвана. Я предполагаю, что компонент навигации создает 10 экземпляров HomeFragment
в вашем сценарии, и каждый из них получит свою собственную модель представления.
Хорошо, одним из решений было бы избегать получение данных из фрагмента и извлекать его при действии один раз вместо (Поскольку вы уже используете контекст деятельности для ViewModelProviders
), а затем наблюдать за ним, используя LiveData
внутри фрагмента.
@JeelVankhede У меня есть много фрагментов, каждому из которых в какой-то момент понадобится определенный объем данных. Поэтому я избегаю получения дополнительных данных, которые пользователю никогда не понадобятся. И я использую GraphQL на сервере, я думаю, что одна из причин, по которой он был создан, — это предотвращение избыточной выборки данных.
Эта строка мне кажется разнообразный: попробуйте ViewModelProviders.of(this@run, viewModelFactory).get(ProductsViewModel::class.java)
@CommonsWare это repository.getProducts(it.filters, it.limit)
вызывается каждый раз, когда я вижу фрагмент. Это мой первый раз, когда я использую точку останова, поэтому я не знаю, использую ли я ее коллективно, но я могу просто сказать, что она вызывается, потому что при запуске приложения поток приостанавливается, пока я не нажму кнопку воспроизведения (этот фрагмент находится дома). Когда я перехожу к другому фрагменту и возвращаюсь, поток снова приостанавливается. надеюсь правильно сделал! Так что это значит? заранее спасибо
«Когда я перехожу к другому фрагменту и возвращаюсь, поток снова приостанавливается» — в отладчике вы увидите стек вызовов, когда ваша точка останова сработает (по умолчанию она находится в левой части инструмента «Отладка»). Посмотрите, какие строки вашего кода вызывают срабатывание точки останова. Если это связано с тем, что каждый раз создается новый экземпляр ProductsViewModel
, то я думаю, что моя предыдущая догадка верна: компонент навигации создает 10 экземпляров HomeFragment
в вашем сценарии, и каждый получит свою собственную модель представления.
@JeelVankhede Спасибо за ответ. Но это не помогает. Тем не менее данные обновляются.
@CommonsWare Я добавил скриншот отладчика, пожалуйста, посмотрите, если я что-то упустил. Я перемещаюсь 4 раза и получил 8 обращений. Мое наблюдение: setInput в productsViewModel, getData в HomeFragment вызывает его.
В onActivityCreated()
вы звоните getData()
. Там у вас есть:
productsViewModel.setInput(productsFilters, 2)
Это, в свою очередь, изменяет значение _input
в вашем ProductsViewModel
. И каждый раз, когда _input
изменяется, лямбда-выражение getProducts
будет оцениваться, вызывая ваш репозиторий.
Таким образом, каждый вызов onActivityCreated()
вызывает вызов вашего репозитория.
Я недостаточно знаю о вашем приложении, чтобы сказать вам, что вам нужно изменить. Вот некоторые возможности:
Переключитесь с onActivityCreated()
на другие методы жизненного цикла. initViewModel()
можно вызвать в onCreate()
, а остальные должны быть в onViewCreated()
.
Пересмотрите свою getData()
реализацию. Вам действительно нужно вызывать setInput()
каждый раз, когда мы переходим к этому фрагменту? Или это должно быть частью initViewModel()
и делаться один раз в onCreate()
? Или, поскольку productsFilters
, похоже, вообще не привязан к фрагменту, должны ли productsFilters
и вызов setInput()
быть частью init
блока ProductsViewModel
, чтобы это произошло только один раз?
Что мне использовать в этой строке кода productsViewModel.products.observe(--WHAT-HERE--,....
** this** или this.activity или **viewLifecycleOwner?
@Nux: это останется там, где оно есть. Однако первые два оператора в getData()
(объявление productsFilters
и вызов setInput()
не зависят от фрагмента, поэтому они могут стать частью блока init
модели представления.
Я сделал, как вы упомянули. Спасибо, теперь нет повторной выборки. Единственная оставшаяся проблема заключается в том, что ViewLifecycleOwner, когда он упоминается в onCreate, вызывает сбой приложения. Я изменил его на это, и когда я перехожу от одного элемента к другому в навигационном меню, все в порядке. Но когда я нажимаю название продукта, чтобы перейти к фрагменту сведений о продукте (через указания, реализованные в редакторе навигации), и возвращаюсь, данные теряются. Что может быть причиной этого?
@Nux: понятия не имею, извини. Вы можете задать отдельный вопрос о переполнении стека, показать текущий код и объяснить там свои симптомы.
@Nux Разве onCreate не вызывается каждый раз, когда вы возвращаетесь к своему фрагменту ?? Я сталкиваюсь с похожей ситуацией.
@AlokBharti Я уже давно закончил приложение, в котором была эта проблема, я думаю, что сейчас у меня нет точного ответа на ваш вопрос.
Если возможно, вы можете помочь мне здесь? Как вам удается предотвратить вызов функции модели представления, которая содержит вызов API, всякий раз, когда вы возвращаетесь к фрагменту??
Когда вы выбираете другие страницы с помощью нижней навигации и возвращаетесь, фрагмент уничтожается и создается заново. Итак, onCreate, onViewCreated и onActivityCreate снова побежит. Но viewModel все еще жив.
Таким образом, вы можете вызвать свою функцию (получитьПродукты) внутри "в этом" в модели представления, чтобы запустить ее один раз.
init {
getProducts()
}
Это практически возможно во всех случаях, предположим, что вы передали параметр фрагменту, множественный сетевой вызов :(. Код становится более грязным.
Одним из простых решений было бы изменить владельца ViewModelProvider с это на требуется активность () в этой строке кода:
ViewModelProviders.of(this, viewModelFactory).get(ProductsViewModel::class.java)
Поэтому, поскольку действие является владельцем модели представления, а жизненный цикл модели представления привязан к действию, а не к фрагменту, навигация между фрагментами внутри действия не приведет к воссозданию модели представления.
определите свою ProductsViewModel с помощью статики в mainActivity и инициализируйте в методе onCreate. Теперь просто используйте это во фрагменте:
MainActivity.productsViewModel.products.observe(viewLifecycleOwner, Observer {
it.products()?.let { products -> productsAdapter.setData(products) }
})
Можете ли вы рассказать, как вы
ViewModelProvider.Factory
создаете экземпляры?