Android ViewModel ek bağımsız değişkenleri


115

AndroidViewModelUygulama bağlamı dışında özel oluşturucuma ek argüman iletmenin bir yolu var mı ? Misal:

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

Ve özel ViewModelsınıfımı kullanmak istediğimde bu kodu parçamda kullanırım:

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

Bu yüzden String paramgeleneğime nasıl ek argüman aktaracağımı bilmiyorum ViewModel. Yalnızca Uygulama bağlamını iletebilirim, ancak ek bağımsız değişkenleri geçemiyorum. Herhangi bir yardım için gerçekten minnettar olurum. Teşekkür ederim.

Düzenleme: Bazı kodlar ekledim. Umarım şimdi daha iyidir.


daha fazla ayrıntı ve kod ekleyin
hugo

Hata mesajı nedir?
Musa Aprico

Hata mesajı yok. ViewModelProvider, AndroidViewModel nesneleri oluşturmak için kullanıldığından, yapıcı için argümanları nereye ayarlayacağımı bilmiyorum.
Mario Rudman

Yanıtlar:


224

ViewModel'iniz için bir fabrika sınıfına sahip olmanız gerekir.

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

Ve görünüm modelini somutlaştırırken, şunu yaparsınız:

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

Kotlin için, yetki verilmiş mülk kullanabilirsiniz:

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

Bir başka yeni seçenek daha var - fabrikanızın somutlaştırılmasıyla uygulamak HasDefaultViewModelProviderFactoryve geçersiz kılmak getDefaultViewModelProviderFactory()ve sonra fabrikayı arayarak ViewModelProvider(this)ya da by viewModels()yapmadan.


4
Her ViewModelsınıfın kendi ViewModelFactory'sine ihtiyacı var mı ?
dmlebron

6
ama her ViewModelbiri farklı bir DI'ye sahip olabilir / olacaktır. create()Yöntemde hangi örneğin geri döndüğünü nasıl anlarsınız ?
dmlebron

1
ViewModel'iniz, yön değişikliğinden sonra yeniden oluşturulacaktır. Her seferinde fabrika yaratamazsınız.
Tim

3
Bu doğru değil. Yeni ViewModeloluşturma yöntemi engeller get(). Belgelere göre: "Mevcut bir ViewModel'i döndürür veya bu ViewModelProvider ile ilişkili kapsamda (genellikle bir parça veya bir etkinlik) yeni bir tane oluşturur." bkz: developer.android.com/reference/android/arch/lifecycle/…
mlyko

2
return modelClass.cast(new MyViewModel(mApplication, mParam))uyarıdan kurtulmak için kullanmaya ne dersiniz
jackycflau

24

Bağımlılık Enjeksiyonu ile Uygulama

Bu, üretim kodu için daha gelişmiş ve daha iyidir.

Square'in AssistedInject ürünü Dagger2 , ağ ve veritabanı isteklerini işleyen bir depo gibi gerekli bileşenleri enjekte edebilen ViewModels için üretime hazır bir uygulama sunar. Ayrıca, etkinlik / parçadaki bağımsız değişkenlerin / parametrelerin manuel olarak enjeksiyonuna izin verir. İşte Gabor Varadi'nin ayrıntılı gönderisi olan Dagger Tips'e dayanan kod Gists ile uygulama adımlarının kısa bir özeti .

Dagger Hilt, 12/7/20 tarihinden itibaren alfa olarak yeni nesil bir çözümdür ve kitaplık yayınlanma durumuna geldiğinde daha basit bir kurulumla aynı kullanım durumunu sunar.

Kotlin'de Yaşam Döngüsü 2.2.0 ile Uygulama

Bağımsız Değişkenleri / Parametreleri Geçirme

// 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'i Bağımsız Değişkenler / Parametreler ile Etkinleştirme

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

Fabrikada oluşturmak geçersiz kılma ederken ben denetlenmeyen döküm 'T ItemViewModel' diyerek bir uyarı olsun
Ssenyonjo

1
Bu uyarı şimdiye kadar benim için sorun olmadı. Bununla birlikte, ViewModel fabrikasını parça aracılığıyla bir örneğini oluşturmak yerine Dagger kullanarak enjekte etmek için yeniden düzenlediğimde daha ayrıntılı olarak inceleyeceğim.
Adam Hurwitz

15

Birden fazla farklı görünüm modeli arasında paylaşılan bir fabrika için mlyko'nun cevabını şu şekilde genişletebilirim:

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

Ve görünüm modellerinin somutlaştırılması:

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

Farklı yapıcılara sahip farklı görünüm modelleri ile.


9
Birkaç nedenden dolayı bu yolu önermiyorum: 1) fabrikadaki parametreler tip güvenli değildir - bu şekilde kodunuzu çalışma zamanında kırabilirsiniz. Mümkün olduğunda her zaman bu yaklaşımdan kaçınmaya çalışın 2) görünüm modeli türlerini kontrol etmek, işleri yapmanın gerçek anlamda OOP yolu değildir. ViewModel'ler temel türe dönüştürüldüğünden, derleme sırasında herhangi bir uyarı olmaksızın çalışma zamanında kodu kırabilirsiniz. Bu durumda, varsayılan android fabrikasını kullanmanızı ve parametreleri önceden başlatılmış görünüm modeline geçirmenizi öneririm.
mlyko

@mlyko Elbette, bunların hepsi geçerli itirazlardır ve görünüm modeli verilerini ayarlamak için kendi yöntem (ler) i her zaman bir seçenektir. Ancak bazen viewmodel'in başlatıldığından emin olmak istersiniz, bu nedenle yapıcı kullanılır. Aksi takdirde, "görünüm modeli henüz başlatılmamış" durumunu kendiniz halletmelisiniz. Örneğin, viewmodel'in LivedData'yı döndüren yöntemleri varsa ve gözlemciler buna çeşitli Görünüm yaşam döngüsü yöntemlerinde eklenirse.
rzehan

4

@ Vilpe89 temel alınarak AndroidViewModel durumları için yukarıdaki Kotlin çözümü

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

}

Sonra bir parça viewModel'i şu şekilde başlatabilir:

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

Ve sonra gerçek ViewModel sınıfı

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

Veya uygun bir yöntemle ...

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

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

    ....
}

Soru, yukarıdakilerin takip etmediği bağlamı kullanmadan bağımsız değişkenlerin / parametrelerin nasıl geçirileceğini sorar: Uygulama bağlamı dışında özel AndroidViewModel kurucuma ek bağımsız değişken iletmenin bir yolu var mı?
Adam Hurwitz

3

Bunu, önceden oluşturulmuş nesnenin geçtiği bir sınıf yaptım.

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

Ve sonra

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

Yapıcıya parametreleri iletmek için her ViewModel için bir ViewModelFactory'ye sahip olmalıyız.
K Pradeep Kumar Reddy

Hayır. Tüm ViewModel'ler için yalnızca bir ViewModelFactory
Danil

HashMap anahtarı olarak kanonik adı kullanmak için herhangi bir neden var mı? Class.simpleName'i kullanabilir miyim?
K Pradeep Kumar Reddy

Evet, ancak yinelenen isimlerin olmadığından emin olmalısınız
Danil

Bu, kodu yazmak için önerilen stil mi? Bu kodu kendiniz mi buldunuz yoksa android belgelerinde mi okudunuz?
K Pradeep Kumar Reddy

1

Dagger tarafından bağımlılıklar olarak sağlanabilecek ViewModel argümanları ile sorunsuz bir şekilde çalışırken, bunu daha basit ve çok daha temiz hale getirecek, çoklu bağlantılara veya fabrika standart metnine gerek duyulmayacak bir kütüphane yazdım: https://github.com/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"
    }    
}

Görünümde:

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) Benim çözümüm biraz Yansıma kullanıyor.

Diyelim ki, bazı argümanlara ihtiyaç duyan yeni ViewModel sınıfını her oluşturduğunuzda aynı görünümlü Factory sınıfını oluşturmak istemiyorsunuz. Bunu Yansıma yoluyla başarabilirsiniz.

Örneğin iki farklı Faaliyetiniz olur:

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

Ve bu Etkinlikler için ViewModels:

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

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

Sonra sihirli kısım, Factory sınıfının uygulaması:

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

1
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'i Activity içinde arayın

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

Daha fazla referans için: Android MVVM Kotlin Örneği


Soru, yukarıdakilerin takip etmediği bağlamı kullanmadan bağımsız değişkenlerin / parametrelerin nasıl geçirileceğini sorar: Uygulama bağlamı dışında özel AndroidViewModel kurucuma ek bağımsız değişken iletmenin bir yolu var mı?
Adam Hurwitz

Özel görünüm modeli yapıcınızda herhangi bir bağımsız değişken / parametre geçirebilirsiniz. Burada bağlam sadece bir örnektir. Yapıcıda herhangi bir özel bağımsız değişkeni iletebilirsiniz.
Dhrumil Şah

Anladım. Görünüm zarar görebileceğinden ve ViewModel güncelliğini yitirmiş olarak kalacağından, bağlamı, görünümleri, etkinlikleri, parçaları, bağdaştırıcıları, Yaşam Döngüsünü görüntülememek, yaşam döngüsüne duyarlı gözlemlenebilirleri gözlemlemek veya kaynakları (çekilebilirler vb.) ViewModel'de tutmak en iyi uygulamadır. bilgi.
Adam Hurwitz

0

Neden böyle yapmıyorsun:

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

ve sonra bunu iki adımda şu şekilde kullanın:

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

2
Yapıcıya parametre koymanın tüm amacı, görünüm modelini yalnızca bir kez başlatmaktır . Eğer ararsanız sizin uygulamayla, myViewModel.initialize(param)içinde onCreatefaaliyet, örneğin, aynı birden çok kez çağrılabilir MyViewModelkullanıcı cihazının yönünü örneği.
Sanlok Lee

@Sanlok Lee Ok. Gereksiz olduğunda başlatmayı önlemek için işleve bir koşul eklemeye ne dersiniz? Düzenlenmiş cevabımı kontrol edin.
Amr Berag
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.