web-dev-qa-db-fra.com

Android ViewModel arguments supplémentaires

Existe-t-il un moyen de passer un argument supplémentaire à mon constructeur personnalisé AndroidViewModel, sauf le contexte d'application Exemple:

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

Et lorsque je veux utiliser ma classe ViewModel personnalisée, j'utilise ce code dans mon fragment:

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

Donc, je ne sais pas comment passer l'argument supplémentaire String param dans ma ViewModel personnalisée. Je ne peux que transmettre le contexte de l'application, mais pas les arguments supplémentaires. J'apprécierais vraiment toute aide. Je vous remercie. 

Edit: j'ai ajouté du code. J'espère que c'est mieux maintenant.

32
Mario Rudman

Vous devez avoir une classe d'usine pour votre 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);
    }
}

Et lorsque vous instanciez le modèle de vue, vous procédez comme suit:

MyViewModel myViewModel = ViewModelProviders.of(this, new MyViewModelFactory(this.getApplication(), "my awesome param")).get(MyViewModel.class);
80
mlyko

Pour une usine partagée entre plusieurs modèles de vues différents, j'étendrais la réponse de mlyko comme ceci:

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

Et instancier des modèles de vue:

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

Avec différents modèles de vues ayant différents constructeurs.

6
rzehan

(KOTLIN) Ma solution utilise un peu de réflexion.

Disons que vous ne voulez pas créer la même classe Factory à la fois chaque fois que vous créez une classe ViewModel qui nécessite des arguments. Vous pouvez accomplir cela via la réflexion.

Par exemple, vous auriez deux activités différentes:

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

Et ViewModels pour ces activités:

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

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

Ensuite, la partie magique, l'implémentation de la classe Factory:

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
vilpe89

J'ai écrit une bibliothèque qui devrait rendre cela plus simple et plus clair, sans multibindings ni embase standard, tout en travaillant de manière transparente avec les arguments ViewModel qui peuvent être fournis comme dépendances par Dagger: 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"
    }    
}

Dans la vue:

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
        })
    }
}
0
Radu Topor

Pourquoi ne pas le faire comme ça:

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

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

    public initialize(String param){
        appDatabase = AppDatabase.getDatabase(this.getApplication());
        myObjectList = appDatabase.myOjectModel().getMyObjectByParam(param);
}
}

et ensuite l'utiliser comme ceci en deux étapes:

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