103 votes

Android ViewModel n'a pas de constructeur à zéro argument

Je suis este pour en savoir plus sur LiveData et ViewModel. Dans la doc, la classe ViewModel a un constructeur comme tel,

public class UserModel extends ViewModel {
  private MutableLiveData<User> user;

  @Inject UserModel(MutableLiveData<User> user) {
    this.user = user;
  }

  public void init() {
    if (this.user != null) {
      return;
    }
    this.user = new MutableLiveData<>();
  }

  public MutableLiveData<User> getUser() {
    return user;
  }
}

Cependant, lorsque j'exécute le code, j'obtiens une exception :

final UserViewModelviewModel = ViewModelProviders.of(this).get(UserViewModel.class);

Causé par : java.lang.RuntimeException : Impossible de créer une instance de la classe UserViewModel Causé par : java.lang.InstantiationException : La classe java.lang.Class n'a pas de constructeur à zéro argument

0 votes

Vous devriez consulter ce lien stackoverflow.com/a/53956997/7558125

68voto

Shahbaz Ahmed Points 664

Lors de l'initialisation des sous-classes de ViewModel en utilisant ViewModelProviders par défaut, il s'attend à ce que votre UserModel pour avoir un constructeur à zéro argument. Dans votre cas, votre constructeur a l'argument MutableLiveData<User> user .

Une façon de résoudre ce problème est d'avoir un constructeur par défaut sans arg pour votre UserModel .

Sinon, si vous souhaitez disposer d'un constructeur à argument non nul pour votre classe ViewModel, vous devrez peut-être créer un constructeur personnalisé ViewModelFactory pour initialiser votre instance ViewModel, qui implémente la classe ViewModelProvider.Factory interface.

Je n'ai pas encore essayé, mais voici un lien vers un excellent échantillon Google à ce sujet : github.com/googlesamples/Android-architecture-components . Plus précisément, regardez cette classe GithubViewModelFactory.java pour le code Java et cette classe GithubViewModelFactory.kt pour le code Kotlin correspondant.

1 votes

Discussion autour de cette question : reddit.com/r/androiddev/comments/6bw1jj/

0 votes

@LostinOWL J'obtiens la même erreur, j'ai vérifié la classe DaggerAppComponent et elle génère correctement tous les graphes de dépendance.

0 votes

Voici l'échantillon de Google avant qu'ils ne le changent en kotlin : github.com/googlesamples/Android-architecture-components/blob/

37voto

yoAlex5 Points 2350

ViewModelFactory qui nous fournira un ViewModel correct à partir de ViewModelModule

public class ViewModelFactory implements ViewModelProvider.Factory {
    private final Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels;

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

    @Override
    public <T extends ViewModel> T create(Class<T> modelClass) {
        Provider<ViewModel> viewModelProvider = viewModels.get(modelClass);

        if (viewModelProvider == null) {
            throw new IllegalArgumentException("model class " + modelClass + " not found");
        }

        return (T) viewModelProvider.get();
    }
}

ViewModelModule est responsable de la liaison de toutes les classes de ViewModel dans
Map<Class<? extends ViewModel>, Provider<ViewModel>> viewModels

@Module
public abstract class ViewModelModule {

    @Binds
    abstract ViewModelProvider.Factory bindViewModelFactory(ViewModelFactory viewModelFactory); 
    //You are able to declare ViewModelProvider.Factory dependency in another module. For example in ApplicationModule.

    @Binds
    @IntoMap
    @ViewModelKey(UserViewModel.class)
    abstract ViewModel userViewModel(UserViewModel userViewModel);

    //Others ViewModels
}

ViewModelKey est une annotation à utiliser comme clé dans la carte et se présente comme suit

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

Vous êtes maintenant en mesure de créer le ViewModel et de satisfaire toutes les dépendances nécessaires à partir du graphe.

public class UserViewModel extends ViewModel {
    private UserFacade userFacade;

    @Inject
    public UserViewModel(UserFacade userFacade) { // UserFacade should be defined in one of dagger modules
        this.userFacade = userFacade;
    }
} 

Instanciation du ViewModel

public class MainActivity extends AppCompatActivity {

    @Inject
    ViewModelFactory viewModelFactory;
    UserViewModel userViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ((App) getApplication()).getAppComponent().inject(this);

        userViewModel = ViewModelProviders.of(this, viewModelFactory).get(UserViewModel.class);

    }
}

Et ne pas forger pour ajouter ViewModelModule en modules liste

@Singleton
@Component(modules = {ApplicationModule.class, ViewModelModule.class})
public interface ApplicationComponent {
    //
}

0 votes

Je reçois une erreur : [dagger.android.AndroidInjector.inject(T)] java.util.Map<java.lang.Class<? extends android.arch.lifecycle.ViewModel>,javax.inject.Provider<andr‌​oid.arch.lifecycle.V‌​iewModel>> cannot be provided without an @Provides-annotated method.

0 votes

Il est difficile de dire quel est le problème sans voir l'ensemble du projet, je peux supposer que 1. l'enfant de ViewModel n'a pas été déclarée dans ViewModelModule . 2. ViewModelModule n'a pas été ajouté à Component

0 votes

@LevonPetrosyan J'ai eu les mêmes problèmes, créer un constructeur sans argument et avec l'annotation @ Inject.

1voto

Radu Topor Points 1076

J'ai écrit une bibliothèque qui devrait permettre d'atteindre cet objectif de manière plus simple et plus propre, sans avoir à recourir à des liens multiples ou à un modèle d'usine, tout en offrant la possibilité de paramétrer davantage l'interface utilisateur. ViewModel au moment de l'exécution : 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
        })
    }
}

-2voto

rushi Points 128

Le problème peut être résolu en étendant UserModel de AndroidViewModel qui est un ViewModel conscient du contexte de l'application et qui requiert Application constructeur uniquement pour les paramètres. (documentation)

Ex- (en kotlin)

class MyVm(application: Application) : AndroidViewModel(application)

Cela fonctionne pour la version 2.0.0-alpha1 .

2 votes

L'AndroidViewModel n'accepte qu'un seul argument, qui est l'application. La seule façon de contourner les deux versions de ViewModel est de créer un Factory.

-3voto

Shahid Ahmad Points 83

Si vous avez un paramètre dans le constructeur, alors :

DAGGER 2 public constructor for @inject dependency

@Inject
public UserViewModel(UserFacade userFacade)
{ 
    this.userFacade = userFacade;
}

Sinon, la dague 2 vous enverra l'erreur "ne peut pas instancier l'objet viewmodel".

0 votes

Pourquoi les gens écrivent-ils des commentaires juste pour commenter ? Il a @Inject, mais son problème est déjà noté ci-dessus, et ce n'est pas ce que vous avez écrit.

0 votes

Il serait bon d'écrire avec un exemple, les réponses ci-dessus n'étaient pas très claires pour moi, donc j'en ai écrit une que je pouvais comprendre facilement et d'autres personnes aussi.

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