web-dev-qa-db-fra.com

Vue AndroidModèle n'a pas de constructeur à zéro argument

Je suis la documentation this pour en savoir plus sur LiveData et ViewModel .

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 je lance le code, j'obtiens une exception:

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

Causée par: Java.lang.RuntimeException: impossible de créer une instance de la classe UserViewModel Causée par: Java.lang.InstantiationException: Java.lang.Class n'a pas de constructeur zéro argument

33
Prabin Timsina

Lors de l'initialisation des sous-classes de ViewModel à l'aide de ViewModelProviders, il s'attend par défaut à ce que votre classe UserModel ait un constructeur d'argument nul. Dans votre cas, votre constructeur a l'argument MutableLiveData<User> user

Une façon de résoudre ce problème consiste à avoir un constructeur sans argument par défaut pour votre UserModel

Sinon, si vous souhaitez avoir un constructeur à argument non nul pour votre classe ViewModel, vous devrez peut-être créer une classe ViewModelFactory personnalisée pour initialiser votre instance ViewModel, qui implémentera l'interface ViewModelProvider.Factory.

Je n'ai pas encore essayé cela, mais voici le lien vers l'excellent exemple de Google pour le même: github.com/googlesamples/Android-architecture-components . Plus précisément, consultez cette classe GithubViewModelFactory.Java pour le code Java et cette classe GithubViewModelFactory.kt pour le code Kotlin correspondant

32
Shahbaz Ahmed

ViewModelFactory qui nous fournira un bon ViewModel 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 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 ressemble à

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

Vous pouvez maintenant créer ViewModel et satisfaire toutes les dépendances nécessaires à partir du graphique.

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 de 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 n'oubliez pas d'ajouter ViewModelModule dans la liste modules

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

Le problème peut être résolu en développant UserModel à partir de AndroidViewModel, qui est ViewModel sensible au contexte de l'application et qui nécessite le constructeur Application avec paramètre uniquement. (Documentation)

Ex- (en kotlin)

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

Cela fonctionne pour la version 2.0.0-alpha1

0
rushi

J'ai écrit une bibliothèque qui devrait rendre la réalisation de cette tâche plus simple et plus nette, sans multibindings ni embase standard, tout en donnant la possibilité de paramétrer davantage la ViewModel à 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
        })
    }
}
0
Radu Topor