Android ViewModel 没有零参数构造函数

Android ViewModel has no zero argument constructor

我正在关注 this 文档以了解 LiveData 和 ViewModel。 在文档中,ViewModel class 具有构造函数,

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

Caused by: java.lang.RuntimeException: Cannot create an instance of class UserViewModel Caused by: java.lang.InstantiationException: java.lang.Class has no zero argument constructor

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

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

使用默认的无参数构造函数

否则,如果您想为您的 ViewModel class 使用非零参数构造函数,您可能必须创建自定义 ViewModelFactory class 来初始化您的 ViewModel 实例,它实现了 ViewModelProvider.Factory 接口。

我还没有尝试过,但是这里有一个 link 到一个优秀的 Google 示例:github.com/googlesamples/android-architecture-components。 具体来说,查看此 class GithubViewModelFactory.java for Java code and this class GithubViewModelFactory.kt 以获取相应的 Kotlin 代码。

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 {
    //
}

如果构造函数中有参数,则:

DAGGER 2 public @inject 依赖的构造函数

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

否则dagger 2会给你报错"can not instantiate viewmodel object"

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

Ex-(在科特林中)

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

这适用于版本 2.0.0-alpha1

我编写了一个库,它应该可以更直接、更清晰地实现这一点,不需要多重绑定或工厂样板,同时还可以在运行时进一步参数化 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
        })
    }
}

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

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

如果您使用如下代码:

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

您会收到一条警告,指出 ViewModelProviders 已被弃用。

您可以改为:

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

或者,要使用委托,请进行以下更改。

  1. 在build.gradle(Module:app)文件中,使用2.2.0版本的lifecycle 组件: 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. 在您的 Fragment 或 Activity 中,将导入更改为:

    导入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 对象

In a Fragment that you want to share the Activity's ViewModel, you'd use

`val viewModel: CalculatorViewModel by activityViewModels()`

**That's the equivalent of passing the Activity instance in the (deprecated) 
ViewModelProviders.of() function.**

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

Default/ No-arg constructor

在视图模型中 class .

在我的例子中,我忘记生成这个构造函数并在我学习时浪费了 30 分钟 - 之后它对我有用。

遇到这个问题的各位,我是这样遇到的:
1- 在您的 ViewModel 中,不要 创建构造函数,只需创建一个接受上下文和其他参数的函数

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

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


  // Your stuff
}

2- 在你的 activity:


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

您可以通过视图模型工厂在视图模型中传递数据。您也可以查看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
    }
}

您可以在 activity 中调用视图模型,如下所示。

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

我有同样的问题,通过将导航 ui 库添加到我的项目来修复它:

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

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

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

当然在您的 ViewModel class 您还需要使用 Annotate HILT 需要什么:@ViewModelInject

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

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)

通过 application api ViewModelProvider.AndroidViewModelFactory.getInstance


关于 ,如果您使用的是 Hilt 并且刚刚添加了 ViewModel,请不要忘记重建您的项目。只是 运行 项目不会创建所需的工厂 类(应该是自动生成的),这是通过艰难的方式发现的。

重建前不存在下面的类:

我有同样的错误。我正在使用 Hilt,在我的例子中,它缺少第二个 hilt 编译器依赖项

现在我有两个:

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

kapt androidx.hilt:hilt-compiler:#version

在我的应用级别 build.gradle 文件中,它有效。

com.google.dagger:hilt-android-compiler 在您使用 Hilt Android Gradle 插件时需要(参见 docs) 和 androidx.hilt:hilt-compiler:#version 当您想要集成 Hilt 和 Jetpack 时显然需要它,例如注入 Android Jetpack ViewModel(参见 docs

如果您使用 hilt,您可能会忘记使用 @AndroidEntryPoint[ 注释您的 activity 或片段=10=]

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

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

与:

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

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

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

终极提示:

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

这里不行:

这里 它确实有效:

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

对于 Koin:

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

导入了 viewModel()

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

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

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

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

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

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

不要忘记为 hiltViewModel() 添加此依赖项 -> implementation("androidx.hilt:hilt-navigation-compose:1.0.0-alpha02")

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

1:在 class 之上添加 @HiltViewModel 2: 使用 Inject intead of ViewModelInject

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

对于刀柄:

为主要活动和片段简单添加 @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