Dagger 2 का उपयोग कैसे करें ViewPager के अंदर समान अंशों के ViewModel को इंजेक्ट करें


10

मैं अपने प्रोजेक्ट में डैगर 2 को जोड़ने की कोशिश कर रहा हूं। मैं अपने टुकड़ों के लिए ViewModels (AndroidX आर्किटेक्चर घटक) को इंजेक्ट करने में सक्षम था।

मेरे पास एक ViewPager है जिसमें एक ही टुकड़े के 2 उदाहरण हैं (प्रत्येक टैब के लिए केवल एक मामूली बदलाव) और प्रत्येक टैब में, मैं LiveDataडेटा परिवर्तन (एपीआई से) पर अपडेट होने के लिए देख रहा हूं ।

मुद्दा यह है कि जब एपीआई प्रतिक्रिया आती है और अपडेट करती है LiveData, तो वर्तमान में दृश्यमान टुकड़े में समान डेटा सभी टैब में पर्यवेक्षकों को भेजा जा रहा है। (मुझे लगता है कि यह शायद इस दायरे के कारण है ViewModel)।

यह मैं अपने डेटा का अवलोकन कर रहा हूं:

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

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

मैं इस वर्ग का उपयोग ViewModelएस प्रदान करने के लिए कर रहा हूँ :

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

मैं अपना बंधन ViewModelइस तरह बांध रहा हूं :

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

मैं @ContributesAndroidInjectorइस तरह से अपने टुकड़े का उपयोग कर रहा हूं :

@Module
abstract class MainFragmentBuildersModule {

    @ContributesAndroidInjector
    abstract fun contributeActivityFragment(): ActivityFragment
}

और मैं इन मॉड्यूल को अपने MainActivityउपसमुच्चय में इस तरह जोड़ रहा हूं :

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

यहाँ मेरा है 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
    }
}

मैं इस तरह का विस्तार DaggerFragmentऔर इंजेक्शन दे रहा हूं 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)
}

keyदोनों टुकड़े के लिए अलग होगा।

मैं यह कैसे सुनिश्चित कर सकता हूं कि केवल वर्तमान अंश का पर्यवेक्षक ही अपडेट हो रहा है।

EDIT 1 ->

मैंने त्रुटि के साथ एक नमूना प्रोजेक्ट जोड़ा है। ऐसा लगता है कि समस्या केवल तब हो रही है जब कोई कस्टम स्कोप जोड़ा गया है। कृपया यहाँ नमूना परियोजना की जाँच करें: Github लिंक

masterशाखा के पास इश्यू वाला ऐप है। यदि आप किसी भी टैब को रीफ़्रेश करते हैं (ताज़ा करने के लिए स्वाइप) तो दोनों टैब में अपडेटेड वैल्यू परिलक्षित हो रही है। यह केवल तब हो रहा है जब मैं इसमें एक कस्टम स्कोप जोड़ता हूं ( @MainScope)।

working_fine शाखा में कोई कस्टम गुंजाइश नहीं है और इसके ठीक काम करने के लिए एक ही ऐप है।

कृपया मुझे बताएं कि क्या प्रश्न स्पष्ट नहीं है।


मुझे नहीं मिल रहा है आप working_fineशाखा से दृष्टिकोण का उपयोग क्यों नहीं करेंगे ? आपको स्कोप की आवश्यकता क्यों है?
अजीजबेकिन

@azizbekian मैं वर्तमान में काम कर रहे ठीक शाखा का उपयोग कर रहा हूँ .. लेकिन मैं जानना चाहता हूँ, क्यों इस दायरे का उपयोग करेगा।
hushed_voice

जवाबों:


1

मैं मूल प्रश्न को फिर से पढ़ाना चाहता हूं, यह यहां है:

मैं वर्तमान में कामकाज का उपयोग कर fine_branchरहा हूं, लेकिन मैं जानना चाहता हूं कि इस दायरे का उपयोग क्यों किया जाएगा।

मेरी समझ के अनुसार आपके पास एक धारणा है, सिर्फ इसलिए कि आप ViewModelविभिन्न कुंजियों का उपयोग करने का एक उदाहरण प्राप्त करने की कोशिश कर रहे हैं , तो आपको अलग-अलग उदाहरण प्रदान किए जाने चाहिए ViewModel:

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

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

वास्तविकता, थोड़ा अलग है। यदि आप निम्न लॉग को टुकड़े में रखते हैं, तो आप देखेंगे कि वे दो टुकड़े सटीक उसी उदाहरण का उपयोग कर रहे हैंPagerItemViewModel :

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

चलो में गोता लगाते हैं और समझते हैं कि ऐसा क्यों होता है।

आंतरिक ViewModelProvider#get()का एक उदाहरण प्राप्त करने के लिए कोशिश करेंगे PagerItemViewModelएक से ViewModelStoreजो मूल रूप से का एक नक्शा हैString करने के लिए ViewModel

जब FirstFragmentका एक उदाहरण के लिए पूछता है , खाली है इसलिए मार डाला जाता है, जिसमें समाप्त होता है । निम्नलिखित कोड के साथ कॉलिंग समाप्त होती है :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अब है null, इसलिए का एक नया उदाहरण PagerItemViewModelबन जाता है और में सहेजा गया है instance(देखें // 2)।

अब ठीक उसी प्रक्रिया के लिए होता है SecondFragment:

  • टुकड़ा एक उदाहरण के लिए पूछता है PagerItemViewModel
  • mapअब खाली नहीं है, लेकिन कुंजी के साथ एक उदाहरण नहीं हैPagerItemViewModelfalse
  • PagerItemViewModelके माध्यम से बनाए जाने के लिए एक नया उदाहरण शुरू किया गया हैmFactory.create(modelClass)
  • अंदर ViewModelProviderFactoryनिष्पादन पहुँच creator.get()जिसका कार्यान्वयन हैDoubleCheck

अब, महत्वपूर्ण क्षण। यह DoubleCheckहै एक ही उदाहरण का DoubleCheckहै कि बनाने के लिए इस्तेमाल किया गया था ViewModelउदाहरण है जब FirstFragmentयह करने के लिए कहा। यह एक ही उदाहरण क्यों है? क्योंकि आपने प्रदाता विधि में एक गुंजाइश लागू कर दी है।

if (result == UNINITIALIZED)(// 1) झूठे और की ठीक उसी उदाहरण के लिए मूल्यांकन कर रही है ViewModelफोन करने वाले को लौट जा रहा है - SecondFragment

अब, दोनों टुकड़े एक ही उदाहरण का उपयोग कर रहे हैं ViewModelइसलिए यह पूरी तरह से ठीक है कि वे एक ही डेटा प्रदर्शित कर रहे हैं।


जवाब के लिए धन्यवाद। यह समझ में आता है। लेकिन गुंजाइश का उपयोग करते हुए इसे ठीक करने का कोई तरीका नहीं है?
hushed_voice

यह मेरा सवाल पहले से था: आपको गुंजाइश का उपयोग करने की आवश्यकता क्यों है? यह ऐसा है जैसे आप पहाड़ पर चढ़ते समय कार का उपयोग करना चाहते हैं और अब आप कह रहे हैं "ठीक है, मुझे लगता है कि मैं कार का उपयोग क्यों नहीं कर सकता, लेकिन मैं पहाड़ पर चढ़ने के लिए कार का उपयोग कैसे कर सकता हूं?" आपके इरादे स्पष्ट नहीं हैं, कृपया स्पष्ट करें।
अजीजबेकिन

शायद मैं गलत हूँ। मेरी उम्मीद यह थी कि गुंजाइश का उपयोग करना बेहतर दृष्टिकोण है। उदाहरण के लिए। अगर मेरे आवेदन में 2 गतिविधियाँ हैं (लॉगिन और मुख्य), लॉगिन के लिए 1 कस्टम स्कोप और मेन के लिए 1 कस्टम स्कोप का उपयोग करना, अनावश्यक गतिविधियों को हटा देगा, जबकि एक गतिविधि सक्रिय है
hushed_voice

> मेरी अपेक्षा यह थी कि स्कोप का उपयोग करना एक बेहतर तरीका है। ऐसा नहीं है कि एक दूसरे से बेहतर है। वे विभिन्न समस्याओं को हल कर रहे हैं, प्रत्येक का उपयोग-मामला है।
अजीज़बेकियन

> अनावश्यक आवृत्तियों को हटा देगा, जबकि एक गतिविधि सक्रिय है यह नहीं देख सकता कि उन "अनावश्यक उदाहरणों" को कहां से बनाया जाना चाहिए। ViewModelगतिविधि / टुकड़े के जीवनचक्र के साथ बनाया गया है और जैसे ही यह जीवन चक्र को नष्ट कर रहा है नष्ट हो जाता है। आपको अपने दम पर ViewModel के जीवन चक्र / निर्माण-विनाश का प्रबंधन नहीं करना चाहिए, यही आर्किटेक्चर घटक आपके लिए उस API के क्लाइंट के रूप में कर रहे हैं।
अजीज़बेकियन

0

दोनों अंशों को लाइवटाटा से अद्यतन प्राप्त होता है क्योंकि दृश्यदर्शी दोनों टुकड़ों को फिर से शुरू होने की स्थिति में रखता है। जब से तुम केवल viewpager में वर्तमान टुकड़ा दिखाई दे, के संदर्भ पर अद्यतन करने की आवश्यकता वर्तमान टुकड़ा, मेजबान गतिविधि के द्वारा परिभाषित किया गया है गतिविधि चाहिए वांछित टुकड़ा करने के लिए स्पष्ट रूप से प्रत्यक्ष अद्यतन।

आपको सभी अंशों के लिए प्रविष्टियों वाले LiveData के लिए Fragment का एक नक्शा बनाए रखने की आवश्यकता है (सुनिश्चित करें कि एक पहचानकर्ता है जो एक ही टुकड़े के दो टुकड़े उदाहरणों को अलग कर सकता है) viewpager में जोड़ा गया है।

अब गतिविधि में एक MediatorLiveData होगा जो सीधे अंशों द्वारा देखे गए मूल लाइवटाटा का अवलोकन करेगा। जब भी मूल लिवटा एक अद्यतन पोस्ट करता है, तो इसे मध्यस्थता में वितरित किया जाएगा और तूरिन में मध्यस्थता केवल वर्तमान चयनित टुकड़े के लाइवता के मूल्य को पोस्ट करेगी। यह लाइवटाटा ऊपर के नक्शे से पुनर्प्राप्त किया जाएगा।

कोड प्रत्यारोपण की तरह दिखेगा -

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

जवाब के लिए धन्यवाद। मैं उपयोग कर रहा हूँ FragmentPagerAdapter(fragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT)तो कैसे दृश्यदर्शी दोनों टुकड़ों को फिर से शुरू की स्थिति में रख रहा है? ऐसा नहीं हो रहा था इससे पहले कि मैं इस परियोजना में 2 डैगर जोड़ दिया।
hushed_voice

मैं उक्त व्यवहार के साथ एक नमूना परियोजना जोड़ने की कोशिश
करूंगा

अरे मैंने एक सैंपल प्रोजेक्ट जोड़ा है। क्या आप कृपया इसकी जाँच कर सकते हैं। मैं इसके लिए एक इनाम भी
जोड़ूंगा

@hushed_voice ज़रूर आपको वापस मिलेगा।
विशाल अरोड़ा
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.