Search code examples
androidsqliteandroid-roomassetsfilenotfoundexception

Get FileNotFoundException while trying to open database in assets using Room


I decided to restructure my database. Also i decided to use Room to organize cooperation between my DB and my APP, instead of using SQL queries and cursors. My database located in Assets folder in Android Studio as well as in assets/files folder on emulator. My class, that copy DB from assets to assets/files folder down below.

public class DatabaseHelper extends SQLiteOpenHelper implements TypeOfTest {
    private static String DB_PATH;
    private static String DB_NAME;
    private static final int SCHEMA = 2;

    public static final String DB_OLD = "ForEastory.db";
    public static final String DB_NEW = "ForEastory2.db";

    private Context myContext;

    public DatabaseHelper(Context context, String dbName) {
        super(context, DB_NAME, null, SCHEMA);
        this.myContext = context;
        DB_NAME = dbName;
        DB_PATH = context.getFilesDir().getPath() + "/"+ DB_NAME;
        Log.d("files dir + name", DB_PATH);
    }

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

    public void create_db(){
        InputStream myInput;
        OutputStream myOutput;
        try {
            File file = new File(DB_PATH);
            if (!file.exists()) {
                this.getReadableDatabase();
                myInput = myContext.getAssets().open(DB_NAME);
                String outFileName = DB_PATH;

                myOutput = new FileOutputStream(outFileName);

                byte[] buffer = new byte[1024];
                int length;
                while ((length = myInput.read(buffer)) > 0) {
                    myOutput.write(buffer, 0, length);
                }

                myOutput.flush();
                myOutput.close();
                myInput.close();
            }
        }
        catch(IOException ex){
            ex.printStackTrace();
        }
    }

    public SQLiteDatabase open() throws SQLException {
        return SQLiteDatabase.openDatabase(DB_PATH, null, SQLiteDatabase.OPEN_READWRITE);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {

    }
}

As you can see, here i have to Database titles. This class work slick with my old DB. Also, using Device FIle Explorer, i can see, that database is actually in assets/files folder on emulator. Maybe, the problem is in Room Database class, where i'm trying to use my prepared database.

@androidx.room.Database(entities = {Question.class, Topic.class, Test.class}, version = 1, exportSchema = false)
public abstract class Database extends RoomDatabase {
    private static Database instance;

    public abstract TestDao testDao();

    public static String ASSET_DIR;

    public static synchronized Database getInstance(Context context)  {
        if (instance == null) {
            try {
                ASSET_DIR = context.getFilesDir().getPath() + "/"+ DatabaseHelper.DB_NEW;
                instance = Room.databaseBuilder(context.getApplicationContext(),
                        Database.class,
                        "AppDB")
                        .createFromAsset(ASSET_DIR)
                        .build();
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
        return instance;
    }
}

Here my TestDao class.

@Dao
public interface TestDao {

    @Query("SELECT * FROM tests WHERE topic_id = :id")
    TopicWithQuestions getTopicWithQuestionsById(int id);
}

And how i use this altogether.

protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.test);

        testNum = findViewById(R.id.testNum);
        getQuestions = findViewById(R.id.get_questions);

        DatabaseHelper databaseHelper = new DatabaseHelper(getApplicationContext(), DatabaseHelper.DB_NEW);
        databaseHelper.create_db();
        try {
            myDb = databaseHelper.open();
        } catch (SQLException e) {
            e.printStackTrace();
        }

        db = Database.getInstance(getApplicationContext());
        testDao = db.testDao();

        getQuestions.setOnClickListener(v -> {
            try {
                int id = Integer.parseInt(testNum.getText().toString());

                //In this place i've catched the exception (RoomTest.java:61)
                TopicWithQuestions topicWithQuestions = testDao.getTopicWithQuestionsById(id);
                List<Question> questionList = topicWithQuestions.getQuestion();

                for (Question question : questionList) {
                    System.out.println(question.getQuestion());
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }

Finally, logcat:

W/System.err: java.lang.RuntimeException: Unable to copy database file.
        at androidx.room.SQLiteCopyOpenHelper.verifyDatabaseFile(SQLiteCopyOpenHelper.java:131)
        at androidx.room.SQLiteCopyOpenHelper.getWritableDatabase(SQLiteCopyOpenHelper.java:87)
        at androidx.room.RoomDatabase.inTransaction(RoomDatabase.java:476)
        at androidx.room.RoomDatabase.assertNotSuspendingTransaction(RoomDatabase.java:281)
        at com.prodadimhaski.eastory2.Room.Dao.TestDao_Impl.getTopicWithQuestionsById(TestDao_Impl.java:33)
        at com.prodadimhaski.eastory2.Room.RoomTest.lambda$onCreate$0$RoomTest(RoomTest.java:61)
        at com.prodadimhaski.eastory2.Room.-$$Lambda$RoomTest$6iMa7yj1shnTdXRtmzsROKAwX-w.onClick(lambda)
        at android.view.View.performClick(View.java:4438)
        at android.view.View$PerformClick.run(View.java:18422)
        at android.os.Handler.handleCallback(Handler.java:733)
        at android.os.Handler.dispatchMessage(Handler.java:95)
        at android.os.Looper.loop(Looper.java:136)
        at android.app.ActivityThread.main(ActivityThread.java:5001)
        at java.lang.reflect.Method.invokeNative(Native Method)
        at java.lang.reflect.Method.invoke(Method.java:515)
        at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:785)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:601)
        at dalvik.system.NativeStart.main(Native Method)
    Caused by: java.io.FileNotFoundException: /data/data/com.prodadimhaski.eastory2/files/ForEastory2.db
        at android.content.res.AssetManager.openAsset(Native Method)
        at android.content.res.AssetManager.open(AssetManager.java:316)
        at android.content.res.AssetManager.open(AssetManager.java:290)
        at androidx.room.SQLiteCopyOpenHelper.copyDatabaseFile(SQLiteCopyOpenHelper.java:178)
        at androidx.room.SQLiteCopyOpenHelper.verifyDatabaseFile(SQLiteCopyOpenHelper.java:128)
        ... 17 more

This one __db.assertNotSuspendingTransaction(); is code fragment at TestDao_Impl.java:33


Solution

  • According to the prepopulated Room Database from the official documentation. You have 2 options:

    1) createFromAssets: in this option, you may create a directory called "databases" under assets folder so your could will be as follow:

    .createFromAssets("/databases/YOUR DATABASE FILENAME")
    

    2) createFromFile: This option may work with the file you are assigning its path.

    .createFromFile(File("YOUR FILE PATH"))
    

    if you are stuck with these two solutions, you can try the manual solution, we may call it manual solution yeah !. by accessing your database file in assets folder.

        private fun copyDBFromStorage(databaseName: String) {
        if (checkIfDBExists(this, databaseName)) return
        val databaseFile = File(this.getDatabasePath(databaseName).toString())
        val sourceLocation = assets.open("Your database file path")
        try {
            val inputStream = sourceLocation
            val os = FileOutputStream(databaseFile)
            val buffer = ByteArray(1024 * 32)
            var length = inputStream.read(buffer)
            while (length > 0) {
                os.write(buffer, 0, length)
                length = inputStream.read(buffer)
            }
            os.flush()
            os.close()
            inputStream.close()
    
        } catch (ex: IOException) {
            ex.printStackTrace();
            throw  RuntimeException("Error copying storage database");
        }
    }
    
    private fun checkIfDBExists(
        context: Context,
        databaseName: String
    ): Boolean {
        val dbfile = File(context.getDatabasePath(databaseName).toString())
        if (dbfile.exists()) return true
        if (!dbfile.parentFile.exists()) {
            dbfile.parentFile.mkdirs()
        }
        return false
    }
    

    Note: you can convert this Kotlin code to java using android studio code conversion feature.

    If you found any problem with that, kindly reply.

    Happy coding 🤓