ViewPager içindeki Aynı Parçaların ViewModel'ini Enjekte Etmek için Hançer 2 nasıl kullanılır


10

Dagger 2'yi projeme eklemeye çalışıyorum. Parçalarım için ViewModels (AndroidX Architecture bileşeni) enjekte edebildim.

Aynı parçanın (her sekmeler için sadece küçük bir değişiklik) 2 örneğine sahip bir ViewPager var ve her sekmesinde, bir LiveDataveri değişikliği (API) güncellenen almak için gözlemliyorum .

Sorun, api yanıtı geldiğinde ve güncellediğinde LiveData, şu anda görünür olan parçadaki aynı verilerin tüm sekmelerdeki gözlemcilere gönderilmesidir. (Bence bu muhtemelen kapsamı nedeniyle ViewModel).

Verilerimi şu şekilde gözlemliyorum:

override fun onActivityCreated(savedInstanceState: Bundle?) {
        super.onActivityCreated(savedInstanceState)

        activityViewModel.expenseList.observe(this, Observer {
            swipeToRefreshLayout.isRefreshing = false
            viewAdapter.setData(it)
        })
    ....
}

ViewModelS sağlamak için bu sınıfı kullanıyorum :

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"
    }
}

Benim ViewModelböyle bağlıyorum:

@Module
abstract class ActivityViewModelModule {
    @MainScope
    @Binds
    @IntoMap
    @ViewModelKey(ActivityViewModel::class)
    abstract fun bindActivityViewModel(viewModel: ActivityViewModel): ViewModel
}

@ContributesAndroidInjectorBenim fragman için böyle kullanıyorum :

@Module
abstract class MainFragmentBuildersModule {

    @ContributesAndroidInjector
    abstract fun contributeActivityFragment(): ActivityFragment
}

Ve bu modülleri MainActivityalt bileşenime şu şekilde ekliyorum :

@Module
abstract class ActivityBuilderModule {
...
    @ContributesAndroidInjector(
        modules = [MainViewModelModule::class, ActivityViewModelModule::class,
            AuthModule::class, MainFragmentBuildersModule::class]
    )
    abstract fun contributeMainActivity(): MainActivity
}

İşte benim 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
    }
}

Ben böyle genişletiyor DaggerFragmentve enjekte ediyorum ViewModelProviderFactory:

@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)
}

keyiki parça için farklı olacaktır.

Sadece geçerli parçanın gözlemcisinin güncellendiğinden nasıl emin olabilirim.

DÜZENLEME 1 ->

Hata ile örnek bir proje ekledim. Sorun yalnızca özel bir kapsam eklendiğinde gerçekleşiyor gibi görünüyor. Lütfen buradan örnek projeye göz atın : Github link

masterşube sorunu ile uygulama vardır. Herhangi bir sekmeyi yenilerseniz (yenilemek için kaydırın) güncellenen değer her iki sekmeye de yansıtılır. Bu sadece ona özel bir kapsam eklediğimde oluyor ( @MainScope).

working_fine şube hiçbir özel kapsam ve onun çalışma para cezası ile aynı app vardır.

Soru net değilse lütfen bize bildirin.


Anlamıyorum neden working_fineşubeden yaklaşımı kullanmayacaksın ? Neden kapsama ihtiyacınız var?
azizbekian

@azizbekian Şu anda çalışan ince dal kullanıyorum .. ama bilmek istiyorum, neden kapsamı kullanmak bunu bozacaktır.
hushed_voice

Yanıtlar:


1

Orijinal soruyu tekrar özetlemek istiyorum, işte burada:

Şu anda çalışmayı kullanıyorum fine_branch, ama kapsamı kullanmak neden bunu bozdu bilmek istiyorum.

Anladığım kadarıyla, sadece ViewModelfarklı anahtarlar kullanarak bir örnek almaya çalıştığınız için bir izleniminiz var, o zaman size farklı örnekler verilmelidir ViewModel:

// in first fragment
ViewModelProvider(...).get("true", PagerItemViewModel::class.java)

// in second fragment
ViewModelProvider(...).get("false", PagerItemViewModel::class.java)

Gerçek, biraz farklı. Aşağıdaki günlüğü parçaya koyarsanız, bu iki parçanın tam olarak aynı örneğini kullandığını görürsünüz PagerItemViewModel:

Log.i("vvv", "${if (oneOrTwo) "one:" else "two:"} viewModel hash is ${viewModel.hashCode()}")

İçeri girelim ve bunun neden olduğunu anlayalım.

Dahili olarak ViewModelProvider#get()bir örneğini elde etmek için çalışacağız PagerItemViewModelbir gelen ViewModelStoretemelde bir haritası Stringiçin ViewModel.

Ne zaman FirstFragmentbir örneği sorar , dolayısıyla boş biter olan yürütülür . aşağıdaki kodla aramayı bitirir :PagerItemViewModelmapmFactory.create(modelClass)ViewModelProviderFactorycreator.get()DoubleCheck

  public T get() {
    Object result = instance;
    if (result == UNINITIALIZED) { // 1
      synchronized (this) {
        result = instance;
        if (result == UNINITIALIZED) {
          result = provider.get();
          instance = reentrantCheck(instance, result); // 2
          /* Null out the reference to the provider. We are never going to need it again, so we
           * can make it eligible for GC. */
          provider = null;
        }
      }
    }
    return (T) result;
  }

instanceŞimdi nulldolayısıyla yeni bir örneği PagerItemViewModeloluşturulur ve kaydedilir instance(// 2 bakınız).

Şimdi aynı prosedür aşağıdakiler için gerçekleşir SecondFragment:

  • fragman bir örneği ister PagerItemViewModel
  • mapşimdi değil boş, ama yok değil bir örneğini içeriyor PagerItemViewModelanahtar ilefalse
  • üzerinden yeni bir örneği PagerItemViewModeloluşturulmaya başlandımFactory.create(modelClass)
  • ViewModelProviderFactoryİcra içi creator.get()uygulama,DoubleCheck

Şimdi, önemli an. Bu DoubleCheckise aynı örneği ait DoubleCheckolduğunu yaratmak için kullanılmıştır ViewModelzaman örneğini FirstFragmentistedin. Neden aynı örnek? Çünkü sağlayıcı yöntemine bir kapsam uyguladınız.

if (result == UNINITIALIZED)(// 1) yanlış ve tam olarak aynı örneğine değerlendirmek ViewModelarayana geri dönmüş olduğunu - SecondFragment.

Şimdi, her iki parça da aynı örneği kullanıyor, ViewModelbu yüzden aynı verileri göstermeleri gayet iyi.


Cevap için teşekkürler. Bu mantıklı. Ancak kapsamı kullanırken bunu düzeltmenin bir yolu yok mu?
hushed_voice

Daha önce sorum buydu: Neden kapsamı kullanmanız gerekiyor? Sanki bir dağa tırmanırken araba kullanmak istiyor ve şimdi "tamam, neden araba kullanamıyorum, ama bir dağa tırmanmak için nasıl araba kullanabilirim?" Niyetlerin açık değil, lütfen açıkla.
azizbekian

Belki ben hatalıyım. Benim beklentim, kapsamı kullanmanın daha iyi bir yaklaşım olmasıydı. Örneğin. Uygulamamda 2 giriş (Giriş ve Ana) varsa, giriş için 1 özel kapsam ve ana için 1 özel kapsam kullanıldığında, bir etkinlik etkinken gereksiz örnekleri kaldırır
hushed_voice

> Benim beklentim, kapsamı kullanmanın daha iyi bir yaklaşım olmasıydı. Biri diğerinden daha iyi değil. Farklı problemleri çözüyorlar, her birinin kullanım durumu var.
azizbekian

> bir etkinlik etkinken gereksiz örnekleri kaldıracaktır Bu "gereksiz örneklerin" nereden oluşturulması gerektiğini göremiyorum. ViewModeletkinlik / parçanın yaşam döngüsü ile oluşturulur ve yaşam çevrimi yok edilir edilmez yok edilir. ViewModel'in yaşam döngüsünü / yaratılış imhasını kendi başınıza yönetmemelisiniz, bu API bileşenlerinin bir istemcisi olarak mimari bileşenlerin sizin için yaptığı şey budur.
azizbekian

0

Viewpager her iki parçayı da devam ettirilmiş durumda tuttuğu için her iki parça da güncellemeyi canlı verilerden alır. Yalnızca viewpager görünür akım parçası üzerinde güncelleştirme gerektirdiğinden, bağlamı geçerli fragmanı, ev sahibi etkinliğiyle tanımlanır aktivite gerektiği arzu edilen fragmanına açıkça direkt güncellemeler.

Viewpager'a eklenen tüm parçalara ait girdileri içeren (aynı parçanın iki parça örneğini ayırt edebilen bir tanımlayıcıya sahip olduğunuzdan emin olun) bir Live to Fragment haritasını tutmanız gerekir.

Şimdi aktivitenin, fragmanların doğrudan gözlemlediği orijinal canlı verileri gözlemleyen bir MediatorLiveData'sı olacaktır. Orijinal canlı veriler bir güncelleme yayınladığında, mediatorLivedata'ya gönderilir ve turen'deki mediatorlivedata, değeri yalnızca seçili olan parçanın livedata'sına gönderir. Bu canlı veriler yukarıdaki haritadan alınacak.

Kod impl değeri şöyle görünür:

class Activity {
    val mapOfFragmentToLiveData<FragmentId, MutableLiveData> = mutableMapOf<>()

    val mediatorLiveData : MediatorLiveData<OriginalData> = object : MediatorLiveData() {
        override fun onChanged(newData : OriginalData) {
           // here get the livedata observed by the  currently selected fragment
           val currentSelectedFragmentLiveData = mapOfFragmentToLiveData.get(viewpager.getSelectedItem())
          // now post the update on this livedata
           currentSelectedFragmentLiveData.value = newData
        }
    }

  fun getOriginalLiveData(fragment : YourFragment) : LiveData<OriginalData> {
     return mapOfFragmentToLiveData.get(fragment) ?: MutableLiveData<OriginalData>().run {
       mapOfFragmentToLiveData.put(fragment, this)
  }
} 

class YourFragment {
    override fun onActivityCreated(bundle : Bundle){
       //get activity and request a livedata 
       getActivity().getOriginalLiveData(this).observe(this, Observer { _newData ->
           // observe here 
})
    }
}

Cevap için teşekkürler. Ben FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)nasıl kullanıyorum viewpager devam her iki parçası tutmak? Projeye hançer 2 eklemeden önce bu olmadı.
hushed_voice

Sözü edilen davranış ile örnek bir proje ekleyeceğim
hushed_voice

Hey örnek bir proje ekledim. Lütfen kontrol eder misin? Ben de bunun için bir lütuf ekleyeceğim (gecikme için özür dilerim)
hushed_voice

@hushed_voice Tabii size geri dönecektir.
Vishal Arora
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.