ボトムナビゲーション と ナビゲーションアーキテクチャコンポーネント を使用しています。ユーザーが1つのアイテムから別のアイテムに(下のナビゲーションを介して)ナビゲートすると、モデルを表示してリポジトリー関数を再度呼び出し、データを再度フェッチします。したがって、ユーザーが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
ラムダ式が評価され、リポジトリが呼び出されます。
したがって、onActivityCreated()
を呼び出すたびに、リポジトリへの呼び出しがトリガーされます。
私はあなたがあなたが変更する必要があるものをあなたに伝えるのにあなたのアプリについて十分に知りません。ここにいくつかの可能性があります:
onActivityCreated()
から他のライフサイクルメソッドに切り替えます。 initViewModel()
はonCreate()
で呼び出すことができ、残りはonViewCreated()
で呼び出す必要があります。
getData()
の実装を再検討してください。このフラグメントに移動するたびに、本当にsetInput()
を呼び出す必要がありますか?または、それはinitViewModel()
の一部であり、onCreate()
で一度実行する必要がありますか?または、productsFilters
はフラグメントにまったく関連付けられていないようなので、productsFilters
およびsetInput()
呼び出しは、init
ブロックの一部である必要がありますProductsViewModel
なので、1回だけ発生しますか?
私も同じ問題を抱えていました。私のアプリも、JetPackナビゲーションアーキテクチャコンポーネントでボトムナビゲーションを使用しています。宛先として複数のフラグメントを持つ単一のアクティビティデザインを使用しています。各フラグメントには独自のViewModelがあり、MainActivityにもSharedViewModelがあります。
フラグメントの1つのViewModelのinitブロックは、ネットワークからデータをフェッチします。問題は、下部のナビゲーションバーから別のフラグメントに切り替えたときに、フラグメントがMainActivityから切り離され、ViewModelがクリアされたことでした。元に戻すと、ViewModelが再作成され、initブロックが再度実行されて、新しいネットワークデータがフェッチされました。 SharedpreferenceとSharedViewModelを使用してこれを解決しました。 SharedViewmodelは、フラグメントの1つではなくMainActivityに関連付けられているため、クリアされません。私のコードは以下です。私はAndroid開発に不慣れなため、このハッキーなソリューションが適切であるかどうかはわかりませんが、機能します。取得するにはアプリケーションコンテキストが必要なので、ViewModelではなくAndroidViewModelから拡張してください。 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」に設定され、SharedViewModelがクリアされるまで「false」のままです。これは通常、アクティビティが完全に破棄され、ユーザーがアプリに戻るときに新しいデータを期待している場合です。