1

I am looking for help to better understand LiveData in Android.

I have created an application that allows a user to view, edit or create entries in a db.

I have used a viewmodel on the form activity to share information between the activity and fragments and to also make the data Lifecycle aware.

The code works great when I load a entry from the database. Any changes are captured and saved back to the database

However when I want to create a new entry using the same code I get Caused by: java.lang.NullPointerException: Attempt to invoke virtual method ...null object reference when I try to update the object in the viewmodel. Sample below tries to set the Name value when a user enters a new one:

public void afterTextChanged(Editable s) {
    patternViewModel.getPattern().getValue().setName(s.toString());
    ((FlyPatternDetailActivity)getActivity()).setHasChanged(true);

    ((FlyPatternDetailActivity)getActivity()).setActionBar(s.toString());
}

My question is how to update the livedata object of my entity if I dont get an object back from my database and I am going to create a new entry.

My viewmodel is:

public class PatternViewModel extends AndroidViewModel {

    private PatternRepository repo;

    private LiveData<FlyPattern> mObservablePattern;


    public PatternViewModel(@NonNull Application application) {
        super(application);
        if (mObservablePattern == null) {
            mObservablePattern = new MutableLiveData<>();
        }
        repo = PatternRepository.getInstance(((FlyTyingJournalApplicationClass) application).getDatabase());

    }


    public void loadPattern(final long id){
        if(id != -1)
            mObservablePattern = repo.getPattern(id);

    }
    public LiveData<FlyPattern> getPattern(){

        return mObservablePattern;
    }


    public void insertPattern() {
        repo.insertPattern(mObservablePattern.getValue());
    }

    public void updateFlyPattern(){
        repo.updatePattern(mObservablePattern.getValue());
    }

    public void deleteFlyPattern(){

        repo.deletePattern(mObservablePattern.getValue());
    }
}

I understand why I am getting the nullpointException.

My question is how to instantiate my mObservablePattern so I can update it.

It gets instantiated if the loadPattern returns an object.

If the loaddata does not run or does return an object from the database then I cannot update mObservablePattern.

Stacktrace from the Error:

 Process: com.rb.android.flytyingjournal, PID: 4957
  java.lang.NullPointerException: Attempt to invoke virtual method 'void com.rb.android.flytyingjournal.entities.FlyPattern.setType(java.lang.String)' on a null object reference
      at com.rb.android.flytyingjournal.flypatterndetails.PatternDetailsFragment$5.onItemSelected(PatternDetailsFragment.java:325)
      at android.widget.AdapterView.fireOnSelected(AdapterView.java:931)
      at android.widget.AdapterView.dispatchOnItemSelected(AdapterView.java:920)
      at android.widget.AdapterView.-wrap1(AdapterView.java)
      at android.widget.AdapterView$SelectionNotifier.run(AdapterView.java:890)
      at android.os.Handler.handleCallback(Handler.java:751)
      at android.os.Handler.dispatchMessage(Handler.java:95)
      at android.os.Looper.loop(Looper.java:154)
      at android.app.ActivityThread.main(ActivityThread.java:6119)
      at java.lang.reflect.Method.invoke(Native Method)
      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
Sufian
  • 6,405
  • 16
  • 66
  • 120
Arron Varga
  • 23
  • 1
  • 5
  • Include full stack trace for `NullPointerException` – John O'Reilly Jan 22 '18 at 15:47
  • 1
    Another duplicate of [What is a NullPointerException, and how do I fix it?](https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it) – Zoe Jan 22 '18 at 15:47
  • No not a duplicate. I understand that i am getting an NPE. My question is how to instantiate a Livedata object if I dont get an entry from the database co i can then create a new entry using the same viewmodel. – Arron Varga Jan 22 '18 at 16:15

1 Answers1

1

You are trying to call setName() on a NULL. Because patternViewModel.getPattern().getValue() returns the value that is held in the LiveData, which might be NULL in your case. You can add a null check:

 if (patternViewModel.getPattern().getValue() == null) {
    FlyPattern flyPattern = new FlyPattern();
    flyPattern.setName("foo");
    patternViewModel.getPattern().setValue(flyPattern);
 } else {
    patternViewModel.getPattern().getValue().setName("foo");
 }

Or you can create a function in the ViewModel called e.g. setFlyPatternName() and use it to update your DB.
In your PatternViewModel.java

public void setFlyPatternName(String name) {
    if (mObservablePattern.getValue == null) {
        FlyPattern flyPattern = new FlyPattern();
        flyPattern.setName(name);
        mObservablePattern.setValue(flyPattern);
        repo.insertFlyPattern();
    } else {
        mObservablePattern.getValue().setName(name);
        repo.updateFlyPattern();
    }
}

Edit: The proper way of doing this is actually a bit different. Normally your repository functions need to work on the background thread, since you are dealing with i/o, but if you want them to work on the mainThread you need to at least create a callback and pass that callback object to your repository function, which will be called when the data is inserted/updated/deleted. And when the callback function is called you need to call the setValue() on the LiveData and set the data to your livedata object.

Create a callback interface:

public interface InsertCallback {
    void onDataInserted(FlyPattern flyPattern);

    void onDataInsertFailed();
}

Change your repositories insert function's body to accept the Callback object as parameter

public void insertFlyPattern(FlyPattern flyPattern, InsertCallback insertCallback) {
    // Do your insertion and if it is successful call insertCallback.onDataInserted(flyPattern), otherwise call insertCallback.onDataInsertFailed(); 
}

In your ViewModel implement InsertCallback and it's method

public class PatternViewModel extends AndroidViewModel implements InsertCallback {
//....

public void onDataInserted(FlyPattern flyPattern) {
    mObservablePattern.setValue(flyPattern);         
}

public void onDataInsertFailed() { 
    //Show error message
} 

}
insa_c
  • 2,851
  • 1
  • 16
  • 11
  • Does not fix the issue as setValue is protected and cannot be used with livedata. – Arron Varga Jan 22 '18 at 17:48
  • Use `MutableLiveData` instead – insa_c Jan 22 '18 at 17:56
  • I did that which fixes my new entry issue. But now my data does not load into the form. – Arron Varga Jan 22 '18 at 18:04
  • It is because you need to call the `setValue()` otherwise observers of the `LiveData` won't get called. I am going to edit my post and add the proper way of doing it. – insa_c Jan 22 '18 at 18:09
  • How would I do the callback when loading the data? So load data if entry found assign to MutableLiveData – Arron Varga Jan 22 '18 at 19:58
  • How would I do the callback when loading the data? So load data if entry found assign to MutableLiveData. It seems i can either get my code to work when there is an entry to load - with livedata or when its a new entry mutablelivedata. I think the mutablelivedata is what I need and with it I can create new entries and save data no problem. What I can get to work is loading data from my repo/room datasource into the MutableLiveData – Arron Varga Jan 22 '18 at 20:03