CorMatt
CorMatt

Reputation: 97

How to fetch a specific data field using Room & LiveData?

I am new to android AND Room Persistence library and I am saving my data in a Entity called SchoolEntry which has plenty of rows, one being the PrimaryKey cadID (an int field). I need to fetch the cadID from the last entry that the user just typed.

For that matter, I'm trying to obtain that using the following logic:

ViewModelEntry recentEntry = new ViewModelEntry(RegisterActivity.this.getApplication());
int lastCadID = recentEntry.getLastEntry().getValue().getCadID();

However, when the program reaches this point, it throws a NullPointerException exception:

java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.Integer com.xxxx.xxxx.entities.SchoolEntry.getCadID()' on a null object

This is how I implemented the getLastEntry() in my ViewModel (ViewModelEntry):

repository = new ReportRepository(application);
public LiveData<SchoolEntry> getLastEntry() {return repository.getLastEntry(); }

This is how I implemented the getLastEntry() in my Repository (ReportRepository):

ReportDatabase db = ReportDatabase.getDatabase(application);
schoolEntryDao = db.schoolEntryDao();
public LiveData<SchoolEntry> getLastEntry() { return schoolEntryDao.getLastEntry(); }

And, finally, this is how I implemented the getLastEntry() in my DAO (SchoolEntryDao):

    @Query("SELECT * FROM SchoolEntry WHERE cadID = (SELECT MAX(cadID) from SchoolEntry)")
    LiveData<SchoolEntry> getLastEntry();

So I have two questions: Why is this reference pointing to a null object? If i use recentEntry.getLastEntry().getValue(), and knowing that getValue will give me an SchoolEntry object (one where I created a getCadID method to obtain the Id of that entry) why is it saying it is null?

And, the most important one: How to obtain this info? Because, clearly, The way I am doing things now is not working.

Thank you for the help!

Upvotes: 0

Views: 935

Answers (2)

cd1
cd1

Reputation: 16534

When you call a Room Dao function which returns a LiveData (i.e. getLastEntry), that function will return immediately with the LiveData object, not necessarily with your content (ie. SchoolEntry). In some moment after that function returns (usually very quickly), Room will populate that LiveData object with the result of your query.

Why is this reference pointing to a null object?

Because you queried the value of that LiveData object right after it was created and you haven't given enough time for Room to fill it with the result. This is the expected behavior for a Room method returning a LiveData. You could, for example, call Thread.sleep for a few arbitrary milliseconds and your result should be there. But please don't do that! There are better ways to solve this.

How to obtain this info?

If you want to stick with the LiveData, you need to observe it and get the result by using a listener:

LiveData<SchoolEntry> lastEntryLiveData = recentEntry.getLastEntry();

// "this" is used here assuming this class is an Activity, as I inferred from your code above
lastEntryLiveData.observe(this, new Observer<SchoolEntry>() {
    @Override
    public void onChanged(SchoolEntry result) {
        int lastCadID = result.getCadID();

        // you can use lastCadID here, and this method may be called
        // multiple times whenever your database changes and the query
        // "SELECT * FROM SchoolEntry WHERE cadID = (SELECT MAX(cadID) from SchoolEntry)"
        // would return a new result
    }
});

This is asynchronous, as all (or most) LiveData accesses should be.

If you want to get the SchoolEntry result of your query only once and synchronously, you need to declare the Dao method as:

@Query("SELECT * FROM SchoolEntry WHERE cadID = (SELECT MAX(cadID) from SchoolEntry)")
SchoolEntry getLastEntry();

(notice the lack of LiveData in the return type)

Then you can call this method directly and it should have your result synchronously. However, that method cannot be run on the main thread, and you need to create a background thread for that. That's quite easy to solve in Kotlin (by using coroutines) and a bit more complicated in plain Java. Take a look at this link for more information about that.

Upvotes: 1

Eric.1
Eric.1

Reputation: 71

I'm not sure if this is the real or correct reason it happens, but "getValue()" is nullable and unless stated otherwise database calls don't run on the main thread to prevent locks. So I assume the getValue function gets called before the object is loaded.

Anyways, LiveData data should afaik be accessed via an Observer. And therein lies your solution.

recentEntry.getLastEntry().observe(this, lastEntry ->
    {
        // do something with lastEntry.getCatId();
    });

Or perhaps the older notation-form is a bit more clear;

recentEntry.getLastEntry().observe(this, new Observer<SchoolEntry>() {
        @Override
        public void onChanged(SchoolEntry lastEntry) {
            //do something with lastEntry.getCatId()
        }
    });

Upvotes: 1

Related Questions