Android生命周期库ViewModel使用dagger 2

52

我有一个ViewModel类,就像在Architecture guide连接ViewModel和存储库部分中定义的那样。当我运行我的应用时,我得到了一个运行时异常。有人知道如何解决这个问题吗?我不应该注入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 没有零参数构造函数 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)

您正在寻找 https://developer.android.com/reference/android/arch/lifecycle/ViewModelProvider.Factory.html - EpicPandaForce
7个回答

99
你需要实现自己的ViewModelProvider.Factory。谷歌创建了一个示例应用程序,演示如何将Dagger 2与ViewModel连接起来。链接。你需要这5个东西:
在ViewModel中:
@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);

在Fragment中:
@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);
        }
    }
}

6
欢迎来到 Stack Overflow。欢迎提供链接以供参考解决问题,但请在链接周围添加一些上下文,以便其他用户了解它们的含义和作用。如果目标网站无法访问或已经永久下线,请引用重要链接中最相关的部分。请参阅 为何和如何删除某些回答?如何撰写一个好的回答? - Gary99
5
您需要定义@ViewModelKey https://github.com/googlesamples/android-architecture-components/blob/388a10dd5a814ba6aaa9bf8dee8e7c1c5840b3a5/GithubBrowserSample/app/src/main/java/com/android/example/github/di/ViewModelKey.java - nmu
9
在你的ViewModelModule中,你还需要声明:@Binds abstract ViewModelProvider.Factory bindViewModelFactory(GithubViewModelFactory factory);这行代码的作用是将GithubViewModelFactoryViewModelProvider.Factory进行绑定。 - Andrzej Sawoniewicz
1
这样做就失去了使用Dagger的意义,因为你仍然需要手动使用viewModelProvider初始化viewModel对象。 - Jono
2
我相信这个答案中还有一些东西缺失。我仍然得到我的工厂为空,Android回退到默认工厂并产生“没有零参数构造函数”异常。 - Slobodan Antonijević
显示剩余6条评论

27
今天我学到了一种避免编写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库中一样,但您可以以自己喜欢的方式注入活动或片段。 唯一需要做的就是确保您从模块提供您的ViewModel

@Module
object MyModule {
    @JvmStatic
    @Provides
    fun myViewModel(someDependency: SomeDependency) = MyViewModel(someDependency)
} 

或者将@Inject注解应用于其构造函数:

class MyViewModel @Inject constructor(
    someDependency: SomeDependency
) : ViewModel() {
    ...
}

2
inline fun <reified T : ViewModel> ViewModelFactory<T>.get(activity: FragmentActivity): T = ViewModelProviders.of(activity, this).get(T::class.java) - nxdrvr
3
明白了,这里的“懒惰”是指来自Dagger的懒加载,而不是Kotlin的懒加载 :( - Calin
1
@AlbertGao,你确定你正在引用Dagger的Lazy而不是Kotlin的吗?正如@cain所指出的那样。 - argenkiwi
1
@argenkiwi 我的错。在添加了 import dagger.Lazy 后,现在一切都正常了,非常感谢! - Albert Gao
纠正,现在我发现了这种解决方案的缺陷,如果您有一个扩展ViewModel的BaseViewModel类,并且该类由您的实际视图模型扩展,则它将无法工作:\ - lelloman
显示剩余6条评论

4

在这个问题中可能不明显的是,ViewModel无法通过这种方式注入,因为ViewModelProvider默认工厂是从

ViewModelProvider.of(LifecycleOwner lo) 

只有使用LifecycleOwner参数的方法才能实例化具有无参默认构造函数的ViewModel。

您在构造函数中有一个参数:'api'

public DispatchActivityModel(API api) {

为了做到这一点,您需要创建一个工厂,以便告诉它如何创建自身。Google的示例代码提供了Dagger配置和工厂代码,如已接受的答案中所述。
DI的创建是为了避免在依赖项上使用new()运算符,因为如果实现更改,则每个引用也必须更改。ViewModel实现明智地使用静态工厂模式,ViewProvider.of().get()使其在没有参数构造函数的情况下不需要注入。因此,在不需要编写工厂的情况下,您当然不需要注入一个工厂。

4

如果您不想使用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);

}

干杯,Piotr

1
但这并没有解决如何在单元测试中使用模拟依赖项初始化ViewModel的问题。 - Simon K. Gerges
如果你查看应用架构指南,那实际上它们似乎就是这样做的(在ViewModel中有一个@Inject)。 - Fred Porciúncula

3
我想为那些遇到这个问题的人提供第三个选择。 Dagger ViewModel库 可以让你以类似Dagger2的方式注入ViewModel,可以选择指定ViewModel的作用域。
它可以消除很多样板代码,并且还允许使用注释以声明方式注入ViewModel:
@InjectViewModel(useActivityScope = true)
public MyFragmentViewModel viewModel;

这还需要一小段代码来设置一个模块,从而生成完全依赖注入的ViewModels,之后只需调用:

void injectFragment(Fragment fragment, ViewModelFactory factory) {
    ViewModelInejectors.inject(frag, viewModelFactory);
}

关于生成的ViewModelInjectors类。
免责声明:这是我的库,但我相信它对提问者和任何想实现相同功能的人都有用。

0

默认的ViewModel工厂用于获取您视图中DispatchActivityModel的实例,使用假定为空的构造函数构建ViewModel。

您可以编写自定义的ViewModel.Factory来规避此问题,但如果要提供您的API类,则仍需要自己完成依赖关系图。

我编写了一个小型库,应该使克服这个常见问题更加简单和清晰,无需多绑定或工厂样板文件,同时还可以在运行时进一步参数化ViewModelhttps://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)
    }
}

0
最近我找到了另一个优雅的解决方案。
我的带注入ViewModel的片段如下:
class SettingsFragment : Fragment() {

    private val viewModel by viewModels(DI::settingsViewModel)
}

为了实现这一点,我创建了自定义的by viewModelsdelegate
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函数作为参数,该函数可以创建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()方法引用传递给片段中的委托:

private val viewModel by viewModels(DI::settingsViewModel)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接