Skip to content

Commit 484b3df

Browse files
Merge pull request #5
Refine conversation screen
2 parents ce63e85 + b9b26b7 commit 484b3df

File tree

10 files changed

+328
-26
lines changed

10 files changed

+328
-26
lines changed

app/build.gradle.kts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ plugins {
22
alias(libs.plugins.android.application)
33
alias(libs.plugins.kotlin.android)
44
alias(libs.plugins.kotlin.compose)
5+
alias(libs.plugins.ksp)
56
}
67

78
android {
@@ -67,6 +68,9 @@ dependencies {
6768
implementation(libs.androidx.material3)
6869
implementation(libs.androidx.navigation.compose)
6970
implementation(libs.androidx.material.icons.extended)
71+
implementation(libs.androidx.room.runtime)
72+
ksp(libs.androidx.room.compiler)
73+
implementation(libs.androidx.room.ktx)
7074
implementation(libs.androidx.datastore.preferences)
7175
testImplementation(libs.junit)
7276
androidTestImplementation(libs.androidx.junit)

app/src/main/java/org/vonderheidt/hips/MainActivity.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import androidx.compose.ui.Modifier
1010
import androidx.lifecycle.lifecycleScope
1111
import kotlinx.coroutines.launch
1212
import org.vonderheidt.hips.data.HiPSDataStore
13+
import org.vonderheidt.hips.data.HiPSDatabase
1314
import org.vonderheidt.hips.navigation.SetupNavGraph
1415
import org.vonderheidt.hips.ui.theme.HiPSTheme
1516

@@ -36,6 +37,10 @@ class MainActivity : ComponentActivity() {
3637
}
3738
}
3839

40+
// Instantiate Room database on app startup
41+
// Application context isn't directly available on conversation screen
42+
HiPSDatabase.startInstance(applicationContext)
43+
3944
// Instantiate DataStore on app startup and read stored settings
4045
// Writes default settings to DataStore if app was just installed
4146
HiPSDataStore.startInstance(applicationContext)
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
package org.vonderheidt.hips.data
2+
3+
import android.content.Context
4+
import androidx.room.Database
5+
import androidx.room.Room
6+
import androidx.room.RoomDatabase
7+
8+
/**
9+
* Class that represents the Room database.
10+
*/
11+
@Database(
12+
entities = [User::class, Message::class],
13+
version = 1
14+
)
15+
abstract class HiPSDatabase : RoomDatabase() {
16+
// Data access objects to access tables
17+
abstract val userDao: UserDao
18+
abstract val messageDao: MessageDao
19+
20+
// Companion object to ensure database is a singleton as abstract class can't be instantiated
21+
companion object {
22+
// Annotate reference to the database instance as volatile so that r/w to it is atomic and immediately visible to all threads
23+
// Avoids race conditions, i.e. multiple threads trying to r/w to the database simultaneously
24+
@Volatile
25+
private var dbInstance: HiPSDatabase? = null
26+
27+
/**
28+
* Function to check if the database is running.
29+
*
30+
* @return Boolean that is true if the database is running, false otherwise.
31+
*/
32+
private fun isRunning(): Boolean {
33+
return dbInstance != null
34+
}
35+
36+
/**
37+
* Function to start the database in a thread-safe manner.
38+
*
39+
* @param context The application context.
40+
*/
41+
fun startInstance(context: Context) {
42+
// If the database is already running, there is nothing to do
43+
if (isRunning()) {
44+
return
45+
}
46+
47+
// Otherwise, start the database
48+
// Synchronized allows only one thread to execute the code inside {...}, so other threads can't start the database simultaneously
49+
synchronized(lock = this) {
50+
if (!isRunning()) {
51+
// Database builder specifically requires the application context
52+
dbInstance = Room.databaseBuilder(context.applicationContext, HiPSDatabase::class.java, "hips.db").build()
53+
}
54+
}
55+
}
56+
57+
/**
58+
* Function to get the currently running database instance.
59+
*
60+
* @return The database instance.
61+
*/
62+
fun getInstance(): HiPSDatabase {
63+
// Database can be assumed to be running as startInstance is called on app startup in MainActivity
64+
return dbInstance!!
65+
}
66+
}
67+
}

app/src/main/java/org/vonderheidt/hips/data/Message.kt

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,35 @@
11
package org.vonderheidt.hips.data
22

3+
import androidx.room.ColumnInfo
4+
import androidx.room.Entity
5+
import androidx.room.ForeignKey
6+
import androidx.room.ForeignKey.Companion.CASCADE
7+
38
/**
49
* Class that represents a chat message in a conversation.
10+
*
11+
* @param senderID User ID of the message's sender.
12+
* @param receiverID User ID of the message's receiver.
13+
* @param timestamp Time the message was sent at (milliseconds since Unix epoch, i.e. 1970-01-01 00:00).
14+
* @param content Content of the message.
515
*/
16+
@Entity(
17+
primaryKeys = ["sender_id", "receiver_id", "timestamp"],
18+
foreignKeys = [
19+
ForeignKey(entity = User::class, parentColumns = ["id"], childColumns = ["sender_id"], onDelete = CASCADE, onUpdate = CASCADE),
20+
ForeignKey(entity = User::class, parentColumns = ["id"], childColumns = ["receiver_id"], onDelete = CASCADE, onUpdate = CASCADE)
21+
]
22+
)
623
data class Message (
7-
val senderID: Int,
8-
val receiverID: Int,
24+
@ColumnInfo(name = "sender_id") val senderID: Int,
25+
@ColumnInfo(name = "receiver_id") val receiverID: Int,
926
val timestamp: Long,
1027
val content: String
1128
) {
12-
/**
13-
* Some sample messages for the conversation screen.
14-
*/
1529
companion object {
30+
/**
31+
* Some sample messages for the conversation screen.
32+
*/
1633
val Samples = listOf(
1734
Message(1, 0, System.currentTimeMillis() - 5982341, "Oi, Lionel! What a match today! I honestly thought we were done for at halftime being 2-0 down, but that second half was brilliant! We really pulled it together, didn’t we?"),
1835
Message(0, 1, System.currentTimeMillis() - 4553793, "Absolutely, Cristiano! That comeback was mental! Your goal really got everyone buzzing. And that assist I had? Proper chuffed to finally chip in like that! But can we talk about the ref? What a wanker!"),
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package org.vonderheidt.hips.data
2+
3+
import androidx.room.Dao
4+
import androidx.room.Delete
5+
import androidx.room.Query
6+
import androidx.room.Upsert
7+
8+
/**
9+
* Interface to implement the data access object (DAO) for the Message data class.
10+
*/
11+
@Dao
12+
interface MessageDao {
13+
// Covers all CRUD operations
14+
15+
/**
16+
* Function to upsert (update or insert) a message into the database.
17+
*
18+
* @param message A message.
19+
*/
20+
@Upsert
21+
suspend fun upsertMessage(message: Message)
22+
23+
/**
24+
* Function to get all messages between two users from the database.
25+
*
26+
* @param userID1 A user ID.
27+
* @param userID2 Another user ID.
28+
* @return List of all messages between the two users.
29+
*/
30+
@Query("SELECT * FROM message WHERE (sender_id = :userID1 AND receiver_id = :userID2) OR (sender_id = :userID2 AND receiver_id = :userID1) ORDER BY timestamp;")
31+
suspend fun getConversation(userID1: Int, userID2: Int): List<Message>
32+
33+
/**
34+
* Function to delete a message from the database.
35+
*
36+
* @param message The message.
37+
*/
38+
@Delete
39+
suspend fun deleteMessage(message: Message)
40+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package org.vonderheidt.hips.data
2+
3+
import androidx.room.Entity
4+
import androidx.room.PrimaryKey
5+
6+
/**
7+
* Class that represents a user.
8+
*
9+
* @param id The user ID.
10+
* @param name A user name.
11+
*/
12+
@Entity
13+
data class User(
14+
@PrimaryKey val id: Int,
15+
val name: String
16+
)
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package org.vonderheidt.hips.data
2+
3+
import androidx.room.Dao
4+
import androidx.room.Upsert
5+
6+
/**
7+
* Interface to implement the data access object (DAO) for the User data class.
8+
*/
9+
@Dao
10+
interface UserDao {
11+
/**
12+
* Function to upsert (update or insert) a user into the database.
13+
*
14+
* @param user A user.
15+
*/
16+
@Upsert
17+
suspend fun upsertUser(user: User)
18+
}

0 commit comments

Comments
 (0)