I have a FooActivity: AppCompatActivity()
that uses a FooViewModel
to look up a Foo
from a database and then present information about it in a few Fragment
s. Here's how I set it up:
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
}
And in the FooInfoFragment
class:
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
}
So far so good. My layout shows the various info of the Foo
, eg @{viewModel.foo.name}
.
The issue is that in my FooInfoFragment.onCreateView
when I attempt to access viewModel.foo.value?.name
after binding, viewModel
is null.
binding.viewModel = viewModel
Log.wtf(TAG, "${viewModel.foo.value?.name} shouldn't be null!")
return binding.root
I don't understand why it's null in my Fragment but not in my layout. Help?
Reading between the lines here that your problem is that you don't expect viewModel.foo.value
to be nullable, not that you are getting a Java NPE for a null viewModel
when you access viewModel.foo
.
If foo
is a LiveData, its value
parameter is always nullable, even if its type is not. That is how the LiveData class is defined. value
is nullable because it defaults to null before it has been set. After it is set, it won't be null again. If you are sure that you always set its value before accessing it, you can use value!!
or value ?: error("Value was accessed before it was set.")
.
Or you can write an extension property to access it more cleanly:
val <T: Any> LiveData<T>.requiredValue: T get() =
value ?: error("Non-null value cannot be accessed before value is first set.")
However, LiveData values should rarely need to be accessed directly. The intent with LiveData is that changes to the value will be observed, in which case, you would never run into this issue.