I started learning kotlin recently, and I am trying to implement a database using SQLite. When I try using the functions the app crashes. I don't know how to find the error log so I have added the functions I made and where the functions are implemented. Thank you for your help.
package com.example.mrtayyab.sqlitedbkotlin
import android.content.ClipDescription
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.database.DatabaseUtils
import android.database.sqlite.SQLiteDatabase
import android.database.sqlite.SQLiteOpenHelper
class DatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, 1) {
companion object {
val DATABASE_NAME = "passwords.db"
val TABLE_NAME = "passwords_table"
val COL_1 = "ID"
val COL_2 = "DESCRIPTION"
val COL_3 = "PASSWORD"
val COL_4 = "DATE_TIME"
}
override fun onCreate(db: SQLiteDatabase) {
db.execSQL("CREATE TABLE IF NOT EXISTS $TABLE_NAME(ID INTEGER PRIMARY KEY AUTOINCREMENT , DESCRIPTION TEXT , PASSWORD TEXT , DATE_TIME INTEGER)")
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
onCreate(db)
}
fun insertData(description: String, password: String, date_time: String): Boolean? {
val db = this.writableDatabase
val cv = ContentValues()
cv.put(COL_2, description)
cv.put(COL_3, password)
cv.put(COL_4, date_time)
val res = db.insert(TABLE_NAME, null, cv)
return !res.equals(-1)
}
fun getData(id: String, COL_NUM: String): Cursor {
val column = arrayOf("$COL_NUM")
val columnValue = arrayOf("$id")
val db = this.writableDatabase
return db.query("$TABLE_NAME", column, "$COL_1", columnValue, null, null, null )
}
fun getTableCount(): Long {
val db = this.readableDatabase
val count = DatabaseUtils.queryNumEntries(db, TABLE_NAME)
return count
}
}
Here is the code where it is implemented
package com.example.juliaojonah_hci_outcome2_v1
import android.content.Context
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.view.View
import android.widget.LinearLayout
import android.widget.ScrollView
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.mrtayyab.sqlitedbkotlin.DatabaseHelper
import com.google.gson.Gson
import kotlinx.android.synthetic.main.activity_main.*
import java.text.DateFormat
import java.util.*
import kotlin.collections.ArrayList
class passwords : AppCompatActivity() {
lateinit var myDb: DatabaseHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_passwords)
val dateTime = Calendar.getInstance().time
val dateFormatted = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(dateTime)
val dateTextView : TextView = findViewById(R.id.textDate2) as TextView
dateTextView.setText(dateFormatted)
val recyclerView = findViewById(R.id.savedPasswords) as RecyclerView
recyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
val counter = myDb.getTableCount()
val passwords = ArrayList<PasswordCluster>()
if (counter > 0) {
for (i in 1..counter) {
var id = i
var description = myDb.getData(id.toString(), "COL_2")
var password = myDb.getData(id.toString(), "COL_3")
var dateTime = myDb.getData(id.toString(), "COL_4")
passwords.add(PasswordCluster(description.toString(), password.toString(), dateTime.toString()))
}
}
/*
passwords.add(PasswordCluster("Amazon", "56,78,90,12", "Friday"))
passwords.add(PasswordCluster("Amazon", "56,78,90,12", "Friday"))
passwords.add(PasswordCluster("Amazon", "56,78,90,12", "Friday"))
passwords.add(PasswordCluster("Amazon", "56,78,90,12", "Friday"))
passwords.add(PasswordCluster("Amazon", "56,78,90,12", "Friday"))
*/
val adapter = CustomAdapter(passwords)
recyclerView.adapter = adapter
}
fun firstActivity(view: View){
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}
I use the save data function in this last piece of code and it doesn't crash, but I don't know if it is working correctly
package com.example.juliaojonah_hci_outcome2_v1
import android.content.Context
import android.content.Intent
import android.os.Bundle
import android.text.TextUtils
import android.view.View
import android.widget.Button
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.mrtayyab.sqlitedbkotlin.DatabaseHelper
import com.google.gson.Gson
import kotlinx.android.synthetic.main.activity_main.*
import java.security.spec.PSSParameterSpec
import java.text.DateFormat
import java.util.*
import kotlin.random.Random
class MainActivity : AppCompatActivity() {
lateinit var myDb: DatabaseHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dateTime = Calendar.getInstance().time
val dateFormatted = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(dateTime)
val dateTextView : TextView = findViewById(R.id.textDate) as TextView
dateTextView.setText(dateFormatted)
myDb = DatabaseHelper(this)
}
fun secondActivity(view: View){
val intent = Intent(this, passwords::class.java)
startActivity(intent)
}
fun randomise(view: View){
val passwordSet1 = Random.nextInt(0,99)
val passwordSet2 = Random.nextInt(0,99)
val passwordSet3 = Random.nextInt(0,99)
val passwordSet4 = Random.nextInt(0,99)
val editText1 = findViewById<EditText>(R.id.password1)
editText1.setText(passwordSet1.toString())
val editText2 = findViewById<EditText>(R.id.password2)
editText2.setText(passwordSet2.toString())
val editText3 = findViewById<EditText>(R.id.password3)
editText3.setText(passwordSet3.toString())
val editText4 = findViewById<EditText>(R.id.password4)
editText4.setText(passwordSet4.toString())
}
fun save(view: View){
var correct = true
val description = descriptionName.text.toString().trim()
val password = password1.text.toString().trim() + "-" + password2.text.toString().trim() + "-" + password3.text.toString().trim() + "-" + password4.text.toString().trim()
val dateTime = Calendar.getInstance().time
val dateFormatted = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(dateTime)
if (TextUtils.isEmpty(description)){
descriptionName.error = "Enter description"
correct = false
}
if (password == "---"){
password1.error = "Enter password"
password2.error = "Enter password"
password3.error = "Enter password"
password4.error = "Enter password"
correct = false
}
var isInserted = myDb.insertData(description, password, dateFormatted)
if (isInserted == true && correct == true){
Toast.makeText(this, "Data Saved", Toast.LENGTH_LONG).show()
Toast.makeText(this, isInserted.toString(), Toast.LENGTH_LONG).show()
} else {
Toast.makeText(this, "Data Not Saved", Toast.LENGTH_LONG).show()
}
}
}
Here is the error that first appears when I try to start up the password activity
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.juliaojonah_hci_outcome2_v1, PID: 20054
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.juliaojonah_hci_outcome2_v1/com.example.juliaojonah_hci_outcome2_v1.passwords}: kotlin.UninitializedPropertyAccessException: lateinit property myDb has not been initialized
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2646)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property myDb has not been initialized
at com.example.juliaojonah_hci_outcome2_v1.passwords.onCreate(passwords.kt:42)
at android.app.Activity.performCreate(Activity.java:6662)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2599)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
This is what happens when I try to run the .getData function
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.example.juliaojonah_hci_outcome2_v1, PID: 20221
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.example.juliaojonah_hci_outcome2_v1/com.example.juliaojonah_hci_outcome2_v1.passwords}: kotlin.UninitializedPropertyAccessException: lateinit property myDb has not been initialized
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2646)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
Caused by: kotlin.UninitializedPropertyAccessException: lateinit property myDb has not been initialized
at com.example.juliaojonah_hci_outcome2_v1.passwords.onCreate(passwords.kt:51)
at android.app.Activity.performCreate(Activity.java:6662)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2599)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2707)
at android.app.ActivityThread.-wrap12(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1460)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6077)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:866)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:756)
When I try using the functions the app crashes.
lateinit property myDb has not been initialized
is saying that you need to initialise myDB. that is you need to include :-
myDB = DatabaseHelper(this)
setContentView(R.layout.activity_main)
I am using sqlite incorrectly in this kotlin class You aren't too far away but you appear to misunderstand queries and for Android Cursors. In short you are querying the database in a loop when a single query can return all of that information and you can then loop through the result the Cursor.
Here's some of the issues.
In the getData function you should be using "${COL_1}=?"
for the 3rd paramter to the call to query as this parameter is the WHERE clause less the WHERE keyword. You are effectively saying SELECT whatever_column_is_passed FROM passwords_table WHERE x;
However, you then have a few issues in regard to processing (not processing it) the Cursor returned from the getData.
First you assume that the number of rows in the table val counter = myDb.getTableCount()
is going to equate to the rows returned and that the id's will be numbered 1,2,3....... This may initially be the case but if a row is deleted or if an insert fails then the sequence may not be monotonically increasing.
When going through the loop you are trying to assign a Cursor to a String e.g. var description = myDb.getData(id.toString(), "COL_2")
will result in description being a Cursor. To get around the issue of trying to build a PasswordCluster not liking a Cursor you have used the Cursor's toString method which will result in the default object toString method being used, which returns details about the Cursos not the expected value (description, password or datetime).
It appears that you want to get a list of PasswordCluster's into passwords (val passwords = ArrayList<PasswordCluster>()
) fro the RecyclerView.
I'd suggest that you should have a function in the DatabaseHelper class that retruns the list of all passwords as an ArrayList e.g.
fun getListOfAllPasswordClusters(): ArrayList<PasswordCluster> {
val rv = ArrayList<PasswordCluster>()
val db = this.writableDatabase
val csr = db.query(TABLE_NAME,null /* ALL columns */,null,null,null,null,null)
//return db.query("$TABLE_NAME", column, "$COL_1", columnValue, null, null, null )
while (csr.moveToNext()) {
rv.add(
PasswordCluster(
csr.getString(csr.getColumnIndex(COL_2)),
csr.getString(csr.getColumnIndex(COL_3)),
csr.getString(csr.getColumnIndex(COL_4))
)
)
/* NOTE ideally you should also store the id in PasswordCluster
So assuming a constructor that takes
Long /* id */
,String /* description */
,String /* password */
,String /* date_time */
E.G.
rv.add(
PasswordCluster(
csr.getLong(csr.getColumnIndex(COL_1)),
csr.getString(csr.getColumnIndex(COL_2)),
csr.getString(csr.getColumnIndex(COL_3)),
csr.getString(csr.getColumnIndex(COL_4))
)
)
*/
}
csr.close()
return rv
}
Then you could have :-
class passwords : AppCompatActivity() {
lateinit var myDb: DatabaseHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_passwords)
val dateTime = Calendar.getInstance().time
val dateFormatted = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM).format(dateTime)
val dateTextView : TextView = findViewById(R.id.textDate2) as TextView
dateTextView.setText(dateFormatted)
val recyclerView = findViewById(R.id.savedPasswords) as RecyclerView
recyclerView.layoutManager = LinearLayoutManager(this, RecyclerView.VERTICAL, false)
val passwords = getListOfAllPasswordClusters()
val adapter = CustomAdapter(passwords)
recyclerView.adapter = adapter
}
fun firstActivity(view: View){
val intent = Intent(this, MainActivity::class.java)
startActivity(intent)
}
}
This is an example based solely upon adding the suggested getListOfAllPasswordClusters() (with the commented out part replacing the rv.add(.........)
code so the id is retrieved) function and using the following code in the activity :-
class MainActivity : AppCompatActivity() {
lateinit var myDB: DatabaseHelper
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
myDB = DatabaseHelper(this)
addSomeDataIfNone()
val passwords = myDB.getListOfAllPasswordClusters()
for (p: PasswordCluster in passwords) {
Log.d("PASSWORDCLUSTER","ID=${p.id} Description=${p.description} password=${p.password} datetime=${p.date_time}")
}
}
fun addSomeDataIfNone() {
if (DatabaseUtils.queryNumEntries(myDB.writableDatabase,DatabaseHelper.TABLE_NAME) > 0) return
myDB.insertData("Test1","password1","2019-12-01")
myDB.insertData("Test2","password2","2019-12-02")
myDB.insertData("Test3","password3","2019-12-03")
}
}
The runs and results in :-
2019-12-01 07:59:58.289 D/PASSWORDCLUSTER: ID=1 Description=Test1 password=password1 datetime=2019-12-01
2019-12-01 07:59:58.289 D/PASSWORDCLUSTER: ID=2 Description=Test2 password=password2 datetime=2019-12-02
2019-12-01 07:59:58.289 D/PASSWORDCLUSTER: ID=3 Description=Test3 password=password3 datetime=2019-12-03