1

so I want to make an app that lists foods that I have stored in an already existing SQLite database I created. I have my foodDB.db file in the /assets/databases folder.

I know I must use a DatabaseHelper class. I have done my fair share of research, I have looked at plenty of stackoverflow posts and youtube tutorials but none satisfy my critera of populating a ListView from an existing database in the asset folder. I find them very confusing and not clear.

Could someone please clearly explain to me how I would go about this? I am really struggling and would appreciate some guidance.

My database is called foodDatabase.db.
My database contains 1 table called dataset.
The table dataset contains the colums: id, name, description, protein, fat, carbohydrates, energy, starch, sugar, cholesterol.

1 Answers1

0

First a few points to be noted.

  • 1 - you have said that the file is FoodDB.db but then said that the database is called foodDatabase.db. The database name is the file name. As such the following example uses FoodDB.db as the file that is in the assets (you could rename the file when copying from the assets but I've not done that in this answer.)

  • 2 - You do not need a Database Helper and the example doesn't use one.

The are two parts :-

  • 1) Accessing the database to extract the data for the ListView. In the following example this is copied from the assets to the standard database location (/data/data/the_package/databases/the_database_name).

    • The assumption has been made that once copied from the assets the database will then subsequently be used (i.e it is copied once for the lifetime of the App).
  • 2) Displaying the extracted data (obtained as a Cursor) in a ListView.

To do 2 you will need the layout to include a ListView, as such the following layout has been used. activity_main.xml :-

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="fooddb.so49328656populatelistview.MainActivity">

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"
        />
    <ListView
        android:id="@+id/foodlist"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
    </ListView>
</LinearLayout>
  • Note tools:context="fooddb.so49328656populatelistview.MainActivity" would have to reflect YOUR package

The Database Helper - Not used as warned

The invoking activity MainActivity.java (see notes) :-

public class MainActivity extends AppCompatActivity {

    static final String DBNAME = "FoodDB.db";
    static final String DBASSETPATH = "databases/" + DBNAME;
    static final String FOODTABLE = "dataset";
    static final String FOODCOLUMN = "Food";
    static final String IDCOLUMN = "ID";

    ListView mFoodList;
    SQLiteDatabase mDB;
    SimpleCursorAdapter mSCA;
    Cursor mCsr;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mFoodList = (ListView) this.findViewById(R.id.foodlist);
        mDB = openFoodDB();
        if (mDB != null) {
            mCsr = mDB.query(FOODTABLE,
                    new String[]{IDCOLUMN + " AS _id",
                            FOODCOLUMN
                    },
                    null,null,null,null,null);
            mSCA = new SimpleCursorAdapter(this,android.R.layout.simple_list_item_1,mCsr,
                    new String[]{FOODCOLUMN},
                    new int[]{android.R.id.text1},0);
            mFoodList.setAdapter(mSCA);
        } else {
            Toast.makeText(this,"Unable to open Database.",Toast.LENGTH_LONG);
        }
    }

    private SQLiteDatabase openFoodDB() {
        String dbpath = this.getDatabasePath(DBNAME).getPath();
        if (this.getDatabasePath(DBNAME).exists()) {
            Log.d("OPENFOODDB","Opening already existing Database");
            return SQLiteDatabase.openDatabase(dbpath,null,SQLiteDatabase.OPEN_READWRITE);
        }
        InputStream is;
        byte[] buffer;
        FileOutputStream db;
        try {
             is =  this.getAssets().open(DBASSETPATH);
             buffer = new byte[is.available()];
             is.read(buffer);
             is.close();
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("OPENFOODDB","Unable to locate or buffer input from assets " + DBASSETPATH);
            return null;
        }
        // Just in case the databases directory doesn't exist create it.
        File dbmkdir = (this.getDatabasePath(DBNAME)).getParentFile();
        dbmkdir.mkdirs();
        try {
            db = new FileOutputStream(this.getDatabasePath(DBNAME).getPath());
        } catch (Exception e) {
            e.printStackTrace();
            Log.d("OPENFOODDB","Unable to create outputstream for DB at path " + dbpath);
            try {
                is.close();
            } catch (Exception e2) {
            }
            return null;
        }
        try {
            db.write(buffer);
            db.flush();
            db.close();
            is.close();
        } catch (Exception e) {
            Log.d("OPENFOODDB","Failed to copy asset to DB");
            e.printStackTrace();
            return null;
        }
        return SQLiteDatabase.openDatabase(dbpath,null,SQLiteDatabase.OPEN_READWRITE);
    }
}

Notes

  • the openFoodDB method returns the SQLiteDatabase after copying if from the assets file if the database does not exist. The method will return null if there were issues.

    • if the database exists then the log will contain a message like D/OPENFOODDB: Opening already existing Database
    • there will be no log messages if the database was copied from the assets file and opened successfully. Messages will only be logged if there is an issue.
    • if for example the assets file is missing then you would get a message in the log like D/OPENFOODDB: Unable to locate or buffer input from assets databases/FoodDB.db this would be preceded with a stack trace

e.g. :-

03-16 22:17:04.008 1529-1529/? W/System.err: java.io.FileNotFoundException: databases/FoodDB.db
03-16 22:17:04.008 1529-1529/? W/System.err:     at android.content.res.AssetManager.openAsset(Native Method)
03-16 22:17:04.008 1529-1529/? W/System.err:     at android.content.res.AssetManager.open(AssetManager.java:315)
03-16 22:17:04.008 1529-1529/? W/System.err:     at android.content.res.AssetManager.open(AssetManager.java:289)
03-16 22:17:04.008 1529-1529/? W/System.err:     at fooddb.so49328656populatelistview.MainActivity.openFoodDB(MainActivity.java:63)
03-16 22:17:04.008 1529-1529/? W/System.err:     at fooddb.so49328656populatelistview.MainActivity.onCreate(MainActivity.java:37)
03-16 22:17:04.008 1529-1529/? W/System.err:     at android.app.Activity.performCreate(Activity.java:5008)
03-16 22:17:04.008 1529-1529/? W/System.err:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1079)
03-16 22:17:04.008 1529-1529/? W/System.err:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2023)
03-16 22:17:04.008 1529-1529/? W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084)
03-16 22:17:04.008 1529-1529/? W/System.err:     at android.app.ActivityThread.access$600(ActivityThread.java:130)
03-16 22:17:04.008 1529-1529/? W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195)
03-16 22:17:04.008 1529-1529/? W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:99)
03-16 22:17:04.008 1529-1529/? W/System.err:     at android.os.Looper.loop(Looper.java:137)
03-16 22:17:04.008 1529-1529/? W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:4745)
03-16 22:17:04.008 1529-1529/? W/System.err:     at java.lang.reflect.Method.invokeNative(Native Method)
03-16 22:17:04.008 1529-1529/? W/System.err:     at java.lang.reflect.Method.invoke(Method.java:511)
03-16 22:17:04.008 1529-1529/? W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
03-16 22:17:04.008 1529-1529/? W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
03-16 22:17:04.008 1529-1529/? W/System.err:     at dalvik.system.NativeStart.main(Native Method)
  • A CursorAdapter requires a column specifically named _id hence the use of IDCOLUMN + " AS _id".

The Result :-

enter image description here

MikeT
  • 51,415
  • 16
  • 49
  • 68
  • hi mike, sorry for the late reply. I have done as you said but it still does not work. I have noticed that my foodDB.db file looks like this when I open it. [link](https://gyazo.com/900076adbb190d83bee62d18cc956733) . Is this wrong? –  Mar 19 '18 at 12:18
  • also I created a new project and named it the exact same as yours. so the only difference would be the db file we are using. On the `tools:context=` line in the activity_main.xml file. you said it must reflect my package. so to my understanding mine should be this? `tools:context="com.example.tom.so49328656populatelistview.MainActivity"` although i misunderstood this. hope to hear from you again soon. thanks for you initial respones :) –  Mar 19 '18 at 13:45
  • You cannot use Android Studio to open SQLite databases it has no inbuilt handler (however the screenshot appears to show a valid SQLite database i.e. first 16 characters appear to be ***SQLite format 3\000*** as per [Database File Format](https://www.sqlite.org/fileformat.html)). The case of the file name is taken into consideration so `static final String DBNAME = "FoodDB.db";` would have to be changed to `static final String DBNAME = "foodDB.db";` (lowercase **f** for the first character). – MikeT Mar 19 '18 at 19:18
  • can you look at the other comment I added about the `tools:context` line. I'm getting 'E/memtrack: Couldn't load memtrack module' error in Logcat when I run the app. –  Mar 19 '18 at 19:48
  • Just create the layout as a new one, all you need is the ListView with an id of foodlist e.g. ` `. The ensure that in the MainActivity that `setContentView(R.layout.activity_main);` reflects the layout. – MikeT Mar 19 '18 at 21:31
  • I can't get it to work. I noticed that `mFoodList = (ListView) this.findViewById(R.id.foodlist);` Says "Casting this.findViewById(R.id.foodlist is redundant." Also on tutorials I have looked at people don't use the asset/database directory. Just copy the db into the asset folder? I'm just trying to work out why it worked fine for you but not for me. –  Mar 19 '18 at 22:59
  • I cast to ListView just in case someone is using a older version of Android Studio, it being redundant isn't a failure (just saying you don't need to cast). Some use asset some use database directory. The if the latter then path should be databases/name_of_the_file else just name_of_the_file. I don't believe that the `can't load memtrack` is the issue. – MikeT Mar 20 '18 at 01:16