'Why might Room database IDs all show as 0 in a log/Toast message?

I'm trying to get the id of the last inserted object into a database using Room with Android. I can fetch the last object using an SQL query and can call other methods to get the various properties of that object which the user has set when saving the object. But getId() always returns 0. When I examine the table contents in Android Studio's app inspector, I can clearly see that Room is generating a unique primary key for each row, but I just can't get at it. Can anyone suggest what the problem might be?

Here's the Dao query:

@Query("SELECT * FROM gamebooks_table WHERE gamebookId=gamebookId ORDER BY gamebookId DESC LIMIT 1")
LiveData<Gamebook> getSingleGamebookByID();

And here's the annotated entity class:

@Entity(tableName = "gamebooks_table")

public class Gamebook {   

    @PrimaryKey(autoGenerate = true)
    private long gamebookId;

    private String gamebookName;
    private String gamebookComment;
    private String gamebookPublisher;
    private float gamebookStarRating;

    public Gamebook(String gamebookName, String gamebookComment, String gamebookPublisher, float gamebookStarRating) {
        this.gamebookName = gamebookName;
        this.gamebookComment = gamebookComment;
        this.gamebookPublisher = gamebookPublisher;
        this.gamebookStarRating = gamebookStarRating;
    }

    public long getGamebookId() {
        return gamebookId;
    }

    public String getGamebookName() {
        return gamebookName;
    }

    public String getGamebookComment() {
        return gamebookComment;
    }

    public String getGamebookPublisher() {
        return gamebookPublisher;
    }

    public float getGamebookStarRating(){
        return gamebookStarRating;
    }

    public void setGamebookId(long gamebookId) {
        this.gamebookId = gamebookId;
    }
}

SOLVED Finally sorted this by adding an Observer to my DAO method which returns a single gamebook. Within the Observer's onChanged() method, I can loop through all Gamebooks in the LiveData List (even though there's only one because I'm limiting it to one in the SQL query) and call getId() to get their respective IDs.

            mainViewModel.getSingleGamebook().observe(this, new Observer<List<Gamebook>>() {
        @Override
        public void onChanged(List<Gamebook> gamebooks) {

            int i=0;

            for(Gamebook gamebook : gamebooks){
                gamebookId= gamebook.getGamebookId();
                Log.d(TAG, "Gamebook Name: "+gamebook.getGamebookName()+ " Database ID: " +gamebookId);
                i++;
            }

        }
    });


Solution 1:[1]

I believe that your issue is due to the only constructor being available not setting the id so the LiveData uses the default value of 0 for a long.

I'd suggest having a default constructor and thus all setters/getters and (optionally) using @Ignore annotation for one of the constructors..

  • without @Ignore you get warnings Gamebook.java:8: warning: There are multiple good constructors and Room will pick the no-arg constructor. You can use the @Ignore annotation to eliminate unwanted constructors. public class Gamebook {

e.g. :-

@Entity(tableName = "gamebooks_table")
public class Gamebook {

   @PrimaryKey(autoGenerate = true)
   private long gamebookId;

   private String gamebookName;
   private String gamebookComment;
   private String gamebookPublisher;
   private float gamebookStarRating;
   
   public Gamebook(){} /*<<<<< ADDED */

   @Ignore /*<<<<< ADDED - is not required - could be on the default constructor but not both*/
   public Gamebook(String gamebookName, String gamebookComment, String gamebookPublisher, float gamebookStarRating) {
      this.gamebookName = gamebookName;
      this.gamebookComment = gamebookComment;
      this.gamebookPublisher = gamebookPublisher;
      this.gamebookStarRating = gamebookStarRating;
   }

   public long getGamebookId() {
      return gamebookId;
   }

   public String getGamebookName() {
      return gamebookName;
   }

   public String getGamebookComment() {
      return gamebookComment;
   }

   public String getGamebookPublisher() {
      return gamebookPublisher;
   }

   public float getGamebookStarRating(){
      return gamebookStarRating;
   }

   public void setGamebookId(long gamebookId) {
      this.gamebookId = gamebookId;
   }

   /* ADDED setters */
   public void setGamebookName(String gamebookName) {
      this.gamebookName = gamebookName;
   }

   public void setGamebookComment(String gamebookComment) {
      this.gamebookComment = gamebookComment;
   }

   public void setGamebookPublisher(String gamebookPublisher) {
      this.gamebookPublisher = gamebookPublisher;
   }

   public void setGamebookStarRating(float gamebookStarRating) {
      this.gamebookStarRating = gamebookStarRating;
   }
}

You also probably want to be able to pass the respective id to the getSingleGamebookByID, so you may wish to change this to:-

@Query("SELECT * FROM gamebooks_table WHERE gamebookId=:gamebookId /*<<<<< ADDED to use id passed */ ORDER BY gamebookId DESC LIMIT 1")
LiveData<Gamebook> getSingleGamebookByID(long gamebookId /*<<<<< ADDED to use id passed */);
  • you would probably want to remove the comments.

Note the LiveData aspect has not been tested and is conjecture.


Example

This example shows that room is fine with your original code but that the issues is on the LiveData/Viewmodel side :-

public class MainActivity extends AppCompatActivity {

    TheDatabase db;
    GamebookDao dao;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        /* Note The Database has .allowMainThreadQueries */
        db = TheDatabase.getInstance(this);
        dao = db.getGamebookDao();

        long gb1id = dao.insert(new Gamebook("Gamebook1","blah","Gamebook1 Publisher", 10.1F));
        long gb2id = dao.insert(new Gamebook("Gamebook2","blah","Gamebook2 Publisher", 6.1F));
        long gb3id = dao.insert(new Gamebook("Gamebook3","blah","Gamebook3 Publisher", 10.1F));

        logGameBook(dao.getSingleGamebookByID());
        logGameBook(dao.getSingleGamebookByID());
        logGameBook(dao.getSingleGamebookByID());

        /* Alternative that allows the ID to be specified */
        logGameBook(dao.getSingleGamebookByIDAlternative(gb1id));
        logGameBook(dao.getSingleGamebookByIDAlternative(gb2id));
        logGameBook(dao.getSingleGamebookByIDAlternative(gb3id));
    }

    void logGameBook(Gamebook gb) {
        Log.d("GAMEBOOKINFO","Gamebook is " + gb.getGamebookName() + " id is " + gb.getGamebookId());
    }
}
  • The above uses your original code, the TheDatabase is a basic @Database annotated class BUT with .allowMainThreadQueries so it is run on the main thread.

The log, after running, includes:-

2022-03-12 08:16:12.556 D/GAMEBOOKINFO: Gamebook is Gamebook3 id is 3
2022-03-12 08:16:12.558 I/chatty: uid=10132(a.a.so71429144javaroomidreturnedaszero) identical 1 line
2022-03-12 08:16:12.561 D/GAMEBOOKINFO: Gamebook is Gamebook3 id is 3

2022-03-12 08:16:12.568 D/GAMEBOOKINFO: Gamebook is Gamebook1 id is 1
2022-03-12 08:16:12.572 D/GAMEBOOKINFO: Gamebook is Gamebook2 id is 2
2022-03-12 08:16:12.574 D/GAMEBOOKINFO: Gamebook is Gamebook3 id is 3
  • Note how the first just returns the same object and thus id.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1