Android 使用 dagger 2 的生命周期库 ViewModel

Android lifecycle library ViewModel using dagger 2

我有一个 ViewModel class,就像 Architecture guideConnecting 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=,您仍然需要自己完成依赖关系图].

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

最近我找到了另一个解决这个问题的优雅方法。

我注入了 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)