I'm experimenting with using an existing database that is provided to the app in the Assets folder.
When I run the app for the very first time, I get an NullPointerException on an InputSteam object.
If I run the app a second time, then the input stream is not null but this time, I get an SQLiteException: no such table.
Very unusual, I'm wondering if anyone could help find the cause.
In this project, I created a simple SQLite database file and stored it in the Assets folder. It is called Customers.db and contains one table called CustomerList. The table columns are ID (integer primary key), CustomerName and Country.
In the DatabaseHelper object, the method loadDatabase() loads the database from the assets folder into the phone's internal memory.
DatabaseHelper method getRecords() returns an Array of Customer objects and these are the fields of these customer objects are listed in a RecyclerView in the MainActivity. To simply the experiment, as a first step, the getRecords() method return all the rows of the table.
When run for the first time the following exception is reported:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.mo.useexistingdatabasedemo/com.mo.useexistingdatabasedemo.MainActivity}: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.io.InputStream.read(byte[], int, int)' on a null object reference
And the second time it's run, the input stream seems to be no longer null, but instead an sqliteexception is reported:
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.mo.useexistingdatabasedemo/com.mo.useexistingdatabasedemo.MainActivity}: android.database.sqlite.SQLiteException: no such table: CustomerList (code 1 SQLITE_ERROR): , while compiling: SELECT * FROM CustomerList
In ,my project class DatabaseHelper is defined as follows (and its methods are called from the MainActivity).
package com.mo.useexistingdatabasedemo;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.Buffer;
import java.util.ArrayList;
public class DatabaseHelper extends SQLiteOpenHelper {
public static ArrayList<Customer> arrayList = new ArrayList<>();
public static final String DATABASE_NAME = "Customers.db";
public static final String TABLE_NAME = "CustomerList";
public static final int DATABASE_VERSION = 1;
public static final String DATABASE_DIRECTORY_PATH = "data/data/com.mo" +
".useexistingdatabasedemo/databases";
public static final String DATABASE_FILE_PATH = DATABASE_DIRECTORY_PATH + DATABASE_NAME;
Context context;
InputStream inputStream;
OutputStream outputStream;
Buffer buffer;
public static final String SELECT_ALL_TABLE = "SELECT * FROM " + TABLE_NAME;
public DatabaseHelper(#Nullable Context context) {
super(context, DATABASE_NAME, null, 2);
this.context = context;
}
public void loadDatabase() {
String path = DATABASE_DIRECTORY_PATH + DATABASE_NAME;
File dbFile = new File(path); // the descriptor of a file in internal memory to which
// we will write the db in the assets folder.
// if the database isn't already in internal memory, copy it over from assets.
if (!dbFile.exists()) {
try {
inputStream = context.getAssets().open(DATABASE_NAME);
} catch (IOException e) {
Log.e("IOException input stream: ",
"Exception opening input stream " + e.getMessage());
}
OutputStream outputStream = null;
try {
outputStream = new FileOutputStream(path);
} catch (FileNotFoundException e) {
Log.e("IOException, output stream: ",
"Exception creating output stream " + e.getMessage());
}
// create a buffer of 1024 bytes length
byte[] buffer = new byte[1024];
int len;
try {
while ((len = inputStream.read(buffer, 0, 1024)) > 0)
outputStream.write(buffer, 0, len);
outputStream.flush();
inputStream.close();
} catch (IOException e) {
Log.e("IOException", "Exception occurred in while block" + e.getMessage());
}
}
Log.i("Load database", "The method public loadDatabase() executed succesffully");
}
#Override
public void onCreate(SQLiteDatabase db) {
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
public ArrayList getRecords() {
SQLiteDatabase database = getWritableDatabase();
Log.i("The attached database is :", database.getAttachedDbs().toString());
Cursor cursor = database.rawQuery("SELECT * FROM " + TABLE_NAME, null);
while (cursor.moveToNext()) {
Customer customer = new Customer();
customer.customerName = cursor.getColumnName(1).toString();
customer.country = cursor.getColumnName(2).toString();
arrayList.add(customer);
}
return arrayList;
}
}
Unusual behaviour in app using existing database
The Issue(s)
Not at all unusual. You are not copying the database to it's expected location, which is data/data/your_package/databases (directory) However no need to know or even code that (see later).
First run and the copy fails BUT the file is created.
Second run as the file exists, no attempt is made to copy. So theSQLiteOpenHelper subclass can't find the database, as it's not in the expected location, so it invokes the onCreate method and creates the database without any tables and hence the table not found.
The Fix
I would suggest NOT hard coding the path but getting the path via the Context's getDatabasePath method. e.g.
String dbpath = context.getDatabasePath(name).getPath();
or as a File :-
File db = context.getDatabasePath(name);
After acquiring I suggest checking if the parent directory exists and if not then issuing a mkdirs so that the databases folder is created if it doesn't exist (for older API's it will not and it's non existence prevents the copy working but with the file existing, I believe I saw that later versions create the directory). e.g. something like :-
File db = context.getDatabasePath(name);
if (!db.getParentFile().exists()) db.mkdirs();
String dbpath = db.getPath(); // may not be needed as the File object is what is then required
Here's is an example of a pretty bulletproof SQLiteOpenHelper subclass that will copy a pre-existing database.
Related
In my app i used sqlite db file for getting some data. It works fine for most of the users but some of them getting crash Cause by: android.database.sqlite.SQLiteException: no such table: getCtgList (code 1): ,
while compiling: "SELECT *FROM Category WHERE tag =?", new String[]{string}"
Below is my SQLite DBHelper class to create and copy database. in asster/font.db has a Category table . after copying this table some of device missing this table. I searched a lot to fix this error but didn't find any solution. and I'm getting trending issues for app crashing on google play store. Please help me to fix this error.
Code:
package com.softtechbd.nickname_finder.Database;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.Nullable;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
public class DBHelper extends SQLiteOpenHelper {
public static final String location = "/data/data/com.com.softtechbd.stylishnicknamegenerator/databases";
private static final String DB_Name = "font_data.db";
private static final int DB_Ver = 2;
private Context mContext;
private SQLiteDatabase database;
private Object String;
public DBHelper(#Nullable Context context) {
super(context, DB_Name, null, DB_Ver);
this.mContext= context;
initialize();
}
private void initialize() {
if (databaseExists() ){
SharedPreferences prefs = PreferenceManager
.getDefaultSharedPreferences(mContext);
int dbVersion = prefs.getInt("db_ver", 1);
if (DB_Ver != dbVersion) {
File dbFile = mContext.getDatabasePath(DB_Name);
if (!dbFile.delete()) {
Log.w("dbexists", "Unable to update database");
}
}
}
if (!databaseExists()){
this.getReadableDatabase();
try {
copyDatabase();
}catch (Exception e){
e.printStackTrace();
}
copyDatabase();
//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.
}
}
private void copyDatabase(){
try {
InputStream inputStream = mContext.getAssets().open(DB_Name);
FileOutputStream fileOutputStream = new FileOutputStream(
"/data/data/"+mContext.getPackageName()+"/databases/"+DB_Name);
byte[]arrby = new byte[1024];
do {
int n;
if((n=inputStream.read(arrby)) <=0 ){
fileOutputStream.flush();
fileOutputStream.close();
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences((mContext)).edit();
editor.putInt("db_ver",DB_Ver);
editor.apply();
// Toast.makeText(mContext, "SQLite DB copied", Toast.LENGTH_SHORT).show();
return;
}
fileOutputStream.write(arrby,0,n);
}while (true);
}catch (Exception e){
e.printStackTrace();
}
}
private void closeDatabase(){
SQLiteDatabase sqLiteDatabase = this.database;
if(sqLiteDatabase!= null){
sqLiteDatabase.close();
}
}
private boolean databaseExists() {
File dbFile = mContext.getDatabasePath(DB_Name);
return dbFile.exists();
}
#Override
public void onCreate(SQLiteDatabase db) {
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
public void openDatabase(){
String string = this.mContext.getDatabasePath(DB_Name).getPath();
SQLiteDatabase sqLiteDatabase = this.database;
if(sqLiteDatabase!=null && sqLiteDatabase.isOpen()){
return;
}
this.database=SQLiteDatabase.openDatabase(string,null,0);
}
public ArrayList<String> getSymbols() {
ArrayList<String> arrayList = new ArrayList<>();
openDatabase();
Cursor cursor = database.rawQuery("SELECT *FROM symbols ", null);
cursor.moveToFirst();
while (!cursor.isAfterLast()){
arrayList.add(cursor.getString( cursor.getColumnIndex("content")));
cursor.moveToNext();
}
cursor.close();
closeDatabase();
return arrayList;
}
public ArrayList<String> getHotName (){
ArrayList<String> arrayList = new ArrayList<>();
openDatabase();
Cursor cursor = database.rawQuery("SELECT *FROM hot_names", null);
cursor.moveToFirst();
while (!cursor.isAfterLast()){
arrayList.add(cursor.getString( cursor.getColumnIndex("contents")));
cursor.moveToNext();
}
cursor.close();
closeDatabase();
return arrayList;
}
public ArrayList<String> getList(String string){
ArrayList<String> arrayList = new ArrayList<>();
this.openDatabase();
Cursor cursor = this.database.rawQuery("SELECT * FROM emotication_detail WHERE parent_id =?",new String[]{string});
cursor.moveToFirst();
while (!cursor.isAfterLast()){
arrayList.add( cursor.getString( cursor.getColumnIndex("content")) );
// item sample = new item(cursor.getInt(0),cursor.getString(1));
cursor.moveToNext();
}
cursor.close();
this.closeDatabase();
return arrayList;
}
public ArrayList<String> getCtgList (String string){
ArrayList<String> arrayList = new ArrayList<>();
openDatabase();
Cursor cursor = database.rawQuery("SELECT *FROM Category WHERE tag =?", new String[]{string});
cursor.moveToFirst();
while (!cursor.isAfterLast()){
arrayList.add(cursor.getString( cursor.getColumnIndex("content")));
cursor.moveToNext();
}
cursor.close();
closeDatabase();
return arrayList;
}
}
Error log:
Fatal Exception: android.database.sqlite.SQLiteException: no such table: Category (code 1 SQLITE_ERROR): , while compiling: SELECT *FROM Category WHERE tag =?
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(SQLiteConnection.java)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:903)
at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:514)
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:1408)
at android.database.sqlite.SQLiteDatabase.rawQuery(SQLiteDatabase.java:1347)
at com.softtechbd.nickname_finder.Database.DBHelper.getCtgList(DBHelper.java:18)
at com.softtechbd.nickname_finder.Fragments.pick_category_list.onCreateView(pick_category_list.java:4)
at androidx.fragment.app.Fragment.R(Fragment.java:15)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:27)
at androidx.fragment.app.FragmentManagerImpl.b0(FragmentManagerImpl.java:49)
at androidx.fragment.app.FragmentManagerImpl.moveToState(FragmentManagerImpl.java:42)
at androidx.fragment.app.BackStackRecord.executeOps(BackStackRecord.java:6)
at androidx.fragment.app.FragmentManagerImpl.executeOps(FragmentManagerImpl.java:7)
at androidx.fragment.app.FragmentManagerImpl.removeRedundantOperationsAndExecute(FragmentManagerImpl.java:88)
at androidx.fragment.app.FragmentManagerImpl.execPendingActions(FragmentManagerImpl.java:4)
at androidx.fragment.app.FragmentManagerImpl$2.run(FragmentManagerImpl.java:2)
at android.os.Handler.handleCallback(Handler.java:873)
at android.os.Handler.dispatchMessage(Handler.java:99)
at android.os.Looper.loop(Looper.java:193)
at android.app.ActivityThread.main(ActivityThread.java:6758)
at java.lang.reflect.Method.invoke(Method.java)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:497)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:912)
I would suggest changing :-
private boolean databaseExists() {
File dbFile = mContext.getDatabasePath(DB_Name);
return dbFile.exists();
}
to :-
private boolean databaseExists() {
File dbFile = mContext.getDatabasePath(DB_Name);
if (dbFile.exists()) {
return true;
}
dbFile.mkdirs(); //<<<<<<<<<< creates the databases folder
return false;
}
along with removing the line this.getReadableDatabase();
The use of getReadableDatabase (or getWritableDatabase as they are effectively the same in most siutations) hides the real underlying issue which is that the databases folder not existing results in the copy failing. That is getReadableDatabase will create the missing databases folder.
Although this works for older devices, newer devices (Android 9+) default to using write-ahead logging (WAL). If a database is created using WAL then two additional files, the WAL file (database file name suffixed with -wal) and the shared memory file (database file name suffixed with -shm) are created.
If just the database file is overwritten (as per the comments) then the -wal file is incompatible with the new database and the attempt to open the database fails, the failure is captured and instead an empty database file is created and returned and hence the loss of any tables. Hence the issue that you are facing.
Creating the databases folder if the database file does not exist, eliminates the need to use getReadable database to create the databases folder and thus the creation of -wal and -shm files. Thus why getReadableDatabase should not be used prior to the copy of the assets file.
Alternately you could delete the -wal and -shm files if they exist, prior to the copy.
i.e. the comments
WAL instead of recording changes in the log allowing them to be rolled back (journal mode) writes the changes to the WAL file and they are applied to the actual database when committed, thus the WAL file is effectively part of the database (roll back is effectively deleting WAL file). You may wish to refer to https://sqlite.org/wal.html
I would also suggest NOT hard coding the database path in the line
FileOutputStream fileOutputStream = new FileOutputStream("/data/data/"+mContext.getPackageName()+"/databases/"+DB_Name);
but instead use mContext.getDatabasePath(DB_NAME) to get the path.
e.g.
FileOutputSream fileOutputStrean = new FileOutputStream(mContext.getDatabasePath(DB_NAME));
Note the above code has not been tested so it may contain some errors, rather it is the principle that is being communicated.
An alternative approach could be to force journal mode using the journal_mode pragma or the SQliteDatabase disableWriteAheadLogging method. https://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#disableWriteAheadLogging()
This is my App tab shows
E/SQLiteLog: (1) no such table: quest
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.developmethis.csguide.csquizmodule, PID: 22684
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.developmethis.csguide.csquizmodule/com.developmethis.csguide.csquizmodule.MainActivity}: android.database.sqlite.SQLiteException: no such table: quest (code 1): , while compiling: SELECT * FROM quest WHERE q_id='cp1'
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2480)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2540)
at android.app.ActivityThread.access$900(ActivityThread.java:150)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1390)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:168)
at android.app.ActivityThread.main(ActivityThread.java:5781)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687)
Caused by: android.database.sqlite.SQLiteException: no such table: quest (code 1): , while compiling: SELECT * FROM quest WHERE q_id='cp1'
at android.database.sqlite.SQLiteConnection.nativePrepareStatement(Native Method)
at android.database.sqlite.SQLiteConnection.acquirePreparedStatement(SQLiteConnection.java:891)
at android.database.sqlite.SQLiteConnection.prepare(SQLiteConnection.java:502)
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:68)
at android.database.sqlite.SQLiteDatabase.rawQueryWithFactory(SQLiteDatabase.java:1402)
at android.database.sqlite.SQLiteDatabase.rawQuery(SQLiteDatabase.java:1341)
at com.developmethis.csguide.csquizmodule.DbHelper.getAllQuestions(DbHelper.java:96)
at com.developmethis.csguide.csquizmodule.MainActivity.onCreate(MainActivity.java:38)
at android.app.Activity.performCreate(Activity.java:6248)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1125)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2433)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2540)
at android.app.ActivityThread.access$900(ActivityThread.java:150)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1390)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:168)
at android.app.ActivityThread.main(ActivityThread.java:5781)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687)
I have copied the SQLite file into assets folder.this is my directory
What happening is I copied the file from the assets folder and then I'm trying to read data from the SQL table quest although I have double checked the data in the SQLite file through DB browser
This is the view from DB browser
Here is the code of my DBhelper class :
package com.developmethis.csguide.csquizmodule;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.FileHandler;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
public class DbHelper extends SQLiteOpenHelper{
private static final int DATABASE_VERSION = 1;
public static final String DATABASE_NAME = "sample.sqlite";
public static final String TABLE_QUEST = "quest";
public static final String KEY_QUIZ_ID = "q_id";
private final static String DB_PATH = "/data/data/package com.developmethis.csguide/databases/";
String dbName;
Context context;
File dbFile;
public DbHelper(Context context) {
super(context, DATABASE_NAME, null, 1);
this.context = context;
this.dbName = DATABASE_NAME;
dbFile= new File(DB_PATH + DATABASE_NAME);
}
#Override
public synchronized SQLiteDatabase getWritableDatabase() {
if(!dbFile.exists()){
SQLiteDatabase db = super.getWritableDatabase();
copyDataBase(db.getPath());
}
return super.getWritableDatabase();
}
#Override
public synchronized SQLiteDatabase getReadableDatabase() {
if(!dbFile.exists()){
SQLiteDatabase db = super.getReadableDatabase();
copyDataBase(db.getPath());
}
return super.getReadableDatabase();
}
#Override
public void onCreate(SQLiteDatabase db) {}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {}
private void copyDataBase(String dbPath){
try{
InputStream assestDB = context.getAssets().open("databases/"+dbName);
OutputStream appDB = new FileOutputStream(dbPath,false);
byte[] buffer = new byte[1024];
int length;
while ((length = assestDB.read(buffer)) > 0) {
appDB.write(buffer, 0, length);
}
appDB.flush();
appDB.close();
assestDB.close();
}catch(IOException e){
e.printStackTrace();
}
}
public List<question> getAllQuestions(String quizID) {
SQLiteDatabase dbase;
List<question> quesList = new ArrayList<question>();
// Select All Query
String selectQuery = "SELECT * FROM " + TABLE_QUEST+" WHERE "+KEY_QUIZ_ID+"='"+quizID+"'";
dbase=this.getReadableDatabase();
Cursor cursor = dbase.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
question quest = new question();
quest.setID(cursor.getInt(cursor.getColumnIndex("id")));
quest.setQUIZ_ID(cursor.getString(cursor.getColumnIndex("q_id")));
quest.setQUESTION(cursor.getString(cursor.getColumnIndex("question")));
quest.setANSWER(cursor.getString(cursor.getColumnIndex("answer")));
quest.setOPTA(cursor.getString(cursor.getColumnIndex("opta")));
quest.setOPTB(cursor.getString(cursor.getColumnIndex("optb")));
quest.setOPTC(cursor.getString(cursor.getColumnIndex("optc")));
quesList.add(quest);
} while (cursor.moveToNext());
}
// return quest list
return quesList;
}
}
This is the view from DB browser
Your view from db browser shows that the asset file is in the assets folder/directory. Your code :-
InputStream assestDB = context.getAssets().open("databases/"+dbName);
Is saying to look in assets/databases folder/directory.
Either
a) create a databases directory in the assets directory and move sample.sqlite into the databases directory.
or
b) change the code to InputStream assestDB = context.getAssets().open(dbName);
I would also suggest using :-
dbFile= new File(context.getDatabasePath(dbname));
dbfile.mkdirs();
instead of :-
dbFile= new File(DB_PATH + DATABASE_NAME);
You can then do sway with the DB_PATH variable.
This will then get the appropriate path name and if need be create any directories if they don't exist (sometimes I've come across the databases directory not existing).
Note! Only re-run the App after deleting the App's data or uninstalling the App. If you do not then the check for the existing database will find one and not copy the database.
PS as you're trapping the exceptions (
try....
catch(IOException e){
e.printStackTrace();
}
) when copying the asset file, you should always check the log for stack traces prior to an error. You would have had these.
Did you try CREATE TABLE IF NOT EXISTS after opening db:
val db = openOrCreateDatabase(path, null)
db.execSQL("CREATE TABLE IF NOT EXISTS (Varchar,VARCHAR-or-XYZZ);")
or equivalent in java if your code is in java.
I have done many tests on an android emulator running in version 4.4.
On my app I create a sqlite database with one table using SQLiteOpenHelper:
package com.findwords.modeles;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import com.findwords.MainActivity;
import com.findwords.controleurs.MenuController;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
/**
* Created by louk on 02/01/14.
*/
public class DictionaryDbHelper extends SQLiteOpenHelper{
// declare constants fields
private static final String DB_PATH = "/data/data/com.findwords/databases/";
private static final String DB_NAME = "dictionary_db";
private static final int DB_VERSION = 1;
// declared constant SQL Expression
private static final String DB_CREATE =
"CREATE TABLE dictionary ( " +
"_id integer PRIMARY KEY AUTOINCREMENT, " +
"word text NOT NULL, " +
"definition text NOT NULL, " +
"length integer NOT NULL " +
");";
private static final String DB_DESTROY =
"DROP TABLE IF EXISTS dictionnary";
/*
* constructor
*/
public DictionaryDbHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
/**
* 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.getReadableDatabase();
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(){
SQLiteDatabase checkDB = null;
try{
String myPath = DB_PATH + DB_NAME;
checkDB = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
}catch(SQLiteException e){
//database does't exist yet.
}
if(checkDB != null){
checkDB.close();
}
return checkDB != null ? true : false;
}
/**
* 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 = MenuController.getInstance().getMainActivity().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;
while ((length = myInput.read(buffer))>0){
myOutput.write(buffer, 0, length);
}
//Close the streams
myOutput.flush();
myOutput.close();
myInput.close();
}
/*
* (non-Javadoc)
* #see android.database.sqlite.SQLiteOpenHelper#onCreate(android.database.sqlite.SQLiteDatabase)
*/
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(DB_CREATE);
try {
createDataBase();
} catch (IOException e) {
e.printStackTrace();
}
}
/*
* (non-Javadoc)
* #see android.database.sqlite.SQLiteOpenHelper#onUpgrade(android.database.sqlite.SQLiteDatabase, int, int)
*/
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(DB_DESTROY);
onCreate(db);
}
}
Moreover I have written an adapter with a method open:
/*
* open database connection
*/
public DictionaryDbAdapter open() throws SQLException {
mDbHelper = new DictionaryDbHelper(mContext);
mDb = mDbHelper.getWritableDatabase();
return this;
}
It's working well on the emulator so the onCreate() method of the SQLiteOpenHelper class is called and create the database, but is not called on my phone (Google Nexus 5).
My phone is not rooted so I can't access the folder /data/data/com.myapp/databases .
However I want this application to work on any phone so I don't want to root my phone.
Thanks in advance to anyone who could help me.
Let i try to explain you some things.
In an application to connect to the database , we specify the name and version of the database . In this situation, the following may occur :
1) There is no database . This may be for example in the case of initial setting program. In this case, the application itself must create the database and all the tables in it. And further, it is already working with the newly created database.
2) Database exists, but its version is outdated. It may be the case update. For example a new version of the program need additional fields in the old tables or new tables . In this case, the application must update existing tables and create new ones if necessary.
3) There is a database and its actual version . In this case, the application successfully connects to the database and running.
As you know , the phrase " application must " tantamount to the phrase " the developer must ", ie it is our task . To handle the situations described above , we need to create a class that inherits for SQLiteOpenHelper. Call it DBHelper. This class will provide us with methods to create or update the database in case of their absence or obsolescence.
onCreate - a method that will be called if the database to which we want to connect - does not exist(it's your case)
The onCreate method is called only for the first time - when the DB is actually created. So if you uninstall your app and then install it again - it will get called, but if you install on top of the existing copy onCreate will not be called (since the DB already exists)
As #Asahi said, Database is only created only if you reinstall the app. But since you said that My phone is not rooted so I can't access the folder /data/data/com.myapp/databases, I want to point out that you can connect your mobile to the computer, install the correct USB drivers and use DDMS to see the file structure of your mobile phone. There you can see the database of your app along with the Shared Preferences and other files.
PS :- To see all the folder of real device on ddms you need root access. If your device is not rooted and you don't want to root your one then you can install the device on emulator which shows all folders in DDMS.
My code is now as follows:
Main app java:
String TAG = "DATABASES";
try {
String destPath = "/data/data/" + getPackageName()
+ "/databases/data.db";
File f = new File(destPath);
if(!f.exists()){
Log.v(TAG,"Dest DB doesn't exist");
InputStream in = getAssets().open("airports.db");
OutputStream out = new FileOutputStream(destPath);
byte[] buffer = new byte[1024];
int length;
while ((length = in.read(buffer)) > 0) {
out.write(buffer, 0, length);
}
in.close();
out.close();
} else {
Log.v(TAG,"File exists: " + destPath);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
Log.v(TAG,"ioexeption");
e.printStackTrace();
}
DBManager dbManager = new DBManager(this);
Log.v(TAG,"Database is there with version: "+dbManager.getReadableDatabase().getVersion());
//String sql = "select * from airports where IATA='GRO' ";
String sql = "select count(*) from airports";
SQLiteDatabase db = dbManager.getReadableDatabase();
Cursor cursor = db.rawQuery(sql, null);
Log.v(TAG,"Query Result:"+cursor);
cursor.close();
db.close();
dbManager.close();
My DBManager.java:
package com.jammo.mywidget4;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class DBManager extends SQLiteOpenHelper {
private static final int DATABASE_VERSION = 1;
private static final String TAG = "DATABASES";
public DBManager(Context context) {
super(context, "data.db", null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
Log.v(TAG,"On create Called:"+db.getPath());
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
Now the execution runs ok in the main.java on initiation of "db", however fails on the next line where it tries to execute rawQuery()
FYI "select count(*) from airports" run on my Sqlite DB Manager GUI returns 1650 rows.
Error log is saying :
03-04 21:54:24.200: E/AndroidRuntime(11513): FATAL EXCEPTION: main
03-04 21:54:24.200: E/AndroidRuntime(11513): java.lang.RuntimeException: Unable to start activity ComponentInfo{com.jammo.mywidget4/com.jammo.mywidget4.SectorInfo}: android.database.sqlite.SQLiteException: no such table: airports: , while compiling: select count(*) from airports
Local /assets/airports.db "seems" to be being detected and copied to /data/data/mypackage/databases/data.db as "file is found"
Many thanks
J
Try it without using your DBManager class.
Comment out this line...
DBManager dbManager = new DBManager(this);
...then open the database explicitly by replacing this line...
SQLiteDatabase db = dbManager.getReadableDatabase();
...with...
SQLiteDatabase db = SQLiteDatabase.openDatabase(destPath, null, SQLiteDatabase.OPEN_READONLY);
Then clear the app data so the database is re-copied from your assets directory.
EDIT: Even though my suggestion worked, it's actually just a workaround and the root of your problem was this...
String destPath = "/data/data/" + getPackageName() + "/databases/data.db";
You should avoid using hard-coded paths with Android - they may work fine for many devices but there is no guarantee they'll work with all devices or even with all versions of Android.
The problem was you were creating / copying your database to that path but the DBManager class was presumably creating an empty database located somewhere on a different path in the file-system.
To fix this for all devices and Android versions, before copying the database, I would use...
SQLiteDatabase.openOrCreateDatabase("data.db", null)
...to create a 'dummy' (empty) database. Then I'd call...
getDatabasePath("data.db")
...to get the absolute path to data.db. You can then overwrite that database during the copy operation and from then on you can use a class which extends SQLiteOpenHelper and it will find the correct database.
I am new to Databases and do not know if this would be the best way to do what I want to achieve. I want to create a custom list of restaurants around me, then search them, and sort them on ranking, title, or location.
Would I need to create a database for this? I technically could use a text file and Arrays but I feel like this is very inefficient. I would need a Node to contain the following data: Name of Establishment, Address, Phone Number, Ranking (Based of Our Ranking System).
How should I go about doing this?
Yes, you definitely want to use a database. If you use a database local to the phone, you need to use an SQLite database. Here is a good place to start.
If you want a database that is preloaded in the phone, put it in your assets folder. Here is an example of a database helper class with the database packaged with the phone:
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.DateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Vector;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import android.util.Log;
public class DataBaseHelper extends SQLiteOpenHelper{
private static final String TAG = "DataBaseHelper";
//The Androids default system path of your application database.
private static String DB_PATH = "/data/data/yourpackage/databases/";
private static String DB_NAME = "DatabaseName";
public SQLiteDatabase myDataBase;
private final Context myContext;
/**
* Constructor
* Takes and keeps a reference of the passed context in order to access to the application assets and resources.
* #param context
*/
public DataBaseHelper(Context context) {
super(context, DB_NAME, null, 1);
this.myContext = context;
}
/**
* 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 doesnt
*/
private boolean checkDataBase(){
SQLiteDatabase checkDB = null;
try{
String myPath = DB_PATH + DB_NAME;
checkDB = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READWRITE);
}catch(SQLiteException e){
//database doest exist yet.
}
if(checkDB != null){
checkDB.close();
}
return checkDB != null ? true : false;
}
/**
* 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);
Log.d(TAG, "found the database");
// 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[2048];
int length;
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) {
}
// Add your public helper methods to access and get content from the database.
// You could return cursors by doing "return myDataBase.query(....)" so itd be easy
// to you to create adapters for your views.
A SQLite database would be an excellent way to accomplish your application goals. Based on what you said, a single table called "Restaurants" containing 5 fields: _id, Name, Address, Phone, Rank would do the trick.
The SQLite SQL documentation is located here: http://www.sqlite.org/lang.html . Some of the commands you'll need to be familiar with are CREATE TABLE, INSERT, UPDATE and SELECT. Sqlite for Android is a well-documented standard found across the web. You should have no trouble finding examples of how to accomplish your task.
Remember to close any database objects that you create else, you'll create a leak.
Good luck!
What you want to do is create a Web Service, this lives in the "cloud" on the Internets somewhere. Then you have your mobile app periodically contact the web service and request restaurant data perhaps based on the users current location, so the app passes the gps coordinates to the web service and the Web Service checks its database for restaurants within X distance of that location, then the Web Service returns the list to the app in a format such as Json or XML, the App parses this info and displays it to the user.
Ideally you would also have a database on the phone that would store results on the device this reduces the amount of requests you have to pull from the server, saving on battery, data and server resources. This gets a little complicated as you have to decide what to ask for intelligently and the server has to decide what to send you.
There was a Google I/O talk on how to do this.
http://www.youtube.com/watch?v=xHXn3Kg2IQE