Reputation: 150
How can I export a Room database to a .db file so I can use it later? I would like to export it to Download folder in Android device storage. I already have setup export to .CSV but now i need to .db.
This is so I can reimport it if the user is switching devices and needs his data on the other device.
Is there a way just to use .CSV data or do i have to use .db?
Upvotes: 6
Views: 8428
Reputation: 302
When you work in Android Studio, the database is stored in the device's memory, namely in /data/data/<name_of_the_app>/databases/<name_of_the_db>
,
where <name_of_the_app>
will look like com.android.thebestappever
(you choose this name when creating an app);
<name_of_the_db>
is also chosen by you when you instantiate the DB in your application.
You can access this DB file through the Device Explorer. You can open it by navigating to View -> Tool Windows -> Device Explorer (for example, in Android Studio Giraffe).
When you find your database file, you will also notice two more additional files next to it. If your DB is called thebestdb
, there will be three files there: thebestdb
, thebestdb-shm
and thebestdb-wal
.
shm
and wal
are auxiliary files for the write-ahead logging (you can read more about it at https://www.sqlite.org/wal.html).
The thing is, the main DB file might not contain all the latest data because it might be stored only in shm
and wal
and not yet synchronised with the main file.
In order to "commit" these changes and get the main DB file up to date, you need to execute PRAGMA wal_checkpoint
. You can do it anywhere inside the code of your app.
If you instantiated your database with the databaseBuilder
like so:
db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "thebestdb"
).allowMainThreadQueries().build()
then you have a RoomDatabase instance which you could use to do that synchronisation like this:
val cursor = db.query("PRAGMA wal_checkpoint", arrayOf())
cursor.moveToFirst()
Now your database file is up to date and you can save it by clicking on it in the Device Explorer. Do the right click -> Save As...
If you get an error, try to double click this file first and then try "Save As..." again.
Now, when you have this file saved, you can even put it into /app/src/main/assets/database/thebestdb.db
of your codebase and use it in createFromAsset()
if you want:
db = Room.databaseBuilder(
applicationContext,
AppDatabase::class.java, "thebestdb"
).createFromAsset("database/thebestdb.db")
.allowMainThreadQueries().build()
Upvotes: 1
Reputation: 17536
run the app
perform actions in app to get the DB created, and populated with the data you want
Android Studio > Device File Explorer
in the drop-down menu, select the device that has the DB that we want to export. for me, it is this emulator
navigate to data/data/your.app.package.name/databases
right click on your DB file > save as
Upvotes: 2
Reputation: 56958
Here's a CSV solution that will backup and restore ALL non-system/room/android tables BUT, it does not handle BLOBS (that is more complex) it may have space issues, it may have issues if Foreign Keys constraints are included (backing up the file is the suggested way as it is far less likely to encounter issues, other than the suggested restart of the App which is just an inconvenience).
Furthermore, for brevity and convenience, it
It revolves around 4 functions, in this example within the @Database annotated class (TheDatabase
). The functions are :-
getTableColumnnames
(see example/demo)
createAutoCSV
function)
createAutoCSV
(see example/demo)
writeText
directlyrestoreFromCSV
function).{COMMA}
(value can easily be changed) as it, like many values are constants.An example of the CSV that String this returns is :-
{START}
{TABLE},theTable
1,DATA1{COMMA}_1659427794369
....
{TABLE},theOtherTable
1,OTHERDATA1_1659427794{COMMA},{COMMA}1659427794398{COMMA},
....
{END}
{START}
and {END}
indicate the start and end of the data (not really needed){TABLE}
followed by a comma and the tablename itself.backupAsCSV()
(see example demo)
restoreFromCSV
(see example/demo )
this, in a single transaction (so there should only be one write to the database file itself, albeit it perhaps numerous pages) for each table encountered (should be ALL non SQLite/Room/Android tables):-
Note there is no protection built in against a restore from a previous backup file to a changed schema (which may well result in failures when inserting (e.g. NOT NULL conflict), or subsequent failures)
Note neither is there is any order logic that considers relationships (ensuring that Foreign Key parents are populated before the children)
In short, as suggested, backing up the database file(s) is the way to go as all of the pitfalls of using a CSV file are irrelevant. The only drawback is that it is suggested that the App is restarted after a restore via the file(s).
Working Example/Demo
Here's the full code that:-
The files/classes:-
RoomEntities.kt
(all the database code in one file for convenience/brevity)
/* Database level */
const val THEDATABASE_DATABASE_NAME = "thedatabase.whatever"
const val THEDATABASE_DATABASE_BACKUP_SUFFIX = "-bkp"
const val THEDATBASE_DATABASE_BACKUP_CSV_SUFFIX = ".csv"
const val SQLITE_WALFILE_SUFFIX = "-wal"
const val SQLITE_SHMFILE_SUFFIX = "-shm"
/* Tables */
const val THETABLE_TABLENAME = "theTable"
const val THEOTHERTABLE_TABLENAME = "theOtherTable"
/* Columns TheTable */
const val THETABLE_ID_COLUMN = THETABLE_TABLENAME + "_id"
const val THETABLE_OTHER_COLUMN = THEOTHERTABLE_TABLENAME + "_other"
/* Columns TheOtherTable */
const val THEOTHERTABLE_ID_COLUMN = THEOTHERTABLE_TABLENAME + "_id"
const val THEOTHERTABLE_OTHER_COLUMN = THEOTHERTABLE_TABLENAME + "_other"
const val THEOTHERTABLE_ANOTHER_COLUMN = THEOTHERTABLE_TABLENAME + "_another"
const val THEOTHERTABLE_YETANOTHER_COLUMN = THEOTHERTABLE_TABLENAME + "_yetanother"
/*CSV level */
const val CSV_NEWLINE = "\n"
const val CSV_INDICATOR_START = "{START}"
const val CSV_INDICATOR_END = "{END}"
const val CSV_INDICATOR_TABLE = "{TABLE}"
const val CSV_COMMA_REPLACE = "{COMMA}"
@Entity(tableName = THETABLE_TABLENAME)
data class TheTable(
@PrimaryKey @ColumnInfo(name = THETABLE_ID_COLUMN) var id: Long?=null,
@ColumnInfo(name = THETABLE_OTHER_COLUMN) var other: String
)
@Entity(tableName = THEOTHERTABLE_TABLENAME)
data class TheOtherTable(
@PrimaryKey @ColumnInfo(name = THEOTHERTABLE_ID_COLUMN) var id: Long? = null,
@ColumnInfo(name = THEOTHERTABLE_OTHER_COLUMN) var other: String,
@ColumnInfo(name = THEOTHERTABLE_ANOTHER_COLUMN) var another: String,
@ColumnInfo(name = THEOTHERTABLE_YETANOTHER_COLUMN ) var yetanother: ByteArray = byteArrayOf(1,2,3,4,5,127,-127)
)
@Dao
interface AllDAO {
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(theTable: TheTable): Long
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(theOtherTable: TheOtherTable): Long
@Query("SELECT * FROM $THETABLE_TABLENAME")
fun getAllFromTheTable(): List<TheTable>
@Query("SELECT * FROM $THEOTHERTABLE_TABLENAME")
fun getAllFromTheOtherTable(): List<TheOtherTable>
}
@Database(entities = [TheTable::class,TheOtherTable::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDAO
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance==null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,
THEDATABASE_DATABASE_NAME)
.allowMainThreadQueries()
.build()
}
return instance as TheDatabase
}
}
/**
* Backup the database
*/
fun backupDatabase(context: Context): Int {
var result = -99
if (instance==null) return result
val dbFile = context.getDatabasePath(THEDATABASE_DATABASE_NAME)
val dbWalFile = File(dbFile.path + SQLITE_WALFILE_SUFFIX)
val dbShmFile = File(dbFile.path + SQLITE_SHMFILE_SUFFIX)
val bkpFile = File(dbFile.path + THEDATABASE_DATABASE_BACKUP_SUFFIX)
val bkpWalFile = File(bkpFile.path + SQLITE_WALFILE_SUFFIX)
val bkpShmFile = File(bkpFile.path + SQLITE_SHMFILE_SUFFIX)
if (bkpFile.exists()) bkpFile.delete()
if (bkpWalFile.exists()) bkpWalFile.delete()
if (bkpShmFile.exists()) bkpShmFile.delete()
try {
dbFile.copyTo(bkpFile,true)
if (dbWalFile.exists()) dbWalFile.copyTo(bkpWalFile,true)
if (dbShmFile.exists()) dbShmFile.copyTo(bkpShmFile, true)
result = 0
} catch (e: IOException) {
e.printStackTrace()
}
return result
}
/**
* Restore the database and then restart the App
*/
fun restoreDatabase(context: Context,restart: Boolean = true) {
if(!File(context.getDatabasePath(THEDATABASE_DATABASE_NAME).path + THEDATABASE_DATABASE_BACKUP_SUFFIX).exists()) {
return
}
if (instance == null) return
val dbpath = instance!!.getOpenHelper().readableDatabase.path
val dbFile = File(dbpath)
val dbWalFile = File(dbFile.path + SQLITE_WALFILE_SUFFIX)
val dbShmFile = File(dbFile.path + SQLITE_SHMFILE_SUFFIX)
val bkpFile = File(dbFile.path + THEDATABASE_DATABASE_BACKUP_SUFFIX)
val bkpWalFile = File(bkpFile.path + SQLITE_WALFILE_SUFFIX)
val bkpShmFile = File(bkpFile.path + SQLITE_SHMFILE_SUFFIX)
dbShmFile.delete() /* ADDED for VACUUM BACKUP */
dbWalFile.delete() /* ADDED for VACUUM BACKUP */
try {
bkpFile.copyTo(dbFile, true)
if (bkpWalFile.exists()) bkpWalFile.copyTo(dbWalFile, true)
if (bkpShmFile.exists()) bkpShmFile.copyTo(dbShmFile,true)
} catch (e: IOException) {
e.printStackTrace()
}
if (restart) {
val i = context.packageManager.getLaunchIntentForPackage(context.packageName)
i!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
context.startActivity(i)
System.exit(0)
}
}
fun backupAsCSV() {
val csvFile = File(instance!!.getOpenHelper().writableDatabase.path + THEDATBASE_DATABASE_BACKUP_CSV_SUFFIX)
csvFile.delete()
csvFile.writeText(createAutoCSV())
}
fun restoreFromCSV() {
var db = instance!!.getOpenHelper().writableDatabase
db.beginTransaction() /* want to run in a single transaction */
var currentTableName = ""
val TAG = "CSVRESTORE"
val csvFile = File(instance!!.getOpenHelper().writableDatabase.path + THEDATBASE_DATABASE_BACKUP_CSV_SUFFIX)
csvFile.forEachLine {
/* if Start header then skip */
if (it.equals(CSV_INDICATOR_START)) {
Log.d(TAG,"START INDICATED")
} else {
/* if end header then skip */
if (it.equals(CSV_INDICATOR_END)) {
Log.d(TAG, "END INDICATED")
} else {
/* If table header then delete all rows for the table */
if (it.substring(0, CSV_INDICATOR_TABLE.length).equals(CSV_INDICATOR_TABLE)) {
currentTableName = it.substring(CSV_INDICATOR_TABLE.length + 1)
Log.d("CSVRESTORE","NEW TABLE INDICATED. TableName is $currentTableName")
db.execSQL("DELETE FROM $currentTableName")
Log.d(TAG,"$currentTableName cleared of data")
} else {
/* Otherwise it is actual data for a row so insert the data */
/* Note replacing the comma replacement string with a comma */
Log.d(TAG,"Actual Data is $it")
/* split the CSV into individual elements (column data) */
val splitdata = it.split(",")
var resolvedData: ArrayList<String> = arrayListOf()
val sql = StringBuilder().append("INSERT INTO $currentTableName VALUES(")
var afterFirstData = false
/* For each split element (column) */
/* 1. add a ? for parameter binding */
/* 2. add the resolved (commas reinstated) values for the parameters */
for (s in splitdata) {
if (afterFirstData) sql.append(",")
afterFirstData = true
sql.append("?")
resolvedData.add(s.replace(CSV_COMMA_REPLACE,","))
}
sql.append(");")
db.execSQL(sql.toString(),resolvedData.toArray())
}
}
}
}
db.setTransactionSuccessful()
db.endTransaction()
}
private fun createAutoCSV(): String {
val replaceCommaInData = CSV_COMMA_REPLACE /* commas in the data will be replaced by this */
val rv = StringBuilder().append(CSV_INDICATOR_START)
val sql = StringBuilder()
var afterFirstTable = false
var afterFirstColumn = false
var afterFirstRow = false
val suppDb = instance!!.getOpenHelper().writableDatabase
var currentTableName: String = ""
val csr = instance!!.query(
"SELECT name FROM sqlite_master " +
"WHERE type='table' " +
"AND name NOT LIKE('sqlite_%') " +
"AND name NOT LIKE('room_%') " +
"AND name NOT LIKE('android_%')",
null
)
while (csr.moveToNext()) {
sql.clear()
sql.append("SELECT ")
currentTableName = csr.getString(0)
//if (afterFirstTable) rv.append("$CSV_NEWLINE")
afterFirstTable = true
afterFirstColumn = false
rv.append(CSV_NEWLINE).append("$CSV_INDICATOR_TABLE,$currentTableName")
for (columnName in getTableColumnNames(currentTableName,suppDb)) {
if (afterFirstColumn) sql.append("||','||")
afterFirstColumn = true
sql.append("replace(`$columnName`,',','$replaceCommaInData')")
}
sql.append(" FROM `${currentTableName}`")
val csr2 = instance!!.query(sql.toString(),null)
afterFirstRow = false
while (csr2.moveToNext()) {
//if (!afterFirstRow) rv.append("$CSV_NEWLINE")
afterFirstRow = true
rv.append(CSV_NEWLINE).append(csr2.getString(0))
}
}
rv.append(CSV_NEWLINE).append("$CSV_INDICATOR_END")
return rv.toString()
}
private fun getTableColumnNames(tableName: String, suppDB: SupportSQLiteDatabase): List<String> {
val rv = arrayListOf<String>()
val csr = suppDB.query("SELECT name FROM pragma_table_info('${tableName}')",null)
while (csr.moveToNext()) {
rv.add(csr.getString(0))
}
csr.close()
return rv.toList()
}
}
MainActivity
(layout should be easy enough to build- see the lateinit
's)
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDAO
lateinit var backupButton: Button
lateinit var csvBackupButton: Button
lateinit var restoreButton: Button
lateinit var restoreFromCSVButton: Button
lateinit var addDataButton: Button
lateinit var listView: ListView
lateinit var listViewOther: ListView
var listAdapter: SimpleCursorAdapter? = null
var listAdapterOther: SimpleCursorAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
backupButton = this.findViewById(R.id.backup)
csvBackupButton = this.findViewById(R.id.csvBackup)
restoreButton = this.findViewById(R.id.restore)
restoreFromCSVButton = this.findViewById(R.id.restoreFromCSV)
addDataButton = this.findViewById(R.id.addData)
listView = this.findViewById(R.id.datalist)
listViewOther = this.findViewById(R.id.datalistOther)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
setButtonListeners()
setOrRefreshListView()
}
fun setButtonListeners() {
addDataButton.setOnClickListener {
dao.insert(TheTable(other = "DATA1,_${System.currentTimeMillis()}"))
dao.insert(TheTable(other = "DATA2_,${System.currentTimeMillis()}"))
dao.insert(TheTable(other = ",DATA3_${System.currentTimeMillis()}"))
/*******************************************************************************************************/
/* Note for CSV backup/restore BLOBS are NOT handled properly (that would entail even more complexity) */
/*******************************************************************************************************/
dao.insert(
TheOtherTable(
other = "OTHERDATA1_${System.currentTimeMillis() / 1000},",
another = ",${System.currentTimeMillis()},",
yetanother = byteArrayOf(0,0,0,0,0)
)
)
dao.insert(
TheOtherTable(
other = "OTHERDATA2_${System.currentTimeMillis() / 1000},",
another = "${System.currentTimeMillis()}")
)
dao.insert(
TheOtherTable(
other = "OTHER,DATA3_${System.currentTimeMillis() / 1000}",
another = "${System.currentTimeMillis()}",
yetanother = byteArrayOf(0x00,0x10,0x20,0x30,0x40,0x50,0x60,0x70))
)
dao.insert(
TheOtherTable(other = "OTHERDATA,4_${System.currentTimeMillis() / 1000}",
another = "${System.currentTimeMillis()}")
)
setOrRefreshListView()
}
backupButton.setOnClickListener {
db.backupDatabase(this)
}
csvBackupButton.setOnClickListener {
db.backupAsCSV()
}
restoreButton.setOnClickListener {
db.restoreDatabase(this)
setOrRefreshListView()
}
restoreFromCSVButton.setOnClickListener {
db.restoreFromCSV()
setOrRefreshListView()
}
}
/* initialise or refresh the 2 ListViews */
private fun setOrRefreshListView() {
var csr = db.getOpenHelper().writableDatabase.query("SELECT $THETABLE_ID_COLUMN AS ${BaseColumns._ID}, $THETABLE_OTHER_COLUMN FROM $THETABLE_TABLENAME")
if (listAdapter==null) {
listAdapter = SimpleCursorAdapter(
this,
android.R.layout.simple_list_item_2,
csr,
arrayOf("_id", THETABLE_OTHER_COLUMN),
intArrayOf(android.R.id.text1,android.R.id.text2), 0
)
listView.adapter = listAdapter
} else {
listAdapter!!.swapCursor(csr)
}
var csr2 = db.getOpenHelper().writableDatabase.query("SELECT $THEOTHERTABLE_ID_COLUMN AS ${BaseColumns._ID}, $THEOTHERTABLE_OTHER_COLUMN, $THEOTHERTABLE_ANOTHER_COLUMN FROM $THEOTHERTABLE_TABLENAME;")
if (listAdapterOther==null) {
listAdapterOther = SimpleCursorAdapter(
this,
android.R.layout.simple_list_item_2,
csr2,
arrayOf(THEOTHERTABLE_OTHER_COLUMN, THEOTHERTABLE_ANOTHER_COLUMN),
intArrayOf(android.R.id.text1,android.R.id.text2),
0
)
listViewOther.adapter = listAdapterOther
} else {
listAdapterOther!!.swapCursor(csr2)
}
}
}
When run after clicking ADD DATA once (3 rows in 1st table and 4 in 2nd) and then clicking both the BACKUP and CSV BACKUP buttons.
Then after clicking ADD DATA button again to add some data after the backups.
Then clicking the CSV RESTORE button
Again ADD DATA is clicked one or more times and then RESTORE button is clicked (restore from the File backup)
Upvotes: 1
Reputation: 56958
It would be far simpler to backup the database file(s) and probably also faster than trying to manipulate CSV data.
There is also the advantage that the database can then be used in SQLite Tools simply by copying the files.
P.S. The extension ( e.g. .csv .db) is only an indication of the file's use. In the example .whatever
has been used.
An issue is that after restoring the database the can be problems with accessing the restored data.
Another issue is that Room uses Write-Ahead Logging (WAL) by default and it doesn't appear to be that easy to checkpoint the database.
Here's an example/demo App that uses a Room database with a single table with just 2 columns. When the App is run there is a single activity that has 4 UI components:-
A button to allow the database to be backed up.
A button to allow the database to be restored.
A button that allows some data to be added (3 rows).
A List of the current data.
Note for simplicity and brevity the all work is undertaken on the main thread
Also for simplicity and brevity, all the database code is in a single file RoomEntities
Also for simplicity the backup is made to the same directory data/data/the_package_name/databases
RoomEntities (all the database code, including the backup and restore code) :-
const val THETABLE_TABLENAME = "theTable"
const val THETABLE_ID_COLUMN = THETABLE_TABLENAME + "_id"
const val TheTABLE_OTHER_COLUMN = THETABLE_TABLENAME + "_other"
const val THEDATABASE_DATABASE_NAME = "thedatabase.whatever"
const val THEDATABASE_DATABASE_BACKUP_SUFFIX = "-bkp"
const val SQLITE_WALFILE_SUFFIX = "-wal"
const val SQLITE_SHMFILE_SUFFIX = "-shm"
@Entity(tableName = THETABLE_TABLENAME)
data class TheTable(
@PrimaryKey @ColumnInfo(name = THETABLE_ID_COLUMN) var id: Long?=null,
@ColumnInfo(name = TheTABLE_OTHER_COLUMN) var other: String
)
@Dao
interface AllDAO {
@Insert(onConflict = OnConflictStrategy.IGNORE)
fun insert(theTable: TheTable): Long
@Query("SELECT * FROM $THETABLE_TABLENAME")
fun getAllFromTheTable(): List<TheTable>
}
@Database(entities = [TheTable::class], exportSchema = false, version = 1)
abstract class TheDatabase: RoomDatabase() {
abstract fun getAllDao(): AllDAO
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance==null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,
THEDATABASE_DATABASE_NAME)
.allowMainThreadQueries()
.build()
}
return instance as TheDatabase
}
}
/**
* Backup the database
*/
fun backupDatabase(context: Context): Int {
var result = -99
if (instance==null) return result
val dbFile = context.getDatabasePath(THEDATABASE_DATABASE_NAME)
val dbWalFile = File(dbFile.path + SQLITE_WALFILE_SUFFIX)
val dbShmFile = File(dbFile.path + SQLITE_SHMFILE_SUFFIX)
val bkpFile = File(dbFile.path + THEDATABASE_DATABASE_BACKUP_SUFFIX)
val bkpWalFile = File(bkpFile.path + SQLITE_WALFILE_SUFFIX)
val bkpShmFile = File(bkpFile.path + SQLITE_SHMFILE_SUFFIX)
if (bkpFile.exists()) bkpFile.delete()
if (bkpWalFile.exists()) bkpWalFile.delete()
if (bkpShmFile.exists()) bkpShmFile.delete()
checkpoint()
try {
dbFile.copyTo(bkpFile,true)
if (dbWalFile.exists()) dbWalFile.copyTo(bkpWalFile,true)
if (dbShmFile.exists()) dbShmFile.copyTo(bkpShmFile, true)
result = 0
} catch (e: IOException) {
e.printStackTrace()
}
return result
}
/**
* Restore the database and then restart the App
*/
fun restoreDatabase(context: Context,restart: Boolean = true) {
if(!File(context.getDatabasePath(THEDATABASE_DATABASE_NAME).path + THEDATABASE_DATABASE_BACKUP_SUFFIX).exists()) {
return
}
if (instance == null) return
val dbpath = instance!!.getOpenHelper().readableDatabase.path
val dbFile = File(dbpath)
val dbWalFile = File(dbFile.path + SQLITE_WALFILE_SUFFIX)
val dbShmFile = File(dbFile.path + SQLITE_SHMFILE_SUFFIX)
val bkpFile = File(dbFile.path + THEDATABASE_DATABASE_BACKUP_SUFFIX)
val bkpWalFile = File(bkpFile.path + SQLITE_WALFILE_SUFFIX)
val bkpShmFile = File(bkpFile.path + SQLITE_SHMFILE_SUFFIX)
try {
bkpFile.copyTo(dbFile, true)
if (bkpWalFile.exists()) bkpWalFile.copyTo(dbWalFile, true)
if (bkpShmFile.exists()) bkpShmFile.copyTo(dbShmFile,true)
checkpoint()
} catch (e: IOException) {
e.printStackTrace()
}
if (restart) {
val i = context.packageManager.getLaunchIntentForPackage(context.packageName)
i!!.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
context.startActivity(i)
System.exit(0)
}
}
private fun checkpoint() {
var db = this.getOpenHelper().writableDatabase
db.query("PRAGMA wal_checkpoint(FULL);",null)
db.query("PRAGMA wal_checkpoint(TRUNCATE);",null)
}
}
As can be seen from the code, the backup and restore is basically just copying files.
The Activity's layout is:-
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
/>
<Button
android:id="@+id/backup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="BACKUP"
>
</Button>
<Button
android:id="@+id/restore"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="RESTORE"
>
</Button>
<Button
android:id="@+id/addData"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="ADD DATA"
>
</Button>
<ListView
android:id="@+id/datalist"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
>
</ListView>
</LinearLayout>
The Activity (MainActivity) is:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: AllDAO
lateinit var backupButton: Button
lateinit var restoreButton: Button
lateinit var addDataButton: Button
lateinit var listView: ListView
var listAdapter: SimpleCursorAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
backupButton = this.findViewById(R.id.backup)
restoreButton = this.findViewById(R.id.restore)
addDataButton = this.findViewById(R.id.addData)
listView = this.findViewById(R.id.datalist)
db = TheDatabase.getInstance(this)
dao = db.getAllDao()
setButtonListeners()
setOrRefreshListView()
}
fun setButtonListeners() {
addDataButton.setOnClickListener {
dao.insert(TheTable(other = "DATA_${System.currentTimeMillis()}"))
dao.insert(TheTable(other = "DATA_${System.currentTimeMillis()}"))
dao.insert(TheTable(other = "DATA_${System.currentTimeMillis()}"))
setOrRefreshListView()
}
backupButton.setOnClickListener {
db.backupDatabase(this)
}
restoreButton.setOnClickListener {
db.restoreDatabase(this)
}
}
fun setOrRefreshListView() {
var csr = db.getOpenHelper().writableDatabase.query("SELECT $THETABLE_ID_COLUMN AS _id, $TheTABLE_OTHER_COLUMN FROM $THETABLE_TABLENAME")
if (listAdapter==null) {
listAdapter = SimpleCursorAdapter(
this,
android.R.layout.simple_list_item_2,
csr,
arrayOf("_id", TheTABLE_OTHER_COLUMN),
intArrayOf(android.R.id.text1,android.R.id.text2), 0
)
listView.adapter = listAdapter
} else {
listAdapter!!.swapCursor(csr)
}
}
}
Results
When first Run then the Appears as:-
If the backup button is clicked then the files will be backed up (even if there is no data (there will actually be some data in the file though, such as the file header and the system tables)).
If after clicking the Backup button, the restore button is clicked (even if no data has been added) then the database is actually restored and the App is restarted.
If the AddData button is clicked the 3 rows of data are added and displayed (another 3 added if clicked again and so on).
As the Backup buttons was previously clicked. Clicking the restore button will restore to the backup that has no data and restart the App displaying no data.
Adding 2 sets of data and also clicking the Backup button
Adding another 2 sets of data
Clicking restore then the data is restored to just the 6 rows of data
Upvotes: 7