Android 使用 dagger 2 的生命周期库 ViewModel
Android lifecycle library ViewModel using dagger 2
我有一个 ViewModel class,就像 Architecture guide 的 Connecting ViewModel and repository 部分中定义的一样。当我 运行 我的应用程序出现 运行 时间异常。有谁知道如何解决这个问题?我不应该注入 ViewModel 吗?有没有办法告诉 ViewModelProvider
使用 Dagger 来创建模型?
public class DispatchActivityModel extends ViewModel {
private final API api;
@Inject
public DispatchActivityModel(API api) {
this.api = api;
}
}
Caused by: java.lang.InstantiationException: java.lang.Class has no zero argument constructor
at java.lang.Class.newInstance(Native Method)
at android.arch.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:143)
at android.arch.lifecycle.ViewModelProviders$DefaultFactory.create(ViewModelProviders.java:143)
at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:128)
at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:96)
at com.example.base.BaseActivity.onCreate(BaseActivity.java:65)
at com.example.dispatch.DispatchActivity.onCreate(DispatchActivity.java:53)
at android.app.Activity.performCreate(Activity.java:6682)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2619) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1478)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6121)
您需要实现自己的 ViewModelProvider.Factory
。 Google 创建了一个示例应用程序,演示了如何将 Dagger 2 与 ViewModel 连接起来。 LINK。你需要这 5 样东西:
在视图模型中:
@Inject
public UserViewModel(UserRepository userRepository, RepoRepository repoRepository) {
定义注释:
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
Class<? extends ViewModel> value();
}
在 ViewModelModule 中:
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(UserViewModel.class)
abstract ViewModel bindUserViewModel(UserViewModel userViewModel);
在片段中:
@Inject
ViewModelProvider.Factory viewModelFactory;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
userViewModel = ViewModelProviders.of(this, viewModelFactory).get(UserViewModel.class);
工厂:
@Singleton
public class GithubViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
@Inject
public GithubViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;
}
@SuppressWarnings("unchecked")
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
Provider<? extends ViewModel> creator = creators.get(modelClass);
if (creator == null) {
for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
if (modelClass.isAssignableFrom(entry.getKey())) {
creator = entry.getValue();
break;
}
}
}
if (creator == null) {
throw new IllegalArgumentException("unknown model class " + modelClass);
}
try {
return (T) creator.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
如果您不想使用 Robert 的回答中提到的工厂,我相信还有第二种选择。这不一定是更好的解决方案,但了解选项总是好的。
您可以让您的 viewModel 保留默认构造函数并注入您的依赖项,就像您在系统创建的活动或其他元素中所做的那样。
示例:
视图模型:
public class ExampleViewModel extends ViewModel {
@Inject
ExampleDependency exampleDependency;
public ExampleViewModel() {
DaggerExampleComponent.builder().build().inject(this);
}
}
组件:
@Component(modules = ExampleModule.class)
public interface ExampleComponent {
void inject(ExampleViewModel exampleViewModel);
}
模块:
@Module
public abstract class ExampleModule {
@Binds
public abstract ExampleDependency bindExampleDependency(ExampleDependencyDefaultImplementation exampleDependencyDefaultImplementation);
}
干杯,
彼得
我想为遇到这个问题的任何人提供第三种选择。 Dagger ViewModel library 将允许您以类似 Dagger2 的方式注入 ViewModels,可选择指定 ViewModel 的范围。
它删除了很多样板文件,还允许使用注释以声明方式注入 ViewModels:
@InjectViewModel(useActivityScope = true)
public MyFragmentViewModel viewModel;
它还需要少量代码来设置一个模块,从中可以生成完全依赖注入的 ViewModels,之后就像调用一样简单:
void injectFragment(Fragment fragment, ViewModelFactory factory) {
ViewModelInejectors.inject(frag, viewModelFactory);
}
在生成的 ViewModelInjectors class 上。
免责声明:这是我的图书馆,但我相信它对这个问题的作者和其他想要实现同样目标的人也有用。
问题中可能不明显的是,无法以这种方式注入 ViewModel,因为您从
获得的 ViewModelProvider 默认工厂
ViewModelProvider.of(LifecycleOwner lo)
只有 LifecycleOwner 参数的方法只能实例化具有无参数默认构造函数的 ViewModel。
您的构造函数中有一个参数:'api':
public DispatchActivityModel(API api) {
为此,您需要创建一个工厂,以便您可以告诉它如何创建自己。 google 中的示例代码为您提供了可接受的答案中提到的 Dagger 配置和工厂代码。
创建 DI 是为了避免在依赖项上使用 new() 运算符,因为如果实现发生变化,每个引用也必须随之变化。 ViewModel 实现明智地使用了 ViewProvider.of().get() 中的静态工厂模式,这使得在无参数构造函数的情况下不需要注入。所以在你不需要写工厂的情况下你当然不需要注入工厂。
今天我学会了一种避免为我的 ViewModel
类:
编写工厂的方法
class ViewModelFactory<T : ViewModel> @Inject constructor(
private val viewModel: Lazy<T>
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = viewModel.get() as T
}
编辑: 正如@Calin 在评论中指出的那样,我们在上面的代码片段中使用了 Dagger 的 Lazy
,而不是 Kotlin 的。
无需注入 ViewModel
,您可以将通用 ViewModelFactory
注入您的活动和片段,并获取任何 ViewModel
:
的实例
class MyActivity : Activity() {
@Inject
internal lateinit var viewModelFactory: ViewModelFactory<MyViewModel>
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
this.viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(MyViewModel::class.java)
...
}
...
}
我将 AndroidInjection.inject(this)
与 dagger-android
库一起使用,但您可以按照自己喜欢的方式注入 activity 或分段。剩下的就是确保您从模块中提供 ViewModel
:
@Module
object MyModule {
@JvmStatic
@Provides
fun myViewModel(someDependency: SomeDependency) = MyViewModel(someDependency)
}
或将 @Inject
注释应用于其构造函数:
class MyViewModel @Inject constructor(
someDependency: SomeDependency
) : ViewModel() {
...
}
用于在视图中获取 DispatchActivityModel
实例的默认 ViewModel 工厂使用假定的空构造函数构造 ViewModel。
您可以编写您的自定义 ViewModel.Factory
来绕过它,但是如果您想提供您的 API
[=29=,您仍然需要自己完成依赖关系图].
我写了一个小库,应该可以更直接、更清晰地克服这个常见问题,不需要多重绑定或工厂样板,同时还可以在运行时进一步参数化 ViewModel
:
https://github.com/radutopor/ViewModelFactory
@ViewModelFactory
public class DispatchActivityModel extends ViewModel {
private final API api;
public DispatchActivityModel(@Provided API api) {
this.api = api;
}
}
在视图中:
public class DispatchActivity extends AppCompatActivity {
@Inject
private DispatchActivityModelFactory2 viewModelFactory;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appComponent.inject(this);
DispatchActivityModel viewModel = ViewModelProviders.of(this, viewModelFactory.create())
.get(UserViewModel.class)
}
}
正如我提到的,您还可以轻松地将运行时参数添加到您的 ViewModel
实例中:
@ViewModelFactory
public class DispatchActivityModel extends ViewModel {
private final API api;
private final int dispatchId;
public DispatchActivityModel(@Provided API api, int dispatchId) {
this.api = api;
this.dispatchId = dispatchId;
}
}
public class DispatchActivity extends AppCompatActivity {
@Inject
private DispatchActivityModelFactory2 viewModelFactory;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appComponent.inject(this);
final int dispatchId = getIntent().getIntExtra("DISPATCH_ID", -1);
DispatchActivityModel viewModel = ViewModelProviders.of(this, viewModelFactory.create(dispatchId))
.get(UserViewModel.class)
}
}
最近我找到了另一个解决这个问题的优雅方法。
我注入了 ViewModel 的片段看起来像:
class SettingsFragment : Fragment() {
private val viewModel by viewModels(DI::settingsViewModel)
}
为了实现这一点,我创建了自定义 by viewModels
delegate:
inline fun <reified VM : ViewModel> Fragment.viewModels(
crossinline viewModelProducer: () -> VM
): Lazy<VM> {
return lazy(LazyThreadSafetyMode.NONE) { createViewModel { viewModelProducer() } }
}
inline fun <reified VM : ViewModel> Fragment.createViewModel(
crossinline viewModelProducer: () -> VM
): VM {
val factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <VM : ViewModel> create(modelClass: Class<VM>) = viewModelProducer() as VM
}
val viewModelProvider = ViewModelProvider(this, factory)
return viewModelProvider[VM::class.java]
}
此 属性 委托期望 lambda function 可以创建 ViewModel 实例作为参数。
我们可以像这样使用 Dagger2 提供此 lambda:
@Component(
modules = [MyModule::class]
)
interface MyComponent {
//1) Declare function that provides our ViewModel in Component
fun settingsViewModel(): SettingsViewModel
}
@Module
abstract class MyModule {
@Module
companion object {
//2) Create provides method than provides our ViewModel in Module
@Provides
@JvmStatic
fun provideSettingsViewModel(
... // Pass your ViewModel dependencies
): SettingsViewModel {
return SettingsViewModel(
...// Pass your ViewModel dependencies
)
}
}
}
// 3) Build Component somewhere, for example in singleton-object.
object DI {
private var component: MyComponent by lazy {
MyComponent.builder().build()
}
// 4) Declare method that delegates ViewModel creation to Component
fun settingsViewModel() = component.settingsViewModel()
}
最后我们可以将 DI.settingsViewModel()
method reference 传递给片段中的委托:
private val viewModel by viewModels(DI::settingsViewModel)
我有一个 ViewModel class,就像 Architecture guide 的 Connecting ViewModel and repository 部分中定义的一样。当我 运行 我的应用程序出现 运行 时间异常。有谁知道如何解决这个问题?我不应该注入 ViewModel 吗?有没有办法告诉 ViewModelProvider
使用 Dagger 来创建模型?
public class DispatchActivityModel extends ViewModel {
private final API api;
@Inject
public DispatchActivityModel(API api) {
this.api = api;
}
}
Caused by: java.lang.InstantiationException: java.lang.Class has no zero argument constructor at java.lang.Class.newInstance(Native Method) at android.arch.lifecycle.ViewModelProvider$NewInstanceFactory.create(ViewModelProvider.java:143) at android.arch.lifecycle.ViewModelProviders$DefaultFactory.create(ViewModelProviders.java:143) at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:128) at android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:96) at com.example.base.BaseActivity.onCreate(BaseActivity.java:65) at com.example.dispatch.DispatchActivity.onCreate(DispatchActivity.java:53) at android.app.Activity.performCreate(Activity.java:6682) at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118) at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2619) at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727) at android.app.ActivityThread.-wrap12(ActivityThread.java) at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1478) at android.os.Handler.dispatchMessage(Handler.java:102) at android.os.Looper.loop(Looper.java:154) at android.app.ActivityThread.main(ActivityThread.java:6121)
您需要实现自己的 ViewModelProvider.Factory
。 Google 创建了一个示例应用程序,演示了如何将 Dagger 2 与 ViewModel 连接起来。 LINK。你需要这 5 样东西:
在视图模型中:
@Inject
public UserViewModel(UserRepository userRepository, RepoRepository repoRepository) {
定义注释:
@Documented
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@MapKey
@interface ViewModelKey {
Class<? extends ViewModel> value();
}
在 ViewModelModule 中:
@Module
abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(UserViewModel.class)
abstract ViewModel bindUserViewModel(UserViewModel userViewModel);
在片段中:
@Inject
ViewModelProvider.Factory viewModelFactory;
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
userViewModel = ViewModelProviders.of(this, viewModelFactory).get(UserViewModel.class);
工厂:
@Singleton
public class GithubViewModelFactory implements ViewModelProvider.Factory {
private final Map<Class<? extends ViewModel>, Provider<ViewModel>> creators;
@Inject
public GithubViewModelFactory(Map<Class<? extends ViewModel>, Provider<ViewModel>> creators) {
this.creators = creators;
}
@SuppressWarnings("unchecked")
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
Provider<? extends ViewModel> creator = creators.get(modelClass);
if (creator == null) {
for (Map.Entry<Class<? extends ViewModel>, Provider<ViewModel>> entry : creators.entrySet()) {
if (modelClass.isAssignableFrom(entry.getKey())) {
creator = entry.getValue();
break;
}
}
}
if (creator == null) {
throw new IllegalArgumentException("unknown model class " + modelClass);
}
try {
return (T) creator.get();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
如果您不想使用 Robert 的回答中提到的工厂,我相信还有第二种选择。这不一定是更好的解决方案,但了解选项总是好的。
您可以让您的 viewModel 保留默认构造函数并注入您的依赖项,就像您在系统创建的活动或其他元素中所做的那样。 示例:
视图模型:
public class ExampleViewModel extends ViewModel {
@Inject
ExampleDependency exampleDependency;
public ExampleViewModel() {
DaggerExampleComponent.builder().build().inject(this);
}
}
组件:
@Component(modules = ExampleModule.class)
public interface ExampleComponent {
void inject(ExampleViewModel exampleViewModel);
}
模块:
@Module
public abstract class ExampleModule {
@Binds
public abstract ExampleDependency bindExampleDependency(ExampleDependencyDefaultImplementation exampleDependencyDefaultImplementation);
}
干杯, 彼得
我想为遇到这个问题的任何人提供第三种选择。 Dagger ViewModel library 将允许您以类似 Dagger2 的方式注入 ViewModels,可选择指定 ViewModel 的范围。
它删除了很多样板文件,还允许使用注释以声明方式注入 ViewModels:
@InjectViewModel(useActivityScope = true)
public MyFragmentViewModel viewModel;
它还需要少量代码来设置一个模块,从中可以生成完全依赖注入的 ViewModels,之后就像调用一样简单:
void injectFragment(Fragment fragment, ViewModelFactory factory) {
ViewModelInejectors.inject(frag, viewModelFactory);
}
在生成的 ViewModelInjectors class 上。
免责声明:这是我的图书馆,但我相信它对这个问题的作者和其他想要实现同样目标的人也有用。
问题中可能不明显的是,无法以这种方式注入 ViewModel,因为您从
获得的 ViewModelProvider 默认工厂ViewModelProvider.of(LifecycleOwner lo)
只有 LifecycleOwner 参数的方法只能实例化具有无参数默认构造函数的 ViewModel。
您的构造函数中有一个参数:'api':
public DispatchActivityModel(API api) {
为此,您需要创建一个工厂,以便您可以告诉它如何创建自己。 google 中的示例代码为您提供了可接受的答案中提到的 Dagger 配置和工厂代码。
创建 DI 是为了避免在依赖项上使用 new() 运算符,因为如果实现发生变化,每个引用也必须随之变化。 ViewModel 实现明智地使用了 ViewProvider.of().get() 中的静态工厂模式,这使得在无参数构造函数的情况下不需要注入。所以在你不需要写工厂的情况下你当然不需要注入工厂。
今天我学会了一种避免为我的 ViewModel
类:
class ViewModelFactory<T : ViewModel> @Inject constructor(
private val viewModel: Lazy<T>
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = viewModel.get() as T
}
编辑: 正如@Calin 在评论中指出的那样,我们在上面的代码片段中使用了 Dagger 的 Lazy
,而不是 Kotlin 的。
无需注入 ViewModel
,您可以将通用 ViewModelFactory
注入您的活动和片段,并获取任何 ViewModel
:
class MyActivity : Activity() {
@Inject
internal lateinit var viewModelFactory: ViewModelFactory<MyViewModel>
private lateinit var viewModel: MyViewModel
override fun onCreate(savedInstanceState: Bundle?) {
AndroidInjection.inject(this)
super.onCreate(savedInstanceState)
this.viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(MyViewModel::class.java)
...
}
...
}
我将 AndroidInjection.inject(this)
与 dagger-android
库一起使用,但您可以按照自己喜欢的方式注入 activity 或分段。剩下的就是确保您从模块中提供 ViewModel
:
@Module
object MyModule {
@JvmStatic
@Provides
fun myViewModel(someDependency: SomeDependency) = MyViewModel(someDependency)
}
或将 @Inject
注释应用于其构造函数:
class MyViewModel @Inject constructor(
someDependency: SomeDependency
) : ViewModel() {
...
}
用于在视图中获取 DispatchActivityModel
实例的默认 ViewModel 工厂使用假定的空构造函数构造 ViewModel。
您可以编写您的自定义 ViewModel.Factory
来绕过它,但是如果您想提供您的 API
[=29=,您仍然需要自己完成依赖关系图].
我写了一个小库,应该可以更直接、更清晰地克服这个常见问题,不需要多重绑定或工厂样板,同时还可以在运行时进一步参数化 ViewModel
:
https://github.com/radutopor/ViewModelFactory
@ViewModelFactory
public class DispatchActivityModel extends ViewModel {
private final API api;
public DispatchActivityModel(@Provided API api) {
this.api = api;
}
}
在视图中:
public class DispatchActivity extends AppCompatActivity {
@Inject
private DispatchActivityModelFactory2 viewModelFactory;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appComponent.inject(this);
DispatchActivityModel viewModel = ViewModelProviders.of(this, viewModelFactory.create())
.get(UserViewModel.class)
}
}
正如我提到的,您还可以轻松地将运行时参数添加到您的 ViewModel
实例中:
@ViewModelFactory
public class DispatchActivityModel extends ViewModel {
private final API api;
private final int dispatchId;
public DispatchActivityModel(@Provided API api, int dispatchId) {
this.api = api;
this.dispatchId = dispatchId;
}
}
public class DispatchActivity extends AppCompatActivity {
@Inject
private DispatchActivityModelFactory2 viewModelFactory;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
appComponent.inject(this);
final int dispatchId = getIntent().getIntExtra("DISPATCH_ID", -1);
DispatchActivityModel viewModel = ViewModelProviders.of(this, viewModelFactory.create(dispatchId))
.get(UserViewModel.class)
}
}
最近我找到了另一个解决这个问题的优雅方法。
我注入了 ViewModel 的片段看起来像:
class SettingsFragment : Fragment() {
private val viewModel by viewModels(DI::settingsViewModel)
}
为了实现这一点,我创建了自定义 by viewModels
delegate:
inline fun <reified VM : ViewModel> Fragment.viewModels(
crossinline viewModelProducer: () -> VM
): Lazy<VM> {
return lazy(LazyThreadSafetyMode.NONE) { createViewModel { viewModelProducer() } }
}
inline fun <reified VM : ViewModel> Fragment.createViewModel(
crossinline viewModelProducer: () -> VM
): VM {
val factory = object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <VM : ViewModel> create(modelClass: Class<VM>) = viewModelProducer() as VM
}
val viewModelProvider = ViewModelProvider(this, factory)
return viewModelProvider[VM::class.java]
}
此 属性 委托期望 lambda function 可以创建 ViewModel 实例作为参数。
我们可以像这样使用 Dagger2 提供此 lambda:
@Component(
modules = [MyModule::class]
)
interface MyComponent {
//1) Declare function that provides our ViewModel in Component
fun settingsViewModel(): SettingsViewModel
}
@Module
abstract class MyModule {
@Module
companion object {
//2) Create provides method than provides our ViewModel in Module
@Provides
@JvmStatic
fun provideSettingsViewModel(
... // Pass your ViewModel dependencies
): SettingsViewModel {
return SettingsViewModel(
...// Pass your ViewModel dependencies
)
}
}
}
// 3) Build Component somewhere, for example in singleton-object.
object DI {
private var component: MyComponent by lazy {
MyComponent.builder().build()
}
// 4) Declare method that delegates ViewModel creation to Component
fun settingsViewModel() = component.settingsViewModel()
}
最后我们可以将 DI.settingsViewModel()
method reference 传递给片段中的委托:
private val viewModel by viewModels(DI::settingsViewModel)