web-dev-qa-db-ja.com

Android ViewModelには引数なしのコンストラクタがありません

LiveDataとViewModelについて学ぶために this ドキュメンテーションに従っています。ドキュメントでは、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;
  }
}

ただし、コードを実行すると、例外が発生します。

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

原因:Java.lang.RuntimeException:クラスUserViewModelのインスタンスを作成できません原因:Java.lang.InstantiationException:Java.lang.Classにゼロ引数コンストラクターがありません

41
Prabin Timsina

ViewModelを使用してViewModelProvidersのサブクラスを初期化するとき、デフォルトでは、UserModelクラスにゼロ引数コンストラクターがあると想定されます。あなたの場合、コンストラクタには引数MutableLiveData<User> userがあります

これを修正する1つの方法は、UserModelのデフォルトの引数なしコンストラクタを使用することです

それ以外の場合、ViewModelクラスにゼロ以外の引数コンストラクターが必要な場合、カスタムViewModelFactoryクラスを作成して、ViewModelインスタンスを初期化し、ViewModelProvider.Factoryインターフェイスを実装する必要があります。

私はまだこれを試していませんが、同じもののためのグーグルからの優秀なサンプルへのリンクはここにあります: github.com/googlesamples/Android-architecture-components 。具体的には、このクラスをチェックアウト GithubViewModelFactory.Java Javaコードおよびこのクラス GithubViewModelFactory.kt 対応するKotlinコード

41
Shahbaz Ahmed

ViewModelFactoryは、ViewModelModuleから適切なViewModelを提供します

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は、ViewModelクラス全体のバインドを担当します
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は、マップのキーとして使用するための注釈で、次のようになります

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

これで、ViewModelを作成し、グラフから必要なすべての依存関係を満たすことができます。

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

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

    }
}

ViewModelModulemodulesリストに追加することを偽造しないでください

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

私は、実行時にViewModelをさらにパラメーター化する機能を提供しながら、これをより簡単で、よりクリーンで、マルチバインディングやファクトリーボイラープレートを必要としないライブラリを作成しました。 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"
    }    
}

ビューで:

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
Radu Topor

この問題は、UserModelをアプリケーションコンテキスト対応のViewModelであり、AndroidViewModelパラメーターのみのコンストラクターを必要とするApplicationを拡張することで解決できます。 (ドキュメント)

Ex-(コトリン)

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

これはバージョン2.0.0-alpha1で機能します。

0
rushi