Android ViewModel अतिरिक्त तर्क


111

क्या AndroidViewModelआवेदन के संदर्भ को छोड़कर मेरे कस्टम कंस्ट्रक्टर के लिए अतिरिक्त तर्क पारित करने का एक तरीका है । उदाहरण:

public class MyViewModel extends AndroidViewModel {
    private final LiveData<List<MyObject>> myObjectList;
    private AppDatabase appDatabase;

    public MyViewModel(Application application, String param) {
        super(application);
        appDatabase = AppDatabase.getDatabase(this.getApplication());

        myObjectList = appDatabase.myOjectModel().getMyObjectByParam(param);
    }
}

और जब मैं अपना कस्टम ViewModelवर्ग बनाना चाहता हूं, तो मैं इस कोड का उपयोग अपने टुकड़े में करता हूं:

MyViewModel myViewModel = ViewModelProvider.of(this).get(MyViewModel.class)

इसलिए मुझे नहीं पता कि मैं String paramअपने तर्क में अतिरिक्त तर्क कैसे पारित करूं ViewModel। मैं केवल आवेदन संदर्भ पास कर सकता हूं, लेकिन अतिरिक्त तर्क नहीं। मेरे द्वारा किसी भी सहायता की वास्तव में सराहना की जाएगी। धन्यवाद।

संपादित करें: मैंने कुछ कोड जोड़े हैं। मुझे उम्मीद है कि अब यह बेहतर है।


अधिक विवरण और कोड जोड़ें
hugo

त्रुटि संदेश क्या है?
मूसा एपिको

कोई त्रुटि संदेश नहीं है। मुझे बस यह नहीं पता कि व्यूअरोडेलप्रोइडर के रूप में कंस्ट्रक्टर के लिए तर्क कहां सेट करना है, इसका उपयोग AndroidViewModel ऑब्जेक्ट बनाने के लिए किया जाता है।
मारियो रुडमैन

जवाबों:


217

आपको अपने ViewModel के लिए एक कारखाना वर्ग होना चाहिए।

public class MyViewModelFactory implements ViewModelProvider.Factory {
    private Application mApplication;
    private String mParam;


    public MyViewModelFactory(Application application, String param) {
        mApplication = application;
        mParam = param;
    }


    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        return (T) new MyViewModel(mApplication, mParam);
    }
}

और जब दृश्य मॉडल को तत्काल करना, आप इस तरह करते हैं:

MyViewModel myViewModel = ViewModelProvider(this, new MyViewModelFactory(this.getApplication(), "my awesome param")).get(MyViewModel.class);

कोटलिन के लिए, आप प्रत्यायोजित संपत्ति का उपयोग कर सकते हैं:

val viewModel: MyViewModel by viewModels { MyViewModelFactory(getApplication(), "my awesome param") }

एक और नया विकल्प भी है - अपने कारखाने की तात्कालिकता के साथ लागू करने HasDefaultViewModelProviderFactoryऔर ओवरराइड getDefaultViewModelProviderFactory()करने के लिए और फिर आप कारखाने के बिना ViewModelProvider(this)या कॉल करेंगे by viewModels()


4
क्या हर ViewModelवर्ग को अपने ViewModelFactory की आवश्यकता है?
dmlebron

6
लेकिन हर ViewModelकोई / अलग डीआई होगा। आपको कैसे पता चलेगा कि कौन सा उदाहरण create()विधि पर लौटा है ?
dmlebron

1
आपका ViewModel बदलाव ओरिएंटेशन के बाद फिर से बनाया जाएगा। आपका कैंट हर बार फैक्ट्री बनाता है।
टिम

3
यह सच नहीं है। नई ViewModelरचना विधि को रोकती है get()। प्रलेखन के आधार पर: "एक मौजूदा ViewModel को लौटाता है या इस दायरे में (आम तौर पर एक टुकड़ा या एक गतिविधि), इस ViewModelProvider के साथ जुड़ा हुआ एक नया बनाता है।" देखें: developer.android.com/reference/android/arch/lifecycle/...
mlyko

2
के बारे return modelClass.cast(new MyViewModel(mApplication, mParam))में चेतावनी से छुटकारा पाने के लिए उपयोग करने के बारे में कैसे
jackycflau

23

निर्भरता इंजेक्शन के साथ लागू करें

यह उत्पादन कोड के लिए अधिक उन्नत और बेहतर है।

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

डैगर हिल्ट, अगली पीढ़ी का समाधान है, अल्फा में 7/12/20 के रूप में, लाइब्रेरी के रिलीज़ होने की स्थिति में एक सरल सेटअप के साथ एक ही उपयोग के मामले की पेशकश।

कोटलिन में जीवनचक्र 2.2.0 के साथ लागू करें

पासिंग आर्ग्युमेंट्स / पैरामीटर्स

// Override ViewModelProvider.NewInstanceFactory to create the ViewModel (VM).
class SomeViewModelFactory(private val someString: String): ViewModelProvider.NewInstanceFactory() {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T = SomeViewModel(someString) as T
} 

class SomeViewModel(private val someString: String) : ViewModel() {
    init {
        //TODO: Use 'someString' to init process when VM is created. i.e. Get data request.
    }
}

class Fragment: Fragment() {
    // Create VM in activity/fragment with VM factory.
    val someViewModel: SomeViewModel by viewModels { SomeViewModelFactory("someString") } 
}

SavedState को तर्क / पैरामीटर के साथ सक्षम करना

class SomeViewModelFactory(
        private val owner: SavedStateRegistryOwner,
        private val someString: String) : AbstractSavedStateViewModelFactory(owner, null) {
    override fun <T : ViewModel?> create(key: String, modelClass: Class<T>, state: SavedStateHandle) =
            SomeViewModel(state, someString) as T
}

class SomeViewModel(private val state: SavedStateHandle, private val someString: String) : ViewModel() {
    val feedPosition = state.get<Int>(FEED_POSITION_KEY).let { position ->
        if (position == null) 0 else position
    }
        
    init {
        //TODO: Use 'someString' to init process when VM is created. i.e. Get data request.
    }
        
     fun saveFeedPosition(position: Int) {
        state.set(FEED_POSITION_KEY, position)
    }
}

class Fragment: Fragment() {
    // Create VM in activity/fragment with VM factory.
    val someViewModel: SomeViewModel by viewModels { SomeViewModelFactory(this, "someString") } 
    private var feedPosition: Int = 0
     
    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        someViewModel.saveFeedPosition((contentRecyclerView.layoutManager as LinearLayoutManager)
                .findFirstVisibleItemPosition())
    }    
        
    override fun onViewStateRestored(savedInstanceState: Bundle?) {
        super.onViewStateRestored(savedInstanceState)
        feedPosition = someViewModel.feedPosition
    }
}

फैक्ट्री में ओवरराइडिंग करते समय मुझे एक चेतावनी मिलती है जिसमें कहा जाता है कि अनियंत्रित कास्ट 'आइटम व्यूमॉडल टू टी'
सेंसोनजो

1
यह चेतावनी अब तक मेरे लिए कोई मुद्दा नहीं है। हालाँकि, मैं इसे और आगे देखूंगा जब मैं खंड के माध्यम से इसका एक उदाहरण बनाने के बजाय डैगर का उपयोग करके इसे इंजेक्ट करने के लिए ViewModel फैक्ट्री को रिफ्लेक्टर करता हूं।
एडम हर्विट्ज

15

कई अलग-अलग दृश्य मॉडल के बीच साझा किए गए एक कारखाने के लिए मैं इस तरह mlyko के उत्तर का विस्तार करूंगा:

public class MyViewModelFactory extends ViewModelProvider.NewInstanceFactory {
    private Application mApplication;
    private Object[] mParams;

    public MyViewModelFactory(Application application, Object... params) {
        mApplication = application;
        mParams = params;
    }

    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        if (modelClass == ViewModel1.class) {
            return (T) new ViewModel1(mApplication, (String) mParams[0]);
        } else if (modelClass == ViewModel2.class) {
            return (T) new ViewModel2(mApplication, (Integer) mParams[0]);
        } else if (modelClass == ViewModel3.class) {
            return (T) new ViewModel3(mApplication, (Integer) mParams[0], (String) mParams[1]);
        } else {
            return super.create(modelClass);
        }
    }
}

और तत्काल मॉडल देखें:

ViewModel1 vm1 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), "something")).get(ViewModel1.class);
ViewModel2 vm2 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), 123)).get(ViewModel2.class);
ViewModel3 vm3 = ViewModelProviders.of(this, new MyViewModelFactory(getApplication(), 123, "something")).get(ViewModel3.class);

अलग-अलग व्यू मॉडल के साथ अलग-अलग कंस्ट्रक्टर होते हैं।


9
मैं इस तरह से अनुशंसा नहीं करता हूं क्योंकि कुछ कारण: कारखाने में 1) पैरामीटर सुरक्षित नहीं हैं - इस तरह आप रनटाइम पर अपना कोड तोड़ सकते हैं। हमेशा इस दृष्टिकोण से बचने की कोशिश करें जब 2) दृश्य मॉडल प्रकारों की जाँच करना वास्तव में चीजों को करने का ओओपी तरीका नहीं है। चूँकि ViewModels को आधार प्रकार में डाला जाता है, फिर से आप संकलन के दौरान बिना किसी चेतावनी के रनटाइम के दौरान कोड को तोड़ सकते हैं .. इस मामले में मैं डिफ़ॉल्ट एंड्रॉइड फैक्ट्री का उपयोग करने का सुझाव दूंगा और पहले से ही देखे गए मॉडल के मापदंडों को पास कर सकता हूं।
mlyko

@mlyko ज़रूर, ये सभी वैध आपत्तियाँ हैं और व्यूमॉडल डेटा सेट करने के लिए स्वयं की विधि (एस) हमेशा एक विकल्प है। लेकिन कभी-कभी आप यह सुनिश्चित करना चाहते हैं कि व्यूमोडल को इनिशियलाइज़ किया गया है, इसलिए कंस्ट्रक्टर का उपयोग। अन्यथा आपको अपने आप को स्थिति को संभालना होगा "व्यूमोडल अभी तक आरंभीकृत नहीं हुआ है"। उदाहरण के लिए यदि व्यूमॉडल में वे विधियाँ हैं जो LivedData और पर्यवेक्षकों को लौटाती हैं, जो कि विभिन्न दृश्य जीवनचक्र विधियों से जुड़ी हैं।
रज्जन

3

AndroidViewModel मामलों के लिए उपरोक्त कोटलिन समाधान @ vilpe89 के आधार पर

class ExtraParamsViewModelFactory(private val application: Application, private val myExtraParam: String): ViewModelProvider.NewInstanceFactory() {
override fun <T : ViewModel?> create(modelClass: Class<T>): T = SomeViewModel(application, myExtraParam) as T

}

तब एक टुकड़ा के रूप में viewModel आरंभ कर सकते हैं

class SomeFragment : Fragment() {
 ....
    private val myViewModel: SomeViewModel by viewModels {
        ExtraParamsViewModelFactory(this.requireActivity().application, "some string value")
    }
 ....
}

और फिर वास्तविक ViewModel वर्ग

class SomeViewModel(application: Application, val myExtraParam:String) : AndroidViewModel(application) {
....
}

या किसी उपयुक्त विधि में ...

override fun onActivityCreated(...){
    ....

    val myViewModel = ViewModelProvider(this, ExtraParamsViewModelFactory(this.requireActivity().application, "some string value")).get(SomeViewModel::class.java)

    ....
}

प्रश्न पूछता है कि संदर्भ का उपयोग किए बिना तर्क / मापदंडों को कैसे पारित किया जाए जो उपरोक्त का पालन नहीं करता है: क्या आवेदन के संदर्भ को छोड़कर मेरे कस्टम AndroidViewModel निर्माता को अतिरिक्त तर्क पारित करने का एक तरीका है?
एडम हर्विट्ज

3

मैंने इसे एक वर्ग बनाया है जिसमें पहले से ही बनाई गई वस्तु पारित हो गई है।

private Map<String, ViewModel> viewModelMap;

public ViewModelFactory() {
    this.viewModelMap = new HashMap<>();
}

public void add(ViewModel viewModel) {
    viewModelMap.put(viewModel.getClass().getCanonicalName(), viewModel);
}

@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    for (Map.Entry<String, ViewModel> viewModel : viewModelMap.entrySet()) {
        if (viewModel.getKey().equals(modelClass.getCanonicalName())) {
            return (T) viewModel.getValue();
        }
    }
    return null;
}

और तब

ViewModelFactory viewModelFactory = new ViewModelFactory();
viewModelFactory.add(new SampleViewModel(arg1, arg2));
SampleViewModel sampleViewModel = ViewModelProviders.of(this, viewModelFactory).get(SampleViewModel.class);

हमारे पास प्रत्येक ViewModel के लिए एक ViewModelFactory होना चाहिए जो कि कंस्ट्रक्टर को पैरामीटर पास करे ??
के प्रदीप कुमार रेड्डी

सभी ViewModels के लिए केवल एक ही ViewModelFactory
Danil

क्या हैशपैड कुंजी के रूप में विहित नाम का उपयोग करने का कोई कारण है? क्या मैं class.simpleName का उपयोग कर सकता हूं?
के प्रदीप कुमार रेड्डी

हां, लेकिन आपको यह सुनिश्चित करना चाहिए कि कोई डुप्लिकेट नाम नहीं हैं
Danil

क्या यह कोड लिखने की अनुशंसित शैली है? आप स्वयं इस कोड को लेकर आए हैं या आपने इसे एंड्रॉइड डॉक्स में पढ़ा है?
के प्रदीप कुमार रेड्डी

1

मैंने एक लाइब्रेरी लिखी है, जो इसे और अधिक सरल और साफ-सुथरा बनाने का काम करना चाहिए, किसी मल्टीबाइंडिंग या फैक्ट्री बॉयलरप्लेट की जरूरत नहीं है, जबकि ViewModel तर्कों के साथ मूल रूप से काम करते हुए, जो डैगर द्वारा निर्भरता के रूप में प्रदान किए जा सकते हैं: https ://github.radutopor/ViewModelFactory

@ViewModelFactory
class UserViewModel(@Provided repository: Repository, userId: Int) : ViewModel() {

    val greeting = MutableLiveData<String>()

    init {
        val user = repository.getUser(userId)
        greeting.value = "Hello, $user.name"
    }    
}

दृश्य में:

class UserActivity : AppCompatActivity() {
    @Inject
    lateinit var userViewModelFactory2: UserViewModelFactory2

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_user)
        appComponent.inject(this)

        val userId = intent.getIntExtra("USER_ID", -1)
        val viewModel = ViewModelProviders.of(this, userViewModelFactory2.create(userId))
            .get(UserViewModel::class.java)

        viewModel.greeting.observe(this, Observer { greetingText ->
            greetingTextView.text = greetingText
        })
    }
}

1

(KOTLIN) मेरा समाधान रिफ्लेक्शन का बहुत कम उपयोग करता है।

कहते हैं कि आप हर बार नई ViewModel क्लास बनाने के लिए एक ही दिखने वाली फ़ैक्टरी क्लास नहीं बनाना चाहते हैं, जिसमें कुछ तर्कों की ज़रूरत होती है। आप इसे प्रतिबिंब के माध्यम से पूरा कर सकते हैं।

उदाहरण के लिए आपके पास दो अलग-अलग गतिविधियाँ होंगी:

class Activity1 : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val args = Bundle().apply { putString("NAME_KEY", "Vilpe89") }
        val viewModel = ViewModelProviders.of(this, ViewModelWithArgumentsFactory(args))
            .get(ViewModel1::class.java)
    }
}

class Activity2 : FragmentActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)

        val args = Bundle().apply { putInt("AGE_KEY", 29) }
        val viewModel = ViewModelProviders.of(this, ViewModelWithArgumentsFactory(args))
            .get(ViewModel2::class.java)
    }
}

और उन गतिविधियों के लिए ViewModels:

class ViewModel1(private val args: Bundle) : ViewModel()

class ViewModel2(private val args: Bundle) : ViewModel()

फिर मैजिक पार्ट, फैक्ट्री क्लास का कार्यान्वयन:

class ViewModelWithArgumentsFactory(private val args: Bundle) : NewInstanceFactory() {
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        try {
            val constructor: Constructor<T> = modelClass.getDeclaredConstructor(Bundle::class.java)
            return constructor.newInstance(args)
        } catch (e: Exception) {
            Timber.e(e, "Could not create new instance of class %s", modelClass.canonicalName)
            throw e
        }
    }
}

0

ऐसा क्यों नहीं है:

public class MyViewModel extends AndroidViewModel {
    private final LiveData<List<MyObject>> myObjectList;
    private AppDatabase appDatabase;
    private boolean initialized = false;

    public MyViewModel(Application application) {
        super(application);
    }

    public initialize(String param){
      synchronized ("justInCase") {
         if(! initialized){
          initialized = true;
          appDatabase = AppDatabase.getDatabase(this.getApplication());
          myObjectList = appDatabase.myOjectModel().getMyObjectByParam(param);
    }
   }
  }
}

और फिर इसे दो चरणों में इस तरह उपयोग करें:

MyViewModel myViewModel = ViewModelProvider.of(this).get(MyViewModel.class)
myViewModel.initialize(param)

2
कंस्ट्रक्टर में पैरामीटर डालने का पूरा बिंदु केवल एक बार व्यू मॉडल को इनिशियलाइज़ करना है । अपने कार्यान्वयन के साथ, यदि आप गतिविधि myViewModel.initialize(param)में कॉल onCreateकरते हैं, उदाहरण के लिए, इसे उसी समय पर कई बार कहा जा सकता है MyViewModelजब उपयोगकर्ता डिवाइस को घुमाता है।
सानलोक ली

@ सनलोक ली ओके। अनावश्यक होने पर प्रारंभिक को रोकने के लिए फ़ंक्शन में एक शर्त जोड़ने के बारे में कैसे। मेरे संपादित उत्तर की जाँच करें।
अमर बेराग

0
class UserViewModelFactory(private val context: Context) : ViewModelProvider.NewInstanceFactory() {
 
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return UserViewModel(context) as T
    }
 
}
class UserViewModel(private val context: Context) : ViewModel() {
 
    private var listData = MutableLiveData<ArrayList<User>>()
 
    init{
        val userRepository : UserRepository by lazy {
            UserRepository
        }
        if(context.isInternetAvailable()) {
            listData = userRepository.getMutableLiveData(context)
        }
    }
 
    fun getData() : MutableLiveData<ArrayList<User>>{
        return listData
    }

गतिविधि में Viewmodel को कॉल करें

val userViewModel = ViewModelProviders.of(this,UserViewModelFactory(this)).get(UserViewModel::class.java)

अधिक संदर्भ के लिए: Android MVVM Kotlin उदाहरण


प्रश्न पूछता है कि संदर्भ का उपयोग किए बिना तर्क / मापदंडों को कैसे पारित किया जाए जो उपरोक्त का पालन नहीं करता है: क्या आवेदन के संदर्भ को छोड़कर मेरे कस्टम AndroidViewModel निर्माता को अतिरिक्त तर्क पारित करने का एक तरीका है?
एडम हर्वित्ज

आप अपने कस्टम व्यूमोडेल कंस्ट्रक्टर में कोई भी तर्क / पैरामीटर पास कर सकते हैं। यहाँ संदर्भ केवल एक उदाहरण है। आप कंस्ट्रक्टर में कोई भी कस्टम तर्क पास कर सकते हैं।
ध्रूमिल शाह

समझ लिया। संदर्भ, दृश्य, गतिविधियाँ, टुकड़े, एडेप्टर, दृश्य जीवनचक्र को पास न करना, दृश्यमॉडल में दृश्य जीवनचक्र के प्रति सजग वेधशालाओं या धारण संसाधनों (ड्रॉबल इत्यादि) को देखना सबसे अच्छा अभ्यास है क्योंकि दृश्य नष्ट हो सकता है और ViewModel पुरानी के साथ बनी रहेगी जानकारी।
एडम हर्वित्ज
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.