Search code examples
androidandroid-sqliteandroid-room

What's the initial migration, setting up Room (SQLite) in an existing app?


I'm working on setting up a small SQLite database in my app, which so far has been storing data in the OS MediaStore. I want to migrate away from that, and use Room.

The initial table is very simple, basically just a TEXT field for a description, and some IDs to the actual image in the MediaStore. However, I want to set up a couple of other tables, too. Does this look somewhat sensible:

import android.provider.BaseColumns;
import android.provider.MediaStore;

public class AlbumContract {

    public static class PhotoEntry implements BaseColumns {
        public static final String TABLE_NAME = "photo";
        public static final String COLUMN_NAME_IMAGE_ID = "fk_image_id";
        public static final String COLUMN_NAME_IMAGE_URI = "mediastore_uri";
        public static final String COLUMN_NAME_EVENT_ID = "fk_event_id";
        public static final String COLUMN_NAME_STACK_ID = "fk_stack_id";
        public static final String COLUMN_NAME_DESCRIPTION = "description";
    }

    public static class StackEntry implements BaseColumns {
        public static final String TABLE_NAME = "stack";
        public static final String COLUMN_NAME_STACK_LEADER = "fk_photo_id";
    }

    public static class EventEntry implements BaseColumns {
        public static final String TABLE_NAME = "event";
        public static final String COLUMN_NAME_TITLE = "title";
        public static final String COLUMN_NAME_SUBTITLE = "subtitle";
        public static final String COLUMN_NAME_ALBUM_ID = "fk_album_id";
    }

    public static class AlbumEntry implements BaseColumns {
        public static final String TABLE_NAME = "album";
        public static final String COLUMN_NAME_TITLE = "title";
        public static final String COLUMN_NAME_SUBTITLE = "subtitle";
    }

    public static final String SQL_CREATE_PHOTO =
            "CREATE TABLE " + PhotoEntry.TABLE_NAME + " (" +
                    PhotoEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                    PhotoEntry.COLUMN_NAME_IMAGE_ID + " INTEGER, " +
                    PhotoEntry.COLUMN_NAME_IMAGE_URI + " TEXT, " +
                    PhotoEntry.COLUMN_NAME_STACK_ID + " INTEGER, " +
                    PhotoEntry.COLUMN_NAME_EVENT_ID + " INTEGER, " +
                    PhotoEntry.COLUMN_NAME_DESCRIPTION + " TEXT, " +
                    // Foreign key to MediaStore.Images
                    "FOREIGN KEY (" + PhotoEntry.COLUMN_NAME_IMAGE_ID + ") " +
                    "REFERENCES " + MediaStore.Images.Media.EXTERNAL_CONTENT_URI + "(" + MediaStore.Images.Media._ID + ") " +
                    // Foreign key to "stack" table
                    "FOREIGN KEY (" + PhotoEntry.COLUMN_NAME_STACK_ID + ") " +
                    "REFERENCES " + StackEntry.TABLE_NAME + "(" + StackEntry._ID + ") " +
                    // Foreign key to "event" table
                    "FOREIGN KEY (" + PhotoEntry.COLUMN_NAME_EVENT_ID + ") " +
                    "REFERENCES " + EventEntry.TABLE_NAME + "(" + EventEntry._ID + ") " +
                    ")";

    public static final String SQL_CREATE_STACK =
            "CREATE TABLE " + StackEntry.TABLE_NAME + " (" +
                    StackEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                    StackEntry.COLUMN_NAME_STACK_LEADER + " INTEGER " +
                    "FOREIGN KEY (" + StackEntry.COLUMN_NAME_STACK_LEADER + ") " +
                    "REFERENCES " + PhotoEntry.TABLE_NAME + "(" + PhotoEntry._ID + ") " +
                    ")";

    public static final String SQL_CREATE_EVENT =
            "CREATE TABLE " + EventEntry.TABLE_NAME + " (" +
                    EventEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                    EventEntry.COLUMN_NAME_ALBUM_ID + " INTEGER, " +
                    EventEntry.COLUMN_NAME_TITLE + " TEXT, " +
                    EventEntry.COLUMN_NAME_SUBTITLE + " TEXT, " +
                    "FOREIGN KEY (" + EventEntry.COLUMN_NAME_ALBUM_ID + ") " +
                    "REFERENCES " + AlbumEntry.TABLE_NAME + "(" + AlbumEntry._ID + ") " +
                    ")";

    public static final String SQL_CREATE_ALBUM =
            "CREATE TABLE " + AlbumEntry.TABLE_NAME + " (" +
                    AlbumEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
                    AlbumEntry.COLUMN_NAME_TITLE + " TEXT, " +
                    AlbumEntry.COLUMN_NAME_SUBTITLE + " TEXT)";
}

The app lets the user store captions/descriptions to images on the device, so if the user annotates an image, then the image ID + caption is stored in the photo table. There is a 1:n relationship to the stack table, where several images can be part of a "stack". Futhermore, sequences of photos (assuming strictly increasing/consecutive IDs) can be grouped as an event. And several events can be grouped as an album. I'm trying to formalize this using foreign key constraints.

But I want to start small, with just the photo table and get that to work :) My question is: How do I get off on the right leg with migrations, as in onUpgrade() etc? I guess the first migration is going from no database to setting one up?

public class MyappDbHelper extends SQLiteOpenHelper {
    // If you change the database schema, you must increment the database version.
    public static final int DATABASE_VERSION = 1;
    public static final String DATABASE_NAME = "Myapp.db";

    public MyappDbHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }
    public void onCreate(SQLiteDatabase db) {
        //db.execSQL(AlbumContract.SQL_CREATE_ALBUM);  // I'm starting with just the PHOTO and STACK tables
        //db.execSQL(AlbumContract.SQL_CREATE_EVENT);
        db.execSQL(AlbumContract.SQL_CREATE_PHOTO);
        db.execSQL(AlbumContract.SQL_CREATE_STACK);
    }
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        //db.execSQL(SQL_DELETE_ENTRIES);
        onCreate(db);
    }
    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        onUpgrade(db, oldVersion, newVersion);
    }
}

Solution

  • It seems you are trying to use old approach to create SQLite database. If you use Room library to work with SQLite, then you must create @Entity and @Dao(Data Access Object) classes for your data, and the @Database class which extends RoomDatabase.

    For your case code looks like this:

    @Entity
    public class Photo {
    
        @PrimaryKey
        public int photoId;
    
        @ColumnInfo(name = "mediastoreUri")
        public String mediastoreUri;
    
        @ColumnInfo(name = "description")
        public String description;
    
        @ColumnInfo(name = "photoEventId")
        public long photoEventId;
    }
    
    @Entity
    public class Event {
    
        @PrimaryKey
        public int eventId;
    
        @ColumnInfo(name = "title")
        public String title;
    
        @ColumnInfo(name = "subtitle")
        public String subtitle;
    }
    
    // One-to-Many relationship
    public class EventWithPhotos {
    
        @Embedded
        public Event event;
    
        @Relation(
            parentColumn = "eventId",
            entityColumn = "photoEventId"
        )
        public List<Photo> photos;
    }
    
    @Dao
    public interface PhotoDao {
    
        @Query("SELECT * FROM Photo")
        List<Photo> getAll();
    
        @Query("SELECT * FROM Photo WHERE photoId IN (:ids)")
        List<Photo> loadAllByIds(int[] ids);
    
        @Insert
        void insertAll(Photo... photos);
    
        @Delete
        void delete(Photo photo);
    }
    
    @Database(entities = {Photo.class, Event.class}, version = 1)
    public abstract class AppDatabase extends RoomDatabase {
        public abstract PhotoDao userDao();
    }
    

    For more details see: Room overview, Relationships in Room, Migrations in Room