Search code examples
android-sqlite

The table in the database (SQLite) is not created in the oncreate method


I am developing a mobile application in Android Studio. I have a "BreakfastDatabaseHelper" class for an SQLite database. Initially, I created the "breakfast" and "calories_summary" tables and methods for them there.

package com.example.calorieapp.ui.dashboard;

import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Locale;

public class BreakfastDatabaseHelper extends SQLiteOpenHelper {

    private static final String DATABASE_NAME = "breakfast_database";
    private static final int DATABASE_VERSION = 2;

    static final String TABLE_BREAKFAST = "breakfast";
    public static final String COLUMN_ID = "_id";
    public static final String COLUMN_PRODUCT_NAME = "product_name";
    public static final String COLUMN_GRAMS = "grams";
    public static final String COLUMN_CALORIES = "calories";
    public static final String COLUMN_PROTEIN = "protein";
    public static final String COLUMN_FAT = "fat";
    public static final String COLUMN_CARBOHYDRATE = "carbohydrate";
    public static final String COLUMN_DATE = "date";

    // SQL query to create the breakfast table
    private static final String CREATE_BREAKFAST_TABLE = "CREATE TABLE " + TABLE_BREAKFAST + " (" +
            COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            COLUMN_PRODUCT_NAME + " TEXT, " +
            COLUMN_GRAMS + " REAL, " +
            COLUMN_CALORIES + " REAL, " +
            COLUMN_PROTEIN + " REAL, " +
            COLUMN_FAT + " REAL, " +
            COLUMN_CARBOHYDRATE + " REAL, " +
            COLUMN_DATE + " TEXT);";


    static final String TABLE_CALORIES_SUMMARY = "calories_summary";
    public static final String COLUMN_DATE_SUMMARY = "date_summary";
    public static final String COLUMN_TOTAL_CALORIES = "total_calories";

    // SQL query to create the calories_summary table
    // SQL query to create the calories_summary table
    private static final String CREATE_CALORIES_SUMMARY_TABLE = "CREATE TABLE " + TABLE_CALORIES_SUMMARY + " (" +
            COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            COLUMN_DATE_SUMMARY + " TEXT, " +
            COLUMN_TOTAL_CALORIES + " REAL);";

    static final String TABLE_PROTEIN_SUMMARY = "protein_summary";
    public static final String COLUMN_DATE_SUMMARY_PROTEIN = "date_summary_protein";
    public static final String COLUMN_TOTAL_PROTEIN = "total_protein";

    // SQL query to create the calories_summary table
    // SQL query to create the calories_summary table

    private static final String CREATE_PROTEIN_SUMMARY_TABLE = "CREATE TABLE " + TABLE_PROTEIN_SUMMARY + " (" +
            COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
            COLUMN_DATE_SUMMARY_PROTEIN + " TEXT, " +
            COLUMN_TOTAL_PROTEIN + " REAL);";


    public BreakfastDatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, DATABASE_VERSION);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(CREATE_BREAKFAST_TABLE);
        db.execSQL(CREATE_CALORIES_SUMMARY_TABLE);
        db.execSQL(CREATE_PROTEIN_SUMMARY_TABLE);

        Log.d("BreakfastDatabaseHelper", "Tables created: breakfast, calories_summary");
    }


    public void updateCaloriesSummary(String date) {
        // Выполняем запрос для получения суммы калорий по выбранной дате с округлением до сотых
        String query = "SELECT ROUND(SUM(" + COLUMN_CALORIES + "), 2) FROM " + TABLE_BREAKFAST +
                " WHERE " + COLUMN_DATE + " = ?";
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(query, new String[]{date});

        double totalCalories = 0;

        // Если есть результат, переходим к первой записи
        if (cursor.moveToFirst()) {
            totalCalories = cursor.getDouble(0);
        }

        cursor.close();

        // Теперь вставляем или обновляем данные в таблице calories_summary
        ContentValues values = new ContentValues();
        values.put(COLUMN_DATE_SUMMARY, date);
        values.put(COLUMN_TOTAL_CALORIES, totalCalories);

        db = this.getWritableDatabase();
        db.replace(TABLE_CALORIES_SUMMARY, null, values);
        db.close();
    }


    public double getTotalCaloriesSummary(String date) {
        // Выполняем запрос для получения суммы калорий из таблицы calories_summary по выбранной дате
        String query = "SELECT " + COLUMN_TOTAL_CALORIES + " FROM " + TABLE_CALORIES_SUMMARY +
                " WHERE " + COLUMN_DATE_SUMMARY + " = ?" +
                " ORDER BY " + COLUMN_ID + " DESC";  // Упорядочиваем по убыванию id

        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(query, new String[]{date});

        double totalCalories = 0;

        // Если есть результат, переходим к первой записи
        if (cursor.moveToFirst()) {
            totalCalories = cursor.getDouble(0);
        }

        cursor.close();
        db.close();

        return totalCalories;
    }



    public void updateProteinSummary(String date) {
        // Выполняем запрос для получения суммы калорий по выбранной дате с округлением до сотых
        String query = "SELECT ROUND(SUM(" + COLUMN_PROTEIN + "), 2) FROM " + TABLE_BREAKFAST +
                " WHERE " + COLUMN_DATE + " = ?";
        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(query, new String[]{date});

        double totalProtein = 0;

        // Если есть результат, переходим к первой записи
        if (cursor.moveToFirst()) {
            totalProtein = cursor.getDouble(0);
        }

        cursor.close();

        // Теперь вставляем или обновляем данные в таблице protein_summary
        ContentValues values = new ContentValues();
        values.put(COLUMN_DATE_SUMMARY_PROTEIN, date);
        values.put(COLUMN_TOTAL_PROTEIN, totalProtein);

        db = this.getWritableDatabase();
        db.replace(TABLE_PROTEIN_SUMMARY, null, values);
        db.close();
    }


    public double getTotalProteinSummary(String date) {
        // Выполняем запрос для получения суммы калорий из таблицы protein_summary по выбранной дате
        String query = "SELECT " + COLUMN_TOTAL_PROTEIN + " FROM " + TABLE_PROTEIN_SUMMARY +
                " WHERE " + COLUMN_DATE_SUMMARY_PROTEIN + " = ?" +
                " ORDER BY " + COLUMN_ID + " DESC";  // Упорядочиваем по убыванию id

        SQLiteDatabase db = this.getReadableDatabase();
        Cursor cursor = db.rawQuery(query, new String[]{date});

        double totalProtein = 0;

        // Если есть результат, переходим к первой записи
        if (cursor.moveToFirst()) {
            totalProtein = cursor.getDouble(0);
        }

        cursor.close();
        db.close();

        return totalProtein;
    }



    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.d("BreakfastDatabaseHelper", "Upgrading database from version " + oldVersion + " to " + newVersion);
        // Handle database upgrades if needed
    }

}

Now I want to create a table "protein_summary". I do it by analogy "calories_summary", add the line db.execSQL(CREATE_PROTEIN_SUMMARY_TABLE) to the onCreate method, but the table is not created. What could this be related to?


Solution

  • The onCreate method is only called when the database does not exist. The typical way to amend the schema is to utilise the onUpgrade method. The onUpgrade method is called/invoked when the version number is increased.

    • this is a common misconception as an activity's onCreate method appears to always be called, but as a database persists then it is only created the once for the App (unless deleted). Underlying is a file, if the file exists then the database exists.

    So you need to increase the version number DATABASE_VERSION (typically by increasing it by 1) and then have the onUpgrade method create the table noting that you should cater for the respective upgrade by testing the oldVersion and newVersion.

    You should also update the onCreate to also create the amended schema so that a new installation creates the correct schema for the version.

    If you use CREATE TABLE IF NOT EXISTS .... rather than CREATE TABLE .... then you can have the onUpgrade call the onCreate and so only the missing table(s) get created.

    As a Demonstration based upon the code in the question.

    Consider the following changes

    private static final int DATABASE_VERSION = 1;
    
    • changed from 2 to 1 (not sure if you would jump to 3 from 2)

    and:-

    private static final String CREATE_BREAKFAST_TABLE = "CREATE TABLE IF NOT EXISTS " + ....
    private static final String CREATE_CALORIES_SUMMARY_TABLE = "CREATE TABLE IF NOT EXISTS " + ....
    private static final String CREATE_PROTEIN_SUMMARY_TABLE = "CREATE TABLE IF NOT EXISTS " + ....
    

    And JUST FOR EASE of the DEMO:-

    if (DATABASE_VERSION > 1) db.execSQL(CREATE_PROTEIN_SUMMARY_TABLE);
    
    • instead of just db.execSQL(CREATE_PROTEIN_SUMMARY_TABLE);

    And lastly:-

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        Log.d("BreakfastDatabaseHelper", "Upgrading database from version " + oldVersion + " to " + newVersion);
        // Handle database upgrades if needed
        if (oldVersion < 2) {
            onCreate(db);
        }
    }
    

    Now with the following Activity code:-

    public class MainActivity extends AppCompatActivity {
    
        BreakfastDatabaseHelper dbHelper;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            dbHelper = new BreakfastDatabaseHelper(this);
            SQLiteDatabase db = dbHelper.getWritableDatabase();
            Log.d("DBVERSION","Database version is " + db.getVersion());
            Cursor csr = db.rawQuery("SELECT * FROM sqlite_master",null);
            DatabaseUtils.dumpCursor(csr);
            csr.close();
        }
    }
    

    The the log shows:-

    2024-03-23 12:41:58.636 D/BreakfastDatabaseHelper: Tables created: breakfast, calories_summary
    2024-03-23 12:41:58.638 D/DBVERSION: Database version is 1
    2024-03-23 12:41:58.638 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@8962cfb
    2024-03-23 12:41:58.639 I/System.out: 0 {
    2024-03-23 12:41:58.640 I/System.out:    type=table
    2024-03-23 12:41:58.640 I/System.out:    name=android_metadata
    2024-03-23 12:41:58.640 I/System.out:    tbl_name=android_metadata
    2024-03-23 12:41:58.640 I/System.out:    rootpage=3
    2024-03-23 12:41:58.640 I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
    2024-03-23 12:41:58.640 I/System.out: }
    2024-03-23 12:41:58.640 I/System.out: 1 {
    2024-03-23 12:41:58.640 I/System.out:    type=table
    2024-03-23 12:41:58.640 I/System.out:    name=breakfast
    2024-03-23 12:41:58.640 I/System.out:    tbl_name=breakfast
    2024-03-23 12:41:58.640 I/System.out:    rootpage=4
    2024-03-23 12:41:58.640 I/System.out:    sql=CREATE TABLE breakfast (_id INTEGER PRIMARY KEY AUTOINCREMENT, product_name TEXT, grams REAL, calories REAL, protein REAL, fat REAL, carbohydrate REAL, date TEXT)
    2024-03-23 12:41:58.640 I/System.out: }
    2024-03-23 12:41:58.640 I/System.out: 2 {
    2024-03-23 12:41:58.641 I/System.out:    type=table
    2024-03-23 12:41:58.641 I/System.out:    name=sqlite_sequence
    2024-03-23 12:41:58.641 I/System.out:    tbl_name=sqlite_sequence
    2024-03-23 12:41:58.641 I/System.out:    rootpage=5
    2024-03-23 12:41:58.641 I/System.out:    sql=CREATE TABLE sqlite_sequence(name,seq)
    2024-03-23 12:41:58.641 I/System.out: }
    2024-03-23 12:41:58.641 I/System.out: 3 {
    2024-03-23 12:41:58.641 I/System.out:    type=table
    2024-03-23 12:41:58.641 I/System.out:    name=calories_summary
    2024-03-23 12:41:58.641 I/System.out:    tbl_name=calories_summary
    2024-03-23 12:41:58.641 I/System.out:    rootpage=6
    2024-03-23 12:41:58.641 I/System.out:    sql=CREATE TABLE calories_summary (_id INTEGER PRIMARY KEY AUTOINCREMENT, date_summary TEXT, total_calories REAL)
    2024-03-23 12:41:58.641 I/System.out: }
    2024-03-23 12:41:58.641 I/System.out: <<<<<
    

    i.e. just the original 2 tables

    With just the single change:-

    private static final int DATABASE_VERSION = 2;
    

    And the App rerun, then the log:-

    2024-03-23 12:55:42.359 D/BreakfastDatabaseHelper: Upgrading database from version 1 to 2
    2024-03-23 12:55:42.360 D/BreakfastDatabaseHelper: Tables created: breakfast, calories_summary
    2024-03-23 12:55:42.362 D/DBVERSION: Database version is 2
    2024-03-23 12:55:42.362 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@8962cfb
    2024-03-23 12:55:42.363 I/System.out: 0 {
    2024-03-23 12:55:42.363 I/System.out:    type=table
    2024-03-23 12:55:42.364 I/System.out:    name=android_metadata
    2024-03-23 12:55:42.364 I/System.out:    tbl_name=android_metadata
    2024-03-23 12:55:42.364 I/System.out:    rootpage=3
    2024-03-23 12:55:42.364 I/System.out:    sql=CREATE TABLE android_metadata (locale TEXT)
    2024-03-23 12:55:42.364 I/System.out: }
    2024-03-23 12:55:42.364 I/System.out: 1 {
    2024-03-23 12:55:42.364 I/System.out:    type=table
    2024-03-23 12:55:42.365 I/System.out:    name=breakfast
    2024-03-23 12:55:42.365 I/System.out:    tbl_name=breakfast
    2024-03-23 12:55:42.365 I/System.out:    rootpage=4
    2024-03-23 12:55:42.365 I/System.out:    sql=CREATE TABLE breakfast (_id INTEGER PRIMARY KEY AUTOINCREMENT, product_name TEXT, grams REAL, calories REAL, protein REAL, fat REAL, carbohydrate REAL, date TEXT)
    2024-03-23 12:55:42.365 I/System.out: }
    2024-03-23 12:55:42.365 I/System.out: 2 {
    2024-03-23 12:55:42.365 I/System.out:    type=table
    2024-03-23 12:55:42.365 I/System.out:    name=sqlite_sequence
    2024-03-23 12:55:42.365 I/System.out:    tbl_name=sqlite_sequence
    2024-03-23 12:55:42.365 I/System.out:    rootpage=5
    2024-03-23 12:55:42.366 I/System.out:    sql=CREATE TABLE sqlite_sequence(name,seq)
    2024-03-23 12:55:42.366 I/System.out: }
    2024-03-23 12:55:42.366 I/System.out: 3 {
    2024-03-23 12:55:42.366 I/System.out:    type=table
    2024-03-23 12:55:42.366 I/System.out:    name=calories_summary
    2024-03-23 12:55:42.366 I/System.out:    tbl_name=calories_summary
    2024-03-23 12:55:42.366 I/System.out:    rootpage=6
    2024-03-23 12:55:42.366 I/System.out:    sql=CREATE TABLE calories_summary (_id INTEGER PRIMARY KEY AUTOINCREMENT, date_summary TEXT, total_calories REAL)
    2024-03-23 12:55:42.366 I/System.out: }
    2024-03-23 12:55:42.366 I/System.out: 4 {
    2024-03-23 12:55:42.366 I/System.out:    type=table
    2024-03-23 12:55:42.366 I/System.out:    name=protein_summary
    2024-03-23 12:55:42.366 I/System.out:    tbl_name=protein_summary
    2024-03-23 12:55:42.366 I/System.out:    rootpage=7
    2024-03-23 12:55:42.367 I/System.out:    sql=CREATE TABLE protein_summary (_id INTEGER PRIMARY KEY AUTOINCREMENT, date_summary_protein TEXT, total_protein REAL)
    2024-03-23 12:55:42.367 I/System.out: }
    2024-03-23 12:55:42.367 I/System.out: <<<<<
    

    i.e. the 3 tables now exist.

    • if any data existed in the original 2 tables then that would be retained as the CREATE TABLE IF NOT EXISTS .... for the 2 original tables would be noops as the tables exist.

    Perhaps note the very first line, output according to your original code from the onUpgrade method.

    Also note that accidentally (on purpose) the logged output from onCreate was not changed to reflect the new table. Thus the benefit of actually ascertaining what is via the query on sqlite_master (the database's schema).