25

Is there any way to enforce non-nullability of LiveData values? Default Observer implementation seems to have @Nullable annotation which forces an IDE to suggest that the value might be null and should be checked manually:

public interface Observer<T> {
    /**
     * Called when the data is changed.
     * @param t  The new data
     */
    void onChanged(@Nullable T t);
}
Vadim Kotov
  • 8,084
  • 8
  • 48
  • 62
Igor Bubelov
  • 5,154
  • 5
  • 25
  • 24

7 Answers7

11

A new option is available if you use Kotlin. You can replace LiveData with StateFlow. It is more suitable for Kotlin code and provides built-in null safety.

Instead of using:

class MyViewModel {
    val data: LiveData<String> = MutableLiveData(null) // the compiler will allow null here!
}

class MyFragment: Fragment() {
    model.data.observe(viewLifecycleOwner) {
        // ...
    }
}

You can use:

class MyViewModel {
    val data: StateFlow<String> = MutableStateFlow(null) // compilation error!
}

class MyFragment: Fragment() {
    lifecycleScope.launch {
        model.data.collect {
          // ...
        }
    }
}

StateFlow is part of coroutines and to use the lifecycleScope you need to add the lifecycle-extensions dependency:

implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"

Note that this API has been experimental before coroutines 1.4.0.

Here's some additional reading about replacing LiveData with StateFlow.

As Igor Bubelov pointed out, another advantage of this approach is that it's not Android specific so it can be used in shared code in multiplatform projects.

Sir Codesalot
  • 7,045
  • 2
  • 50
  • 56
  • 1
    That's what I've been using lately and I like it too. Bonus points: coroutines (and flows) aren't Android specific which allows us to use the same tools in backend/multiplatform mobile projects. It's easy to use, it's more generic and there are no performance or convenience penalties so it seems like a better option. – Igor Bubelov Oct 26 '20 at 09:15
  • You're absolutely right. This is also one of the reasons why I'm using it. I'll add a note to the answer. – Sir Codesalot Oct 26 '20 at 09:37
  • I needed implementation "androidx.lifecycle:lifecycle-runtime-ktx:$lifecycle_version", thanks to https://stackoverflow.com/a/56035529/7439163 – alr3000 Feb 08 '23 at 21:28
6

It's possible to do it safely only if you are in control of the code which sets the data because you'll also have to wrap the LiveData class. This way the data setting methods will be protected with @NonNull and you can be sure that the data has already been checked before reaching the Observer.

Wrap the LiveData class:

public class NonNullMutableLiveData<T> extends MutableLiveData<T> implements NonNullLiveData<T> {

    private final @NonNull T initialValue;

    public NonNullMutableLiveData(@NonNull T initialValue) {
        this.initialValue = initialValue;
    }

    @Override
    public void postValue(@NonNull T value) {
        super.postValue(value);
    }

    @Override
    public void setValue(@NonNull T value) {
        super.setValue(value);
    }

    @NonNull
    @Override
    public T getValue() {
        //the only way value can be null is if the value hasn't been set yet.
        //for the other cases the set and post methods perform nullability checks.
        T value = super.getValue();
        return value != null ? value : initialValue;
    }

    //convenience method
    //call this method if T is a collection and you modify it's content
    public void notifyContentChanged() {
        postValue(getValue());
    }

    public void observe(@NonNull LifecycleOwner owner, @NonNull NonNullObserver<T> observer) {
        super.observe(owner, observer.getObserver());
    }
}

Create an interface for exposing as immutable:

public interface NonNullLiveData<T> {

    @NonNull T getValue();

    void observe(@NonNull LifecycleOwner owner, @NonNull NonNullObserver<T> observer);
}

Finally, wrap the Observer:

//not implementing Observer<T> to make sure this class isn't passed to
//any class other than NonNullMutableLiveData.
public abstract class NonNullObserver<T> {

    public Observer<T> getObserver() {
        return new ActualObserver();
    }

    public abstract void onValueChanged(@NonNull T t);

    private class ActualObserver implements Observer<T> {

        @Override
        public void onChanged(@Nullable T t) {
            //only called through NonNullMutableLiveData so nullability check has already been performed.
            //noinspection ConstantConditions
            onValueChanged(t);
        }
    }
}

Now you can create your data like this:

class DataSource {
    private NonNullMutableLiveData<Integer> data = new NonNullMutableLiveData<>(0);

    public NonNullLiveData<Integer> getData() {
        return data;
    }
}

And use it like this:

dataSource.getData().observe(this, new NonNullObserver<Integer>() {
            @Override
            public void onValueChanged(@NonNull Integer integer) {

            }
        });

Completely null safe.

Sir Codesalot
  • 7,045
  • 2
  • 50
  • 56
5

If you use Kotlin, you can create much nicer non-null observe function with extension. There is an article about it. https://medium.com/@henrytao/nonnull-livedata-with-kotlin-extension-26963ffd0333

Henry Tao
  • 1,104
  • 10
  • 14
  • Do you have the java version of that? – Pei Apr 20 '18 at 15:05
  • Hmmmm. I can add Java version but you won't have nice looking usage like Kotlin, simply because Java does not have extension function. – Henry Tao Apr 20 '18 at 17:14
  • Downvote for forcing the LiveData to be null-safe. It is nullable because at the time of access LiveData might not have any value yet – Farid Jan 19 '23 at 14:36
4

While there a few things you can do, it is your responsibility to make sure you don't pass null to the LiveData. In addition to that, every 'solution' is more a suppression of the warning, which can be dangerous (if you do get a null value, you might not handle it and Android Studio will not warn you).

Assert

You can add assert t != null;. The assert will not be executed on Android, but Android Studio understands it.

class PrintObserver implements Observer<Integer> {

    @Override
    public void onChanged(@Nullable Integer integer) {
        assert integer != null;
        Log.d("Example", integer.toString());
    }
}

Suppress the warning

Add an annotation to suppress the warning.

class PrintObserver implements Observer<Integer> {

    @Override
    @SuppressWarnings("ConstantConditions")
    public void onChanged(@Nullable Integer integer) {
        Log.d("Example", integer.toString());
    }
}

Remove the annotation

This also works in my installation of Android Studio, but it might not work for you, but you could try to just remove the @Nullable annotation from the implementation:

class PrintObserver implements Observer<Integer> {

    @Override
    public void onChanged(Integer integer) {
        Log.d("Example", integer.toString());
    }
}

Default methods

It's unlikely you can use this on Android, but purely from a Java perspective, you could define a new interface and add a null check in a default method:

interface NonNullObserver<V> extends Observer<V> {

    @Override
    default void onChanged(@Nullable V v) {
        Objects.requireNonNull(v);
        onNonNullChanged(v);
        // Alternatively, you could add an if check here.
    }

    void onNonNullChanged(@NonNull V value);
}
niknetniko
  • 133
  • 3
  • 9
  • 2
    Yea, it looks like the only thing we can do for now is to use workarounds. Thanks for describing the most common approaches! – Igor Bubelov Oct 24 '17 at 04:00
3
fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, observer: (t: T) -> Unit) {
    this.observe(owner, Observer {
        it?.let(observer)
    })
}
metis
  • 1,024
  • 2
  • 10
  • 26
  • 2
    hie! metis! wouldn't it be great that you add some text to the answer! the answer seems legit, but a little more English explanation will bring more audience to find it helpful. – Rizwan Atta Nov 21 '19 at 06:56
0

You would have to do some additional work to handle null values that come from the library itself.

For example, when you return a LiveData from a @Dao in Room, like:

@Dao interface UserDao {

    @get:Query("SELECT * FROM users LIMIT 1")
    val user: LiveData<User>

}

And observe the user live data, it will call the onChanged callback with a null value if there is no user.

arekolek
  • 9,128
  • 3
  • 58
  • 79
0

To convert a nullable LiveData to a non-nullable LiveData, use Transformation.
Example on Kotlin:

val eventsList: LiveData<List<Event>> = Transformations.map(dao.getEvents()) { list ->
            list ?: emptyList()
}

dao.getEvents() return data with type List<Event>?, then Transformations return empty List<Event>! if list is null.

Falchio
  • 174
  • 1
  • 14