1

Hi I am new to android development. I have already created SQLite Database and saved it in my assets folder in Android Studio. My app has to use the existing database instead of creating a new one. The problem that I am facing is that when I want to display the data on the screen it is throwing an error at the Cursor that is executing the SQL statement. Kindly help.

The name of the database is test.db and the table name is MASTER. This is my DataBaseHelper class

public class DataBaseHelper extends SQLiteOpenHelper {
    private static String TAG = "DataBaseHelper"; // Tag just for the LogCat window
    //destination path (location) of our database on device
    private static String DB_PATH = "";
    private static String DB_NAME ="test,db";// Database name
    private SQLiteDatabase mDataBase;
    private final Context mContext;

    public DataBaseHelper(Context context)
    {
        super(context, DB_NAME, null, 1);// 1? Its database Version
        if(android.os.Build.VERSION.SDK_INT >= 17){
            DB_PATH = context.getApplicationInfo().dataDir + "/databases/";
        }
        else
        {
            DB_PATH = context.getApplicationInfo().dataDir + "/databases/";
        }
        this.mContext = context;
    }

    public void createDataBase() throws IOException
    {
        //If the database does not exist, copy it from the assets.

        boolean mDataBaseExist = checkDataBase();
        if(!mDataBaseExist)
        {
            this.getReadableDatabase();
            this.close();
            try
            {
                //Copy the database from assests
                copyDataBase();
                Log.e(TAG, "createDatabase database created");
            }
            catch (IOException mIOException)
            {
                throw new Error("ErrorCopyingDataBase");
            }
        }
    }

    //Check that the database exists here: /data/data/your package/databases/Da Name
    private boolean checkDataBase()
    {
        File dbFile = new File(DB_PATH + DB_NAME);
        //Log.v("dbFile", dbFile + "   "+ dbFile.exists());
        return dbFile.exists();
    }

    //Copy the database from assets
    private void copyDataBase() throws IOException
    {
        InputStream mInput = mContext.getAssets().open(DB_NAME);
        String outFileName = DB_PATH + DB_NAME;
        OutputStream mOutput = new FileOutputStream(outFileName);
        byte[] mBuffer = new byte[1024];
        int mLength;
        while ((mLength = mInput.read(mBuffer))>0)
        {
            mOutput.write(mBuffer, 0, mLength);
        }
        mOutput.flush();
        mOutput.close();
        mInput.close();
    }

    //Open the database, so we can query it
    public boolean openDataBase() throws SQLException
    {
        String mPath = DB_PATH + DB_NAME;
        //Log.v("mPath", mPath);
        mDataBase = SQLiteDatabase.openDatabase(mPath, null, SQLiteDatabase.CREATE_IF_NECESSARY);
        //mDataBase = SQLiteDatabase.openDatabase(mPath, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
        return mDataBase != null;
    }

    @Override
    public synchronized void close()
    {
        if(mDataBase != null)
            mDataBase.close();
        super.close();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}

This is my TestAdapter class

public class TestAdapter
    {
        protected static final String TAG = "DataAdapter";

        private final Context mContext;
        private SQLiteDatabase mDb;
        private DataBaseHelper mDbHelper;

        public TestAdapter(Context context)
        {
            this.mContext = context;
            mDbHelper = new DataBaseHelper(mContext);
        }

        public TestAdapter createDatabase() throws SQLException
        {
            try
            {
                mDbHelper.createDataBase();
            }
            catch (IOException mIOException)
            {
                Log.e(TAG, mIOException.toString() + "  UnableToCreateDatabase");
                throw new Error("UnableToCreateDatabase");
            }
            return this;
        }

        public TestAdapter open() throws SQLException
        {
            try
            {
                mDbHelper.openDataBase();
                mDbHelper.close();
                mDb = mDbHelper.getReadableDatabase();
            }
            catch (SQLException mSQLException)
            {
                Log.e(TAG, "open >>"+ mSQLException.toString());
                throw mSQLException;
            }
            return this;
        }

        public void close()
        {
            mDbHelper.close();
        }

        public Cursor getTestData() {
            try
            {
                String sql ="SELECT * FROM MASTER;";

                Cursor mCur = mDb.rawQuery(sql, null);
                if (mCur!=null)
                {
                    mCur.moveToNext();
                }
                return mCur;
            }
            catch (SQLException mSQLException)
            {
                Log.e(TAG, "getTestData >>"+ mSQLException.toString());
                throw mSQLException;
            }
        }
    }

This is my MainActivity class

public class MainActivity extends AppCompatActivity {

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

        Button button=findViewById(R.id.submit);
        button.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {

                TestAdapter mDbHelper = new TestAdapter(MainActivity.this);
                mDbHelper.createDatabase();
                mDbHelper.open();
                Cursor testdata = mDbHelper.getTestData();
                Toast.makeText(MainActivity.this,testdata.getString(0),Toast.LENGTH_SHORT).show();
                mDbHelper.close();
            }
        });

    }
}

And this is the logcat

2019-02-04 15:47:30.227 2594-2594/com.example.myapplication E/SQLiteLog: (1) no such table: MASTER
2019-02-04 15:47:30.228 2594-2594/com.example.myapplication E/DataAdapter: getTestData >>android.database.sqlite.SQLiteException: no such table: MASTER (code 1): , while compiling: SELECT * FROM MASTER;
2019-02-04 15:47:30.228 2594-2594/com.example.myapplication D/AndroidRuntime: Shutting down VM
2019-02-04 15:47:30.242 2594-2594/com.example.myapplication E/AndroidRuntime: FATAL EXCEPTION: main
    Process: com.example.myapplication, PID: 2594
    android.database.sqlite.SQLiteException: no such table: MASTER (code 1): , while compiling: SELECT * FROM MASTER;
        at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
        at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:890)
        at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:501)
        at android.database.sqlite.SQLiteSession.prepare(SQLiteSession.java:588)
        at android.database.sqlite.SQLiteProgram.<init>(SQLiteProgram.java:58)
        at android.database.sqlite.SQLiteQuery.<init>(SQLiteQuery.java:37)
        at android.database.sqlite.SQLiteDirectCursorDriver.query(SQLiteDirectCursorDriver.java:46)
        at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1392)
        at android.database.sqlite.SQLiteDatabase.rawQuery(SQLiteDatabase.java:1331)
        at com.example.myapplication.TestAdapter.getTestData(TestAdapter.java:63)
        at com.example.myapplication.MainActivity$1.onClick(MainActivity.java:38)
        at android.view.View.performClick(View.java:6297)
        at android.view.View$PerformClick.run(View.java:24797)
        at android.os.Handler.handleCallback(Handler.java:790)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6626)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:438)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:811)

The logcat says that there is no MASTER table but when I viewed the database via SQLiteDB Browser it was there at that time.

ankuranurag2
  • 2,300
  • 15
  • 30
Amay
  • 104
  • 6

2 Answers2

3

I believe that your main issue is that you have used private static String DB_NAME ="test,db";// Database name

Instead of private static String DB_NAME ="test.db";// Database name

That is you have coded a comma , instead of a period . and thus that the database file in the assets folder will not be found and thus not copied.

When run for the first time the file test,db will be created, due to the use of this.getReadableDatabase();, which results in the database being created, which will be empty, and thus that for subsequent runs no attempt will be made to copy the file from the assets folder as the database exists and hence as the database is empty then the attempt to access the table fails as the table does not exist.

  • Note that getRedableDatabase will in most situations actually get a writable database

    Create and/or open a database. This will be the same object returned by getWritableDatabase() unless some problem, such as a full disk, requires the database to be opened read-only. In that case, a read-only database object will be returned. If the problem is fixed, a future call to getWritableDatabase() may succeed, in which case the read-only database object will be closed and the read/write object will be returned in the future. getReadableDatabase

I believe that using getReadableDatabase has only been used to circumvent the issue that initially the databases folder doesn't exist and thus that an attempt to copy the file from the assets fails because the parent folder doesn't exist. A better solution is to not use getReadableDatabase but to check to see if the directory exists and if not to create it.

This use of getReabableDatabase introduces even greater issues when using Android 9+ as the default is then to use WAL (Write Ahead Logging) which results in additional files (the database name suffixed with -shm and -wal).

As such using :-

//Check that the database exists here: /data/data/your package/databases/Da Name
private boolean checkDataBase()
{
    File dbFile = new File(DB_PATH + DB_NAME);
    if (dbFile.exists()) return true;
    if (!dbFile.getParentFile().exists()) dbFile.getParentFile().mkdirs();
    return false;
}

Does away with the need for using getReabableDatabase and the complications that arise from it actually creating a database file even if the ensuing copy fails due to the assets file not existing.

To be even more careful and cope with the potential that the -shm and -wal files may inadvertently exist then the above could even be extended to :-

private boolean checkDataBase()
{
    File dbFile = new File(DB_PATH + DB_NAME);
    if (dbFile.exists()) return true;
    if (!dbFile.getParentFile().exists()) dbFile.getParentFile().mkdirs();
    if (new File(DB_PATH + DB_NAME + "-shm").exists())
        new File(DB_PATH + DB_NAME + "-shm").delete();
    if ((new File(DB_PATH + DB_NAME + "-wal")).exists())
        new File(DB_PATH + DB_NAME + "-wal").delete();
    return false;
}

In general using DB_PATH = context.getApplicationInfo().dataDir + "/databases/"; is not recommened, rather the more specific DB_PATH = mContext.getDatabasePath(DB_NAME).getPath(); is recommended as there is no need to hard code file sperators and folder names.

A such the following is probably a better overall database helper :-

public class DataBaseHelper extends SQLiteOpenHelper {
    private static String TAG = "DataBaseHelper"; // Tag just for the LogCat window
    //destination path (location) of our database on device
    private static String DB_PATH = "";
    private static String DB_NAME ="test.db";// Database name //<<<<<<<<<< CHANGED TO FIX PRIMARY ISSUE
    private SQLiteDatabase mDataBase;
    private final Context mContext;

    public DataBaseHelper(Context context)
    {
        super(context, DB_NAME, null, 1);// 1? Its database Version
        this.mContext = context;
        DB_PATH = mContext.getDatabasePath(DB_NAME).getPath();
    }

    public void createDataBase() throws IOException
    {
        //If the database does not exist, copy it from the assets.

        boolean mDataBaseExist = checkDataBase();
        if(!mDataBaseExist)
        {
            //this.getReadableDatabase(); //<<<<<<<<<< REMOVED (commented out)
            //this.close(); //<<<<<<<<<< REMOVED ()commented out
            try
            {
                //Copy the database from assests
                copyDataBase();
                Log.e(TAG, "createDatabase database created");
            }
            catch (IOException mIOException)
            {
                mIOException.printStackTrace(); //<<<<<<<<<< might as well include the actual cause in the log
                throw new Error("ErrorCopyingDataBase");
            }
        }
    }

    //Check that the database exists here: /data/data/your package/databases/Da Name
    private boolean checkDataBase()
    {
        File dbFile = new File(DB_PATH); //<<<<<<<<<< just the path used
        if (dbFile.exists()) return true; //<<<<<<<<<< return true of the db exists (see NOTE001)
        if (!dbFile.getParentFile().exists()) dbFile.getParentFile().mkdirs();
        if (new File(DB_PATH + "-shm").exists())
            new File(DB_PATH + "-shm").delete();
        if ((new File(DB_PATH + "-wal")).exists())
            new File(DB_PATH + "-wal").delete();
        return false;
    }

    /** NOTE001
     *  Just checking the file does leave scope for a non sqlite file to be copied from the assets folder
     *  and be copied resulting in an exception. The above could be extended to apply additional checks
     *  if considered required e.g. checking the first sixteen bytes for The header string: "SQLite format 3\000"
     */

    //Copy the database from assets
    private void copyDataBase() throws IOException
    {
        InputStream mInput = mContext.getAssets().open(DB_NAME);
        String outFileName = DB_PATH; //<<<<<<<<<< just the path used
        OutputStream mOutput = new FileOutputStream(outFileName);
        byte[] mBuffer = new byte[1024];
        int mLength;
        while ((mLength = mInput.read(mBuffer))>0)
        {
            mOutput.write(mBuffer, 0, mLength);
        }
        mOutput.flush();
        mOutput.close();
        mInput.close();
    }

    //Open the database, so we can query it
    public boolean openDataBase() throws SQLException
    {
        String mPath = DB_PATH;
        //Log.v("mPath", mPath);
        mDataBase = SQLiteDatabase.openDatabase(mPath, null, SQLiteDatabase.CREATE_IF_NECESSARY);
        //mDataBase = SQLiteDatabase.openDatabase(mPath, null, SQLiteDatabase.NO_LOCALIZED_COLLATORS);
        return mDataBase != null;
    }

    /**
     * Note this can be added and the line uncommented (see below) to disable WAL logging which
     * from Anroid 9 (Pie) is the default
     */
    @Override
    public void onConfigure(SQLiteDatabase db) {
        super.onConfigure(db);
        // db.disableWriteAheadLogging(); //<<<<<<<<<< uncomment if you want to not use WAL but use the less efficient joutnal mode.
    }

    @Override
    public synchronized void close()
    {
        if(mDataBase != null)
            mDataBase.close();
        super.close();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }
}
  • Note see comments throughout the code

Extra/Testing

If the above were used (after deleting the App's data or Uninstalling the App to delete the empty database) but with no suitable file in the assets folder (test,db unchanged for the test), then the above would result in the more explanatory :-

02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err: java.io.FileNotFoundException: test,db
02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at android.content.res.AssetManager.openAsset(Native Method)
02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at android.content.res.AssetManager.open(AssetManager.java:313)
02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at android.content.res.AssetManager.open(AssetManager.java:287)
02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at mjt.so54513838.DataBaseHelper.copyDataBase(DataBaseHelper.java:75)
02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at mjt.so54513838.DataBaseHelper.createDataBase(DataBaseHelper.java:42)
02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at mjt.so54513838.TestAdapter.createDatabase(TestAdapter.java:29)
02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at mjt.so54513838.MainActivity$1.onClick(MainActivity.java:23)
02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at android.view.View.performClick(View.java:4780)
02-05 10:17:03.513 5502-5502/mjt.so54513838 W/System.err:     at android.view.View$PerformClick.run(View.java:19866)
02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at android.os.Handler.handleCallback(Handler.java:739)
02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:95)
02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at android.os.Looper.loop(Looper.java:135)
02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5254)
02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at java.lang.reflect.Method.invoke(Method.java:372)
02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
02-05 10:17:03.514 5502-5502/mjt.so54513838 W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)
02-05 10:17:03.514 5502-5502/mjt.so54513838 D/AndroidRuntime: Shutting down VM
02-05 10:17:03.514 5502-5502/mjt.so54513838 E/AndroidRuntime: FATAL EXCEPTION: main
    Process: mjt.so54513838, PID: 5502
    java.lang.Error: ErrorCopyingDataBase
        at mjt.so54513838.DataBaseHelper.createDataBase(DataBaseHelper.java:48)
        at mjt.so54513838.TestAdapter.createDatabase(TestAdapter.java:29)
        at mjt.so54513838.MainActivity$1.onClick(MainActivity.java:23)
        at android.view.View.performClick(View.java:4780)
        at android.view.View$PerformClick.run(View.java:19866)
        at android.os.Handler.handleCallback(Handler.java:739)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:135)
        at android.app.ActivityThread.main(ActivityThread.java:5254)
        at java.lang.reflect.Method.invoke(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:372)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:903)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:698)

Should the App then be run again, without any changes, then the above would also occur rather than the more confusing table not found.

NOTE before running the above code or even if just changing test,db to test.db the database file must be deleted. This can be easily achieved by deleting/clearing the App's data or by uninstalling the App.

The above has been tested on both Android 5.0 (lollipop) (API 22) and Andorid 9 (Pie)(API 28), with the resultant Toast displaying table (albeit it that for convenience the table was change from MASTER to sqlite_master (saved having to create a database file as an existing database file was used)).

MikeT
  • 51,415
  • 16
  • 49
  • 68
0

set your data path directly as a string, hope it will work

 private final static String DATABASE_PATH ="/data/data/com.yourpackagename/databases/";
public SQLiteDatabase openDatabase() throws SQLException
    {   String myPath = DATABASE_PATH + "DB_NAME";myDataBase = SQLiteDatabase.openOrCreateDatabase(myPath, null, null);
        return myDataBase;
    }`
Anand
  • 1,866
  • 3
  • 26
  • 49
Imran khan
  • 123
  • 1
  • 12