I am currently testing the Android API, and to do so, I have developed an application that copies a database that I have made beforehand and testing that application (which calls on several Android classes). Someone directed me to using Robolectric to get coverage of the Android source, however, this has caused some problems. My application runs just fine without it, as well as my test, but now I'm running into errors with the copying of my database. Whenever I run the test, my catch gives me an error from copying the database, and taking out that catch results in an AssertionFailureError
assertTrue(activity.accessAdapter() != null);
assertTrue(activity.accessAdapter().accessHelper().checkDatabase()); // Here
I'm assuming that has something to do with not getting the database copied, as if I put back in the try-catches I had in my code, it results back in setting them off when copying the database.
Here's the code for my SQLiteOpenHelper, or at least what gets called up in copying the database.
public void createDatabase() throws IOException
{
this.getReadableDatabase();
this.close();
try {
copyDatabase();
Log.e(TAG, "Database created.");
} catch (IOException e) {
throw new Error("Error copying database.");
}
}
private void copyDatabase() throws IOException {
InputStream input = ((ShadowContextWrapper) shadowContext).getAssets().open(DATABASE_NAME);
String outFileName = DATABASE_PATH + DATABASE_NAME;
OutputStream output = new FileOutputStream(outFileName);
byte[] buffer = new byte[1024];
int length;
while ((length = input.read(buffer)) > 0) output.write(buffer, 0, length);
output.flush();
output.close();
input.close();
}
I assumed the problem might have to do with the context, so I created a ShadowContext, but that didn't really help either. There are other errors, but those are just methods that call each other up all the way down to the createDatabase() in my SQLiteOpenHelper.
Does anyone know how I can copy this database using Robolectric? I have it saved in assets for my application, but obviously, without the emulator, this is pointless. Thank you.
P.S. I can add any more code if necessary, this is just what I thought was necessary at first.
Related
I am using the following code to copy the db from assets
public void createDataBase() {
boolean dbExist = this.checkDataBase();
if (!dbExist) {
this.getWritableDatabase();
**this.close();**
try {
this.copyDataBase();
} catch (IOException var3) {
var3.printStackTrace();
throw new Error("Error copying database");
}
}
}
In the above code, it was not working in android P if i dint give this.close(). I was unable to create and query the database. But In previous versions it was working fine. I know it is a good practise to close database objects once used. But what is the reason for this behaviour in android Pie?
To get to know this issue in Pie I referred this link but couldn't find the explanation.
Android P - 'SQLite: No Such Table Error' after copying database from assets
I have two databases, one database is the primary. This primary DB is responsible for holding the current data which is up to date and my secondary DB is populated via a cron job, once the primary DB gets obsolete I want to replace it with the secondary DB via a file operation of just over writing the existing DB and refreshing my views. Is it possible to do this, is there a better way?
So far what I have done is:
public void writeToSD() throws IOException {
File f=new File("/mnt/sdcard/dump.db");
FileInputStream fis=null;
FileOutputStream fos=null;
try{
fis=new FileInputStream(f);
fos=new FileOutputStream("/data/data/com.one.two/databases/Bdr");
while(true){
int i=fis.read();
if(i!=-1){
fos.write(i);
}
else{
break;
}
}
fos.flush();
}
catch(Exception e){
e.printStackTrace();
}
finally{
try{
fos.close();
fis.close();
}
catch(IOException ioe){
System.out.println(ioe);
}
}
How about always using the same database files (let's say dbA, dbB) with two instances of SQLiteOpenHelper and using an utility class like this instead of using raw SQLiteOpenHelper:
class Db {
private SQLiteOpenHelper mPrimaryDb;
private SQLiteOpenHelper mSecondaryDb;
public Db(Context context) {
mPrimaryDb = new MyDbHelper(context, "db_a");
mSecondaryDb = new MyDbHelper(context, "db_b");
}
public SQLiteOpenHelper getPrimaryDb() {
return mPrimaryDb;
}
public SQLiteOpenHelper getSecondaryDb() {
return mSecondaryDb;
}
public void swapDb() {
SQLiteOpenHelper tmp = mPrimaryDb;
mPrimaryDb = mSecondaryDb;
mSecondaryDb = tmp;
// TODO: notify data users that data has changed, cleanup the old primary database, etc.
}
{
If you want to use file operations, renaming the data base files is faster. But during file operations all connections have to be closed before any action.
If insertion is too slow, I would not overwrite the database file. I would generate the new database with a temp name and the same table and view structure. After finishing writing to the temp file I would rename the file to the same name as the invariant part of the old database plus a version number or a timestamp . And in my application I would look periodically for a new version, if found I would close all connections to the old file and open the new database.
I have a problem with a database file not being read
I have added the database file in assets called mydb but when i run my code it says its not being located. It is calling this toast Toast.makeText(this, "No contact found", Toast.LENGTH_LONG).show(); This is being called because no records are being returned. Also I know it is finding the file as there is no FileNotFoundException exception. This is an example form Android Application Development book.
public class DatabaseActivity extends Activity {
/** Called when the activity is first created. */
TextView quest, response1, response2;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
TextView quest = (TextView) findViewById(R.id.quest);
try {
String destPath = "/data/data/" + getPackageName() + "/databases/MyDB";
File f = new File(destPath);
if (!f.exists()) {
CopyDB( getBaseContext().getAssets().open("mydb"),
new FileOutputStream(destPath));
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
DBAdapter db = new DBAdapter(this);
//---get a contact---
db.open();
Cursor c = db.getContact(2);
if (c.moveToFirst())
DisplayContact(c);
else
Toast.makeText(this, "No contact found", Toast.LENGTH_LONG).show();
db.close();
}
public void CopyDB(InputStream inputStream, OutputStream outputStream)
throws IOException {
//---copy 1K bytes at a time---
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, length);
}
inputStream.close();
outputStream.close();
}
public void DisplayContact(Cursor c)
{
quest.setText(String.valueOf(c.getString(1)));
//quest.setText(String.valueOf("this is a text string"));
}
}
Is there a better way to upload data.
A couple of things come to mind here...
because of the !f.exists() check, then once the database exists (and maybe empty) then it will never copy it again. So maybe for now, copy it all the time, until you work out kinks and then add in the !f.exists()
I've had mixed results with e.printStackTrace(), maybe change to Log.e(TAG, "message", e) and see if you start seeing errors showing up in LogCat
As for a better way... I've done this a couple different ways...
1. Is to create a file (json, cvs, etc) and then process and load it, if the database is empty
2. Similar to the first, except that I create a java serialized object array and load it to the database, if the database is empty.
Also I don't know what DBAdapter looks like, and since it wraps the database the issue may be there.
I use the following code to add rows to my database :
public void insert(String kern, String woord) {
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KERN, kern);
values.put(WOORD, woord);
db.insertOrThrow(TABLE_NAME, null, values);
return;
Currently, I'm invoking this insert() 3.455 times, to add all words to the database, using : insert("Fruits", "Banana"); It takes forever.
How can I change this code to work faster? I'm thinking in the line of foreach, but don't know how to implement.. Thanks!
/Edit; The solution provided by #hovanessyan works and will do the job. AND.. note that if you have a lot of lines that have to be put in, you might be confronted with the method exceeds max byte limit error. In that case, review the other solution, that suggests packing the database in the actual .APK file.
You can wrap-up those inserts into transaction.
db.beginTransaction();
try {
// do all the inserts here
//method call here, that does 1 insert; For example
addOneEntry(kern,woord);
...
db.setTransactionSuccessful();
} catch (SQLException e) {
//catch exceptions
} finally {
db.endTransaction();
}
private void addOneEntry(String kern, String woord) {
//prepare ContentValues
//do Insert
}
You can use bulkInsert:
ContentValues[] cvArr = new ContentValues[rows.size()];
int i = 0;
for (MyObject row : rows) {
ContentValues values = new ContentValues();
values.put(KERN, myObject.getKern());
values.put(WOORD, myObject.getWoord);
cvArr[i++] = values;
}// end for
resolver.bulkInsert(Tasks.CONTENT_URI, cvArr);
Using the tips of both hovanessyan and Damian (remind me to rep+1 you as soon as I reach 15 ;), I came up with the following solution:
For relatively small databases (<1,5Mb)
I created the database using SQLite Database Browser, and put it in my Assets folder.
Then, the following code copies the database to the device, if it's not already there:
boolean initialiseDatabase = (new File(DB_DESTINATION)).exists();
public void copyDB() throws IOException{
final String DB_DESTINATION = "/data/data/happyworx.nl.Flitswoorden/databases/WoordData.db";
// Check if the database exists before copying
Log.d("Database exist", "" + initialiseDatabase);
Log.d("Base Context", "" + getBaseContext());
if (initialiseDatabase == false) {
// Open the .db file in your assets directory
InputStream is = getBaseContext().getAssets().open("WoordData.db");
// Copy the database into the destination
OutputStream os = new FileOutputStream(DB_DESTINATION);
byte[] buffer = new byte[1024];
int length;
while ((length = is.read(buffer)) > 0){
os.write(buffer, 0, length);
}
os.flush();
os.close();
is.close();
}}
In my app, a portion of the database is User-customizable.
I call the code above in onStart() with :
try {
copyDB();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
So, when the user presses "reset database to standard" (in preferences screen), I just set the Boolean initialiseDatabase to "false" and wait for the user to go back to the main activity. (thus calling onstart and copying the original database).
I tried to call the Activity.copyDB() from the preferences.java. It's neater, because it doesn't require the user to go back to the main activity to rebuild the database. However, I get an error about not being able to call static references to non-static methods. I don't understand that, but will look into it.
I think I have some basic understanding problem so maybe someone's able to help :-)
I'm developing an Android application using Eclipse and this application will make use of a database (only reading from the database will be implemented). The database contains around 4,000 entries i.e. creating and populating the database via source code is not an option. Thus I have created the database in advance with all its records.
But how can I "embed" this database file into my application and then access it? The databse will be around 500 kB in file size. Downloading from a remote server is not an option either as this is not allowed.
Thanks,
Robert
I solved that problem by:
adding file.db into project/assets folder;
writing next class:
public class LinnaeusDatabase extends SQLiteOpenHelper{
private static String DATABASE_NAME = "Dragonfly.db";
public final static String DATABASE_PATH = "/data/data/com.kan.linnaeus/databases/";
private static final int DATABASE_VERSION = 1;
private SQLiteDatabase dataBase;
private final Context dbContext;
public LinnaeusDatabase(Context context) {
super(context, DBActivity.DatabaseName, null, DATABASE_VERSION);
this.dbContext = context;
DATABASE_NAME = DBActivity.DatabaseName;
// checking database and open it if exists
if (checkDataBase()) {
openDataBase();
} else
{
try {
this.getReadableDatabase();
copyDataBase();
this.close();
openDataBase();
} catch (IOException e) {
throw new Error("Error copying database");
}
Toast.makeText(context, "Initial database is created", Toast.LENGTH_LONG).show();
}
}
private void copyDataBase() throws IOException{
InputStream myInput = dbContext.getAssets().open(DATABASE_NAME);
String outFileName = DATABASE_PATH + DATABASE_NAME;
OutputStream 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();
}
public void openDataBase() throws SQLException {
String dbPath = DATABASE_PATH + DATABASE_NAME;
dataBase = SQLiteDatabase.openDatabase(dbPath, null, SQLiteDatabase.OPEN_READWRITE);
}
private boolean checkDataBase() {
SQLiteDatabase checkDB = null;
boolean exist = false;
try {
String dbPath = DATABASE_PATH + DATABASE_NAME;
checkDB = SQLiteDatabase.openDatabase(dbPath, null,
SQLiteDatabase.OPEN_READONLY);
} catch (SQLiteException e) {
Log.v("db log", "database does't exist");
}
if (checkDB != null) {
exist = true;
checkDB.close();
}
return exist;
}
}
Nice article on ReignDesign blog titled Using your own SQLite database in Android applications. Basically you precreate your database, put it in your assets directory in your apk, and on first use copy to "/data/data/YOUR_PACKAGE/databases/" directory.
I've done this in the past by storing a JSON file in the application in the res/raw resources and then loading the file on first load. From there, you can use the bulk insert mode to batch-import entries. See my database loader for Units as an example of this technique. One benefit of the res/raw style is that you can use the Android resource selecting system to localize the data to different regions, so you could have a different database (or part thereof) for different languages or regions.
You could also put a raw SQL dump in a similar file and load that on first load. You can get the file from the resources by using openRawResource(int). I'd recommend this instead of storing a pre-made sqlite database file to increase compatibility between versions of sqlite, as well as make maintaining the database easier (from an app-development lifecycle POV).
When loading things in bulk, make sure to use transactions, as that'll help speed things up and make the loading more reliable.
In a comment to this answer, CommonsWare recommends against the same method proposed by #evilone here (ie, copying the database from assets yourself). Instead you should use the tried and tested SQLiteAssetHelper. I've used it in Android Studio and it works well.
The directions are fairly clear. Here are a few key excerpts:
If you are using the Gradle build system, simply add the following
dependency in your build.gradle file:
dependencies {
compile 'com.readystatesoftware.sqliteasset:sqliteassethelper:+'
}
Extend SQLiteAssetHelper as you would normally do SQLiteOpenHelper,
providing the constructor with a database name and version number:
public class MyDatabase extends SQLiteAssetHelper {
private static final String DATABASE_NAME = "northwind.db";
private static final int DATABASE_VERSION = 1;
public MyDatabase(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
}
Put your database in assets/databases/northwind.db (using whatever database name you have).
The database is made available for use the first time either
getReadableDatabase() or getWritableDatabase() is called.