How to change the default database file location of Room DataBase? - android

I am using RoomDB in my app. able to perform crud operations.
Actually i want to see the db file.
getDatabasePath("user.db").getAbsolutePath();
above code is giving me the directory where the db file is saves
directory is like this
/data/data/com.example.manvish.roomdb/databases/user.db
but still i am unable to access the data directory even using sudo from command prompt.
now i want to change the DB file location to some other folders in internal memory or SD card. how can i do this?

Java solution:
Grant permissions in Manifest
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Create db (Here SofDatabase is a singleton class)
private static final String DB_NAME = "stack_overflow_db";
private static final String DB_PATH = String.format("%s/%s",
Environment.getExternalStorageDirectory().getAbsolutePath(), DB_NAME);
public static synchronized SofDatabase getInstance(Context aContext) {
if (sInstance == null) {
sInstance = Room.databaseBuilder(aContext, SofDatabase.class, DB_PATH)
.fallbackToDestructiveMigration()
.addCallback(roomCallback).build(); //adding callback from Room
}
return sInstance;
}
Callback
/**
* Get Notified once db is created
*/
private static final RoomDatabase.Callback roomCallback = new RoomDatabase.Callback() {
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
Log.i("SOF", db.getPath()); //which prints out --> I/SOF: /storage/emulated/0/stack_overflow_db
// add some jobs once db is created
}
};

To change the db location just put the path when you build the Room database, an example for a db in your folder, in the internal storage:
fun getDatabase(context: Context, scope: CoroutineScope): TestDatabase {
return INSTANCE ?: synchronized(this) {
val instance =
Room.databaseBuilder(
context.applicationContext,
TestDatabase::class.java,
Environment.getExternalStorageDirectory().absolutePath + "/yourFolder/yourdb"
).build()
INSTANCE = instance
return instance
}
}
This is a Kotlin example, if you need it in Java, just let me know.
Regards.

So guys I'm posting my solution because I got stuck for several hours on this problem and I was almost convinced that we couldn't use a database that is located elsewhere than the "/data/data" internal directory of the application with Room
The solution is however very simple. with the following code we have the IllegalArgumentException exception: "File ... contains a path separator"
private fun buildDatabase(context: Context) : RMSRoomDatabase {
val packageName: String = context.getApplicationInfo().packageName
var path = "sdcard/Android/data/$packageName/files/rms_database.sqlite"
var builder = Room.databaseBuilder(
context,
RMSRoomDatabase::class.java,
path
)
return builder.allowMainThreadQueries()
.fallbackToDestructiveMigration()
.build()
}
But by simply changing the path with an slash as the first character, everything works correctly!
var path = "/sdcard/Android/data/$packageName/files/rms_database.sqlite"

Not sure if this is what you are looking for but in my case just like #M.Yogeshwaran I needed to be able to setup an initial state of the database and work from there so I ended up doing this:
/**
* #param context
*/
public DatabaseService(Context context) {
File dst = context.getDatabasePath(DB_NAME);
dst.delete();
File src = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getPath()+"/database.db");
try {
copy(src,dst);
} catch (IOException e) {
e.printStackTrace();
}
// Init the database singleton
db = Room.databaseBuilder(context, CrediforceDatabase.class, Constants.DB_NAME).build();
}
public static void copy(File src, File dst) throws IOException {
try (InputStream in = new FileInputStream(src)) {
try (OutputStream out = new FileOutputStream(dst)) {
// Transfer bytes from in to out
byte[] buf = new byte[1024];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
}
}
}
To give a bit more clarity:
DB_NAME = "database.db"
For testing purposes, I have the "database.db" that I want to "import" in my downloads folder, later on I will convert this code so instead the device pulls the initial database from the server.
the dst.delete(); is just to delete the existing database file as I was testing things out (not sure if required)
This basically translates to: Start the app > Replace the database file > build the database.
Gotta be careful with the identity_hash in the room_master_table, if this is different from what's supposed to be it's not going to work.
Also, I pulled this idea from this answer:
https://stackoverflow.com/a/50383879/2150472
Hope it helps anyone!
P.S, if what you need is just to be able to see the data and perform CRUD operations you can always use this plugging: https://github.com/amitshekhariitbhu/Android-Debug-Database

Related

Android 11 + Kotlin: Reading a .zip File

I've got an Android app written in Kotlin targeting framework 30+, so I'm working within the new Android 11 file access restrictions. The app needs to be able to open an arbitrary .zip file in the shared storage (chosen interactively by the user) then do stuff with the contents of that .zip file.
I'm getting a URI for the .zip file in what I'm led to understand is the canonical way:
val activity = this
val getContent = registerForActivityResult(ActivityResultContracts.GetContent()) {
CoroutineScope(Dispatchers.Main).launch {
if(it != null) doStuffWithZip(activity, it)
...
}
}
getContent.launch("application/zip")
My problem is that the Java.util.zip.ZipFile class I'm using only knows how to open a .zip file specified by a String or a File, and I don't have any easy way to get to either of those from a Uri. (I'm guessing that the ZipFile object needs the actual file rather than some kind of stream because it needs to be able to seek...)
The workaround I'm using at present is to turn the Uri into an InputStream, copy the contents to a temp file in private storage, and make a ZipFile instance from that:
private suspend fun <T> withZipFromUri(
context: Context,
uri: Uri, block: suspend (ZipFile) -> T
) : T {
val file = File(context.filesDir, "tempzip.zip")
try {
return withContext(Dispatchers.IO) {
kotlin.runCatching {
context.contentResolver.openInputStream(uri).use { input ->
if (input == null) throw FileNotFoundException("openInputStream failed")
file.outputStream().use { input.copyTo(it) }
}
ZipFile(file, ZipFile.OPEN_READ).use { block.invoke(it) }
}.getOrThrow()
}
} finally {
file.delete()
}
}
Then, I can use it like this:
suspend fun doStuffWithZip(context: Context, uri: Uri) {
withZipFromUri(context, uri) { // it: ZipFile
for (entry in it.entries()) {
dbg("entry: ${entry.name}") // or whatever
}
}
}
This works, and (in my particular case, where the .zip file in question is never more than a couple MB) is reasonably performant.
But, I tend to regard programming by temporary file as the last refuge of the terminally incompetent, thus I can't escape the feeling that I'm missing a trick here. (Admittedly, I am terminally incompetent in the context of Android + Kotlin, but I'd like to learn to not be...)
Any better ideas? Is there a cleaner way to implement this that doesn't involve making an extra copy of the file?
Copying from external source (and risking downvoting to oblivion) and this isn't quite an answer, but too long for a comment
public class ZipFileUnZipExample {
public static void main(String[] args) {
Path source = Paths.get("/home/mkyong/zip/test.zip");
Path target = Paths.get("/home/mkyong/zip/");
try {
unzipFolder(source, target);
System.out.println("Done");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void unzipFolder(Path source, Path target) throws IOException {
// Put the InputStream obtained from Uri here instead of the FileInputStream perhaps?
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(source.toFile()))) {
// list files in zip
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null) {
boolean isDirectory = false;
// example 1.1
// some zip stored files and folders separately
// e.g data/
// data/folder/
// data/folder/file.txt
if (zipEntry.getName().endsWith(File.separator)) {
isDirectory = true;
}
Path newPath = zipSlipProtect(zipEntry, target);
if (isDirectory) {
Files.createDirectories(newPath);
} else {
// example 1.2
// some zip stored file path only, need create parent directories
// e.g data/folder/file.txt
if (newPath.getParent() != null) {
if (Files.notExists(newPath.getParent())) {
Files.createDirectories(newPath.getParent());
}
}
// copy files, nio
Files.copy(zis, newPath, StandardCopyOption.REPLACE_EXISTING);
// copy files, classic
/*try (FileOutputStream fos = new FileOutputStream(newPath.toFile())) {
byte[] buffer = new byte[1024];
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}*/
}
zipEntry = zis.getNextEntry();
}
zis.closeEntry();
}
}
// protect zip slip attack
public static Path zipSlipProtect(ZipEntry zipEntry, Path targetDir)
throws IOException {
// test zip slip vulnerability
// Path targetDirResolved = targetDir.resolve("../../" + zipEntry.getName());
Path targetDirResolved = targetDir.resolve(zipEntry.getName());
// make sure normalized file still has targetDir as its prefix
// else throws exception
Path normalizePath = targetDirResolved.normalize();
if (!normalizePath.startsWith(targetDir)) {
throw new IOException("Bad zip entry: " + zipEntry.getName());
}
return normalizePath;
}
}
This apparently works with pre-existing files; however since you already have an InputStream read from the Uri - you can adapt this and give it a shot.
EDIT:
It seems like it's extracting to Files as well - you could store the individual ByteArrays somewhere then decide what to do with them later on. But I hope you get the general idea - you can do all of this in-memory, without having to use the disk (temp files or files) in between.
Your requirement is a bit vague and unclear however, so I don't know what you're trying to do, merely suggesting a venue/approach to try out
What about a simple ZipInputStream ? –
Shark
Good idea #Shark.
InputSteam is = getContentResolver().openInputStream(uri);
ZipInputStream zis = new ZipInputStream(is);
#Shark has it with ZipInputStream. I'm not sure how I missed that to begin with, but I sure did.
My withZipFromUri() method is much simpler and nicer now:
suspend fun <T> withZipFromUri(
context: Context,
uri: Uri, block: suspend (ZipInputStream) -> T
) : T =
withContext(Dispatchers.IO) {
kotlin.runCatching {
context.contentResolver.openInputStream(uri).use { input ->
if (input == null) throw FileNotFoundException("openInputStream failed")
ZipInputStream(input).use {
block.invoke(it)
}
}
}.getOrThrow()
}
This isn't call-compatible with the old one (since the block function now takes a ZipInputStream as a parameter rather than a ZipFile). In my particular case -- and really, in any case where the consumer doesn't mind dealing with entries in the order they appear -- that's OK.
Okio (3-Alpha) has a ZipFileSystem https://github.com/square/okio/blob/master/okio/src/jvmMain/kotlin/okio/ZipFileSystem.kt
You could probably combine it with a custom FileSystem that reads the content of that file. It will require a fair bit of code but will be efficient.
This is an example of a custom filesystem https://github.com/square/okio/blob/88fa50645946bc42725d2f33e143628e7892be1b/okio/src/jvmMain/kotlin/okio/internal/ResourceFileSystem.kt
But I suspect it's simpler to convert the URI to a file and avoid any copying or additional code.
It's easy to check the .zip and .rar files in the Android-Kotlin FileAdapter(work with file manager), add the bellow function to your code:
private fun isZip(name: String): Boolean {
return name.contains(".zip") || name.contains(".rar")
}

Use a pre-populated sqllite db file with Room [duplicate]

I'd like to use Room with a pre-populated database, but I can't understand how to tell Room where to find my database.
I've now put it in src/main/assets/databases and when I create the instance for the Room database I create it this way:
Room.databaseBuilder(
getApplicationContext(),
AppDatabase.class,
"justintrain.db"
)
.allowMainThreadQueries()
.build();
This way tho, I think it's creating a new database every time, or anyways, it's not using the pre-populated one.
How can I make it to find my database?
This is how I solved it, and how you can ship your application with a pre-populated database (up to Room v. alpha5)
put your SQLite DB database_name.db into the assets/databases folder
take the files from this repo and put them in a package called i.e. sqlAsset
in your AppDatabase class, modify your Room's DB creation code accordingly:
Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class,
"database_name.db")
.openHelperFactory(new AssetSQLiteOpenHelperFactory())
.allowMainThreadQueries()
.build();
Note that you have to use "database_name.db" and not getDatabasePath() or other methods: it just needs the name of the file.
UPDATE (Nov 7th 2019)
Room now supports using a pre-packaged database out of the box, since version 2.2.0
https://developer.android.com/jetpack/androidx/releases/room#2.2.0
Solution before version 2.2.0: Simple solution without any other external libraries.
Room relies on the existing Android framework code to create or open a database. If you look into the source code of FrameworkSQLiteOpenHelper (Room's version of SQLiteOpenHelper) it internally calls SQLiteOpenHelper.getReadableDatabase() and other methods wherever needed.
So, the simplest solution is to just copy the DB file from assets directory to mContext.getDatabasePath("my-database.sqlite") before creating the DB with Room.
In your case, the code looks something like this -
private final String DB_NAME = "my-database.sqlite";
private MyDatabase buildDatabase(Context context) {
final File dbFile = context.getDatabasePath(DB_NAME);
if(!dbFile.exists()) {
copyDatabaseFile(dbFile.getAbsolutePath());
}
return Room.databaseBuilder(context.getApplicationContext(),
MyDatabase.class, DB_NAME)
.build();
}
private void copyDatabaseFile(String destinationPath) {
// code to copy the file from assets/database directory to destinationPath
}
This link has the code needed to copy the DB - link with code
I was having the same problem so I created a library which does exactly that.
the accepted answer work but I think it's easier to use a library.
AppDatabase db = RoomAsset
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "database_name.db")
.build();
Add it to your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
Add the dependency
dependencies {
// ... other dependencies
implementation 'com.github.humazed:RoomAsset:v1.0'
}
you can find the library here: https://github.com/humazed/RoomAsset
Working 2019 solution without hacks or dependencies (Kotlin)
Place your .db file in assets/databases (or really any folder in there, as long as it's under assets).
Use Room 2.2's existing createFromAsset() function, passing in the path to the database. For example, if your database file is named my_data.db and is under the databases directory of the assets folder, then you would do createFromAsset("databases/my_data.db").
Assuming your database name (e.g., my_data) is stored in a constant variable named DATABASE_NAME, you can use this sample code:
Room.databaseBuilder(
context.applicationContext,
MyDatabase::class.java,
DATABASE_NAME
)
.createFromAsset("databases/$DATABASE_NAME.db")
.build()
Important: Make sure the schema of your data class/entity precisely matches the schema of your .db file. For example, if a column isn't explicitly marked as NOT NULL in the .db file, then that means the column can have null
values in it. In Kotlin, you would have to match that with val colName: dataType? = null in your data class. If you just do val colName: dataType, Kotlin will compile that to a NOT NULL column, and that will throw an exception when you try to run your app.
Note: If instead you want to create a Room database from a database file that you download onto the Android device itself, you can alternatively use the createFromFile() function. Check out the official documentation on how to do this.
Room now supports Prepopulated Databases. Just prepare your database by using a program like SQLite Browser or any other of your choice. Then put it in Assets Folder probably in a subfolder called database then call:
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromAsset("database/myapp.db")
.build()
If you did not provide your database as an Asset but you downloaded it or it is in File System then then the method is:
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromFile(File("mypath"))
.build()
For more description or database migrations about this Feature you can check the Documentation Training.
Similar solution with room without using external libraries:
1. Copy your database in assets folder
2. Copy your database from assets folder
public class MainActivity extends AppCompatActivity {
public static AppDatabase db;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
copyDatabase(getApplicationContext(), "yourdatabase.db");
db = Room.databaseBuilder(getApplicationContext(), .class, "yourdatabase.db").allowMainThreadQueries().build();
}
private void copyDatabase(Context context, String databaseName) {
final File dbPath = context.getDatabasePath(databaseName);
// If the database already exists, return
if (dbPath.exists()) {
Log.d("Activity", "db Path Exists");
return;
}
// Make sure we have a path to the file
dbPath.getParentFile().mkdirs();
// Try to copy database file
try {
final InputStream inputStream = context.getAssets().open(databaseName);
final OutputStream output = new FileOutputStream(dbPath);
byte[] buffer = new byte[8192];
int length;
while ((length = inputStream.read(buffer, 0, 8192)) > 0) {
output.write(buffer, 0, length);
}
output.flush();
output.close();
inputStream.close();
}
catch (IOException e) {
Log.d("Activity", "Failed to open file", e);
e.printStackTrace();
}
}
}
Starting with Room 2.2, you can pre-populate your database using command below:
Room.databaseBuilder(appContext, TestDatabase.class, “Sample.db”)
.createFromAsset(“database/myapp.db”)
.build()
you just copy assets/databases to app/databases
and than add addMigrations() in databaseBuilder
it will keep your data

How to use Room Persistence Library with pre-populated database?

I'd like to use Room with a pre-populated database, but I can't understand how to tell Room where to find my database.
I've now put it in src/main/assets/databases and when I create the instance for the Room database I create it this way:
Room.databaseBuilder(
getApplicationContext(),
AppDatabase.class,
"justintrain.db"
)
.allowMainThreadQueries()
.build();
This way tho, I think it's creating a new database every time, or anyways, it's not using the pre-populated one.
How can I make it to find my database?
This is how I solved it, and how you can ship your application with a pre-populated database (up to Room v. alpha5)
put your SQLite DB database_name.db into the assets/databases folder
take the files from this repo and put them in a package called i.e. sqlAsset
in your AppDatabase class, modify your Room's DB creation code accordingly:
Room.databaseBuilder(context.getApplicationContext(),
AppDatabase.class,
"database_name.db")
.openHelperFactory(new AssetSQLiteOpenHelperFactory())
.allowMainThreadQueries()
.build();
Note that you have to use "database_name.db" and not getDatabasePath() or other methods: it just needs the name of the file.
UPDATE (Nov 7th 2019)
Room now supports using a pre-packaged database out of the box, since version 2.2.0
https://developer.android.com/jetpack/androidx/releases/room#2.2.0
Solution before version 2.2.0: Simple solution without any other external libraries.
Room relies on the existing Android framework code to create or open a database. If you look into the source code of FrameworkSQLiteOpenHelper (Room's version of SQLiteOpenHelper) it internally calls SQLiteOpenHelper.getReadableDatabase() and other methods wherever needed.
So, the simplest solution is to just copy the DB file from assets directory to mContext.getDatabasePath("my-database.sqlite") before creating the DB with Room.
In your case, the code looks something like this -
private final String DB_NAME = "my-database.sqlite";
private MyDatabase buildDatabase(Context context) {
final File dbFile = context.getDatabasePath(DB_NAME);
if(!dbFile.exists()) {
copyDatabaseFile(dbFile.getAbsolutePath());
}
return Room.databaseBuilder(context.getApplicationContext(),
MyDatabase.class, DB_NAME)
.build();
}
private void copyDatabaseFile(String destinationPath) {
// code to copy the file from assets/database directory to destinationPath
}
This link has the code needed to copy the DB - link with code
I was having the same problem so I created a library which does exactly that.
the accepted answer work but I think it's easier to use a library.
AppDatabase db = RoomAsset
.databaseBuilder(context.getApplicationContext(), AppDatabase.class, "database_name.db")
.build();
Add it to your root build.gradle at the end of repositories:
allprojects {
repositories {
...
maven { url "https://jitpack.io" }
}
}
Add the dependency
dependencies {
// ... other dependencies
implementation 'com.github.humazed:RoomAsset:v1.0'
}
you can find the library here: https://github.com/humazed/RoomAsset
Working 2019 solution without hacks or dependencies (Kotlin)
Place your .db file in assets/databases (or really any folder in there, as long as it's under assets).
Use Room 2.2's existing createFromAsset() function, passing in the path to the database. For example, if your database file is named my_data.db and is under the databases directory of the assets folder, then you would do createFromAsset("databases/my_data.db").
Assuming your database name (e.g., my_data) is stored in a constant variable named DATABASE_NAME, you can use this sample code:
Room.databaseBuilder(
context.applicationContext,
MyDatabase::class.java,
DATABASE_NAME
)
.createFromAsset("databases/$DATABASE_NAME.db")
.build()
Important: Make sure the schema of your data class/entity precisely matches the schema of your .db file. For example, if a column isn't explicitly marked as NOT NULL in the .db file, then that means the column can have null
values in it. In Kotlin, you would have to match that with val colName: dataType? = null in your data class. If you just do val colName: dataType, Kotlin will compile that to a NOT NULL column, and that will throw an exception when you try to run your app.
Note: If instead you want to create a Room database from a database file that you download onto the Android device itself, you can alternatively use the createFromFile() function. Check out the official documentation on how to do this.
Room now supports Prepopulated Databases. Just prepare your database by using a program like SQLite Browser or any other of your choice. Then put it in Assets Folder probably in a subfolder called database then call:
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromAsset("database/myapp.db")
.build()
If you did not provide your database as an Asset but you downloaded it or it is in File System then then the method is:
Room.databaseBuilder(appContext, AppDatabase.class, "Sample.db")
.createFromFile(File("mypath"))
.build()
For more description or database migrations about this Feature you can check the Documentation Training.
Similar solution with room without using external libraries:
1. Copy your database in assets folder
2. Copy your database from assets folder
public class MainActivity extends AppCompatActivity {
public static AppDatabase db;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
copyDatabase(getApplicationContext(), "yourdatabase.db");
db = Room.databaseBuilder(getApplicationContext(), .class, "yourdatabase.db").allowMainThreadQueries().build();
}
private void copyDatabase(Context context, String databaseName) {
final File dbPath = context.getDatabasePath(databaseName);
// If the database already exists, return
if (dbPath.exists()) {
Log.d("Activity", "db Path Exists");
return;
}
// Make sure we have a path to the file
dbPath.getParentFile().mkdirs();
// Try to copy database file
try {
final InputStream inputStream = context.getAssets().open(databaseName);
final OutputStream output = new FileOutputStream(dbPath);
byte[] buffer = new byte[8192];
int length;
while ((length = inputStream.read(buffer, 0, 8192)) > 0) {
output.write(buffer, 0, length);
}
output.flush();
output.close();
inputStream.close();
}
catch (IOException e) {
Log.d("Activity", "Failed to open file", e);
e.printStackTrace();
}
}
}
Starting with Room 2.2, you can pre-populate your database using command below:
Room.databaseBuilder(appContext, TestDatabase.class, “Sample.db”)
.createFromAsset(“database/myapp.db”)
.build()
you just copy assets/databases to app/databases
and than add addMigrations() in databaseBuilder
it will keep your data

Android: copy database from asset folder, but only get an empty file

guys, I have the problem when copying database from local assets folder to /data/data/package_name/databases directory. As I use the http://www.reigndesign.com/blog/using-your-own-sqlite-database-in-android-applications/ tutorial to do it, I can only get an empty file.
I quoted the part of copyDataBase() method and there is no difference. Every time the app start, it will create the directory and empty database. So is there any way to make the copyDataBase() work?
Thank you very much!!
Why wouldn't you copy from assets? It's perfectly normal to do so. But you can't do it in the onCreate, at that point an empty database is already created. You need to do it prior. I usually do it in an override of getWriteableDatabase, something like
public synchronized SQLiteDatabase getWritableDatabase() {
SQLiteDatabase db = null;
if (!doesDatabaseExist()) {
try {
copyDatabase();
db = super.getWritableDatabase();
} catch(Exception ex) {
Log.e("Database Log", getDatabasePath() + " failed to copy correctly. " + ex.getLocalizedMessage());
}
}
else {
db = super.getWritableDatabase();
}
return db;
}
I wouldn't copy any database form the assets-folder. If you need some standard entry's in your Database, you can add them using INSERTs in your onCreate()-method.
Update: Since this is getting down-voted for being wrong (which is kinda right) and I can't delete it, here is a little update.
I'd say it depends upon how many standard entries you want to add to your database. If it's just one or two, shipping a packed DB might not be worth it.
Anyways, some apps come with rather large databases (for example, a recipe collection). You can obviously not add all these in code.
For small test-entries, I'd still prefer simply adding them in onCreate().
For bigger databases, you should pre-populate them and ship em along with your app.
For the later to work, you'll need to copy the database file from assets/ to your app-folder. There is a nice library to handle that for you: android-sqlite-asset-helper
I don't know if it is still usefull but here is the solution for others that get here to see the awnser. The code you used, works for most phones, some older phones have different behaviour with the getReadableDatabase() function. Your problem therefore is not in the copyDataBase function but in the createDataBase function.
in createDataBase() there is the following check;
this.getReadableDatabase();
This checks if there is already a database with the provided name and if not creates an empty database such that it can be overwritten with the one in the assets folder. On newer devices this works flawlessly but there are some devices on which this doesn't work. Mainly older devices. I do not know exactly why, but it seems like the getReadableDatabase() function not only gets the database but also opens it. If you then copy the database from the assets folder over it, it still has the pointer to an empty database and you will get table does not exist errors.
So in order to make it work on all devices you should modify it to the following lines:
SQLiteDatabase db = this.getReadableDatabase();
if (db.isOpen()){
db.close();
}
Even if the database is opened in the check, it is closed thereafter and it will not give you any more trouble.
at the right above example worked for me this way:
db = super.getWritableDatabase();
db.close;
copyDatabase();
otherwise i got an IO error;
Here is the simple and convenient code which I use:
public class DataBaseImportHelper {
private DataBaseImportHelper() {}; // Avoid instantiation
/**
* Creates a empty database on the system and rewrites it with your own database.
*/
public static boolean importDataBase(Context context) {
InputStream myInput = null;
OutputStream myOutput = null;
try {
// Open local db from assets as the input stream
myInput = context.getAssets().open(DATABASE_NAME);
createEmptyDatabase(context); // See this method below
// Open the empty db as the output stream
myOutput = new FileOutputStream(getDatabaseFile(context));
// 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();
myInput.close();
myOutput.close();
return true;
} catch (IOException e) {
e.printStackTrace();
}
return false;
}
/**
* Check if the database already exists.
* #return true if it exists, false if it doesn't
*/
public static boolean isDatabaseExists(Context context) {
return getDatabaseFile(context).exists();
}
private static File getDatabaseFile(Context context) {
return context.getDatabasePath(DatabaseHelper.DATABASE_NAME);
}
/**
* Create an empty database into the default application database
* folder.So we are gonna be able to overwrite that database with our database
*/
private static void createEmptyDatabase(Context context) {
// use anonimous helper to create empty database
new SQLiteOpenHelper(context, DatabaseHelper.DATABASE_NAME, null, 1) {
// Methods are empty. We don`t need to override them
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
#Override
public void onCreate(SQLiteDatabase db) {
}
}.getReadableDatabase().close();
}
}
And using in code:
if(!DataBaseImportHelper.isDatabaseExists(this)){
if (!DataBaseImportHelper.importDataBase(this)){
throw new IllegalStateException("Database doesn`t exist and hasn`t been copied!");
}
}

How to put existing database in the .apk file?

I have prebuild database of mostly string objects. I want to know how to put in my apk file so the database will be already created when the user installs the database.
I found a good example of this: Using your own SQLite database in Android applications
Basically, you export the created database as an sql-file and store it in the assets-folder.
On the first program start, you import the data of the file into your final database.
I think it's the best approach, however your data will be there twice in the apk and the db, using some more storage space.
I've just started developing for Android, and was surprised to discover that bundling a static database is not easy to do. So I did the only reasonable thing: created a library which does just that. Example usage:
import android.database.sqlite.SQLiteDatabase;
import kiwidrew.staticdb.StaticDatabase;
public class BlahBlah extends Activity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
SQLiteDatabase db = StaticDatabase.openDatabase(this, "assets/foobar.db");
// Do normal database stuff with 'db'...
}
}
You get back a standard SQLiteDatabase object, with the only restriction being that it doesn't support writing. (Obviously!)
Note that this will fail unless the database is stored in your .apk without compression. Add the sqlite database using the aapt -0 command or modify your build.xml to pass the <nocompress extension="db" /> flag to the <aapt> tag...
Get the code at http://bitbucket.org/kiwidrew/android-staticdb.
Note: I've literally just finished writing this, and have only done very basic testing so far. Bug reports would be appreciated!
There are no any simple way to read database from assets directly. You should copy your database from assets to data folder in the first run, then when every time your app starts up, you should check database in the data folder and copy it again if the database does not exist.
These steps help you:
1) Execute these commands on your database, if android_metadata table does not exist in your database, android could not open your database.:
CREATE TABLE android_metadata(locale TEXT DEFAULT 'en_US')
INSERT INTO android_metadata VALUES('en_US')
2) Chunk your database because android does not support reading a file that more than 1 MB size from assets.
This python code chunks your database:
def chunk_file(file_name):
input_file = open(file_name, "rb")
chunk_counter = 0;
while True:
chunk = input_file.read(512 * 1024) # 512 KB
if chunk:
output_file_name = file_name + "." + str(chunk_counter).zfill(4)
output_file = open(output_file_name, "wb")
output_file.write(chunk)
output_file.close()
chunk_counter += 1
else:
break
input_file.close()
return
# Input: database.db
# Output: database.db.0000, database.db.0001, database.db.0002, ...
chunk_file("database.db")
Then put database.db.0000, database.db.0001, database.db.0002, ... in the assets folder.
3) Check database exists in the data folder when app starts up.
public static boolean databaseExists() {
boolean result = false;
SQLiteDatabase checkDB = null;
try {
checkDB = SQLiteDatabase.openDatabase(
getApplicationContext().getFilesDir().getPath() + "/database.db",
null, SQLiteDatabase.OPEN_READONLY);
result = true;
} catch (SQLiteException exception) {
result = false;
}
if (checkDB != null) {
checkDB.close();
}
return result;
}
4) If database does not exist in data folder, copy database from assets to data folder.
public static void copyDatabase() throws IOException {
AssetManager assets = getApplicationContext().getAssets();
// database.db.0000, database.db.0001, database.db.0002, ... --> databaseChunks.
String[] databaseChunks = assets.list("");
Arrays.sort(databaseChunks);
OutputStream databaseStream = new FileOutputStream(
getApplicationContext().getFilesDir().getPath() + "/database.db");
for (int i = 0; i < databaseChunks.length; i++) {
String databaseChunkName = databaseChunks[i];
InputStream chunkStream = assets.open(databaseChunkName);
int length;
byte[] buffer = new byte[1024];
while ((length = chunkStream.read(buffer)) > 0) {
databaseStream.write(buffer, 0, length);
}
chunkStream.close();
}
databaseStream.close();
return;
}
5) You can connect to the database now:
SQLiteDatabase database = SQLiteDatabase.openDatabase(
getApplicationContext().getFilesDir().getPath() + "/database.db",
null, SQLiteDatabase.OPEN_READONLY);
// ...
database.close();

Categories

Resources