Search code examples
androiddata-bindingandroid-xmlandroid-databinding

Using a nullable string resource id from view model in Android data binding


I am trying to map API errors (exceptions) to String resources in my view model. My view model looks like the following.

@HiltViewModel
class AccountViewModel @Inject constructor(accountRepository: AccountRepository) : ViewModel() {

  val isLoadingProfile = MutableLiveData(false)
  val profile = MutableLiveData<Profile>()
  val profileLoadError = MutableLiveData<Int>()

  fun loadProfile() {
    isLoadingProfile.postValue(true)
    viewModelScope.launch(Dispatchers.IO) {
      try {
        profile.postValue(accountRepository.getProfile())
      } catch (e: Throwable) {

        // assign String resource id to profileLoadError
        profileLoadError.postValue(
          when (e) {
            is NetworkError -> R.string.network_error
            else -> R.string.unknown_error
          }
        )

      } finally {
        isLoadingProfile.postValue(false)
      }
    }
  }
}

And my error text view XML looks like the following.

<TextView
  android:layout_width="match_parent"
  android:layout_height="wrap_content"
  android:text="@{viewModel.profileLoadError ?? ``}"
  android:visibility="@{viewModel.profileLoadError != null ? View.VISIBLE : View.GONE}" />

But I am getting a class cast exception when I try to run it.

java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.CharSequence

If I remove the null coalescing operator from the data binding expression like the following, I get a resource not found exception.

android:text="@{viewModel.profileLoadError}"
Caused by: android.content.res.Resources$NotFoundException: String resource ID #0x0

I know that it is kind of an idiotic question, but how do I fix this?


Solution

  • After the suggestion from @androidLearner, I created a binding adapter for the android:text attribute.

    @BindingAdapter("android:text")
    fun TextView.setText(@StringRes resId: Int?) {
      resId ?: return
      if (resId == ResourcesCompat.ID_NULL) {
        text = ""
      } else {
        setText(resId)
      }
    }
    

    Now, I can pass the string id without worrying about its nullability.

    android:text="@{viewModel.profileLoadError}"