【问题标题】:Is it possible to enforce non-nullability of LiveData values?是否可以强制 LiveData 值不可为空?
【发布时间】:2017-09-29 12:03:23
【问题描述】:

有没有办法强制 LiveData 值不可为空?默认观察者实现似乎有 @Nullable 注释,它强制 IDE 建议该值可能为 null 并应手动检查:

public interface Observer<T> {
    /**
     * Called when the data is changed.
     * @param t  The new data
     */
    void onChanged(@Nullable T t);
}

【问题讨论】:

    标签: android android-architecture-components android-livedata


    【解决方案1】:

    如果您使用 Kotlin,您可以使用扩展创建更好的非空观察函数。有一篇关于它的文章。 https://medium.com/@henrytao/nonnull-livedata-with-kotlin-extension-26963ffd0333

    【讨论】:

    • 你有那个的java版本吗?
    • 嗯。我可以添加 Java 版本,但你不会像 Kotlin 那样有好看的用法,只是因为 Java 没有扩展功能。
    【解决方案2】:

    只有当您控制设置数据的代码时才能安全地执行此操作,因为您还必须包装 LiveData 类。这样数据设置方法将受到@NonNull的保护,您可以确定在到达Observer之前已经检查过数据。

    包装LiveData 类:

    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());
        }
    }
    

    创建一个暴露为不可变的接口:

    public interface NonNullLiveData<T> {
    
        @NonNull T getValue();
    
        void observe(@NonNull LifecycleOwner owner, @NonNull NonNullObserver<T> observer);
    }
    

    最后,包装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);
            }
        }
    }
    

    现在您可以像这样创建数据:

    class DataSource {
        private NonNullMutableLiveData<Integer> data = new NonNullMutableLiveData<>(0);
    
        public NonNullLiveData<Integer> getData() {
            return data;
        }
    }
    

    并像这样使用它:

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

    完全null安全。

    【讨论】:

      【解决方案3】:

      如果您使用Kotlin,则可以使用新选项。您可以将LiveData 替换为StateFlow。它更适合Kotlin 代码并提供内置的空安全性。

      而不是使用:

      class MyViewModel {
          val data: LiveData<String> = MutableLiveData(null) // the compiler will allow null here!
      }
      
      class MyFragment: Fragment() {
          model.data.observe(viewLifecycleOwner) {
              // ...
          }
      }
      

      你可以使用:

      class MyViewModel {
          val data: StateFlow<String> = MutableStateFlow(null) // compilation error!
      }
      
      class MyFragment: Fragment() {
          lifecycleScope.launch {
              model.data.collect {
                // ...
              }
          }
      }
      

      StateFlowcoroutines 的一部分,要使用lifecycleScope,您需要添加lifecycle-extensions 依赖项:

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

      请注意,此 API 在 coroutines 1.4.0 之前是实验性的。

      Here's 一些关于将LiveData 替换为StateFlow 的额外阅读。

      正如Igor Bubelov 所指出的,这种方法的另一个优点是它不是Android 特定的,因此可以在多平台项目的共享代码中使用。

      【讨论】:

      • 这是我最近一直在使用的,我也喜欢它。加分项:协程(和流)不是特定于 Android 的,这允许我们在后端/多平台移动项目中使用相同的工具。它易于使用,更通用,并且没有性能或便利性损失,因此它似乎是一个更好的选择。
      • 你说得对。这也是我使用它的原因之一。我会在答案中添加注释。
      【解决方案4】:

      虽然您可以做一些事情,但您有责任确保不会将null 传递给LiveData。除此之外,每个“解决方案”更多的是对警告的抑制,这可能很危险(如果您确实获得了 null 值,您可能无法处理它,Android Studio 也不会警告您)。

      断言

      您可以添加assert t != null;。断言不会在 Android 上执行,但 Android Studio 可以理解。

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

      抑制警告

      添加注释以抑制警告。

      class PrintObserver implements Observer<Integer> {
      
          @Override
          @SuppressWarnings("ConstantConditions")
          public void onChanged(@Nullable Integer integer) {
              Log.d("Example", integer.toString());
          }
      }
      

      删除注释

      这也适用于我安装的 Android Studio,但它可能不适合您,但您可以尝试从实现中删除 @Nullable 注释:

      class PrintObserver implements Observer<Integer> {
      
          @Override
          public void onChanged(Integer integer) {
              Log.d("Example", integer.toString());
          }
      }
      

      默认方法

      您不太可能在 Android 上使用它,但纯粹从 Java 的角度来看,您可以定义一个新接口并在默认方法中添加一个 null 检查:

      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);
      }
      

      【讨论】:

      • 是的,看起来我们现在唯一能做的就是使用变通方法。感谢您描述最常见的方法!
      【解决方案5】:
      fun <T> LiveData<T>.observeNonNull(owner: LifecycleOwner, observer: (t: T) -> Unit) {
          this.observe(owner, Observer {
              it?.let(observer)
          })
      }
      

      【讨论】:

      • 嗨!梅蒂斯!在答案中添加一些文字不是很好吗!答案似乎是合法的,但多一点英文解释会让更多的观众觉得它有帮助。
      【解决方案6】:

      您必须做一些额外的工作来处理来自库本身的空值。

      例如,当您从 Room 中的 @Dao 返回 LiveData 时,例如:

      @Dao interface UserDao {
      
          @get:Query("SELECT * FROM users LIMIT 1")
          val user: LiveData<User>
      
      }
      

      并观察user 实时数据,如果没有用户,它将使用null 值调用onChanged 回调。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-01-16
        • 1970-01-01
        • 2012-07-30
        • 2011-03-20
        • 1970-01-01
        • 2021-10-06
        • 2011-06-17
        • 2022-12-31
        相关资源
        最近更新 更多