Search code examples
android-roomnotnull

How do I updated an Android Room column from notNull=true to notNull=false?


Problem: With Android Room, it uses a pre-populated database, I cannot seem to get the table columns to change from notNull=true to notNull=false? The pre-populated database schema is correct but I cannot get Android Room to update correctly to match:

What I have done: I edited the json schema file, removing the NOT NULL for the specific columns, and under the fields I updated the same field column information to "notNull": false. I tried a migration, not knowing if it was correct, using ALTER TABLE Notes ADD COLUMN 'QuestionID' INTEGER and it actually updated the json file to NOT NULL again. I can't seem to find information on how to do this? The Entity does not have these annotations and I wasn't sure it was necessary to define these at the Entity as this DB has other tables without these annotations and they are passing through compilation without issue. I'm sure this is another 80/20 rule where I'm stupid and missing something.

Example Table in the json file The Question, Quote, Term and Deleted fields need to be notNull=false and keep changing back to true... and the pre-populated table is correct.

        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`NoteID` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `SourceID` INTEGER NOT NULL, `CommentID` INTEGER NOT NULL, `QuestionID` INTEGER NOT NULL, `QuoteID` INTEGER NOT NULL, `TermID` INTEGER NOT NULL, `TopicID` INTEGER NOT NULL, `Deleted` INTEGER NOT NULL, FOREIGN KEY(`SourceID`) REFERENCES `Source`(`SourceID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`CommentID`) REFERENCES `Comment`(`CommentID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`TopicID`) REFERENCES `Topic`(`TopicID`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
        "fields": [
          {
            "fieldPath": "noteID",
            "columnName": "NoteID",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "sourceID",
            "columnName": "SourceID",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "commentID",
            "columnName": "CommentID",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "questionID",
            "columnName": "QuestionID",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "quoteID",
            "columnName": "QuoteID",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "termID",
            "columnName": "TermID",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "topicID",
            "columnName": "TopicID",
            "affinity": "INTEGER",
            "notNull": true
          },
          {
            "fieldPath": "deleted",
            "columnName": "Deleted",
            "affinity": "INTEGER",
            "notNull": true
          }```

Solution

  • The schema in the json file is generated and based upon the Entity, changing it will make no difference. It isn't even required (except if using AutoMigration).

    The pre-populated database schema is correct but I cannot get Android Room to update correctly to match:

    You have to either change the Entities accordingly or convert the pre-populated database accordingly. Noting again that the Entities define what Room expects.

    The language used matters as to the exact answer.

    With Kotlin then Notes could be:-

    data class Note(
        @PrimaryKey(autoGenerate = true)
        val NoteId: Long,
        val SourceID: Long?,
        val CommentID: Long?,
        val QuestionID: Long?,
        val QuoteID: Long?,
        val TermID: Long, //<<<<< NOT NULL 
        val TopicID: Long?,
        val Deleted: Long?
    )
    

    The generated java then shows the table create as :-

    _db.execSQL("CREATE TABLE IF NOT EXISTS `Note` (`NoteId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `SourceID` INTEGER, `CommentID` INTEGER, `QuestionID` INTEGER, `QuoteID` INTEGER, `TermID` INTEGER NOT NULL, `TopicID` INTEGER, `Deleted` INTEGER, FOREIGN KEY(`SourceID`) REFERENCES `Source`(`SourceID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`CommentID`) REFERENCES `Comment`(`CommentID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`TopicID`) REFERENCES `Topic`(`TopicID`) ON UPDATE NO ACTION ON DELETE NO ACTION )");
    

    i.e. those with Long? do not have NOT NULL (the TermID column has NOT NULL as Long instead of Long? was used).

    With Java the column type cannot be a primitive type for NULLs to be allowed, as these MUST have a value and cannot be null, so Room will derive NOT NULL. Just the object type (e.g. Long not long) will be taken as NULLs allowed. To force NOT NULL then the @NotNull annotation needs to be used.

    So Java equivalent (named JavaNote to allow both to be used/compiled) could be :-

    @Entity(
            foreignKeys = {
                    @ForeignKey(entity = Source.class,parentColumns = {"SourceID"},childColumns = {"SourceID"}),
                    @ForeignKey(entity = Comment.class,parentColumns = {"CommentID"},childColumns = {"CommentID"}),
                    @ForeignKey(entity = Topic.class,parentColumns = {"TopicID"}, childColumns = {"TopicID"})
            }
    )
    class JavaNote {
        @PrimaryKey(autoGenerate = true)
        long NoteID=0; // primitives cannot be NULL thus imply NOT NULL
        Long SourceID;
        Long CommentID;
        Long QuestionID;
        Long QuoteID;
        @NotNull
        Long TermID; // or long TermID
        Long TopicID;
        Long Deleted;
    }
    

    The generated java then has the table create as :-

    _db.execSQL("CREATE TABLE IF NOT EXISTS `JavaNote` (`NoteID` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `SourceID` INTEGER, `CommentID` INTEGER, `QuestionID` INTEGER, `QuoteID` INTEGER, `TermID` INTEGER NOT NULL, `TopicID` INTEGER, `Deleted` INTEGER, FOREIGN KEY(`SourceID`) REFERENCES `Source`(`SourceID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`CommentID`) REFERENCES `Comment`(`CommentID`) ON UPDATE NO ACTION ON DELETE NO ACTION , FOREIGN KEY(`TopicID`) REFERENCES `Topic`(`TopicID`) ON UPDATE NO ACTION ON DELETE NO ACTION )");
    
    • again the TermID has been purposefully coded to use NOT NULL

    The generated java is available after compiling. It is found in the generated java (use Android View) in the member/class name the same as the class annotated with @Database suffixed with _Impl. The statements themselves are in the createAlltables method.

    e.g. :-

    enter image description here