【问题标题】:Why is my ViewModel null in Fragment but not Fragment's bound layout?为什么我的 ViewModel 在 Fragment 中为 null 而不是 Fragment 的绑定布局?
【发布时间】:2020-10-18 06:33:53
【问题描述】:

我有一个FooActivity: AppCompatActivity(),它使用FooViewModel 从数据库中查找Foo,然后在几个Fragments 中显示有关它的信息。以下是我的设置方式:

private lateinit var viewModel: FooViewModel

override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)
    // Get the Intent that started this activity and extract the FOO_ID
    val id = intent.getLongExtra(FOO_ID, 1L)
    val viewModelFactory = FooViewModelFactory(
        id,
        FooDatabase.getInstance(application).fooDao,
        application)
    viewModel = ViewModelProviders.of(
        this, viewModelFactory).get(FooViewModel::class.java)

    // FooViewModel is bound to Activity's Fragments, so must
    // create FooViewModelFactory before trying to bind/inflate layout
    binding = DataBindingUtil.setContentView(this, R.layout.activity_foo)
    binding.lifecycleOwner = this
    binding.viewModel = viewModel
}

FooInfoFragment 类中:

private val viewModel: FooViewModel by activityViewModels()

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    super.onCreateView(inflater, container, savedInstanceState)
    val binding = FragmentFooInfoBinding.inflate(inflater)
    binding.setLifecycleOwner(this)
    binding.viewModel = viewModel
    // normally viewModel.foo shouldn't be null here, right?
    return binding.root
}

到目前为止一切顺利。我的布局显示了Foo 的各种信息,例如@{viewModel.foo.name}

问题是在我的FooInfoFragment.onCreateView 中,当我在绑定后尝试访问viewModel.foo.value?.name 时,viewModel 为空。

    binding.viewModel = viewModel
    Log.wtf(TAG, "${viewModel.foo.value?.name} shouldn't be null!")
    return binding.root

我不明白为什么它在我的 Fragment 中为空,但在我的布局中却没有。帮忙?

【问题讨论】:

  • activityViewModels()(我想这是来自 fragemnt-ktx )有自己的实现我认为它无法创建一个,因为你有一个参数化的构造函数。
  • @ADM 我知道 viewModel 正在创建by activityViewModels(),因为我看到绑定后Foo 信息在Fragment 布局中呈现。 activityViewModels() 使用 ViewModelFactory 创建 ViewModel。见codelabs.developers.google.com/codelabs/…
  • 视图模型真的是空的吗?在viewModel.foo.value?.name 行中,也可以是foofoo.value。如果是viewmodelviewModel.foo,应用就会崩溃。
  • viewModel 本身为空?还是viewModel.foo.value?.name 为空?如果viewModel.foo.value 则可能是因为尚未设置实时数据。也就是尚未加载数据库中的数据。

标签: android kotlin layout fragment viewmodel


【解决方案1】:

从这里的字里行间看出,您的问题是您不希望 viewModel.foo.value 可以为空,而不是当您访问 viewModel.foo 时,您将获得一个空 viewModel 的 Java NPE。

如果foo 是一个LiveData,它的value 参数总是可以为空的,即使它的类型不是。这就是 LiveData 类的定义方式。 value 可以为空,因为它在设置之前默认为空。设置后,它不会再次为空。如果你确定在访问它之前总是设置它的值,你可以使用value!!value ?: error("Value was accessed before it was set.")

或者你可以编写一个扩展属性来更干净地访问它:

val <T: Any> LiveData<T>.requiredValue: T get() = 
    value ?: error("Non-null value cannot be accessed before value is first set.")

但是,很少需要直接访问 LiveData 值。 LiveData 的目的是观察值的变化,在这种情况下,您永远不会遇到这个问题。

【讨论】:

  • 感谢您的提示。根本原因是从数据库中获取 Foo 的异步操作在 onCreate 返回时没有终止,但在渲染之前确实终止了(所以布局中的所有 LiveData 都正确渲染,让我忘记了这些数据的可用性取决于异步操作)。
【解决方案2】:

FooInfoFragment Fragment 中的“viewModel”尚未初始化,这就是它在 Fragment 中为空的原因。

binding.viewModel = viewModel 
//ViewModel is not initialized 

【讨论】:

  • by activityViewModels() 保证在你第一次访问它的时候就被初始化了。
  • 感谢您让我知道,真的.. 现在 @Tenfour04 viewModel.LiveData.value 在 Observer 之外将始终为空,即使它不可为空(我学得很辛苦)。它只能确认它在观察者中保存数据。如果您想使用一次性异步操作而不是使用 Flow 或 Transformations.switchMap。
猜你喜欢
  • 1970-01-01
  • 2019-12-16
  • 2013-11-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-10-27
  • 1970-01-01
相关资源
最近更新 更多