40

Java POJO Object

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

My Query method

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

Accessing DB

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

On the next line I am checking sections.getValue() which is always giving me null although I have data in the DataBase and later I am getting the value in the onChanged() method.

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

    }
});

But when I omit LiveData from the query I am getting the data as expected. Query Method:

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

Accessing DB:

final List<Section> sections = mDb.sectionDAO().getAllSections();
S Haque
  • 6,881
  • 5
  • 29
  • 35
  • 2
    But why you want to use `sections.getValue()`, while livedata are used to observe the data. May be when you are checking in next line, data is not set in livedata that's why it gives you null. In short if you don't want to use `LiveData` then just use it without `LiveData`. – Moinkhan Jun 08 '17 at 08:42
  • 3
    I want to use livedata...if `sections.getValue()` is null I have to call api for data and insert in into the database which will eventually call the `onChange()` method from where I will get the data. But Because of that `null` value if and getting data from both database and api. – S Haque Jun 08 '17 at 12:13
  • 3
    Exact same problem here. I too am checking if there is value in database, and if there is not then I have to call APIte get datat which then saves the data in Database. Still no solution for me. Android MVVM sucks ! – zulkarnain shah Jul 26 '18 at 11:00
  • I'll just add that, it is helpful to know that `onChanged` is also called the first time the data is initialized/ready, so you don't have to do the initial data retrieval yourself. – karuhanga Jul 01 '18 at 02:40

7 Answers7

34

On the next line I am checking sections.getValue() which is always giving me null although I have data in the DataBase and later I am getting the value in the onChanged() method.

This is normal behavior, because queries that return LiveData, are working asynchronously. The value is null at that moment.

So calling this method

LiveData<List<Section>> getAllSections();

you will get the result later here

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

}
});

from documentation:

Room does not allow accessing the database on the main thread unless you called allowMainThreadQueries() on the builder because it might potentially lock the UI for long periods of time. Asynchronous queries (queries that return LiveData or RxJava Flowable) are exempt from this rule since they asynchronously run the query on a background thread when needed.

Ramesh R
  • 7,009
  • 4
  • 25
  • 38
snersesyan
  • 1,647
  • 17
  • 26
19

I solve this problem through this approach

    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;
    }
S Haque
  • 6,881
  • 5
  • 29
  • 35
  • Can you help me? I'm facing the same problem, where should I put this code? – Douglas Fornaro Sep 21 '17 at 02:24
  • Just in case anyone is facing the same issue, the solution lies on the [MediatorLiveData](https://developer.android.com/reference/android/arch/lifecycle/MediatorLiveData.html) which is a sub-class that can observe on other LiveData. – Rodrigo Queiroz Nov 15 '17 at 12:46
  • 11
    From what I can tell, the `MediatorData` is completely unnecessary. you can add an `Observer` directly to the original `LiveData` with `sections.observer()`. – Code-Apprentice Jul 29 '18 at 18:39
  • kudos for removing source after check that the data is not null or empty, not at the start of the method – HendraWD Jul 19 '19 at 04:05
  • Thanks. I called this inside my repository class to update the actual live data. – Rajeev Jayaswal Jun 19 '20 at 19:33
  • I think the trick is that you have to observe the LiveData if you want to access it. I had some LiveData that I was retrieving from the db and later filtering and the filter would always fail because of a null on the original data. Once I placed an observer on the first set of data in my MainActivity BEFORE the filtered list, it worked fine. – DarkOhms Jul 16 '22 at 16:09
4

LiveData is an asynchronous query, you get the LiveData object but it might contain no data. You could use an extra method to wait for the data to be filled and then extract the data.

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];
}
Ramesh R
  • 7,009
  • 4
  • 25
  • 38
Yanbin Hu
  • 537
  • 6
  • 18
3

I resolved the similar issue as follows

Inside your ViewModel class

private LiveData<List<Section>> mSections;

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

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

    return mSections;
}

This is all required. Never change the LiveData's instance.

Napolean
  • 5,303
  • 2
  • 29
  • 35
  • I may be running into a problem because I'm changing the LiveData instance. Why are you not supposed to change the instance? – DarkOhms Jun 17 '22 at 02:56
3

I would suggest creating another query without LiveData if you need to synchronously fetch data from the database in your code.

DAO:

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

ViewModel:

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();
    }
}
Ramesh R
  • 7,009
  • 4
  • 25
  • 38
MrBinWin
  • 1,269
  • 1
  • 16
  • 30
2

if sections.getValue() is null I have to call api for data and insert in into the database

You can handle this at onChange method:

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

But you should better put this Database/Table initialization logic to a repository class. Check out Google's sample. See DatabaseCreator class.

Devrim
  • 15,345
  • 4
  • 66
  • 74
  • I have already seen that example and the problem with this is, every time the application is launched `createDb(Context context)` is getting called though it has data cause initially `isDatabaseCreated()` will always return false. – S Haque Jun 10 '17 at 06:08
2

For anyone that comes across this. If you are calling LiveData.getValue() and you are consistently getting null. It is possible that you forgot to invoke LiveData.observe(). If you forget to do so getValue() will always return null specially with List<> datatypes.

Farzad Khalafi
  • 301
  • 1
  • 8