After intent is recreated i get exception on every database query. I think it may be because of way of recreating activity, but don't know how to solve this.
I'm recreating it after change of language and dark mode change.
Exception is thrown when recreate activity:
Attempt to re-open an already-closed object: SQLiteDatabase:
Activity
private lateinit var db: SQLiteDatabase
private lateinit var oh: LOTDatabaseHelper
...
onCreate
...
oh = LOTDatabaseHelper(this)
db = oh.readableDatabase
...
onDestroy
...
db.close()
oh.close()
...
and how I recreate intent
val intent = intent
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
startActivity(intent)
finish()
edit:
What I've found is that Activity is created about twice, then one is left and the rest is destroyed. OnDestroy seems to work for all Activities and closes Database, which shouldn't be possible.
Now i'm navigating to Splash screen and then to Activity which uses Database and everything is working. So Why lifecycle is not working well when Activity is replaced with herself?
CodePudding user response:
The open an already closed database is commonly caused by mismatched opens and closes.
Often the fix is to simply NOT open and close the database (unless specifically needed such as restoring the database from a backup copy, or forcing a WAL checkpoint to backup the database) but to instead just open the database once.
There is frequently no need to keep opening and closing the database as when the App finishes the database will be closed (you can always close it it the top most activity's onDestroy). Furthermore opening the database is a resource costly, so many opens is resource wasteful/inefficient.
- Perhaps see Android SQLite DB When to Close
I would suggest removing all of closes. You may also wish to consider a singleton approach and perhaps even have all the DB functionality within the Helper.
As an example perhaps consider:-
The Helper DBHelper :-
class LOTDatabaseHelper(context: Context): SQLiteOpenHelper(
context,
DATABASE_NAME,
null,
DATABASE_VERSION) {
companion object {
const val DATABASE_NAME = "my.db"
const val DATABASE_VERSION = 1
@Volatile
private var instance: SQLiteDatabase? = null
fun getInstance(context: Context): SQLiteDatabase {
if (instance == null) {
instance = LOTDatabaseHelper(context).writableDatabase
}
return instance as SQLiteDatabase
}
}
override fun onCreate(db: SQLiteDatabase?) {
db?.execSQL(Table1.CREATE_SQL)
}
override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
TODO("Not yet implemented")
}
override fun onDowngrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
super.onDowngrade(db, oldVersion, newVersion)
}
fun insert(name: String): Long {
val cv = ContentValues()
cv.put(Table1.COL_NAME,name)
return instance!!.insert(Table1.TABLE_NAME,null,cv)
}
fun getAllFromTable1() : Cursor {
return instance!!.query(Table1.TABLE_NAME,null,null,null,null,null,"${Table1.COL_NAME} ASC")
}
@SuppressLint("Range")
fun logAll() {
val csr = getAllFromTable1()
var row = 1
val nameix = csr.getColumnIndex(COL_NAME)
while (csr.moveToNext()) {
Log.d("CURSORINFO","Row ${row } ${COL_NAME} is ${csr.getString(csr.getColumnIndex(
COL_NAME))}")
//val test = csr.getString(csr.getColumnIndex("${COL_NAME}"))
}
csr.close()
}
// Table stuff
class Table1 {
companion object {
const val TABLE_NAME = "table1"
const val COL_ID = BaseColumns._ID
const val COL_NAME = "${TABLE_NAME}_name"
const val CREATE_SQL = "CREATE TABLE IF NOT EXISTS $TABLE_NAME (${COL_ID} INTEGER PRIMARY KEY, $COL_NAME TEXT);"
}
}
}
An initial Activity for calling the second Activity MainActivity :-
class MainActivity : AppCompatActivity() {
val TAG = "MAINACTIVITYINFO"
private lateinit var dbother: DBHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
dbother = DBHelper.getInstance(this)!!
dbother.insert("TESTOTHER") // Use the Insert in the DBHelper
/* of course you can still get an SQLiteDatabase */
/* There is very little use getting readable as it gets writable */
/* so get writable is more accurate description */
var sqliteDatabase = dbother.writableDatabase //<<<<<< can be used
logCursorInfo(dbother.allAsCursor)
// NOW Start the other Activity
Log.d(TAG,"starting Activity2")
intent = Intent(this,Activity2::class.java)
startActivity(intent)
Log.d(TAG,"after starting Activity2")
}
override fun onDestroy() {
super.onDestroy()
Log.d("ONDESTROY","On Destroy invoked.")
}
override fun onResume() {
super.onResume()
Log.d(TAG,"On Resume invoked")
dbother = DBHelper.getInstance(this)!!
}
//
@SuppressLint("Range")
fun logCursorInfo(csr: Cursor) {
val nameix = csr.getColumnIndex(LOTDatabaseHelper.Table1.COL_NAME)
val idix = csr.getColumnIndex(LOTDatabaseHelper.Table1.COL_ID)
while (csr.moveToNext()) {
//Log.d(TAG,"${LOTDatabaseHelper.Table1.COL_ID} = ${csr.getString(idix)} ${LOTDatabaseHelper.Table1.COL_NAME} = ${csr.getString(nameix)}")
Log.d(TAG,"${csr.getString(csr.getColumnIndex(LOTDatabaseHelper.Table1.COL_NAME))}")
}
}
companion object {
var counter: Int = 0
}
}
- This uses the DBHelper but gets an Instance, it then adds a few rows, extracts them then starts the 2nd Activity.
Activity2 the activity where you are having the issues :-
class Activity2 : AppCompatActivity() {
val TAG = "ACTIVITY2INFO"
private lateinit var dbother: DBHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_2)
Log.d(TAG,"Activity 2 Started (onCreate). $this")
dbother = DBHelper.getInstance(this)!! // Get the Database
dbother.insert("TESTOTHER_ACTIVITY2") // Add a row
var csr = dbother.allAsCursor // get All the rows
DatabaseUtils.dumpCursor(csr) // dump the cursor
csr.close() // close the cursor
/* Start another activity (will loop so stop with counter)*/
Log.d(TAG,"Restarting Activity. counter is ${counter } $this")
if (counter < 3) {
val intent = intent
intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
startActivity(intent) //<<<<<<<<<< RUN 1 Not the Way as starts a new activity
/* Perhaps the following???? */
// this.intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
//this.recreate() //<<<<<<<<<< RUN 2 perhaps the way but no intent
}
Log.d(TAG,"Finishing Activity 2 $this")
finish()
}
companion object {
var counter: Int = 0
}
override fun onResume() {
super.onResume()
Log.d(TAG,"OnResume Invoked. $this")
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG,"OnDestory (after super call - before db close and helper close. $this)")
Log.d(TAG,"OnDestory (after all. $this)")
}
}
- This gets an (the) DBHelper instance, inserts a row, extracts all the data and then starts another activity NOTING that this would loop endlessly so hence the counter.
Hopefully the comments will be of assistance.
As you can see I've included quite a bit of logging. However, as suggested the database is never closed.
Running the App
Here's the results of running the App i.e. the log (from a fresh install) :-
2021-10-06 19:52:50.715 D/DBHELPERINFO: getInstance returning instance a.a.so69454766kotlinsqlite.DBHelper@59860ab
2021-10-06 19:52:50.717 D/MAINACTIVITYINFO: TESTOTHER
2021-10-06 19:52:50.717 D/MAINACTIVITYINFO: starting Activity2
2021-10-06 19:52:50.724 D/MAINACTIVITYINFO: after starting Activity2
2021-10-06 19:52:50.734 D/MAINACTIVITYINFO: On Resume invoked
2021-10-06 19:52:50.734 D/DBHELPERINFO: getInstance returning instance a.a.so69454766kotlinsqlite.DBHelper@59860ab
2021-10-06 19:52:50.848 D/ACTIVITY2INFO: Activity 2 Started (onCreate). a.a.so69454766kotlinsqlite.Activity2@3269902
2021-10-06 19:52:50.848 D/DBHELPERINFO: getInstance returning instance a.a.so69454766kotlinsqlite.DBHelper@59860ab
2021-10-06 19:52:50.850 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@950aa67
2021-10-06 19:52:50.851 I/System.out: 0 {
2021-10-06 19:52:50.852 I/System.out: _id=1
2021-10-06 19:52:50.852 I/System.out: table1_name=TESTOTHER
2021-10-06 19:52:50.852 I/System.out: }
2021-10-06 19:52:50.852 I/System.out: 1 {
2021-10-06 19:52:50.852 I/System.out: _id=2
2021-10-06 19:52:50.852 I/System.out: table1_name=TESTOTHER_ACTIVITY2
2021-10-06 19:52:50.852 I/System.out: }
2021-10-06 19:52:50.852 I/System.out: <<<<<
2021-10-06 19:52:50.856 D/ACTIVITY2INFO: Restarting Activity. counter is 0 a.a.so69454766kotlinsqlite.Activity2@3269902
2021-10-06 19:52:50.864 D/ACTIVITY2INFO: Finishing Activity 2 a.a.so69454766kotlinsqlite.Activity2@3269902
2021-10-06 19:52:51.020 D/ACTIVITY2INFO: Activity 2 Started (onCreate). a.a.so69454766kotlinsqlite.Activity2@7db7a75
2021-10-06 19:52:51.020 D/DBHELPERINFO: getInstance returning instance a.a.so69454766kotlinsqlite.DBHelper@59860ab
2021-10-06 19:52:51.021 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@7fc80ae
2021-10-06 19:52:51.021 I/System.out: 0 {
2021-10-06 19:52:51.021 I/System.out: _id=1
2021-10-06 19:52:51.021 I/System.out: table1_name=TESTOTHER
2021-10-06 19:52:51.021 I/System.out: }
2021-10-06 19:52:51.021 I/System.out: 1 {
2021-10-06 19:52:51.021 I/System.out: _id=2
2021-10-06 19:52:51.022 I/System.out: table1_name=TESTOTHER_ACTIVITY2
2021-10-06 19:52:51.022 I/System.out: }
2021-10-06 19:52:51.022 I/System.out: 2 {
2021-10-06 19:52:51.022 I/System.out: _id=3
2021-10-06 19:52:51.022 I/System.out: table1_name=TESTOTHER_ACTIVITY2
2021-10-06 19:52:51.022 I/System.out: }
2021-10-06 19:52:51.022 I/System.out: <<<<<
2021-10-06 19:52:51.023 D/ACTIVITY2INFO: Restarting Activity. counter is 1 a.a.so69454766kotlinsqlite.Activity2@7db7a75
2021-10-06 19:52:51.030 D/ACTIVITY2INFO: Finishing Activity 2 a.a.so69454766kotlinsqlite.Activity2@7db7a75
2021-10-06 19:52:51.081 D/ACTIVITY2INFO: Activity 2 Started (onCreate). a.a.so69454766kotlinsqlite.Activity2@3433874
2021-10-06 19:52:51.081 D/DBHELPERINFO: getInstance returning instance a.a.so69454766kotlinsqlite.DBHelper@59860ab
2021-10-06 19:52:51.082 I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@5c713d1
2021-10-06 19:52:51.082 I/System.out: 0 {
2021-10-06 19:52:51.082 I/System.out: _id=1
2021-10-06 19:52:51.082 I/System.out: table1_name=TESTOTHER
2021-10-06 19:52:51.082 I/System.out: }
2021-10-06 19:52:51.082 I/System.out: 1 {
2021-10-06 19:52:51.083 I/System.out: _id=2
2021-10-06 19:52:51.083 I/System.out: table1_name=TESTOTHER_ACTIVITY2
2021-10-06 19:52:51.083 I/System.out: }
2021-10-06 19:52:51.083 I/System.out: 2 {
2021-10-06 19:52:51.083 I/System.out: _id=3
2021-10-06 19:52:51.083 I/System.out: table1_name=TESTOTHER_ACTIVITY2
2021-10-06 19:52:51.083 I/System.out: }
2021-10-06 19:52:51.083 I/System.out: 3 {
2021-10-06 19:52:51.083 I/System.out: _id=4
2021-10-06 19:52:51.083 I/System.out: table1_name=TESTOTHER_ACTIVITY2
2021-10-06 19:52:51.083 I/System.out: }
2021-10-06 19:52:51.083 I/System.out: <<<<<
2021-10-06 19:52:51.084 D/ACTIVITY2INFO: Restarting Activity. counter is 2 a.a.so69454766kotlinsqlite.Activity2@3433874
2021-10-06 19:52:51.084 D/ACTIVITY2INFO: Finishing Activity 2 a.a.so69454766kotlinsqlite.Activity2@3433874
2021-10-06 19:52:51.097 D/MAINACTIVITYINFO: On Resume invoked
2021-10-06 19:52:51.097 D/DBHELPERINFO: getInstance returning instance a.a.so69454766kotlinsqlite.DBHelper@59860ab
One thing of note is the the DBHelper instance that is retrieved is always DBHelper@59860ab (i.e. the single instance).
As you have noted what happens with the Activity is that another is is started. You may wish to try using the commented out:-
/* Perhaps the following???? */
// this.intent.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION)
//this.recreate() //<<<<<<<<<< RUN 2 perhaps the way but no intent
- I'm not sure whether this would achieve what you desire though.