1

I have encountered a weird problem. I was querying a database table for a list of words and was able to get the list. But I am not being able to update that table. The weird thing is, the logcat is showing Code 14 error. I uninstalled the app, re-ran it so the db is freshly copied, But nothing has changed.

Here's the dbHandler code:

public VocabDatabase(Context context) {

    super(context, DB_NAME, null, 1);
    this.myContext = context;
    this.DB_PATH = context.getApplicationInfo().dataDir + "/databases/";
}

/**
 * Creates a empty database on the system and rewrites it with your own database.
 */
public void createDataBase() throws IOException {

    boolean dbExist = checkDataBase();

    if (dbExist) {
        //do nothing - database already exist
    } else {
        //By calling this method and empty database will be created into the default system path
        //of your application so we are gonna be able to overwrite that database with our database.
        this.getWritableDatabase();

        try {
            copyDataBase();
        } catch (IOException e) {
            throw new Error("Error copying database");

        }

    }

}

/**
 * Check if the database already exist to avoid re-copying the file each time you open the application.
 *
 * @return true if it exists, false if it doesn't
 */
private boolean checkDataBase() {
    this.getReadableDatabase();

    SQLiteDatabase checkDB = null;

    try {
        String myPath = DB_PATH;
        checkDB = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READWRITE);

    } catch (SQLiteException e) {
        e.printStackTrace();
    }

    if (checkDB != null) {

        checkDB.close();

    }

    return (checkDB != null) ? true : false;
}
private boolean checkDataBaseAlt(){
    File chkdb = new File(DB_PATH);
    return chkdb.exists();
}

/**
 * Copies your database from your local assets-folder to the just created empty database in the
 * system folder, from where it can be accessed and handled.
 * This is done by transfering bytestream.
 */
private void copyDataBase() throws IOException {

    //Open your local db as the input stream
    InputStream myInput = myContext.getAssets().open(DB_NAME);

    // Path to the just created empty db
    String outFileName = DB_PATH + DB_NAME;

    //Open the empty db as the output stream
    OutputStream myOutput = new FileOutputStream(outFileName);

    //transfer bytes from the inputfile to the outputfile
    byte[] buffer = new byte[1024];
    int length = 0;
    while ((length = myInput.read(buffer)) > 0) {
        myOutput.write(buffer, 0, length);
    }

    //Close the streams
    myOutput.flush();
    myOutput.close();
    myInput.close();

}

public void openDataBase() throws SQLException {

    //Open the database
    String myPath = DB_PATH + DB_NAME;
    myDataBase = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READWRITE);

}

@Override
public synchronized void close() {

    if (myDataBase != null)
        myDataBase.close();

    super.close();

}

@Override
public void onCreate(SQLiteDatabase db) {

}

@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
    if (newVersion > oldVersion) {
        try {
            copyDataBase();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

// Add your public helper methods to access and get content from the database.
// You could return cursors by doing "return myDataBase.query(....)" so it'd be easy
// to you to create adapters for your views.

//add your public methods for insert, get, delete and update data in database.

public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) {
    SQLiteDatabase db = this.getWritableDatabase();
    return db.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
}

public long insert(String table, String nullColumnHack, ContentValues contentValues) {
    SQLiteDatabase db = this.getWritableDatabase();
    return db.insert(table, nullColumnHack, contentValues);
}

public Cursor rawQuery(String string, String[] selectionArguments) {
    SQLiteDatabase db = this.getWritableDatabase();
    return db.rawQuery(string, selectionArguments);
}
public long update(String table, ContentValues contentValues, String whereClause, String[] whereArgs) {
    SQLiteDatabase db = this.getWritableDatabase();
    return db.update(table, contentValues, whereClause, whereArgs);
}

}

And here's the service code:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    vdb = new VocabDatabase(this);
    Toast.makeText(this, "Service started", Toast.LENGTH_SHORT).show();
    createDB();
    queryDB();
    return super.onStartCommand(intent, flags, startId);
}
private void createDB() {
    vdb.getWritableDatabase();
    try {
        vdb.createDataBase();
        vdb.openDataBase();
    } catch (Exception e) {
        e.printStackTrace();
    }
}
public void queryDB() {
    String vet = "";
    ArrayList<String> lister = new ArrayList<>();
    Cursor cr = vdb.query(TABLE_NAME, null, null, null, null, null, null);
if (cr.moveToFirst()) {
        do {
            lister.add(cr.getString(0));
        } while (cr.moveToNext());
    }
cr.close();
String vet="";
for (String v : lister) {
            vet += v + "\t";
        }
}

The String-vet is displayed in the toast, and I can see all the words in the first column of the table. But I'm not able to update the rows.

private void updateInDatabase(String up, String pot) {
    ContentValues conval = new ContentValues();
    conval.put(pot, "1");
    try {
        long res = vdb.update(TABLE_NAME, conval, "Word=?", new String[]{up});
    } catch (Exception e) {

    }
}

I've given the storage permissions to it and double checked the same.

Manifest File:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.example.hack.corrector">
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" tools:remove="android:maxSdkVersion"/>
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.INTERNET"/>
<application
    android:allowBackup="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:roundIcon="@mipmap/ic_launcher_round"
    android:supportsRtl="true"
    android:theme="@style/AppTheme">
    <activity android:name=".MainActivity">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />

            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>
    </activity>
    <service android:name=".scrapeservice"/>
</application>

Error Log:

05-17 13:39:04.614 28088-28088/com.example.hack.corrector E/SQLiteLog: (14) cannot open file at line 31282 of [5a3022e081]
05-17 13:39:04.615 28088-28088/com.example.hack.corrector E/SQLiteLog: (14) os_unix.c:31282: (21) open(/data/user/0/com.example.hack.corrector/databases/) - 
05-17 13:39:04.616 28088-28088/com.example.hack.corrector E/SQLiteDatabase: Failed to open database '/data/user/0/com.example.hack.corrector/databases/'.
    android.database.sqlite.SQLiteCantOpenDatabaseException: unknown error (code 14): Could not open database
        at android.database.sqlite.SQLiteConnection.nativeOpen(Native Method)
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:207)
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:191)
        at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
        at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:806)
        at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:791)
        at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694)
        at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:669)
        at com.example.hakc.corrector.VocabDatabase.checkDataBase(VocabDatabase.java:81)
        at com.example.hakc.corrector.VocabDatabase.createDataBase(VocabDatabase.java:48)
        at com.example.hakc.corrector.scrapeservice.createDB(scrapeservice.java:48)
        at com.example.hakc.corrector.scrapeservice.onStartCommand(scrapeservice.java:39)
        at android.app.ActivityThread.handleServiceArgs(ActivityThread.java:3049)
        at android.app.ActivityThread.access$2300(ActivityThread.java:154)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1479)
        at android.os.Handler.dispatchMessage(Handler.java:102)
        at android.os.Looper.loop(Looper.java:157)
        at android.app.ActivityThread.main(ActivityThread.java:5571)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:745)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:635)
05-17 1
Zoe
  • 27,060
  • 21
  • 118
  • 148
Dante
  • 457
  • 1
  • 5
  • 17
  • Don't skip the exception, what is your exception after adding e.printStackTrace(); ? – tommybee May 17 '18 at 08:31
  • Share you `AndroidMenifest.xml` file – Ghulam Moinul Quadir May 17 '18 at 08:32
  • Your DB path isn't complete `/data/user/0/com.example.hack.corrector/databases/'` You only have the directory. – AxelH May 17 '18 at 08:43
  • Possible duplicate of [Code 14: Unable to open database](https://stackoverflow.com/questions/50315910/code-14-unable-to-open-database) – MikeT May 17 '18 at 08:49
  • @MikeT The problem here is updating the database. I am able to retrieve the data from db but unable to update it. – Dante May 17 '18 at 09:44
  • No, the problem happen before that, in the `checkDatabase` method, during the creation of the database triggered by `onStartCommand`. So you don't have read any data yet... – AxelH May 17 '18 at 09:48
  • @Dante I believe your issue is exactly the same i.e as the path is incorrect (does not include the DBNAME) the `checkDatabase` method can't open the database, so returns false. So it copies the database from the assets every time thus giving you the impression that the update isn't working. hence why I said **In BOTH situations the correct use should be DB_PATH + DB_NAME, not just DB_PATH.**. – MikeT May 17 '18 at 10:01

4 Answers4

2

This issue if you are running your app on API level 23 or greater, because of the new real-time permissions model introduced in this. In these versions users grant permissions to apps while the app is running

For getting permissions at runtime, you will have to request the user. You can do that in following way:

Request Permission

String[] permissions = {Manifest.permission.WRITE_EXTERNAL_STORAGE};
requestPermissions(permissions, REQUEST_CODE); //REQUEST_CODE can be any Integer value

And check your Permission Result

@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
    switch (requestCode) {
       case REQUEST_CODE:
         if(grantResults[0] == PackageManager.PERMISSION_GRANTED){
           //Permission granted.
           //Now you can try your database creating and writing stuff.

         }
       else{
           //Permission denied.
         }
        break;
    }
}

If this does not solve your problem then go to this link. Number of solutions are here for the same problem.

android.database.sqlite.SQLiteCantOpenDatabaseException: unknown error (code 14): Could not open database

Ghulam Moinul Quadir
  • 1,638
  • 1
  • 12
  • 17
2

Like the exception tells :

Failed to open database '/data/user/0/com.example.hack.corrector/databases/'.

You are trying to open the SQLite DB with the variable DB_PATH set like

this.DB_PATH = context.getApplicationInfo().dataDir + "/databases/";

You don't set the name of the file, only set the directory path. databases/ is a directory.

Set a file name to your path :

this.DB_PATH = context.getApplicationInfo().dataDir + "/databases/my_db";

EDIT: you have two DB name variable... you are using DB_NAME to open the connection with the helper (in the super constructor) then in the check method you used DB_PATH.

So you are able to get a connection from the helper

this.getWritableDatabase(); //Using the DB at `DB_NAME`

But will failed to get a connection with

String myPath = DB_PATH;
SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READWRITE);

Because you used DB_PATH here not DB_NAME. You are using 2 different DB path.

AxelH
  • 14,325
  • 2
  • 25
  • 55
  • I dont think thats the problem. The query was able to successfully retrieve data to cursor... besides, I’ve tried before, what you have suggested but it didn’t help.. – Dante May 17 '18 at 09:38
1

I believe you will find that the cannot open database error is not actually a failure, but an indication of the issue. That is the open error is encountered by the checkdataBase method because it cannot open the database as the message says. This, as it is trapped, does not result in a failure.

Rather what happens is that checkDatabase method returns false, because it cannot open the database, and so the database is copied from the assets every time the App is run. Thus undoing any changes from a previous run.

It is simple to diagnose/debug this using two lines of code.

  1. by adding Log.d("DBEXISTCHK", "Method checkdataBase returned" + String.valueOf(dbExist));

as per :-

/**
 * Creates a empty database on the system and rewrites it with your own database.
 */
public void createDataBase() throws IOException {

    boolean dbExist = checkDataBase();
    Log.d("DBEXISTCHK", "Method checkdataBase returned" + String.valueOf(dbExist)); //<<<<<<<<<< ADDED

    if (dbExist) {
        //do nothing - database already exist
    } else {
        //By calling this method and empty database will be created into the default system path
        //of your application so we are gonna be able to overwrite that database with our database.
        this.getWritableDatabase();
        try {
            copyDataBase();
        } catch (IOException e) {
            throw new Error("Error copying database");
        }
    }
}

and

  1. by adding Log.d("DBCOPY","Database is being copied from the Assets.");

as per :-

private void copyDataBase() throws IOException {

    Log.d("DBCOPY","Database is being copied from the Assets."); //<<<<<<<<<< ADDED

    //Open your local db as the input stream
    InputStream myInput = myContext.getAssets().open(DB_NAME);

    // Path to the just created empty db
    String outFileName = DB_PATH + DB_NAME;

    //Open the empty db as the output stream
    OutputStream myOutput = new FileOutputStream(outFileName);

    //transfer bytes from the inputfile to the outputfile
    byte[] buffer = new byte[1024];
    int length = 0;
    while ((length = myInput.read(buffer)) > 0) {
        myOutput.write(buffer, 0, length);
    }

    //Close the streams
    myOutput.flush();
    myOutput.close();
    myInput.close();

}

The resultant log would then be along similar to (note the last two lines):-

05-17 10:12:22.591 1152-1152/bcdbfa.basiccopydbfromassets E/SQLiteLog: (14) cannot open file at line 30174 of [00bb9c9ce4]
    (14) os_unix.c:30174: (21) open(/data/data/bcdbfa.basiccopydbfromassets/databases/) - 
05-17 10:12:22.591 1152-1152/bcdbfa.basiccopydbfromassets E/SQLiteDatabase: Failed to open database '/data/data/bcdbfa.basiccopydbfromassets/databases/'.
    android.database.sqlite.SQLiteCantOpenDatabaseException: unknown error (code 14): Could not open database
        at android.database.sqlite.SQLiteConnection.nativeOpen(Native Method)
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:209)
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193)
        at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
        at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:804)
        at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:789)
        at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694)
        at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:669)
        at bcdbfa.basiccopydbfromassets.VocabDatabase.checkDataBase(VocabDatabase.java:85)
        at bcdbfa.basiccopydbfromassets.VocabDatabase.createDataBase(VocabDatabase.java:45)
        at bcdbfa.basiccopydbfromassets.MainActivity.onCreate(MainActivity.java:18)
        at android.app.Activity.performCreate(Activity.java:5008)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1079)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2023)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084)
        at android.app.ActivityThread.access$600(ActivityThread.java:130)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:137)
        at android.app.ActivityThread.main(ActivityThread.java:4745)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:511)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
        at dalvik.system.NativeStart.main(Native Method)
05-17 10:12:22.591 1152-1152/bcdbfa.basiccopydbfromassets W/System.err: android.database.sqlite.SQLiteCantOpenDatabaseException: unknown error (code 14): Could not open database
        at android.database.sqlite.SQLiteConnection.nativeOpen(Native Method)
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:209)
        at android.database.sqlite.SQLiteConnection.open(SQLiteConnection.java:193)
        at android.database.sqlite.SQLiteConnectionPool.openConnectionLocked(SQLiteConnectionPool.java:463)
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:185)
        at android.database.sqlite.SQLiteConnectionPool.open(SQLiteConnectionPool.java:177)
        at android.database.sqlite.SQLiteDatabase.openInner(SQLiteDatabase.java:804)
        at android.database.sqlite.SQLiteDatabase.open(SQLiteDatabase.java:789)
05-17 10:12:22.599 1152-1152/bcdbfa.basiccopydbfromassets W/System.err:     at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:694)
        at android.database.sqlite.SQLiteDatabase.openDatabase(SQLiteDatabase.java:669)
        at bcdbfa.basiccopydbfromassets.VocabDatabase.checkDataBase(VocabDatabase.java:85)
        at bcdbfa.basiccopydbfromassets.VocabDatabase.createDataBase(VocabDatabase.java:45)
        at bcdbfa.basiccopydbfromassets.MainActivity.onCreate(MainActivity.java:18)
        at android.app.Activity.performCreate(Activity.java:5008)
        at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1079)
        at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2023)
        at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084)
        at android.app.ActivityThread.access$600(ActivityThread.java:130)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195)
        at android.os.Handler.dispatchMessage(Handler.java:99)
        at android.os.Looper.loop(Looper.java:137)
        at android.app.ActivityThread.main(ActivityThread.java:4745)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:511)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
        at dalvik.system.NativeStart.main(Native Method)
05-17 10:12:22.599 1152-1152/bcdbfa.basiccopydbfromassets D/DBEXISTCHK: Method checkdataBase returnedfalse
05-17 10:12:22.599 1152-1152/bcdbfa.basiccopydbfromassets D/DBCOPY: Database is being copied from the Assets.

The Fix

The fix is even simpler, it is simply a matter of changing :-

    String myPath = DB_PATH;

to

    String myPath = DB_PATH + DB_NAME;

in the checkDatabase method.

In which case the result in the log (if the logging is left in) will be :-

05-17 10:30:49.809 1265-1265/? D/DBEXISTCHK: Method checkdataBase returnedtrue

Additional

Re the comment

only empty database with the same name is being created.

This is because you are calling getWritableDatabase in the createDB method before calling the createDatabase method. That is, getWritableDatabase will, if there is no database, create an empty database (bar the sqlite_master table and for android the android_metadata table), and then invoke the onCreate method. So that explains why when the full path is correct that the copy of the database is circumvented and that the database empty database exists as a result.

And then the comment

But when I changed 'myPath' back to myPath = DB_Path; the entire database got copied but the problem still persists.

When the path is incorrect the, as previously explained, the checkDatabase method will always return false and thus the copyDatabase method is invoked, this then works as the paths used are correct.

So

private void createDB() {
    vdb.getWritableDatabase(); //<<<<<<<<<< The villainous line
    try {
        vdb.createDataBase();
        vdb.openDataBase();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

should be

private void createDB() {
    try {
        vdb.createDataBase();
        vdb.openDataBase();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

Obviously with all paths being the full path that include the name of the database.

MikeT
  • 51,415
  • 16
  • 49
  • 68
  • Thank you for patiently answering. I've tried your suggestion. I've changed 'myPath'. And though the log is returning "Method checkdataBase returnedtrue", after uninstalling the app and running the code, (fresh start), only empty database with the same name is being created. The contents didn't get copied. But when I changed 'myPath' back to myPath = DB_Path; the entire database got copied but the problem still persists. – Dante May 17 '18 at 11:25
  • @Dante see Additional in the answer. – MikeT May 18 '18 at 12:30
0

I've figured out where the problem lies. It was in the service from which the query is done. In createDB() method, I've used vdb.getWritableDatabase(). When I commented it out, the query worked smoothly. Entire database copied, and queries are being handled perfectly. I need to check the 'update' though.. I sincerely thank you guys for hearing me and taking your time to help me solve this problem. It's thanks to your answers, I've learnt a lot about how databases are communicated with properly.

Dante
  • 457
  • 1
  • 5
  • 17