Saya mencoba menambahkan Belati 2 ke proyek saya. Saya dapat menyuntikkan ViewModels (komponen Arsitektur AndroidX) untuk fragmen saya.
Saya memiliki ViewPager yang memiliki 2 contoh fragmen yang sama (Hanya sedikit perubahan untuk setiap tab) dan di setiap tab, saya mengamati LiveData
untuk mendapatkan pembaruan tentang perubahan data (dari API).
Masalahnya adalah ketika respons api datang dan memperbarui LiveData
, data yang sama dalam fragmen yang saat ini terlihat dikirim ke pengamat di semua tab. (Saya pikir ini mungkin karena ruang lingkupnya ViewModel
).
Inilah cara saya mengamati data saya:
override fun onActivityCreated(savedInstanceState: Bundle?) {
super.onActivityCreated(savedInstanceState)
activityViewModel.expenseList.observe(this, Observer {
swipeToRefreshLayout.isRefreshing = false
viewAdapter.setData(it)
})
....
}
Saya menggunakan kelas ini untuk menyediakan ViewModel
s:
class ViewModelProviderFactory @Inject constructor(creators: MutableMap<Class<out ViewModel?>?, Provider<ViewModel?>?>?) :
ViewModelProvider.Factory {
private val creators: MutableMap<Class<out ViewModel?>?, Provider<ViewModel?>?>? = creators
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel?>? = creators!![modelClass]
if (creator == null) { // if the viewmodel has not been created
// loop through the allowable keys (aka allowed classes with the @ViewModelKey)
for (entry in creators.entries) { // if it's allowed, set the Provider<ViewModel>
if (modelClass.isAssignableFrom(entry.key!!)) {
creator = entry.value
break
}
}
}
// if this is not one of the allowed keys, throw exception
requireNotNull(creator) { "unknown model class $modelClass" }
// return the Provider
return try {
creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
companion object {
private val TAG: String? = "ViewModelProviderFactor"
}
}
Saya mengikat saya ViewModel
seperti ini:
@Module
abstract class ActivityViewModelModule {
@MainScope
@Binds
@IntoMap
@ViewModelKey(ActivityViewModel::class)
abstract fun bindActivityViewModel(viewModel: ActivityViewModel): ViewModel
}
Saya menggunakan @ContributesAndroidInjector
untuk fragmen saya seperti ini:
@Module
abstract class MainFragmentBuildersModule {
@ContributesAndroidInjector
abstract fun contributeActivityFragment(): ActivityFragment
}
Dan saya menambahkan modul ini ke MainActivity
subkomponen saya seperti ini:
@Module
abstract class ActivityBuilderModule {
...
@ContributesAndroidInjector(
modules = [MainViewModelModule::class, ActivityViewModelModule::class,
AuthModule::class, MainFragmentBuildersModule::class]
)
abstract fun contributeMainActivity(): MainActivity
}
Ini milik saya AppComponent
:
@Singleton
@Component(
modules =
[AndroidSupportInjectionModule::class,
ActivityBuilderModule::class,
ViewModelFactoryModule::class,
AppModule::class]
)
interface AppComponent : AndroidInjector<SpenmoApplication> {
@Component.Builder
interface Builder {
@BindsInstance
fun application(application: Application): Builder
fun build(): AppComponent
}
}
Saya memperpanjang DaggerFragment
dan menyuntikkan ViewModelProviderFactory
seperti ini:
@Inject
lateinit var viewModelFactory: ViewModelProviderFactory
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
....
activityViewModel =
ViewModelProviders.of(this, viewModelFactory).get(key, ActivityViewModel::class.java)
activityViewModel.restartFetch(hasReceipt)
}
yang key
akan berbeda untuk kedua fragmen.
Bagaimana saya bisa memastikan bahwa hanya pengamat fragmen saat ini yang diperbarui.
EDIT 1 ->
Saya telah menambahkan proyek sampel dengan kesalahan. Sepertinya masalah hanya terjadi ketika ruang lingkup kustom ditambahkan. Silakan periksa contoh proyek di sini: Tautan Github
master
cabang memiliki aplikasi dengan masalah ini. Jika Anda me-refresh tab apa pun (gesek untuk menyegarkan) nilai yang diperbarui tercermin di kedua tab. Ini hanya terjadi ketika saya menambahkan ruang lingkup kustom untuk itu ( @MainScope
).
working_fine
cabang memiliki aplikasi yang sama tanpa ruang lingkup khusus dan berfungsi dengan baik.
Tolong beri tahu saya jika pertanyaannya tidak jelas.
sumber
working_fine
cabang? Mengapa Anda membutuhkan ruang lingkup?Jawaban:
Saya ingin rekap pertanyaan awal, ini dia:
Sesuai pemahaman saya Anda memiliki kesan, bahwa hanya karena Anda mencoba untuk mendapatkan contoh
ViewModel
menggunakan kunci yang berbeda, maka Anda harus diberikan contoh berbeda dariViewModel
:Kenyataannya, sedikit berbeda. Jika Anda memasukkan fragmen log berikut, Anda akan melihat bahwa kedua fragmen tersebut menggunakan instance yang sama persis
PagerItemViewModel
:Mari selami dan mengerti mengapa ini terjadi.
Internal
ViewModelProvider#get()
akan mencoba untuk mendapatkan sebuah instance dariPagerItemViewModel
dariViewModelStore
yang pada dasarnya merupakan petaString
untukViewModel
.Ketika
FirstFragment
meminta contohPagerItemViewModel
yangmap
kosong, makamFactory.create(modelClass)
dijalankan, yang berakhir diViewModelProviderFactory
.creator.get()
akhirnya memanggilDoubleCheck
dengan kode berikut:The
instance
sekarangnull
, maka contoh baru dariPagerItemViewModel
dibuat dan disimpan dalaminstance
(lihat // 2).Sekarang prosedur yang sama persis terjadi untuk
SecondFragment
:PagerItemViewModel
map
sekarang tidak kosong, tetapi tidak mengandung instancePagerItemViewModel
dengan kuncifalse
PagerItemViewModel
dimulai untuk dibuat melaluimFactory.create(modelClass)
ViewModelProviderFactory
Eksekusi di dalam mencapaicreator.get()
yang implementasinyaDoubleCheck
Sekarang, momen kuncinya. Ini
DoubleCheck
adalah contoh yang sama dariDoubleCheck
yang digunakan untuk membuatViewModel
instance ketikaFirstFragment
diminta untuk itu. Mengapa itu contoh yang sama? Karena Anda telah menerapkan cakupan pada metode penyedia.The
if (result == UNINITIALIZED)
(// 1) sedang mengevaluasi ke false dan contoh yang sama persisViewModel
dikembalikan ke pemanggil -SecondFragment
.Sekarang, kedua fragmen tersebut menggunakan contoh yang sama sehingga sama-sama
ViewModel
baik bahwa mereka menampilkan data yang sama.sumber
ViewModel
dibuat dengan siklus aktivitas / fragmen dan dihancurkan segera setelah siklus hosting itu dihancurkan. Anda seharusnya tidak mengelola siklus hidup / penghancuran ViewModel sendiri, itulah yang dilakukan komponen arsitektur untuk Anda sebagai klien API itu.Kedua fragmen menerima pembaruan dari livedata karena viewpager menjaga kedua fragmen dalam keadaan dilanjutkan. Karena Anda memerlukan pembaruan hanya pada fragmen saat ini yang terlihat di viewpager, konteks fragmen saat ini ditentukan oleh aktivitas host, aktivitas tersebut harus secara eksplisit mengarahkan pembaruan ke fragmen yang diinginkan.
Anda perlu mempertahankan peta Fragmen ke LiveData yang berisi entri untuk semua fragmen (pastikan memiliki pengidentifikasi yang dapat membedakan dua instance fragmen dari fragmen yang sama) ditambahkan ke viewpager.
Sekarang aktivitas akan memiliki MediatorLiveData yang mengamati livata asli yang diamati oleh fragmen secara langsung. Setiap kali livedata asli memposting pembaruan, itu akan dikirim ke mediatorLivedata dan mediatorlivedata di turen hanya akan memposting nilai ke livedata dari fragmen yang dipilih saat ini. Livedata ini akan diambil dari peta di atas.
Implan kode akan terlihat seperti -
sumber
FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)
jadi bagaimana viewpager menjaga kedua fragmen dalam keadaan dilanjutkan? Ini tidak terjadi sebelum saya menambahkan belati 2 ke proyek.