NotFoundException: String resource ID #0x0 when binding string resource

Viewed 4023

I'm currently using bindings to dynamically set the texts of various text views using the android view models. At the moment the view models look something like this:

class MyViewModel(
  resources: Resources,
  remoteClientModel: Model = Model()
) : ObservableViewModel() {

  init {
    observe(remoteClientModel.liveData) {
      notifyChange()
    }

  fun getTextViewTitle(): String = when {
    someComplicatedExpression -> resources.getString(R.string.some_string, null)
    else -> resources.getString(R.string.some_other_string)
  }
}

And the xml layout:

<layout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:app="http://schemas.android.com/apk/res-auto">
<data>
  <import type="android.view.View"/>

  <variable
    name="viewModel"
    type="my.app.signature.MyViewModel"/>
  </data>

  <androidx.constraintlayout.widget.ConstraintLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <TextView
      android:id="@+id/title"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:text="@{viewModel.textViewTitle}"
      android:textAlignment="center"
      android:textStyle="bold"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toBottomOf="parent"/>

  </androidx.constraintlayout.widget.ConstraintLayout>
</layout>

However I would like to remove the "resources: Resources" that is injected into the view model, since the resources are coupled with the Activity. The code now simply returns the string resource id instead:

  fun getTextViewTitle(): Int = when {
    someComplicatedExpression -> R.string.some_string
    else -> R.string.some_other_string
  }

Hence I've removed the activity dependency. The compiler thinks this is fine but it crashes in runtime with the following exception: android.content.res.Resources$NotFoundException: String resource ID #0x0.

This happens when trying to attach the lifeCycleOwner to the binding using:

override fun onActivityCreated(savedInstanceState: Bundle?) {
    // Some more code....
    binding.lifecycleOwner = activity
    // Some more code....

I'm not sure how to remove the resource dependency from the view model without having it crash in runtime.

EDIT:

For clarification: The ObservableViewModel in my example is the very same one as the one found here:

https://developer.android.com/topic/libraries/data-binding/architecture

Used to perform notifyChange.

4 Answers

The issue here is the code is trying to call textView.setText(0) which results in an error since there is no string resource with id 0x0. This is happening because getTextViewTitle() return an Int and the view binding functionality will make it default as 0 (when initializing).

https://developer.android.com/topic/libraries/data-binding/expressions#property_reference

From the docs

Avoiding null pointer exceptions

Generated data binding code automatically checks for null values and avoid null pointer exceptions. For example, in the expression @{user.name}, if user is null, user.name is assigned its default value of null. If you reference user.age, where age is of type int, then data binding uses the default value of 0.

Maybe something like this could work,

android:text='@{viewModel.textViewTitle == 0 ? "" : @string{viewModel.textViewTitle}}'

or

android:text='@{viewModel.textViewTitle, default=""}'

To solve this simply make a context available in the view, so that you can call context.getString(...) in your view.

 <data>
    <import type="androidx.core.content.ContextCompat" />

    <variable
      name="viewModel"
      type="my.application.path.SomeViewModel" />
 </data>

  <....
      ....
      android:text="@{context.getString(viewModel.textResource)}"
      ...
  />

Just convert your int value to String to avoid this crush

android:text='@{String.valueOf(viewModel.profile.walletBalance)}'

In some cases your binding variable itself can be null

<data>
    <variable
        name="viewModel"
        type="SomeViewModel" 
    />
</data>

<TextView
    android:text="@{viewModel == null ? "" : viewModel.textViewTitle}"
/>
Related