Home > Software design >  JetPack Compose Room LiveData ViewModel
JetPack Compose Room LiveData ViewModel

Time:10-16

In a Jetpack Compose component I'm subscribing to Room LiveData object using observeAsState. The initial composition goes fine, data is received from ViewModel/LiveData/Room.

val settings by viewModel.settings.observeAsState(initial = AppSettings()) // Works fine the first time

A second composition is initiated, where settings - A non nullable variable is set to null, and the app crashed with an NPE.

DAO:

@Query("select * from settings order by id desc limit 1")
fun getSettings(): LiveData<AppSettings>

Repository:

fun getSettings(): LiveData<AppSettings> {
        return dao.getSettings()
}

ViewModel:


@HiltViewModel
class SomeViewModel @Inject constructor(
    private val repository: AppRepository
) : ViewModel() {

    val settings = repository.getSettings()
}

Compose:

@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ItemsListScreen(viewModel: AppViewModel = hiltViewModel()) {
    val settings by viewModel.settings.observeAsState(initial = AppSettings())

Edit: Just to clearify, the DB data does not change. the first time settings is fetched within the composable, a valid instance is returned.

Then the component goes into recomposition, when ItemsListScreen is invoked for the second time, then settings is null (the variable in ItemsListScreen).

CodePudding user response:

If you only need to fetch settings when viewModel is initialised, you can try putting it in an init block inside your ViewModel.

CodePudding user response:

Once the LiveData<Appsettings> is subscribed to will have a default value of null. So you get the default value required by a State<T> object, when you call LiveData<T>::observeAsState, followed by the default LiveData<T> value, this being null

LiveData<T> is a Java class that allows nullable objects. If your room database doesn't have AppSettings it will set it a null object on the LiveData<AppSettings> instance. As Room is also a Java library and not aware of kotlin language semantics. You should use LiveData<AppSettings?> in kotlin code and handle null settings objects, or in the repository use some sort of MediatorLiveData to filter out null objects.

Simply put this is an interop issue.

You should use LiveData<AppSettings?> in kotlin code and handle null settings objects, or use some sort of MediatorLiveData<T> that can filter null values for example some extensions functions like :

@Composable
fun <T> LiveData<T?>.observeAsNonNullState(initial : T, default : T) : State<T> =
    MediatorLiveData<T>().apply {
        addSource(this) { t -> value = t ?: default }
    }.observeAsState(initial = initial)

@Composable
fun <T> LiveData<T?>.observeAsNonNullState(initial : T) : State<T> =
    MediatorLiveData<T>().apply {
        addSource(this) { t -> t?.run { value = this } }
    }.observeAsState(initial = initial)
  • Related