Class Declaration and Constructor
class DatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION)
The line declares the DatabaseHelper class, which extends SQLiteOpenHelper. It takes one parameter in its constructor – a Context. This usually is the application context or activity context that it needs in order to use the file system and resources provided by Android.
SQLiteOpenHelper is an android helper class to manage database creation and onupgrade management. By extending it, our DatabaseHelper class will inherit methods used in creating, upgrading, and opening the database.
Companion Object and Constants
companion object {
private const val DATABASE_NAME = "files.db" private const val DATABASE_VERSION = 2 const val TABLE_NAME = "files" const val COLUMN_ID = "id" const val COLUMN_FILE_NAME = "file_name" const val COLUMN_FILE_PATH = "file_path" const val COLUMN_DATE = "date_saved"
}
The companion object in Kotlin is similar to static members in Java. It contains constants used throughout the class:
DATABASE_NAME
: The name of the SQLite database file.DATABASE_VERSION
: The version number of the database schema. It’s set to 2, indicating there’s been an update to the schema.TABLE_NAME
: The name of the table in the database.COLUMN_*
: These constants define the names of the columns in the table.
onCreate Method
override fun onCreate(db: SQLiteDatabase) {
val createTable = "CREATE TABLE $TABLE_NAME (" +
"$COLUMN_ID INTEGER PRIMARY KEY AUTOINCREMENT, " +
"$COLUMN_FILE_NAME TEXT, " +
"$COLUMN_FILE_PATH TEXT, " +
"$COLUMN_DATE TEXT)" db.execSQL(createTable)
}
1. Method Declaration
override fun onCreate(db: SQLiteDatabase)
override
keyword indicates that this method is overriding a method from the superclass (SQLiteOpenHelper).fun
is the Kotlin keyword for declaring a function.onCreate
is the name of the method.db: SQLiteDatabase
is the parameter. It’s an instance of SQLiteDatabase that the method can use to execute SQL commands.
2. Creating the SQL Stateme
val createTable = "CREATE TABLE $TABLE_NAME (" +
"$COLUMN_ID INTEGER PRIMARY KEY AUTOINCREMENT, " +
"$COLUMN_FILE_NAME TEXT, " +
"$COLUMN_FILE_PATH TEXT, " +
"$COLUMN_DATE TEXT)"
This creates a String containing an SQL command to create a new table. Let’s break it down:
CREATE TABLE
is the SQL command to create a new table.$TABLE_NAME
is a variable containing the name of the table (defined in the companion object).- The parentheses
()
contain the definitions for each column in the table:$COLUMN_ID INTEGER PRIMARY KEY AUTOINCREMENT
: This creates an ID column that automatically increments for each new row.$COLUMN_FILE_NAME TEXT
: This creates a column to store the file name as text.$COLUMN_FILE_PATH TEXT
: This creates a column to store the file path as text.$COLUMN_DATE TEXT
: This creates a column to store the date as text.
3. Executing the SQL Statement
db.execSQL(createTable)
db
is the SQLiteDatabase instance passed to the method.execSQL
is a method of SQLiteDatabase that executes an SQL statement directly.createTable
is the SQL statement we created earlier.
Purpose and When It's Called
This is due to the onCreate method inside the SQLiteOpenHelper class which will be invoked by the system, after the latter created the database for the first time. Usually this happens:
- When the app is installed.
- When the application is running for the very first time after installation.
- If the database file has been deleted.
Its purpose is to configure the initial structure of the database, creating all the necessary tables and defining their columns.
onUpgrade Method
Let’s break down the onUpgrade
method and explain it in depth:
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
onCreate(db)
}
1. Method Declaration
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int)
override
keyword indicates that this method is overriding a method from the superclass (SQLiteOpenHelper).fun
is the Kotlin keyword for declaring a function.onUpgrade
is the name of the method.- It takes three parameters:
db: SQLiteDatabase
: An instance of SQLiteDatabase that the method can use to execute SQL commands.oldVersion: Int
: The version number of the database before the upgrade.newVersion: Int
: The new version number of the database after the upgrade.
2. Dropping the Existing Table
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
db.execSQL
executes an SQL statement directly."DROP TABLE IF EXISTS $TABLE_NAME"
is an SQL command that deletes the existing table if it exists.DROP TABLE
is the SQL command to delete a table.IF EXISTS
prevents an error if the table doesn’t exist.$TABLE_NAME
is the name of the table (defined in the companion object).
3. Recreating the Table
onCreate(db)
- This line calls the
onCreate
method we discussed earlier. - It recreates the table with the current schema.
Purpose and When It's Called
The onUpgrade
method is called when the database needs to be upgraded to a new version. This typically happens:
- When you update your app and increase the
DATABASE_VERSION
constant. - When a user updates your app to a version with a higher
DATABASE_VERSION
.
getAllFiles Method
Let’s break down the getAllFiles
method:
fun getAllFiles(): List<Triple<String, String, String>> {
val db = readableDatabase val cursor = db.query(TABLE_NAME, null, null, null, null, null, null)
val files = mutableListOf<Triple<String, String, String>>()
while (cursor.moveToNext()) {
val name = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FILE_NAME))
val path = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FILE_PATH))
val date = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_DATE))
files.add(Triple(name, path, date))
}
cursor.close()
return files
}
1. Method Declaration
fun getAllFiles(): List<Triple<String, String, String>>
fun
is the Kotlin keyword for declaring a function.getAllFiles
is the name of the method.: List<Triple<String, String, String>>
indicates that this method returns a List of Triple objects, where each Triple contains three String values (for file name, file path, and date).
2. Getting a Readable Database Instance
val db = readableDatabase
readableDatabase
is a property inherited fromSQLiteOpenHelper
.- It returns a readable SQLiteDatabase instance.
- Using
readableDatabase
instead ofwritableDatabase
is more efficient when you only need to read from the database.
3. Querying the Database
val cursor = db.query(TABLE_NAME, null, null, null, null, null, null)
db.query()
is a method to perform a SELECT query on the database.- Parameters:
TABLE_NAME
: The name of the table to query.null
: Columns to return.null
means all columns.null
: The WHERE clause.null
means no WHERE clause (select all rows).null
: The values for the WHERE clause. Not used here as there’s no WHERE clause.null
: GROUP BY clause. Not used in this query.null
: HAVING clause. Not used in this query.null
: ORDER BY clause. Not used in this query.
- This query effectively translates to the SQL statement:
SELECT * FROM $TABLE_NAME
4. Preparing the Result List
val files = mutableListOf<Triple<String, String, String>>()
- Creates an empty mutable list to store the results.
- Each item in the list will be a Triple of Strings (file name, file path, date).
5. Iterating Through the Results
while (cursor.moveToNext()) {
val name = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FILE_NAME))
val path = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FILE_PATH))
val date = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_DATE))
files.add(Triple(name, path, date))
}
cursor.moveToNext()
: Moves the cursor to the next row in the result set. Returnsfalse
when there are no more rows.- For each row:
cursor.getString()
: Retrieves the String value from the specified column.cursor.getColumnIndexOrThrow()
: Gets the index of the specified column.Triple(name, path, date)
: Creates a new Triple object with the retrieved values.files.add()
: Adds the Triple to the list of results.
6. Cleaning Up and Returning Results
cursor.close()
return files
cursor.close()
: Closes the cursor to release its resources. This is important to prevent memory leaks.return files
: Returns the list of Triples containing all the file information.
Purpose and Usage
The getAllFiles
method retrieves all file records stored in the database. It’s useful when you need to display a list of all files, perhaps in a file browser or gallery view within your app.
getFilesByDate Method
Let’s break down the getFilesByDate
method
fun getFilesByDate(date: String): List<Pair<String, String>> {
val db = readableDatabase val cursor = db.query(
TABLE_NAME,
null,
"$COLUMN_DATE = ?",
arrayOf(date),
null,
null,
null
)
val files = mutableListOf<Pair<String, String>>()
while (cursor.moveToNext()) {
val name = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FILE_NAME))
val path = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FILE_PATH))
files.add(Pair(name, path))
}
cursor.close()
return files
}
1. Method Declaration
fun getFilesByDate(date: String): List<Pair<String, String>>
fun
is the Kotlin keyword for declaring a function.getFilesByDate
is the name of the method.date: String
is the parameter, expecting a date string to filter the results.: List<Pair<String, String>>
indicates that this method returns a List of Pair objects, where each Pair contains two String values (for file name and file path).
2. Getting a Readable Database Instance
val db = readableDatabase
readableDatabase
is a property inherited fromSQLiteOpenHelper
.- It returns a readable SQLiteDatabase instance.
- Using
readableDatabase
is appropriate here as we’re only reading from the database.
3. Querying the Database
val cursor = db.query(
TABLE_NAME,
null,
"$COLUMN_DATE = ?",
arrayOf(date),
null,
null,
null
)
db.query()
is a method to perform a SELECT query on the database.- Parameters:
TABLE_NAME
: The name of the table to query.null
: Columns to return.null
means all columns."$COLUMN_DATE = ?"
: The WHERE clause. This filters the results to only include rows where the date column matches the provided date.arrayOf(date)
: The values for the WHERE clause. Thedate
parameter is used to replace the?
in the WHERE clause.null
: GROUP BY clause. Not used in this query.null
: HAVING clause. Not used in this query.null
: ORDER BY clause. Not used in this query.
- This query effectively translates to the SQL statement:
SELECT * FROM $TABLE_NAME WHERE $COLUMN_DATE = ?
- The
?
in the WHERE clause is a placeholder that gets replaced with thedate
value, preventing SQL injection.
4. Preparing the Result List
val files = mutableListOf<Pair<String, String>>()
- Creates an empty mutable list to store the results.
- Each item in the list will be a Pair of Strings (file name and file path).
5. Iterating Through the Results
while (cursor.moveToNext()) {
val name = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FILE_NAME))
val path = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FILE_PATH))
files.add(Pair(name, path))
}
For each row in the result set:
cursor.getString()
: Retrieves the String value from the specified column.cursor.getColumnIndexOrThrow()
: Gets the index of the specified column. If the column doesn’t exist, it throws an IllegalArgumentException.COLUMN_FILE_NAME
andCOLUMN_FILE_PATH
are constants defined in the companion object, representing the column names in the database.Pair(name, path)
: Creates a new Pair object with the retrieved file name and path.files.add()
: Adds the Pair to the list of results.
6. Cleaning Up and Returning Results
cursor.close()
return files
cursor.close()
: Closes the cursor to release its resources. This is important to prevent memory leaks.return files
: Returns the list of Pairs containing the file information for the specified date.
Purpose and Usage
The getFilesByDate
method retrieves all file records stored in the database for a specific date. This can be useful in various scenarios:
- Displaying files created or modified on a particular day.
- Implementing a calendar view of files in your app.
- Performing operations (like backups) on files from a specific date.
fileExists Method
Let’s break down the fileExists
method:
fun fileExists(fileName: String): Boolean {
val db = readableDatabase val cursor = db.query(
"files",
arrayOf("id"),
"file_name = ?",
arrayOf(fileName),
null,
null,
null
)
val exists = cursor.count > 0
cursor.close()
return exists
}
1. Method Declaration
fun fileExists(fileName: String): Boolean
fun
is the Kotlin keyword for declaring a function.fileExists
is the name of the method.fileName: String
is the parameter, expecting the name of the file to check for existence.: Boolean
indicates that this method returns a Boolean value (true if the file exists, false otherwise).
2. Getting a Readable Database Instance
val db = readableDatabase
readableDatabase
is a property inherited fromSQLiteOpenHelper
.- It returns a readable SQLiteDatabase instance.
- Using
readableDatabase
is appropriate here as we’re only reading from the database.
3. Querying the Database
val cursor = db.query(
"files",
arrayOf("id"),
"file_name = ?",
arrayOf(fileName),
null,
null,
null
)
db.query()
is a method to perform a SELECT query on the database.- Parameters:
- “files”: The name of table to query. Note that this uses a string literal instead of the TABLE_NAME constant used in other methods.
- arrayOf(“id”): Indicates we only need to retrieve the “id” column. It’s more efficient to do it this way rather than a SELECT all columns when we only need to check existence.
- “file_name = ?”: WHERE. This clause filters the results to include only the rows where the file_name column matches the given fileName.
- arrayOf(fileName): Values to be set for the WHERE. Replaces the? in the WHERE with the parameter named fileName.
- null: GROUP BY clause. In this query, it’s not used.
- null: HAVING clause. Nor is this used in this query.
- null: ORDER BY clause. And once more, it’s not used in this query.
- This query effectively translates to the SQL statement:
SELECT id FROM files WHERE file_name = ?
- The
?
in the WHERE clause is a placeholder that gets replaced with thefileName
value, preventing SQL injection.
4. Checking for Existence
val exists = cursor.count > 0
cursor.count
returns the number of rows in the result set.- If
cursor.count > 0
, it means at least one row was found, indicating that a file with the given name exists in the database. - The result of this comparison (true or false) is stored in the
exists
variable.
5. Cleaning Up and Returning Result
cursor.close()
return exists
cursor.close()
: Closes the cursor to release its resources. This is important to prevent memory leaks.return exists
: Returns the boolean value indicating whether the file exists in the database.
Purpose and Usage
The fileExists
method checks if a file with a given name is already recorded in the database. This can be useful in various scenarios:
- Avoid the duplication of entries while adding new files.
- Check if a file should be added to the database or if it just requires an update.
- Validating provided input from the user while searching for files.
Full DatabaseHelper Class
package com.example.digitaldiaryapp import android.content.Context import android.database.sqlite.SQLiteDatabase import android.database.sqlite.SQLiteOpenHelper import android.content.ContentValues import java.io.File import java.io.IOException class DatabaseHelper(context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) {
companion object {
private const val DATABASE_NAME = "files.db" private const val DATABASE_VERSION = 2 // Changed version to 2 for the upgrade const val TABLE_NAME = "files" const val COLUMN_ID = "id" const val COLUMN_FILE_NAME = "file_name" const val COLUMN_FILE_PATH = "file_path" const val COLUMN_DATE = "date_saved" // New column for the save date
}
override fun onCreate(db: SQLiteDatabase) {
val createTable = "CREATE TABLE $TABLE_NAME (" +
"$COLUMN_ID INTEGER PRIMARY KEY AUTOINCREMENT, " +
"$COLUMN_FILE_NAME TEXT, " +
"$COLUMN_FILE_PATH TEXT, " +
"$COLUMN_DATE TEXT)" // Create the new column db.execSQL(createTable)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
onCreate(db)
}
// Modified insert method to include date fun insertFile(fileName: String, filePath: String, dateSaved: String): Long {
val db = writableDatabase val contentValues = ContentValues().apply {
put(COLUMN_FILE_NAME, fileName)
put(COLUMN_FILE_PATH, filePath)
put(COLUMN_DATE, dateSaved) // Insert the date into the new column
}
return db.insert(TABLE_NAME, null, contentValues)
}
fun getAllFiles(): List<Triple<String, String, String>> { // I have updated this to return date as well val db = readableDatabase val cursor = db.query(TABLE_NAME, null, null, null, null, null, null)
val files = mutableListOf<Triple<String, String, String>>() // Using Triple for file name, path, and date while (cursor.moveToNext()) {
val name = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FILE_NAME))
val path = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FILE_PATH))
val date = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_DATE)) // Retrieve the saved date files.add(Triple(name, path, date))
}
cursor.close()
return files
}
fun getFilesByDate(date: String): List<Pair<String, String>> {
val db = readableDatabase val cursor = db.query(
TABLE_NAME,
null,
$COLUMN_DATE = ?,
arrayOf(date),
null,
null,
null
)
val files = mutableListOf<Pair<String, String>>()
while (cursor.moveToNext()) {
val name = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FILE_NAME))
val path = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FILE_PATH))
files.add(Pair(name, path))
}
cursor.close()
return files
}
fun fileExists(fileName: String): Boolean {
val db = readableDatabase val cursor = db.query(
"files",
arrayOf("id"),
"file_name = ?",
arrayOf(fileName),
null,
null,
null
)
val exists = cursor.count > 0 cursor.close()
return exists
}
fun updateFile(fileName: String, filePath: String, dateSaved: String) {
val db = writableDatabase val values = ContentValues().apply {
put("file_path", filePath)
put("date_saved", dateSaved)
}
db.update("files", values, "file_name = ?", arrayOf(fileName))
}
fun deleteFile(fileName: String): Int {
val db = this.writableDatabase return db.delete(TABLE_NAME, $COLUMN_FILE_NAME = ?, arrayOf(fileName))
}
fun deleteAllFiles(context: Context) {
val db = readableDatabase // Query to get all file names val cursor = db.query(TABLE_NAME, arrayOf(COLUMN_FILE_NAME), null, null, null, null, null)
while (cursor.moveToNext()) {
val fileName = cursor.getString(cursor.getColumnIndexOrThrow(COLUMN_FILE_NAME))
val fileToDelete = File(context.filesDir, fileName)
if (fileToDelete.exists()) {
val deleted = fileToDelete.delete()
if (!deleted) {
throw IOException("Failed to delete file: $fileName")
}
}
}
cursor.close()
// Delete all entries from the database db.delete(TABLE_NAME, null, null)
}
}