Search code examples
javaandroid-room

Why annotationProcessor "androidx.room:room-compiler:$room_version" is ever required?


Previous, I am building my app using

def room_version = '2.5.0'
implementation "androidx.room:room-runtime:$room_version"
annotationProcessor "androidx.room:room-compiler:$room_version"

Today, when I remove

annotationProcessor "androidx.room:room-compiler:$room_version"

The app is still fully build-able. My app does use @Dao, @Entity, ...

May I know, why is it so? I thought the reason we are using annotationProcessor, because we want to support @Dao, @Entity, ...?


Solution

  • The Room compiler annotation processor generates java code from the respective annotations (descried below). The generated code is only then invoked at run time. Hence the lack of the code does not result in a compile time issue (but a run time issue/exception).

    Primarily it generates code for each @Database annotated class and for each @Dao annotated interface or abstract class.

    • The generated code is placed in the java (generated) folder (visible from the Android view of the project explorer).

      • for each @Database abstract class there will be a class that is the same name as the @Database annotated class but suffixed with _Impl.

        • It is the entities parameter of the @Database annotation that defines the @Entity annotated classes that are then deemed to be the tables that Room will use.
      • for each @Dao annotated abstract class or interface (typically the latter) there will be a a class that is the same name as the @Dao annotated class/interface but suffixed with _Impl

    Without the annotation processer for the compiler (aka removing the line), then there will be no complaint to compile time as effectively you are saying that you are not using Room.

    However, if you then try to run the App (if it attempts to use Room), you will then get a run time exception stating that the @Database class suffixed with _Impl does not exist, even though there is no compile time error.

    Demonstration

    A project consists of an abstract class TheDatabase annotated with @Database as per:-

    @Database(entities = {ArtistOriginal.class,ArtistOther1.class,ArtistOther2.class, ArtistOther3.class},version = 1,exportSchema = false)
    
    • i.e 4 @Entity annotated classes.

    Within the TheDatabase is also the line:-

    abstract AllDAOs getAllDAOs();
    
    • i.e. a single (all encompassing @Dao annotated interface/abstract class (the latter in this demo)) it including

    :-

    @Dao
    abstract class AllDAOs {
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        abstract long insert(ArtistOriginal artistOriginal);
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        abstract long insert(ArtistOther1 artistOther1);
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        abstract long insert(ArtistOther2 artistOther2);
    }
    
    • pretty simple, just the ability to insert into each of the 4 tables.

    If the build gradle (module) excludes the room compiler as per:-

    dependencies {
    
        implementation 'androidx.appcompat:appcompat:1.6.1'
        implementation 'com.google.android.material:material:1.11.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
        implementation 'androidx.room:room-runtime:2.6.1'
        testImplementation 'junit:junit:4.13.2'
        androidTestImplementation 'androidx.test.ext:junit:1.1.5'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
        /* annotationProcessor 'androidx.room:room-compiler:2.6.1' */
    }
    

    Activity Code for MainActivity is

    :-

    public class MainActivity extends AppCompatActivity {
    
        TheDatabase db;
        AllDAOs dao;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            db = TheDatabase.getInstance(this);
            dao = db.getAllDAOs();
    
            SupportSQLiteDatabase sdb = db.getOpenHelper().getWritableDatabase();
            /* Ignored as no result will be returned */
            Cursor cursor = sdb.query("INSERT INTO ArtistOriginal (name) VALUES ('Candido_via_query');");
            DatabaseUtils.dumpCursor(cursor);
            cursor.close();
            logDataInDatabase(sdb,"QRYINS");
            /* artist_id will be generated (1??? for the first run) */
            sdb.execSQL("INSERT INTO ArtistOriginal (name) VALUES('Candido_via_execsql');");
            logDataInDatabase(sdb,"EXEINS");
            /* In this case the artist_id is 0 as int cannot be null */
            ArtistOriginal ao = new ArtistOriginal();
            ao.Name = "Candidi_via_@Insert";
            long aiid = dao.insert(ao);
            logDataInDatabase(sdb,"DAOINS");
            /* In this case the artist_id is 0 as int cannot be null */
            ArtistOther1 ao1 = new ArtistOther1();
            ao1.Name = "Candido_via_@Insert";
            long ao1id = dao.insert(ao1);
            /* In this case the artist_id member is null as it is an Integer Object */
            ArtistOther2 ao2 = new ArtistOther2();
            ao2.Name = "Candido_via_@Insert";
            long ao2id = dao.insert(ao2);
    
    
            Cursor csr = db.query("" +
                    "SELECT 'ArtistOriginal',* FROM ArtistOriginal " +
                    "UNION ALL SELECT 'ArtistOther1',* FROM ArtistOther1 " +
                    "UNION ALL SELECT 'ArtistOther2',* FROM ArtistOther2",
                    null
            );
            DatabaseUtils.dumpCursor(csr);
            csr.close();
        }
    
        void logDataInDatabase(SupportSQLiteDatabase sdb,String tag_suffix) {
            Log.d("CSRDUMP_" + tag_suffix,"Dumping Cursor");
            Cursor csr = db.query("" +
                            "SELECT 'ArtistOriginal' AS t,* FROM ArtistOriginal " +
                            "UNION ALL SELECT 'ArtistOther1',* FROM ArtistOther1 " +
                            "UNION ALL SELECT 'ArtistOther2',* FROM ArtistOther2",
                    null);
            DatabaseUtils.dumpCursor(csr);
            csr.close();
        }
    }
    
    • code used is from another answer (and hence the apparent complexity/untypical use)

    Then there is no issue compiling the project:-

    BUILD SUCCESSFUL in 3s
    31 actionable tasks: 31 executed
    

    The Android View shows:-

    enter image description here

    • i.e. there is no java code generated for Room (just for the build)

    However if an attempt is made to run the App then:-

    2024-03-18 09:54:04.872 30483-30483/? E/AndroidRuntime: FATAL EXCEPTION: main
        Process: a.a.so77759888javaroomsimpleextratable, PID: 30483
        java.lang.RuntimeException: Unable to start activity ComponentInfo{a.a.so77759888javaroomsimpleextratable/a.a.so77759888javaroomsimpleextratable.MainActivity}: java.lang.RuntimeException: Cannot find implementation for a.a.so77759888javaroomsimpleextratable.TheDatabase. TheDatabase_Impl does not exist
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2913)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
            at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
            at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
            at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
            at android.os.Handler.dispatchMessage(Handler.java:106)
            at android.os.Looper.loop(Looper.java:193)
            at android.app.ActivityThread.main(ActivityThread.java:6669)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
         Caused by: java.lang.RuntimeException: Cannot find implementation for a.a.so77759888javaroomsimpleextratable.TheDatabase. TheDatabase_Impl does not exist
            at androidx.room.Room.getGeneratedImplementation(Room.kt:58)
            at androidx.room.RoomDatabase$Builder.build(RoomDatabase.kt:1351)
            at a.a.so77759888javaroomsimpleextratable.TheDatabase.getInstance(TheDatabase.java:19)
            at a.a.so77759888javaroomsimpleextratable.MainActivity.onCreate(MainActivity.java:21)
    
    • i.e. The expected TheDatabase_Impl does not exist.

    Now with the Room compiler annotation processor

    :-

    dependencies {
    
        implementation 'androidx.appcompat:appcompat:1.6.1'
        implementation 'com.google.android.material:material:1.11.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
        implementation 'androidx.room:room-runtime:2.6.1'
        testImplementation 'junit:junit:4.13.2'
        androidTestImplementation 'androidx.test.ext:junit:1.1.5'
        androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1'
        annotationProcessor 'androidx.room:room-compiler:2.6.1'
    }
    

    The build works as per :-

    BUILD SUCCESSFUL in 3s
    31 actionable tasks: 11 executed, 20 up-to-date
    

    Android View :-

    enter image description here

    • i.e. the expected java code has been generated

    If an attempt is made to run the App then:-

    The app runs without an exception. The Log includes some expected output:-

    2024-03-18 10:01:34.082 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@8c94956
    2024-03-18 10:01:34.082 I/System.out: <<<<<
    2024-03-18 10:01:34.082 D/CSRDUMP_QRYINS: Dumping Cursor
    2024-03-18 10:01:34.084 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@8f4e6c4
    2024-03-18 10:01:34.086 I/System.out: 0 {
    2024-03-18 10:01:34.086 I/System.out:    t=ArtistOriginal
    2024-03-18 10:01:34.086 I/System.out:    Artist_id=1
    2024-03-18 10:01:34.086 I/System.out:    Name=Candido_via_query
    2024-03-18 10:01:34.086 I/System.out: }
    2024-03-18 10:01:34.086 I/System.out: <<<<<
    2024-03-18 10:01:34.087 D/CSRDUMP_EXEINS: Dumping Cursor
    2024-03-18 10:01:34.087 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@e2e29ad
    2024-03-18 10:01:34.088 I/System.out: 0 {
    2024-03-18 10:01:34.088 I/System.out:    t=ArtistOriginal
    2024-03-18 10:01:34.088 I/System.out:    Artist_id=1
    2024-03-18 10:01:34.088 I/System.out:    Name=Candido_via_query
    ....
    

    App Inspection also confirms that the database exists and contains data e.g. :-

    enter image description here

    If the Room compiler annotation processor is removed, then again the code will compile but as the code is not generated (the old code being removed):-

    enter image description here

    • note unsure if options could be set to retain the previously generated code