52 votes

Bibliothèque de cycle de vie Android ViewModel using dagger 2

J'ai un ViewModel de la classe tout comme celle définie dans la Connexion ViewModel d'un référentiel et de la section de guide d'Architecture. Quand je lance mon application, j'obtiens une exception d'exécution. Personne ne sait comment contourner ce problème? Ne dois-je pas l'injection de ce Dernier? Est-il un moyen de dire à l' ViewModelProvider d'utilisation de la Dague pour créer le modèle?

public class DispatchActivityModel extends ViewModel {

    private final API api;

    @Inject
    public DispatchActivityModel(API api) {
        this.api = api;
    }
}

Causés par: java.lang.InstantiationException: java.lang.Class n'a pas de zéro argument du constructeur à java.lang.Classe.newInstance(Native method) au android.arch.cycle de vie.ViewModelProvider$NewInstanceFactory.créer(ViewModelProvider.java:143) au android.arch.cycle de vie.ViewModelProviders$DefaultFactory.créer(ViewModelProviders.java:143) au android.arch.cycle de vie.ViewModelProvider.get(ViewModelProvider.java:128) au android.arch.cycle de vie.ViewModelProvider.get(ViewModelProvider.java:96) au com.exemple.de la base.BaseActivity.onCreate(BaseActivity.java:65) au com.exemple.de l'expédition.DispatchActivity.onCreate(DispatchActivity.java:53) au android.app.De l'activité.performCreate(l'Activité.java:6682) au android.app.L'Instrumentation.callActivityOnCreate(Instrumentation.java:1118) au android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2619) à android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727) au android.app.ActivityThread.-wrap12(ActivityThread.java) au android.app.ActivityThread$H. handleMessage(ActivityThread.java:1478) au android.os.Gestionnaire d'.dispatchMessage(Handler.java:102) au android.os.Looper.boucle(Looper.java:154) au android.app.ActivityThread.principale(ActivityThread.java:6121)

96voto

Robert Wysocki Points 805

Vous devez mettre en place votre propre ViewModelProvider.Factory. Il y a un exemple d'application créé par Google, montrant comment se connecter Poignard 2 avec Viewmodel. LIEN. Vous avez besoin de ces 5 choses:

Dans Le ViewModel:

@Inject
public UserViewModel(UserRepository userRepository, RepoRepository repoRepository) {

Définir annotation:

@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
    Class<? extends ViewModel> value();
}

Dans ViewModelModule:

@Module
abstract class ViewModelModule {
    @Binds
    @IntoMap
    @ViewModelKey(UserViewModel.class)
    abstract ViewModel bindUserViewModel(UserViewModel userViewModel);

Dans Le Fragment:

@Inject
ViewModelProvider.Factory viewModelFactory;

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
            userViewModel = ViewModelProviders.of(this, viewModelFactory).get(UserViewModel.class);

Usine:

@Singleton
public class GithubViewModelFactory implements ViewModelProvider.Factory {
    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;

    @Inject
    public GithubViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
        this.creators = creators;
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        Provider<? extends ViewModel> creator = creators.get(modelClass);
        if (creator == null) {
            for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
                if (modelClass.isAssignableFrom(entry.getKey())) {
                    creator = entry.getValue();
                    break;
                }
            }
        }
        if (creator == null) {
            throw new IllegalArgumentException("unknown model class " + modelClass);
        }
        try {
            return (T) creator.get();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

27voto

Soflete Points 383

Aujourd'hui j'ai appris une façon d'éviter d'avoir à écrire des usines pour ma ViewModel classes:

class ViewModelFactory<T : ViewModel> @Inject constructor(
    private val viewModel: Lazy<T>
) : ViewModelProvider.Factory {
    @Suppress("UNCHECKED_CAST")
    override fun <T : ViewModel?> create(modelClass: Class<T>): T = viewModel.get() as T
}

EDIT: Comme l'a souligné @Calin dans les commentaires, nous sommes à l'aide de la Dague à l' Lazy dans l'extrait de code ci-dessus, pas de Kotlin.

Plutôt que d'injecter de l' ViewModel, vous pouvez injecter un générique ViewModelFactory dans vos activités et de fragments et d'obtenir une instance d' ViewModel:

class MyActivity : Activity() {

    @Inject
    internal lateinit var viewModelFactory: ViewModelFactory<MyViewModel>
    private lateinit var viewModel: MyViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        AndroidInjection.inject(this)
        super.onCreate(savedInstanceState)
        this.viewModel = ViewModelProviders.of(this, viewModelFactory)
            .get(MyViewModel::class.java)
        ...
    }

    ...
}

J'ai utilisé AndroidInjection.inject(this) comme avec l' dagger-android bibliothèque, mais vous pouvez injecter votre activité ou fragment de la façon que vous préférez. Tout ce qui reste est de s'assurer que vous fournissez votre ViewModel à partir d'un module:

@Module
object MyModule {
    @JvmStatic
    @Provides
    fun myViewModel(someDependency: SomeDependency) = MyViewModel(someDependency)
} 

Ou l'application de la @Inject d'annotation de son constructeur:

class MyViewModel @Inject constructor(
    someDependency: SomeDependency
) : ViewModel() {
    ...
}

4voto

Je crois qu'il y est une deuxième option si vous ne voulez pas utiliser de l'usine mentionné dans la réponse de Robert. Il n'est pas nécessairement la meilleure solution, mais il est toujours bon de connaître les options.

Vous pouvez laisser votre viewModel avec le constructeur par défaut et injecter des dépendances comme vous le feriez dans le cas où des activités ou d'autres éléments créés par le système. Exemple:

ViewModel:

public class ExampleViewModel extends ViewModel {

@Inject
ExampleDependency exampleDependency;

public ExampleViewModel() {
    DaggerExampleComponent.builder().build().inject(this);
    }
}

Composant:

@Component(modules = ExampleModule.class)
public interface ExampleComponent {

void inject(ExampleViewModel exampleViewModel);

}

Module:

@Module
public abstract class ExampleModule {

@Binds
public abstract ExampleDependency bindExampleDependency(ExampleDependencyDefaultImplementation exampleDependencyDefaultImplementation);

}

Cheers, Piotr

4voto

user2812818 Points 1

Ce qui peut ne pas être évident dans la question, c'est que le ViewModel ne peut pas être injecté de cette façon parce que le ViewModelProvider d'Usine par défaut que vous obtenez à partir de la

ViewModelProvider.of(LifecycleOwner lo) 

méthode avec seulement le LifecycleOwner paramètre ne peut instancier un ViewModel qui a un no-arg constructeur par défaut.

Vous avez un param: "api" dans votre constructeur:

public DispatchActivityModel(API api) {

Afin de faire que vous avez besoin pour créer une Usine de sorte que vous pouvez dire comment créer de lui-même. L'exemple de code à partir de google vous donne la Dague config et le code Usine comme mentionné dans la accepté de répondre.

DI a été créé pour éviter l'utilisation de la nouvelle() de l'opérateur sur les dépendances, car si la mise en œuvre du changement, chaque référence devra changer ainsi. Le ViewModel de la mise en œuvre à bon escient utilise un statique modèle de fabrique déjà avec ViewProvider.de().get() qui fait son injection inutile dans le cas de non-arg constructeur. Ainsi, dans le cas où vous n'avez pas besoin d'écrire de l'usine vous n'avez pas besoin d'injecter une usine de cours.

3voto

onepointsixtwo Points 108

J'aimerais fournir une troisième option pour toute personne tombant sur cette question. Le Poignard ViewModel de la bibliothèque vous permettra d'injecter dans un Dagger2 comme avec Viewmodel en indiquant éventuellement le ViewModel de la portée.

Il enlève beaucoup de la réutilisable et permet également le Viewmodel pour être injecté de manière déclarative à l'aide d'une annotation:

@InjectViewModel(useActivityScope = true)
public MyFragmentViewModel viewModel;

Il exige également une petite quantité de code de configuration d'un module à partir de laquelle le complètement de la dépendance injecté Viewmodel peut être généré et après c'est aussi simple que d'appeler:

void injectFragment(Fragment fragment, ViewModelFactory factory) {
    ViewModelInejectors.inject(frag, viewModelFactory);
}

Sur le ViewModelInjectors classe qui est généré.

AVERTISSEMENT: il est dans ma bibliothèque mais je crois que c'est aussi de l'utiliser à l'auteur de cette question et à toute personne voulant obtenir la même chose.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X