【问题标题】:Android lifecycle library ViewModel using dagger 2使用匕首2的Android生命周期库ViewModel
【发布时间】:2017-05-30 19:37:35
【问题描述】:

我有一个 ViewModel 类,就像 Architecture guide连接 ViewModel 和存储库 部分中定义的那样。当我运行我的应用程序时,我得到一个运行时异常。有谁知道如何解决这个问题?我不应该注入 ViewModel 吗?有没有办法告诉ViewModelProvider 使用 Dagger 创建模型?

public class DispatchActivityModel extends ViewModel {

    private final API api;

    @Inject
    public DispatchActivityModel(API api) {
        this.api = api;
    }
}

原因:java.lang.InstantiationException:java.lang.Class 没有零参数构造函数 在 java.lang.Class.newInstance(本机方法) 在 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) 在 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)

【问题讨论】:

标签: android android-lifecycle dagger-2


【解决方案1】:

您需要实现自己的ViewModelProvider.Factory。 Google 创建了一个示例应用程序,演示了如何将 Dagger 2 与 ViewModels 连接起来。 LINK。你需要这 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);

在片段中:

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

【讨论】:

  • 欢迎来到 Stack Overflow。始终欢迎提供指向潜在解决方案的链接,但请在链接周围添加上下文,以便您的其他用户了解它是什么以及为什么存在。始终引用重要链接中最相关的部分,以防目标站点无法访问或永久离线。看看Why and how are some answers deleted?How do I write a good answer?
  • 你还必须在你的 ViewModelModule 中声明:@Binds abstract ViewModelProvider.Factory bindViewModelFactory(GithubViewModelFactory factory);
  • 这违背了使用匕首的目的,因为您仍在使用 viewModelProvider 手动初始化 viewModel 对象。
  • 我相信这个答案中缺少一些东西。我仍然让我的工厂为空,Android 回退到默认工厂并产生“无零参数构造函数”异常。
【解决方案2】:

今天我学会了一种避免为我的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 在 cmets 中指出的那样,我们在上面的代码 sn-p 中使用 Dagger 的 Lazy,而不是 Kotlin 的。

您可以将通用ViewModelFactory 注入到您的活动和片段中,而不是注入ViewModel,并获取任何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() {
    ...
}

【讨论】:

  • inline fun ViewModelFactory.get(activity:FragmentActivity): T = ViewModelProviders.of(activity, this).get(T::class.java)跨度>
  • 我不清楚你为什么要在工厂控制器中有 Lazi?
  • 啊,明白了,是 Dagger 的懒惰,而不是 kotlin 的懒惰 :(
  • @AlbertGao,你确定你引用的是 Dagger 的 Lazy 而不是 Kotlin 的,正如@cain 指出的那样?
  • @argenkiwi 我的错。添加import dagger.Lazy后,现在一切正常,非常感谢!
【解决方案3】:

如果您不想使用罗伯特回答中提到的工厂,我相信还有第二种选择。这不一定是更好的解决方案,但知道选项总是好的。

您可以使用默认构造函数保留 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);

}

干杯, 彼得

【讨论】:

  • 但这并没有解决如何在单元测试中使用模拟依赖初始化 ViewModel
  • 如果您查看Guide to App Architecture,他们实际上就是这样做的(ViewModel 中有一个@Inject)。
【解决方案4】:

问题中可能不明显的是 ViewModel 不能以这种方式注入,因为您从

获得的 ViewModelProvider 默认工厂
ViewModelProvider.of(LifecycleOwner lo) 

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

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

public DispatchActivityModel(API api) {

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

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

【讨论】:

    【解决方案5】:

    我想为遇到此问题的任何人提供第三种选择。 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 类上。

    免责声明:这是我的图书馆,但我相信它对这个问题的作者和其他任何想要实现相同目标的人都有用。

    【讨论】:

      【解决方案6】:

      用于在视图中获取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)
          }
      }
      

      【讨论】:

        【解决方案7】:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-10-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多