LiveData.getValue()与Room一起使用时返回null

40

Java简单对象

public class Section {

    @ColumnInfo(name="section_id")
    public int mSectionId;

    @ColumnInfo(name="section_name")
    public String mSectionName;

    public int getSectionId() {
        return mSectionId;
    }

    public void setSectionId(int mSectionId) {
        this.mSectionId = mSectionId;
    }

    public String getSectionName() {
        return mSectionName;
    }

    public void setSectionName(String mSectionName) {
        this.mSectionName = mSectionName;
    }
}

我的查询方法

@Query("SELECT * FROM section")
LiveData<List<Section>> getAllSections();

访问数据库

final LiveData<List<Section>> sections = mDb.sectionDAO().getAllSections();

在下一行中,我正在检查sections.getValue(),尽管数据库中有数据,但它始终返回null,稍后我会在onChanged()方法中获取该值。

sections.observe(this, new Observer<List<Section>>() {
    @Override
    public void onChanged(@Nullable List<Section> sections){

    }
});

但是当我从查询中省略LiveData时,我得到了预期的数据。

查询方法:

@Query("SELECT * FROM section")
List<Section> getAllSections();

访问数据库:

final List<Section> sections = mDb.sectionDAO().getAllSections();

2
但是为什么你想要使用 sections.getValue() 呢?LiveData 的作用是用来观察数据的。也许当你在下一行检查数据时,数据还没有被设置到 LiveData 中,这就是为什么它会返回 null 的原因。 简而言之,如果你不想使用 LiveData,那么就直接使用它而不需要 LiveData。 - Moinkhan
3
我想使用LiveData... 如果sections.getValue()为空,我必须调用API获取数据并将其插入到数据库中,最终会调用onChange()方法,从中获取数据。但是由于这个null值,我同时从数据库和API获取数据。 - S Haque
我想补充一下,了解 onChanged 在数据初始化/准备就绪时也会被调用,这样你就不必自己进行初始数据检索了。 - Karuhanga
3
我也遇到了完全相同的问题。我正在检查数据库中是否有价值,如果没有,则必须调用API来获取数据并将其保存在数据库中。对我来说仍然没有解决方案。Android MVVM太糟糕了! - zulkarnain shah
7个回答

34

在下一行,我正在检查sections.getValue(),但它始终为null,尽管我有数据库中的数据,稍后我会在onChanged()方法中获取值。

这是正常行为,因为返回LiveData的查询是异步工作的。此时该值为null。

因此调用此方法是

LiveData<List<Section>> getAllSections();

您稍后将在此处获得结果

sections.observe(this, new Observer<List<Section>>() {
@Override
public void onChanged(@Nullable List<Section> sections){

}
});

来自文档:

如果您没有在构建器上调用allowMainThreadQueries(),则Room不允许在主线程上访问数据库,因为这可能会锁定UI很长时间。异步查询(返回LiveData或RxJava Flowable的查询)不受此规则限制,因为它们会在需要时在后台线程上异步运行查询。


19

我通过这种方法解决了这个问题

    private MediatorLiveData<List<Section>> mSectionLive = new MediatorLiveData<>();
    .
    .
    .

    @Override
    public LiveData<List<Section>> getAllSections() {
        final LiveData<List<Section>> sections = mDb.sectionDAO().getAllSections();

        mSectionLive.addSource(sections, new Observer<List<Section>>() {
            @Override
            public void onChanged(@Nullable List<Section> sectionList) {
               if(sectionList == null || sectionList.isEmpty()) {
                  // Fetch data from API
               }else{
                  mSectionLive.removeSource(sections);
                  mSectionLive.setValue(sectionList);
               }
            }
        });
        return mSectionLive;
    }

你能帮我吗?我面临相同的问题,请问我应该把这段代码放在哪里? - Douglas Fornaro
如果有人遇到同样的问题,解决方案在于MediatorLiveData,它是一个可以观察其他LiveData的子类。 - Rodrigo Queiroz
11
据我所知,“MediatorData”是完全不必要的。您可以使用“sections.observer()”直接向原始“LiveData”添加一个“Observer”。 - Code-Apprentice
在检查数据不为 null 或空之后再删除源代码,而不是在方法开头进行操作,这样做非常棒。 - HendraWD
谢谢。我在我的存储库类中调用了这个来更新实际的实时数据。 - Rajeev Jayaswal
我认为诀窍在于如果你想要访问LiveData,你必须观察它。我有一些从数据库中检索的LiveData,稍后进行了过滤,但由于原始数据上的空值,过滤器总是失败。一旦我在MainActivity中的第一组数据上放置了一个观察者,在过滤列表之前,它就可以正常工作了。 - DarkOhms

4

LiveData是一种异步查询,你可以获取LiveData对象,但它可能不包含数据。你可以使用额外的方法等待数据被填充,然后提取数据。

public static <T> T getValue(LiveData<T> liveData) throws InterruptedException {
    final Object[] objects = new Object[1];
    final CountDownLatch latch = new CountDownLatch(1);

    Observer observer = new Observer() {
        @Override
        public void onChanged(@Nullable Object o) {
            objects[0] = o;
            latch.countDown();
            liveData.removeObserver(this);
        }
    };
    liveData.observeForever(observer);
    latch.await(2, TimeUnit.SECONDS);
    return (T) objects[0];
}

你能解释一下这里到底发生了什么吗?是等待了2秒钟吗? - ir2pid
1
当前线程最多等待2秒,直到闩锁计数归零。 - Yanbin Hu

3
我解决了类似的问题,方法如下:
在你的ViewModel类中:
private LiveData<List<Section>> mSections;

@Override
public LiveData<List<Section>> getAllSections() {

    if (mSections == null) {
        mSections = mDb.sectionDAO().getAllSections();
    }

    return mSections;
}

这是必须的。永远不要更改LiveData的实例。


我可能会遇到问题,因为我正在更改LiveData实例。为什么不应该更改实例? - DarkOhms

3

如果你需要在代码中同步获取来自数据库的数据,我建议创建另一个没有LiveData的查询。

DAO:

@Query("SELECT COUNT(*) FROM section")
int countAllSections();

视图模型:

Integer countAllSections() {
    return new CountAllSectionsTask().execute().get();
}

private static class CountAllSectionsTask extends AsyncTask<Void, Void, Integer> {

    @Override
    protected Integer doInBackground(Void... notes) {
        return mDb.sectionDAO().countAllSections();
    }
}

2

如果sections.getValue()为空,我需要调用API获取数据并将其插入数据库

您可以在onChange方法中处理此操作:

sections.observe(this, new Observer<List<Section>>() {
    @Override
    public void onChanged(@Nullable List<Section> sections){
         if(sections == null || sections.size() == 0) {
             // No data in your database, call your api for data
         } else {
             // One or more items retrieved, no need to call your api for data.
         }
    }
});

但你最好将这个数据库/表初始化逻辑放到一个存储库类中。查看Google的示例。请查看DatabaseCreator类。


我已经看过了这个例子,问题在于每次启动应用程序时都会调用createDb(Context context),尽管它已经有数据,因为最初isDatabaseCreated()总是返回false。 - S Haque

2

对于任何遇到这个问题的人。如果你调用LiveData.getValue()并且一直得到null。可能是你忘记调用LiveData.observe()了。如果你忘记这样做,getValue()将始终返回null,特别是对于List<>数据类型。


但是当我们使用Jetpack数据绑定时,观察者不是在Jetpack中吗? - alpyapple
这是两种不同的机制。请提供任何支持getValue()需要observe()的来源。 - JonZarate

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接