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

时间:2017-05-30 19:37:36

标签: android android-lifecycle dagger-2

我有一个ViewModel类,就像Architecture guide连接ViewModel和存储库部分中定义的类一样。当我运行我的应用程序时,我得到运行时异常。有谁知道怎么解决这个问题?我应该不注入ViewModel吗?有没有办法告诉... <properties> <kotlin.version>1.1.2-2</kotlin.version> </properties> ... <dependencyManagement> <dependencies> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-stdlib</artifactId> <version>${kotlin.version}</version> </dependency> <dependency> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-test</artifactId> <version>${kotlin.version}</version> <scope>test</scope> </dependency> </dependencies> </dependencyManagement> ... <build> <plugins> <plugin> <groupId>org.jetbrains.kotlin</groupId> <artifactId>kotlin-maven-plugin</artifactId> <version>${kotlin.version}</version> <executions> <execution> <id>compile</id> <phase>compile</phase> <goals> <goal>compile</goal> </goals> </execution> <execution> <id>test-compile</id> <phase>test-compile</phase> <goals> <goal>test-compile</goal> </goals> </execution> </executions> </plugin> </plugins> </build> 使用Dagger创建模型?

ViewModelProvider
  

引起:java.lang.InstantiationException:java.lang.Class没有零参数构造函数           at java.lang.Class.newInstance(Native Method)           在android.arch.lifecycle.ViewModelProvider $ NewInstanceFactory.create(ViewModelProvider.java:143)           在android.arch.lifecycle.ViewModelProviders $ DefaultFactory.create(ViewModelProviders.java:143)           在android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:128)           在android.arch.lifecycle.ViewModelProvider.get(ViewModelProvider.java:96)           at com.example.base.BaseActivity.onCreate(BaseActivity.java:65)           在com.example.dispatch.DispatchActivity.onCreate(DispatchActivity.java:53)           在android.app.Activity.performCreate(Activity.java:6682)           在android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)           在android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2619)android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2727)           在android.app.ActivityThread.-wrap12(ActivityThread.java)           在android.app.ActivityThread $ H.handleMessage(ActivityThread.java:1478)           在android.os.Handler.dispatchMessage(Handler.java:102)           在android.os.Looper.loop(Looper.java:154)           在android.app.ActivityThread.main(ActivityThread.java:6121)

7 个答案:

答案 0 :(得分:79)

您需要实现自己的ViewModelProvider.Factory。 Google创建了一个示例应用,演示如何将Dagger 2与ViewModels连接。 LINK。你需要这五件事:

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

在片段中:

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

答案 1 :(得分:23)

今天我学会了一种避免为我的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 :(得分:4)

如果您不想使用Robert的答案中提到的工厂,我相信还有第二种选择。它不一定是更好的解决方案,但了解选项总是好的。

您可以使用默认构造函数保留viewModel,并像处理活动或系统创建的其他元素一样注入依赖项。 例如:

视图模型:

contentDisplay

组件:

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

}

干杯, 彼得

答案 3 :(得分:4)

在问题中可能不明显的是ViewModel无法以这种方式注入,因为ViewModelProvider是默认的Factory,它来自

ViewModelProvider.of(LifecycleOwner lo) 
只有LifecycleOwner参数的

方法只能实例化一个具有no-arg默认构造函数的ViewModel。

你的构造函数中有一个参数:'api':

public DispatchActivityModel(API api) {

为了做到这一点,你需要创建一个工厂,以便你可以告诉它如何创建自己。来自谷歌的示例代码为您提供了Dagger配置和工厂代码,如接受的答案中所述。

创建DI是为了避免在依赖项上使用new()运算符,因为如果实现发生更改,则每个引用也必须更改。 ViewModel实现明智地使用了ViewProvider.of()。get()的静态工厂模式,这使得在no-arg构造函数的情况下不需要注入。因此,如果您不需要编写工厂,您当然不需要注入工厂。

答案 4 :(得分:3)

我想为任何绊倒这个问题的人提供第三种选择。 Dagger ViewModel library允许您以类似Dagger2的方式注入ViewModels,可选择指定ViewModel的范围。

它删除了很多样板,并且还允许使用注释以声明方式注入ViewModel:

@InjectViewModel(useActivityScope = true)
public MyFragmentViewModel viewModel;

它还需要少量代码来设置一个模块,从中可以生成完全依赖注入的ViewModel,之后就像调用一样简单:

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

在生成的ViewModelInjectors类上。

免责声明:这是我的图书馆,但我相信这个问题的作者以及其他任何想要达到同样目标的人也会使用它。

答案 5 :(得分:0)

用于在视图中获取DispatchActivityModel实例的默认ViewModel工厂使用假定的空构造函数构造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)
    }
}

答案 6 :(得分:0)

最近,我已经找到了解决该问题的另一种优雅方法。

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