2

I'm trying to copy a pre-populated SQLite database from the assets folder using Room's createFromAssets method. The database is sizeable (one table has ~370k entries; about 18mb total size). The database gets created in the databases folder and it contains the applicable tables, but no data is transferred to the tables.

I've reviewed the following SO questions but haven't found a solution:

  1. How to pre-populate Room Database from asset?
  2. Room: Database not created
  3. How To recreate Room Database From Assets?
  4. Android Room Library fails to copy database from Asset
  5. Room: Database not created

I've also reviewed the following informational pages:

  1. Prepopulate your Room database
  2. RoomDatabase.Builder
  3. Packing the Room: pre-populate your database with this one method
  4. Android Room with a View - Java - Codelab

I've tried waiting several minutes to see if it takes time for the database to populate, but no data ever appears. I've been downloading the database from the virtual device using AS's Device File Explorer and opening it in DB Browser for SQLite, as well as using AS's Database Inspector.

Also, LogCat always displays the following (per logging in MainActivity):

D/MainActivity: onCreate 1st 10 good words NOT FOUND

GoodWordsAndFrequency.db structure:
Table 1 - "bad_words"
Column 1 - "word", TEXT
Table 2 - "english_words"
Column 1 - "id", INTEGER
Column 2 - "word", TEXT
Column 3 - "frequency", INTEGER

app build.gradle dependencies:

dependencies {

    implementation fileTree(dir: 'libs', include: ['*.jar'])
    testImplementation 'junit:junit:4.13.1'
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'com.google.android.material:material:1.4.0'
    // For Room database
    def room_version = "2.3.0"
    implementation "androidx.room:room-runtime:$room_version"
    annotationProcessor "androidx.room:room-compiler:$room_version"
    testImplementation "androidx.room:room-testing:$room_version"

}

ValidWords entity:

import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity(tableName = "english_words")
public class ValidWords {
    @PrimaryKey(autoGenerate = true)
    @ColumnInfo(name = "id")
    public int mID;

    @ColumnInfo(name = "word")
    @NonNull
    public String mWord;

    @ColumnInfo(name = "frequency")
    public int mFrequency;

    public ValidWords(@NonNull String word) {this.mWord = word;}

    public String getWord() {return this.mWord;}

    public void setWord(@NonNull String word) {this.mWord = word;}

    public int getFrequency() {return mFrequency;}

    public void setFrequency(int frequency) {this.mFrequency = frequency;}
}

BadWords entity:

import androidx.annotation.NonNull;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;

@Entity (tableName = "bad_words")
public class BadWords {
    @PrimaryKey
    @NonNull
    @ColumnInfo(name = "word")
    public String mWord;

    public BadWords(@NonNull String word) {this.mWord = word;}

    @NonNull
    public String getWord() {return mWord;}

    public void setWord(@NonNull String mWord) {this.mWord = mWord;}
}

wordDAO interface:

import androidx.room.Dao;
import androidx.room.Insert;
import androidx.room.Query;

import java.util.List;

@Dao
public interface wordsDAO {
    @Query("SELECT * FROM english_words LIMIT 10")
    List<ValidWords> getFirstTenGoodWords();

    @Query("SELECT * FROM bad_words")
    List<BadWords> getBadWords();

    @Query("SELECT * FROM english_words WHERE word LIKE :wordToCheck LIMIT 1")
    ValidWords getMatch(String wordToCheck);

    @Insert(entity = ValidWords.class)
    long insertGoodWordInfo(ValidWords validWord);
}

ReferenceWordsDB database:

import android.content.Context;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;

@Database(entities = {BadWords.class, ValidWords.class}, version = 2)
public abstract class ReferenceWordsDB extends RoomDatabase {

    public abstract wordsDAO wordsDAO();

    private static volatile ReferenceWordsDB INSTANCE;

    static ReferenceWordsDB getDatabase(final Context context){
        if (INSTANCE == null){
            synchronized (ReferenceWordsDB.class){
                if (INSTANCE == null){
                    INSTANCE = Room.databaseBuilder(context.getApplicationContext(),
                            ReferenceWordsDB.class, "GoodWordsAndFrequency.db")
                            .createFromAsset("GoodWordsAndFrequency.db")
                            .fallbackToDestructiveMigration()
                            .allowMainThreadQueries()
                            .build();
                }
            }
        }
        return INSTANCE;
    }
}

MainActivity:

import android.content.Context;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;

import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import androidx.core.content.res.ResourcesCompat;

import java.util.ArrayList;
import java.util.List;
import java.util.Locale;

public class MainActivity extends AppCompatActivity {

    private static ArrayList<ArrayList<ValidWords>> infoFromFindActualWordRunnableClass;
    private final String LOG_CLASS = "MainActivity";
    private ReferenceWordsDB wordsDB;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        final String METHOD = "onCreate";
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        getViews();

        wordsDB = ReferenceWordsDB.getDatabase(this);
        List<ValidWords> first10GoodWords = new ArrayList<>(wordsDB.wordsDAO().getFirstTenGoodWords());

        if (first10GoodWords.size() == 0){
            Log.d(LOG_CLASS, String.format(Locale.US, "%s 1st 10 good words NOT FOUND", METHOD));
        }
        for (ValidWords currentWord : first10GoodWords){
            Log.d(LOG_CLASS, String.format(Locale.US, "%s currentWord word = %s, frequency = %d", METHOD, currentWord.getWord(), currentWord.getFrequency()));
        }

...
    }
}

I'm using Android Studio 4.2.2 with all known updates installed.

Ahnold
  • 157
  • 8
  • 1) What happens when you remove the line `.fallbackToDestructiveMigration()`? 2) In Db Browser what **User Version** is shown when you select the **Edit Pragmas**. 3) Does the source also have the **GoodWordsAndFrequency.db-wal** that is not empty (more than 0 bytes)? – MikeT Jul 24 '21 at 21:52
  • @MikeT 1) If I just delete that line and run the app, I get the same result. If I also delete the 3 files in the 'database' folder and re-run it, I get "java.lang.IllegalStateException: A migration from 1 to 2 was required but not found." 2) The User Version indicates 2 for the version I'm pulling off the device. Let me check the source db I'm putting in assets... 3) I don't have a -wai version of the file in the "assets" folder. There is one in the "databases" folder on the device and it is 0db. FYI, the .db file in the "databases" folder on the device is 18.4 Mb, but the tables are empty?? – Ahnold Jul 24 '21 at 22:33
  • Sounds like Version 1 or 0 in the assets folder. If so use DB Brwoser to change the version to 2 and retry. The fallBackToDestructive says to create from new if no migration. – MikeT Jul 24 '21 at 22:47
  • @MikeT Ok, I feel like a fool. In the source db, I had changed the User Version to 2 but forgot to click Save at the bottom of DB Browser. It didn't prompt me to write changes, so I assumed it saved the Pragmas changes immediately. Should have visually verified the source version before wasting a day researching the problem and writing my question. Thanks for catching it! Want to submit an answer and I'll accept it? – Ahnold Jul 24 '21 at 22:50

1 Answers1

6

At a guess the likely issue is that the Database is being destroyed due to a migration not existing in conjunction with the use of .fallbackToDestructiveMigration() and hence the empty but otherwise valid database.

As such you should check that the version (user_version in Edit Pragmas for Db Browesr) of the source database. It MUST match the version in the code in the @Database. In your case you have @Database(entities = {BadWords.class, ValidWords.class}, version = 2) so the user_version id DB Browser should be 2.

MikeT
  • 51,415
  • 16
  • 49
  • 68