Android ViewModel没有零参数构造函数

时间:2017-05-26 05:57:26

标签: android mvvm android-architecture-components

我正在关注this文档以了解LiveData和ViewModel。 在doc中,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中   没有零参数构造函数

21 个答案:

答案 0 :(得分:38)

在使用ViewModel初始化ViewModelProviders的子类时,默认情况下它期望您的UserModel类具有零参数构造函数。 在您的情况下,您的构造函数具有参数MutableLiveData<User> user

解决此问题的一种方法是为UserModel

设置默认的无参数构造函数

否则,如果您希望ViewModel类具有非零参数构造函数,则可能必须创建一个自定义ViewModelFactory类来初始化ViewModel实例,该实例将实现ViewModelProvider.Factory接口。< / p>

我还没有尝试过这个,但是这里链接到google的优秀样本:github.com/googlesamples/android-architecture-components。 具体来说,请查看此类GithubViewModelFactory.java以获取Java代码,并将此类GithubViewModelFactory.kt签出以获取相应的Kotlin代码

答案 1 :(得分:21)

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

    }
}

不要伪造将ViewModelModule添加到modules列表

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

答案 2 :(得分:17)

在我使用HILT的情况下,它在具有ViewModel的Fragment上面缺少一个注释: @AndroidEntryPoint

@AndroidEntryPoint
class BestFragment : Fragment() { 
....

当然,在ViewModel类中,您还需要使用 HILT需要什么: @ViewModelInject

class BestFragmentViewModel @ViewModelInject constructor(var userManager: UserManager) : ViewModel() {
....
}

答案 3 :(得分:5)

2020年初, Google在androidx生命周期库的版本2.2.0中弃用了ViewModelProviders类

不再需要使用ViewModelProviders创建ViewModel的实例,您可以将Fragment或Activity实例传递给ViewModelProvider构造函数。

如果您使用如下代码:

val viewModel = ViewModelProviders.of(this).get(CalculatorViewModel::class.java)

您将收到 ViewModelProviders已过时的警告。

为避免使用不赞成使用的库,请进行以下更改。

  1. 在build.gradle(模块:app)文件中,使用生命周期的版本2.2.0 组件: implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'

    也添加 implementation "androidx.activity:activity-ktx:1.1.0"

    如果您想改用Fragment中的ViewModel,请使用

    implementation "androidx.fragment:fragment-ktx:1.2.2"

    fragment-ktx自动包含activity-ktx,因此您不需要 在依赖项中同时指定两者。

  2. 您需要在android部分中指定Java 8:

android {
  compileSdkVersion 28
  defaultConfig {
    applicationId "com.kgandroid.calculator"
    minSdkVersion 17
    targetSdkVersion 28
    versionCode 1
    versionName "1.0"
    testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
  }

  buildTypes {
    release {
      minifyEnabled false
      proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
    }
  }

  kotlinOptions {
    jvmTarget = "1.8"
  }
}
  1. 在“片段”或“活动”中,将导入更改为:

    导入androidx.activity.viewModels

  2. 然后,用于创建ViewModel的代码变为:

    val viewModel: CalculatorViewModel by viewModels()

    代替

    val viewModel = ViewModelProviders.of(this).get(CalculatorViewModel::class.java)

    将viewModel对象用作:

    val viewModel:viewModels()的CalculatorViewModel

    viewModel.newNumber.observe(this,Observer {stringResult-> newNumber.setText(stringResult)})

    其中 newNumer是一个LiveData对象

    要共享活动的ViewModel的片段中,您将使用

    val viewModel: CalculatorViewModel by activityViewModels()

    这等同于在(不推荐使用)中传递Activity实例 ViewModelProviders.of()函数。

答案 4 :(得分:5)

我在使用 @ViewModelInject 时遇到了一些问题,因为它已被 HILT 弃用。 要解决此问题,请更改此代码:

class MainViewModel @ViewModelInject constructor(
    val mainRepository: MainRepository
): ViewModel()

与:

@HiltViewModel
class MainViewModel @Inject constructor(
    val mainRepository: MainRepository
): ViewModel()

当然,请记住在您的片段或活动(无论您在哪里实例化您的 @AndroidEntryPoint)上方添加 ViewModel 注释,如下所示:

@AndroidEntryPoint
class UsageFragment : Fragment(R.layout.fragment_usage) {
   .
   .
   .
}

终极提示:

您可以立即查看 HILT 是否正常工作,查看 ViewModel 左侧是否有图标。

这里它不起作用

enter image description here

这里确实有效

enter image description here

如果您在更新代码后没有看到它们,请点击 Build -> Rebuild Project

答案 5 :(得分:4)

如果您使用 hilt,您可能忘记使用 @AndroidEntryPoint

注释您的活动或片段

答案 6 :(得分:2)

对于遇到此问题的每个人,我都是这样遇到的:
1-在您的ViewModel中,不要创建一个构造函数,只需创建一个带有Context和其他参数的函数

public class UserModel extends ViewModel {
  private Context context; 
  private ..........;

  public void init(Context context, ......) {
    this.context = context;
    this..... = ....;
  }


  // Your stuff
}

2-在您的活动中:


UserViewModel viewModel = ViewModelProviders.of(this).get(UserViewModel.class);
viewModel.init(this, .....);

// You can use viewModel as you want

3-在您的片段中

UserViewModel viewModel = ViewModelProviders.of(getActivity()).get(UserViewModel.class);
viewModel.init(getContext(), .....);

// You can use viewModel as you want

答案 7 :(得分:2)

2020-07-29 10:13:25

对于lifecycle_version = '2.2.0',不推荐使用ViewProviders.of API。这是我的情况:

class MainActivityViewModel(application: Application) : AndroidViewModel(application) {

    private var repository: UserRepository

    val allUsers: LiveData<List<User>>
......


error:
val userViewModel = ViewModelProvider(this).get(MainActivityViewModel::class.java)

success:
val factory = ViewModelProvider.AndroidViewModelFactory.getInstance(application)
userViewModel = ViewModelProvider(this,factory).get(MainActivityViewModel::class.java)

通过API application通过ViewModelProvider.AndroidViewModelFactory.getInstance


答案 8 :(得分:2)

我有同样的错误。我正在使用 Hilt ,就我而言,这是缺少的第二个Hilt编译器依赖项

现在我都拥有:

kapt com.google.dagger:hilt-android-compiler:#version

kapt androidx.hilt:hilt-compiler:#version

在我的应用程序级别的build.gradle文件中,它可以正常工作。

当您使用Hilt Android Gradle插件(请参见docs)和 androidx.hilt:hilt-compiler时,需要

com.google.dagger:hilt-android-compiler :#version 当您想要Hilt和Jetpack集成时(例如注入Android Jetpack ViewModel(请参阅docs

,显然需要

答案 9 :(得分:1)

对于刀柄:

简单地为主要活动和片段添加@AndroidEntryPoint,为视图模型添加@HiltViewModel

示例:

@HiltViewModel
class SplashViewModel @Inject constructor(

@AndroidEntryPoint
class SplashFragment : Fragment() {
    private lateinit var b: SplashFragmentBinding
    private val vm: SplashViewModel by viewModels()

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    private lateinit var binding: ActivityMainBinding

答案 10 :(得分:1)

此失败的最常见原因是在 Fragment/Activity 的开头缺少 @AndroidEntryPoint,如下所示:

**@AndroidEntryPoint**
class MyFragment : Fragment {
val viewModel by viewModels<MyViewModel>()
}

同样,您的 ViewModel 应由 HiltViewModel 注释,如下所示:

@HiltViewModel
class MyViewModel@Inject constructor(
private val var1: Type1
) : ViewModel()

答案 11 :(得分:0)

关于accepted answer,如果您使用的是Hilt并且刚刚添加了ViewModel,请不要忘记重建项目。正如很难发现的那样,仅运行项目并不会创建所需的工厂类(应该自动生成)。

重建之前,以下类不存在:

enter image description here

答案 12 :(得分:0)

如果您使用的是 dagger hilt 和 2.31 或更高版本,则不要在视图模型类中使用“ViewModelInject”。 Dagger 提供了使用视图模型的新方法,因此请按照以下说明进行操作。

1:在类的顶部添加@HiltViewModel 2:使用 Inject 而不是 ViewModelInject

@HiltViewModel
class AuthViewModel @Inject constructor( 
private val authRepository: AuthRepository,
    ...
) : ViewModel() 
{...}

答案 13 :(得分:0)

如果您使用导航组合并在 NavHost 块内调用屏幕,则刀柄无法注入视图模型。为此,您可以使用这种方式;

    NavHost(navHostController, startDestination = "HomeScreen") {
    composable("HomeScreen") {
        HomeScreen(homeScreenViewModel = hiltViewModel())
    }
}

不要忘记为 hiltViewModel() -> implementation("androidx.hilt:hilt-navigation-compose:1.0.0-alpha02")

添加此依赖项

答案 14 :(得分:0)

我遇到了同样的问题,通过向我的项目添加导航ui库来解决此问题:

implementation 'androidx.navigation:navigation-ui-ktx:2.2.2'

答案 15 :(得分:0)

您可以通过viewmodel工厂在viewmodel中传递数据。您也可以签出this example供参考。

class UserViewModelFactory(private val context: Context) : ViewModelProvider.NewInstanceFactory() {
 
    override fun <T : ViewModel?> create(modelClass: Class<T>): T {
        return UserViewModel(context) as T
    }
 
}
class UserViewModel(private val context: Context) : ViewModel() {
 
    private var listData = MutableLiveData<ArrayList<User>>()
 
    init{
        val userRepository : UserRepository by lazy {
            UserRepository
        }
        if(context.isInternetAvailable()) {
            listData = userRepository.getMutableLiveData(context)
        }
    }
 
    fun getData() : MutableLiveData<ArrayList<User>>{
        return listData
    }
}

您可以在活动中调用viewmodel,如下所示。

val userViewModel = ViewModelProviders.of(this,UserViewModelFactory(this)).get(UserViewModel::class.java)

答案 16 :(得分:0)

创建一个不含任何参数的构造函数,即

Default/ No-arg constructor

在viewmodel类中。

就我而言,我忘了生成此构造函数,而在学习时浪费了30分钟-在那之后对我有用。

答案 17 :(得分:0)

我写了一个库,该库应该使实现这一过程更加直接和简洁,不需要多重绑定或工厂样板,同时还可以在运行时进一步参数化ViewModelhttps://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
        })
    }
}

答案 18 :(得分:-1)

可以通过从UserModel扩展AndroidViewModel来解决该问题,Application是应用程序上下文感知的ViewModel,并且需要class MyVm(application: Application) : AndroidViewModel(application) 仅参数构造函数。 (documentation)

Ex-(科特林语)

2.0.0-alpha1

这适用于版本com.example.app.App

答案 19 :(得分:-1)

如果你在构造函数中有参数:

@inject依赖的DAGGER 2公共构造函数

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

否则dagger 2会向您发送错误“无法实例化viewmodel对象”

答案 20 :(得分:-1)

对于 Koin:

我遇到了这个问题,结果我只是从 AndroidX 导入了 viewModels() 而不是从 Koin 导入了 viewModel()