Skip to content

Conversation

kiranbollepalli9
Copy link

@kiranbollepalli9 kiranbollepalli9 commented Sep 4, 2025

Issue Number

  • No Existing issue.

Purpose

  • Refactoring the code.
  • Migration of library versions.
  • Migration from GreenDAO to Room.

Technical Details

  • Room migration, Implemented ServiceLocator for DI (HILT library has a problem in 2.57.1, waiting for new release)
  • Improved code quality by refactoring the code.

Screenshots

  • N/A: No changes

Testing Instructions

  • Run the app and test.

Regression Tests

  • I tested my changes on Android 16 (API 36)
  • I tested my changes on Android 15 (API 35)
  • I tested my changes on Android 14 (API 34)
  • I tested my changes on Android 13 (API 33)
  • I tested my changes on Android 12L (API 32)
  • I tested my changes on Android 12 (API 31)
  • I tested my changes on Android 11 (API 30)
  • I tested my changes on Android 10 (API 29)
  • I tested my changes on Android 9 (API 28)
  • I tested my changes on Android 8.1 (API 27)
  • I tested my changes on Android 8.0 (API 26)

UI Tests

No UI Changes

Content Tests

N/A

Summary by CodeRabbit

  • New Features

    • Chat screen refreshed with improved UI behavior: auto-scrolls to latest message, recent 24‑hour messages load on launch, and send button enables/disables based on input.
    • Emoji responses simulated for richer conversations.
  • Refactor

    • Storage and initialization rewritten for more reliable, modern local persistence and dependency wiring.
    • Preferences and app startup streamlined; launcher updated to the new chat screen.

 - Introduced Constants and moved the constants
 - Created Message Factory to generate random emoji messages
- Migrating from GreemDAO to Room with DAO. Its in progress.
- Reverting HILT dependency to avoid build issue.
- Fixing crashes.
- Removing GreenDAO dependency and related code. Now code migration to Room is completed
- Move initialization of SharedPreference to Service Locator
- Created ViewModel and moved the relevent logic from Activity to ViewModel
- Minor refactoring of ChatApplication file.
- Reverted HILT Di, as there is an issue with the library(2.57.1). The fix for it is still not merged but waiting for new release.
- Minor enhancements
@kiranbollepalli9 kiranbollepalli9 requested a review from a team as a code owner September 4, 2025 15:57
@kiranbollepalli9 kiranbollepalli9 changed the title Feature/refactor code Migration from GreenDAO to Room and other refactoring Sep 4, 2025
Copy link

coderabbitai bot commented Sep 4, 2025

Walkthrough

Migrates persistence from GreenDAO to Room, adds a ServiceLocator for dependency provisioning, replaces the old ChatActivity with a new UI + ChatViewModel using coroutines/StateFlow, introduces Room entities/DAO/mappers and centralized constants, updates manifest and Gradle/version-catalog.
elimu.ai's mission is to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

Changes

Cohort / File(s) Summary
Build configuration
app/build.gradle
Remove GreenDAO plugin/DSL/dependency; add Kotlin KAPT plugin, Room (runtime, ktx, compiler via kapt), androidx.activity:activity-ktx, and JavaPoet.
Version catalog
gradle/libs.versions.toml
Remove GreenDAO versions/libraries; add versions and libraries for Room, Activity KTX, JavaPoet; add kotlin-kapt plugin.
Manifest entry point
app/src/main/AndroidManifest.xml
Change launcher activity android:name from .ChatActivity to .ui.ChatActivity.
Old Activity removed
app/src/main/java/ai/elimu/chat/ChatActivity.kt
Delete legacy ChatActivity implementation.
UI layer (new)
app/src/main/java/ai/elimu/chat/ui/ChatActivity.kt, .../ChatViewModel.kt, .../MessageListArrayAdapter.kt
Add new ChatActivity (ComponentActivity) and ChatViewModel (StateFlow/coroutines); move/update MessageListArrayAdapter to ai.elimu.chat.ui and switch prefs access to ServiceLocator.
Room data layer (new)
app/src/main/java/ai/elimu/chat/data/local/AppDatabase.kt, .../ChatMessageDao.kt, .../MessageEntity.kt, .../MessageMapper.kt
Add RoomDatabase, DAO with suspend queries/inserts, MessageEntity @entity, and mappers between entity and domain model.
Dependency provisioning
app/src/main/java/ai/elimu/chat/di/ServiceLocator.kt
New ServiceLocator initializes Room DB ("chat-db-new"), captures deviceId, and exposes ChatMessageDao and SharedPreferences (initialize-before-use enforced).
Application init
app/src/main/java/ai/elimu/chat/ChatApplication.kt
Remove greenDAO DaoSession and init; initialize ServiceLocator; update version-code check to use Constants and ServiceLocator-provided SharedPreferences; remove public daoSession and local PREF constant.
Receiver adjustments
app/src/main/java/ai/elimu/chat/receiver/StudentUpdateReceiver.kt
Switch to ServiceLocator-provided SharedPreferences and Constants keys; replace local PREF_* with EXTRA_* intent keys; legacy DAO update code commented out.
Domain model (Kotlin)
app/src/main/java/ai/elimu/chat/model/Message.kt
Add Kotlin data class Message, MessageBuilder, and generateEmojiMessage utility.
Constants centralized
app/src/main/java/ai/elimu/chat/util/Constants.kt
New Constants object: pref keys, sample student IDs, and EMOJI_UNICODES array.
Removed GreenDAO artifacts
app/src/main/java/ai/elimu/chat/dao/DaoSession.java, .../MessageDao.java, .../dao/converter/CalendarConverter.kt, app/src/main/java/ai/elimu/chat/model/Message.java
Delete generated GreenDAO session, DAO, converter, and Java entity.
Removed device helper
app/src/main/java/ai/elimu/chat/util/DeviceInfoHelper.kt
Delete DeviceInfoHelper; device ID retrieval moved into ServiceLocator.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant App as ChatApplication
  participant SL as ServiceLocator
  participant DB as Room(AppDatabase)

  Note over App,DB: App startup and dependency initialization
  User->>App: Launch app
  App->>SL: initialize(context)
  SL->>DB: Build Room DB ("chat-db-new")
  SL-->>App: Provide DAO, deviceId, SharedPreferences
  App->>SL: provideSharedPreference()
  App->>App: checkAndUpdateVersionCode()
  App-->>User: App ready
Loading
sequenceDiagram
  autonumber
  actor User
  participant UI as ChatActivity
  participant VM as ChatViewModel
  participant SL as ServiceLocator
  participant DAO as ChatMessageDao
  participant DB as Room DB

  Note over UI,VM: Load recent messages
  UI->>VM: onCreate() -> loadRecentMessages()
  VM->>SL: provideChatMessageDao()
  VM->>DAO: getMessages(last24hTimestamp)
  DAO->>DB: SELECT messages WHERE timestamp > t
  DB-->>DAO: List<MessageEntity>
  DAO-->>VM: Entities
  VM->>VM: map to domain Message
  VM-->>UI: StateFlow update (messages)

  Note over UI,DAO: Send a message
  User->>UI: Tap Send
  UI->>VM: sendMessage(text)
  VM->>SL: provideDeviceId(), provideSharedPreference()
  VM->>DAO: insert(MessageEntity)
  DAO->>DB: INSERT message
  DB-->>DAO: rowId
  VM-->>UI: StateFlow update (append)

  alt Optional simulated replies
    VM->>VM: maybeSimulateMessage(studentId)
    VM-->>UI: StateFlow update (emoji message)
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Title Check ✅ Passed The title "Migration from GreenDAO to Room and other refactoring" succinctly and accurately summarizes the primary change—replacing GreenDAO with Room—while noting additional refactoring; it is concise, specific, and directly related to the changeset (deletion of GreenDAO files and addition of Room entities/DAOs plus supporting refactors). This phrasing gives a teammate scanning history a clear sense of the main intent without noisy details.
Description Check ✅ Passed The PR description follows the repository template and provides Purpose, Technical Details, and a populated Regression Tests checklist while clearly stating the migration to Room and the ServiceLocator approach, so the overall structure is met. However the Testing Instructions are too brief ("Run the app and test") and the description lacks explicit database migration details or step-by-step verification guidance for reviewers (e.g., how to validate existing data after migration or how to exercise key flows). Because the template is mostly filled but missing actionable verification and migration details, the check passes with recommended improvements.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0b490a4 and 5360403.

📒 Files selected for processing (2)
  • app/build.gradle (2 hunks)
  • gradle/libs.versions.toml (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/build.gradle
  • gradle/libs.versions.toml
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (9)
  • GitHub Check: build (windows-latest, 21)
  • GitHub Check: build (macos-latest, 17)
  • GitHub Check: build (ubuntu-latest, 17)
  • GitHub Check: build (windows-latest, 17)
  • GitHub Check: build (ubuntu-latest, 21)
  • GitHub Check: build (macos-latest, 21)
  • GitHub Check: lint
  • GitHub Check: test (28)
  • GitHub Check: test (29)

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (7)
app/src/main/AndroidManifest.xml (2)

2-3: Fix AGP warning: remove manifest package attribute

AGP ignores package="ai.elimu.chat"; keeping it causes pipeline noise. Remove it.

Apply:

-<manifest xmlns:android="http://schemas.android.com/apk/res/android"
-    package="ai.elimu.chat">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">

5-12: Add missing namespace in Gradle
The android.namespace property isn’t declared in app/build.gradle’s android { … } block—add

android {
    namespace = "ai.elimu.chat"
    //
}

above your defaultConfig to ensure resource/class resolution remains correct.
elimu.ai's mission is to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

app/src/main/java/ai/elimu/chat/ui/MessageListArrayAdapter.kt (1)

66-71: Fix nullability: build is failing when constructing File from a String?

message.studentAvatar is nullable; Kotlin doesn’t smart-cast through TextUtils. Replace with a safe-call to avoid the “String? where String was expected” error and potential NPE.

Apply this diff:

-        if (!TextUtils.isEmpty(message.studentAvatar)) {
-            val file = File(message.studentAvatar)
-            if (file.exists()) {
-                val bitmap = BitmapFactory.decodeFile(file.absolutePath)
-                viewHolder.imageViewAvatar!!.setImageBitmap(bitmap)
-            }
-        } else if ("00000000aaaaaaaa_2" == message.studentId) {
+        message.studentAvatar?.takeIf { it.isNotBlank() }?.let { path ->
+            val file = File(path)
+            if (file.exists()) {
+                val bitmap = BitmapFactory.decodeFile(file.absolutePath)
+                viewHolder.imageViewAvatar!!.setImageBitmap(bitmap)
+            }
+        } ?: run {
+            if ("00000000aaaaaaaa_2" == message.studentId) {
                 // Penguin
-            viewHolder.imageViewAvatar!!.setImageDrawable(context.getDrawable(R.drawable.penguin))
+                viewHolder.imageViewAvatar!!.setImageDrawable(
+                    AppCompatResources.getDrawable(context, R.drawable.penguin)
+                )
+            }
+        }

Also add the import suggested in the next comment.

app/build.gradle (4)

46-48: Migrate from deprecated lintOptions to the modern lint DSL.

AGP 8+ removed lintOptions. Use lint {}.

-    lintOptions {
-        abortOnError true
-    }
+    lint {
+        abortOnError = true
+    }

87-105: Guard regex usage to avoid IllegalStateException when no SNAPSHOT is present.

matcher.group() is called without verifying find() succeeded.

-        matcher.find()
-        println("match: ${matcher.group()}")
-
-        def newVersionName = matcher.group().replace("-SNAPSHOT", "")
-        println("newVersionName: ${newVersionName}")
-
-        def newFileContent = originalFileContent.replaceFirst("versionName \"\\d+\\.\\d+\\.\\d+-SNAPSHOT\"", newVersionName)
+        if (matcher.find()) {
+            println("match: ${matcher.group()}")
+            def newVersionName = matcher.group().replace("-SNAPSHOT", "")
+            println("newVersionName: ${newVersionName}")
+            def newFileContent = originalFileContent.replaceFirst("versionName \"\\d+\\.\\d+\\.\\d+-SNAPSHOT\"", newVersionName)
+            file.write(newFileContent)
+        } else {
+            println("No -SNAPSHOT versionName found; skipping.")
+        }
-        file.write(newFileContent)

111-130: Make versionName derivation robust; current substring slicing is brittle.

substring(0,1/1,4/4,7) will break if versionCode changes width. Pad and parse instead.

-        def newVersionName = newVersionCode.toString().substring(0, 1).toInteger() + "." + newVersionCode.toString().substring(1, 4).toInteger() + "." + newVersionCode.toString().substring(4, 7).toInteger()
+        def codeStr = String.format("%07d", newVersionCode)
+        def major = Integer.parseInt(codeStr.substring(0, 1))
+        def minor = Integer.parseInt(codeStr.substring(1, 4))
+        def patch = Integer.parseInt(codeStr.substring(4, 7))
+        def newVersionName = "${major}.${minor}.${patch}"

131-149: Same regex guard needed here as in removeSnapshot.

Prevent IllegalStateException when versionName has already appended -SNAPSHOT.

-        matcher.find()
-        println("match: ${matcher.group()}")
-
-        def newVersionName = "${matcher.group().substring(12, matcher.group().length() - 1)}-SNAPSHOT\""
-        println("newVersionName: ${newVersionName}")
-
-        def newFileContent = originalFileContent.replaceFirst("versionName \"\\d+\\.\\d+\\.\\d+\"", "versionName ${newVersionName}")
-        file.write(newFileContent)
+        if (matcher.find()) {
+            println("match: ${matcher.group()}")
+            def base = matcher.group().substring(12, matcher.group().length() - 1)
+            def newVersionName = "${base}-SNAPSHOT\""
+            println("newVersionName: ${newVersionName}")
+            def newFileContent = originalFileContent.replaceFirst("versionName \"\\d+\\.\\d+\\.\\d+\"", "versionName ${newVersionName}")
+            file.write(newFileContent)
+        } else {
+            println("No release versionName found; skipping.")
+        }
🧹 Nitpick comments (31)
app/src/main/java/ai/elimu/chat/util/Constants.kt (2)

17-101: Avoid exposing a mutable IntArray; provide a safe accessor

IntArray elements can be mutated by callers. Consider exposing a defensive copy to prevent accidental modification.

Apply:

 object Constants {
@@
-    val EMOJI_UNICODES = intArrayOf(
+    private val EMOJI_UNICODES = intArrayOf(
@@
-    )
+    )
+
+    fun emojiCodepoints(): IntArray = EMOJI_UNICODES.clone()
 }

Follow-up: optionally migrate call sites to Constants.emojiCodepoints() when convenient.


13-16: Minor naming nit

Consider STUDENT_ID_AKILI / STUDENT_ID_PENGUIN for readability and consistency with other constant naming styles.

app/src/main/AndroidManifest.xml (2)

6-6: Prefer fully-qualified component names

Since the manifest package is removed/ignored, using FQCN prevents resolution surprises.

Apply:

-        android:name=".ChatApplication"
+        android:name="ai.elimu.chat.ChatApplication"
@@
-            android:name=".ui.ChatActivity"
+            android:name="ai.elimu.chat.ui.ChatActivity"
@@
-            android:name=".receiver.StudentUpdateReceiver"
+            android:name="ai.elimu.chat.receiver.StudentUpdateReceiver"

Also applies to: 14-14, 23-25


23-31: Security posture check (optional)

android:exported="true" on the receiver is required with an intent-filter; confirm this is intentional and that the broadcast cannot leak PII. If not required externally, set exported="false".

app/src/main/java/ai/elimu/chat/data/local/AppDatabase.kt (1)

6-9: Export schema & wire migrations
elimu.ai's mission is to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months. Add exportSchema = true to your @Database annotation in AppDatabase.kt, configure room.schemaLocation in your Gradle module (e.g. via kapt { arguments { arg("room.schemaLocation", "$projectDir/schemas") } }), and in ServiceLocator.initialize (ServiceLocator.kt:22) call .addMigrations(...) (or explicitly .fallbackToDestructiveMigration()) on the Room.databaseBuilder.

app/src/main/java/ai/elimu/chat/data/local/MessageMapper.kt (1)

6-15: Use named arguments to prevent constructor-order bugs

Constructor parameter order drift can silently mis-map fields.

Apply:

 fun MessageEntity.toMessage(): Message {
-    return Message(
-        this.id,
-        this.deviceId,
-        this.studentId,
-        this.studentAvatar,
-        Calendar.getInstance().apply { timeInMillis = [email protected] },
-        this.text
-    )
+    return Message(
+        id = this.id,
+        deviceId = this.deviceId,
+        studentId = this.studentId,
+        studentAvatar = this.studentAvatar,
+        timeSent = Calendar.getInstance().apply { timeInMillis = [email protected] },
+        text = this.text
+    )
 }
app/src/main/java/ai/elimu/chat/data/local/MessageEntity.kt (2)

7-16: Add indices for common queries

Index timestamp (sorting) and student_id (filtering) to avoid full scans as data grows.

Apply:

-import androidx.room.Entity
+import androidx.room.Entity
+import androidx.room.Index
@@
-@Entity(tableName = "messages")
+@Entity(
+    tableName = "messages",
+    indices = [
+        Index(value = ["timestamp"]),
+        Index(value = ["student_id"])
+    ]
+)

10-15: Column naming consistency (optional)

Consider aligning DB column names with domain names (text, timeSent) or snake_case throughout to reduce mental mapping. Not mandatory.

app/src/main/java/ai/elimu/chat/ui/MessageListArrayAdapter.kt (3)

3-6: Use AppCompatResources for drawables

For broader compatibility and theme-aware retrieval, import AppCompatResources (used in the previous diff).

 import ai.elimu.chat.util.Constants
+import androidx.appcompat.content.res.AppCompatResources

72-75: Extract magic constant for studentId

"00000000aaaaaaaa_2" should live in Constants (e.g., Constants.STUDENT_ID_PENGUIN) to avoid duplication and clarify intent.

Happy to add the constant and wire usages across the codebase.


46-56: Enable view recycling and view types to avoid excessive inflations

getView always inflates, ignoring convertView and the left/right item view types. Implement getItemViewType/getViewTypeCount and reuse convertView accordingly to reduce GC and jank in long chats.

Minimal outline (outside this hunk):

override fun getViewTypeCount() = 2
override fun getItemViewType(position: Int): Int =
    if (TextUtils.isEmpty(messages[position].studentId) || messages[position].studentId == studentId) 0 else 1

Then in getView, inflate only when convertView == null or when its type mismatches.

app/src/main/java/ai/elimu/chat/ChatApplication.kt (1)

30-43: Simplify version persistence; avoid double branches and synchronous commits

The else-if (oldVersionCode == 0) is redundant when newVersionCode > 0; and commit = true blocks the main thread. Prefer a single check and apply().

-            sharedPreferences.edit(commit = true) {
-                putInt(
-                    Constants.PREF_APP_VERSION_CODE,
-                    newVersionCode
-                )
-            }
-        } else if (oldVersionCode == 0) {
-            sharedPreferences.edit(commit = true) {
-                putInt(
-                    Constants.PREF_APP_VERSION_CODE,
-                    newVersionCode
-                )
-            }
-        }
+            sharedPreferences.edit {
+                putInt(Constants.PREF_APP_VERSION_CODE, newVersionCode)
+            }
+        }

If you want to log downgrades too, compare != and adjust the log message accordingly.

app/src/main/java/ai/elimu/chat/data/local/ChatMessageDao.kt (3)

18-19: Stable ordering for time-based reads

Return messages ordered by timestamp to avoid flicker and inconsistent pagination.

-    @Query("SELECT * FROM messages WHERE timestamp > :timeStamp")
+    @Query("SELECT * FROM messages WHERE timestamp > :timeStamp ORDER BY timestamp ASC")

9-10: Define conflict strategy for inserts

Without an onConflict policy, Room defaults to ABORT. If messages can be re-inserted (same PK/unique key), consider @upsert (Room 2.6+) or REPLACE/IGNORE.

-import androidx.room.Insert
+import androidx.room.Insert
+import androidx.room.OnConflictStrategy
 ...
-    @Insert
+    @Insert(onConflict = OnConflictStrategy.ABORT) // or REPLACE / use @Upsert if available
     suspend fun insert(message: MessageEntity): Long

Confirm MessageEntity’s primary key/unique constraints before picking a policy.


21-29: Return number of rows updated from UPDATE query

Returning Int helps detect whether any rows were affected.

-    suspend fun updateStudentInfoForDevice(
+    suspend fun updateStudentInfoForDevice(
         deviceId: String,
         studentId: String?,
         studentAvatar: String?
-    )
+    ): Int

Consider adding an index on device_id to make this update efficient.

app/build.gradle (1)

61-68: Remove JavaPoet; it’s unnecessary at runtime.

Room’s compiler shades/uses JavaPoet; the app shouldn’t depend on it. Drop implementation libs.javapoet to reduce size and avoid needless methods.

-    implementation libs.javapoet

Optional: enable Room schema generation for safer migrations (kept outside this hunk):

kapt {
  arguments {
    arg("room.schemaLocation", "$projectDir/schemas")
    arg("room.incremental", "true")
    arg("room.expandProjection", "true")
  }
}
app/src/main/java/ai/elimu/chat/ui/ChatViewModel.kt (6)

19-19: Drop the empty constructor.

class ChatViewModel : ViewModel() is cleaner and appeases detekt.

-class ChatViewModel() : ViewModel() {
+class ChatViewModel : ViewModel() {

24-27: Hide internals; expose StateFlow read-only.

Make dependencies private and expose _messages via asStateFlow().

-    val messages: StateFlow<List<Message>> = _messages
-
-    val sharedPreferences = ServiceLocator.provideSharedPreference()
-
-    val chatMessageDao = ServiceLocator.provideChatMessageDao()
+    val messages: StateFlow<List<Message>> = _messages
+    private val sharedPreferences = ServiceLocator.provideSharedPreference()
+    private val chatMessageDao = ServiceLocator.provideChatMessageDao()

Also (outside this hunk):

import kotlinx.coroutines.flow.asStateFlow
// Then: val messages: StateFlow<List<Message>> = _messages.asStateFlow()

40-56: Guard against blank messages at the source.

Prevent empty/whitespace-only sends.

-    fun sendMessage(message: String) {
+    fun sendMessage(message: String) {
+        if (message.isBlank()) return

57-61: Rename local to avoid shadowing; atomically update StateFlow.

Use builtMessage and _messages.update { ... } to avoid RMW races.

-            val message = messageBuilder.build()
-            chatMessageDao.insert(message.toEntity())
-            _messages.value = _messages.value + message
+            val builtMessage = messageBuilder.build()
+            chatMessageDao.insert(builtMessage.toEntity())
+            _messages.update { it + builtMessage }

Outside this hunk:

import kotlinx.coroutines.flow.update

64-73: Use atomic update when appending simulated messages.

Prevents lost updates under concurrent emissions.

-                val message = generateEmojiMessage(studentId)
-                _messages.value = _messages.value + message
+                val msg = generateEmojiMessage(studentId)
+                _messages.update { it + msg }

28-38: Remove redundant assignment and simplify
Replace:

- val uiMessages = newMessages.map { it.toMessage() }
- _messages.value = emptyList()
- _messages.value = uiMessages
+ _messages.value = newMessages.map { it.toMessage() }

Room’s suspend DAO methods automatically run on a background thread, so no additional dispatcher is needed.
elimu.ai’s mission is to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

app/src/main/java/ai/elimu/chat/ui/ChatActivity.kt (3)

21-22: Remove unnecessary @SuppressLint.

No restricted APIs here; drop the suppression.

-@SuppressLint("RestrictedApi")
-class ChatActivity : ComponentActivity() {
+class ChatActivity : ComponentActivity() {

53-59: Collect with repeatOnLifecycle to avoid leaks.

Scope collection to STARTED+.

-        lifecycleScope.launch {
-            viewModel.messages.collect { newMessages ->
-                messages.clear()
-                messages.addAll(newMessages)
-                refreshMessageList()
-            }
-        }
+        lifecycleScope.launch {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                viewModel.messages.collect { newMessages ->
+                    messages.clear()
+                    messages.addAll(newMessages)
+                    refreshMessageList()
+                }
+            }
+        }

Outside this hunk:

import androidx.lifecycle.repeatOnLifecycle
import androidx.lifecycle.Lifecycle

48-50: Prefer Activity context for adapter to honor theming.

-        arrayAdapter = MessageListArrayAdapter(applicationContext, messages)
+        arrayAdapter = MessageListArrayAdapter(this, messages)
app/src/main/java/ai/elimu/chat/receiver/StudentUpdateReceiver.kt (3)

26-42: Remove dead commented code; replace with a concise TODO.

Large commented blocks rot quickly; keep a single actionable TODO (migrate to Room backfill).

-            /*  if (TextUtils.isEmpty(existingStudentId)) {
-                              // Update previously sent messages on the current device // TODO: Migrate to room
-                                val chatApplication = context.applicationContext as ChatApplication
-                                val messageDao = chatApplication.daoSession!!.messageDao
-                                val existingMessages = messageDao.queryBuilder()
-                                    .where(
-                                        MessageDao.Properties.DeviceId.eq(getDeviceId(context)),
-                                        MessageDao.Properties.StudentId.isNull()
-                                    )
-                                    .list()
-                                Log.i(javaClass.getName(), "existingMessages.size(): " + existingMessages.size)
-                                for (message in existingMessages) {
-                                    message.studentId = studentId
-                                    message.studentAvatar = studentAvatar
-                                    messageDao.update(message)
-                                }
-            }*/
+            // TODO: Backfill existing messages with studentId/studentAvatar using Room.

44-49: Use apply() for async SharedPreferences writes in a receiver.

Avoid blocking the main thread with commit = true.

-            sharedPreferences.edit(commit = true) {
+            sharedPreferences.edit {
                 putString(
                     Constants.PREF_STUDENT_ID,
                     studentId
                 )
             }

53-58: Same here: prefer apply() over commit.

-            sharedPreferences.edit(commit = true) {
+            sharedPreferences.edit {
                 putString(
                     Constants.PREF_STUDENT_AVATAR,
                     studentAvatar
                 )
             }
app/src/main/java/ai/elimu/chat/di/ServiceLocator.kt (1)

29-30: Consider security implications of device ID retrieval.

The TODO comment indicates planned changes to device ID retrieval. Since this chat app supports children's learning, consider privacy-focused alternatives like generating a UUID and storing it in SharedPreferences rather than using hardware identifiers.

-        //TODO: Need to change this
-        deviceId = Settings.Secure.getString(context.contentResolver, Settings.Secure.ANDROID_ID)
+        // Generate or retrieve a privacy-friendly device identifier
+        deviceId = sharedPreferences.getString("device_uuid", null) 
+            ?: UUID.randomUUID().toString().also { uuid ->
+                sharedPreferences.edit().putString("device_uuid", uuid).apply()
+            }

You'll need to import java.util.UUID and ensure sharedPreferences is initialized before this line.

app/src/main/java/ai/elimu/chat/model/Message.kt (2)

15-31: Consider simplifying with data class constructor.

The MessageBuilder pattern is implemented correctly but may be unnecessary complexity for a simple data class. Kotlin's named parameters and default values could provide similar flexibility with less code.

+data class Message(
+    val id: Long = 0,
+    val deviceId: String = "",
+    val studentId: String? = null,
+    val studentAvatar: String? = null,
+    val timeSent: Calendar = Calendar.getInstance(),
+    val text: String = ""
+)

This would allow creation like: Message(studentId = "student123", text = "Hello!")


40-51: Improve random emoji generation efficiency.

The current implementation works but has some inefficiencies and potential issues.

 private fun getRandomEmoji(): String {
-    val randomIndex = (Math.random() * EMOJI_UNICODES.size).toInt()
+    val randomIndex = EMOJI_UNICODES.indices.random()
     val unicode = EMOJI_UNICODES[randomIndex]
-
-    /**
-     * See http://apps.timwhitlock.info/emoji/tables/unicode
-     * @param unicode Example: "U+1F601" --> "0x1F601"
-     * @return
-     */
-    val emoji = String(Character.toChars(unicode))
-    return emoji
+    return String(Character.toChars(unicode))
 }

This uses Kotlin's random() function which is more idiomatic and thread-safe than Math.random().

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 0d74525 and 0b490a4.

📒 Files selected for processing (21)
  • app/build.gradle (2 hunks)
  • app/src/main/AndroidManifest.xml (1 hunks)
  • app/src/main/java/ai/elimu/chat/ChatActivity.kt (0 hunks)
  • app/src/main/java/ai/elimu/chat/ChatApplication.kt (1 hunks)
  • app/src/main/java/ai/elimu/chat/dao/DaoSession.java (0 hunks)
  • app/src/main/java/ai/elimu/chat/dao/MessageDao.java (0 hunks)
  • app/src/main/java/ai/elimu/chat/dao/converter/CalendarConverter.kt (0 hunks)
  • app/src/main/java/ai/elimu/chat/data/local/AppDatabase.kt (1 hunks)
  • app/src/main/java/ai/elimu/chat/data/local/ChatMessageDao.kt (1 hunks)
  • app/src/main/java/ai/elimu/chat/data/local/MessageEntity.kt (1 hunks)
  • app/src/main/java/ai/elimu/chat/data/local/MessageMapper.kt (1 hunks)
  • app/src/main/java/ai/elimu/chat/di/ServiceLocator.kt (1 hunks)
  • app/src/main/java/ai/elimu/chat/model/Message.java (0 hunks)
  • app/src/main/java/ai/elimu/chat/model/Message.kt (1 hunks)
  • app/src/main/java/ai/elimu/chat/receiver/StudentUpdateReceiver.kt (1 hunks)
  • app/src/main/java/ai/elimu/chat/ui/ChatActivity.kt (1 hunks)
  • app/src/main/java/ai/elimu/chat/ui/ChatViewModel.kt (1 hunks)
  • app/src/main/java/ai/elimu/chat/ui/MessageListArrayAdapter.kt (3 hunks)
  • app/src/main/java/ai/elimu/chat/util/Constants.kt (1 hunks)
  • app/src/main/java/ai/elimu/chat/util/DeviceInfoHelper.kt (0 hunks)
  • gradle/libs.versions.toml (1 hunks)
💤 Files with no reviewable changes (6)
  • app/src/main/java/ai/elimu/chat/util/DeviceInfoHelper.kt
  • app/src/main/java/ai/elimu/chat/dao/MessageDao.java
  • app/src/main/java/ai/elimu/chat/dao/DaoSession.java
  • app/src/main/java/ai/elimu/chat/ChatActivity.kt
  • app/src/main/java/ai/elimu/chat/model/Message.java
  • app/src/main/java/ai/elimu/chat/dao/converter/CalendarConverter.kt
🧰 Additional context used
🧬 Code graph analysis (2)
app/src/main/java/ai/elimu/chat/ui/ChatViewModel.kt (1)
app/src/main/java/ai/elimu/chat/model/Message.kt (1)
  • generateEmojiMessage (33-38)
app/src/main/java/ai/elimu/chat/ui/ChatActivity.kt (1)
app/src/main/java/ai/elimu/chat/ui/ChatViewModel.kt (1)
  • sendMessage (40-62)
🪛 GitHub Actions: Gradle Build
app/src/main/AndroidManifest.xml

[warning] 1-1: Setting the namespace via the package attribute in the source AndroidManifest.xml is no longer supported, and the value is ignored. Recommendation: remove package="ai.elimu.chat" from the source AndroidManifest.xml.

app/src/main/java/ai/elimu/chat/ui/MessageListArrayAdapter.kt

[warning] 67-67: Java type mismatch: inferred type is 'String?', but 'String' was expected.

🪛 detekt (1.23.8)
app/src/main/java/ai/elimu/chat/ui/ChatActivity.kt

[warning] 19-19: An empty default constructor can be removed.

(detekt.empty-blocks.EmptyDefaultConstructor)

🔇 Additional comments (13)
app/src/main/java/ai/elimu/chat/util/Constants.kt (1)

3-16: Good centralization of keys and IDs

This consolidation reduces magic strings across the codebase and supports maintainability—nicely aligned with elimu.ai's mission to build innovative learning software that empowers out-of-school children to teach themselves basic reading, writing and math within 6 months. 📖✍🏽🔢

app/src/main/java/ai/elimu/chat/data/local/AppDatabase.kt (1)

10-12: DAO exposure LGTM

Single DAO surface is fine for the current scope. Ensure DAO methods are suspend/Flow-based to keep I/O off the main thread.

app/src/main/java/ai/elimu/chat/data/local/MessageMapper.kt (1)

17-25: Include id when mapping to MessageEntity
Map Message.idMessageEntity.id to preserve existing IDs on updates (Room will still auto-generate when id == 0). Also verify that studentId and studentAvatar nullability matches between Message and MessageEntity.
elimu.ai’s mission is to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

app/src/main/java/ai/elimu/chat/data/local/MessageEntity.kt (1)

7-16: Confirm migration strategy

ServiceLocator.kt (lines 22–26) uses

Room.databaseBuilder(
    context.applicationContext,
    AppDatabase::class.java,
    "chat-db-new"
).build()

with no .addMigrations() or fallbackToDestructiveMigration(), so it creates a fresh DB instead of reusing GreenDAO’s file. Confirm that wiping existing data is intentional and acceptable, or configure proper migrations or fallbackToDestructiveMigration() if you plan to migrate legacy data.

elimu.ai’s mission is to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

app/src/main/java/ai/elimu/chat/ui/MessageListArrayAdapter.kt (1)

38-38: Accessing SharedPreferences via ServiceLocator is fine; confirm initialization order

This relies on ChatApplication calling ServiceLocator.initialize() first (it does). Just flagging that using DI injection later (post-Hilt fix) will improve testability.

If you want, I can switch this to accept studentId via constructor or a small StudentPrefs wrapper to decouple UI from the locator.

app/src/main/java/ai/elimu/chat/ChatApplication.kt (1)

16-18: Good: Initialize DI early, then run version checks

This sequencing avoids NPEs when preferences are requested. Nicely aligned with the new ServiceLocator.

As always, keeping things solid helps us focus on elimu.ai's mission: to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

app/build.gradle (1)

7-10: No action needed: project-level build.gradle already includes google()
AndroidX and Room will resolve via the existing google() entries in the root build.gradle repositories.
elimu.ai’s mission is to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

app/src/main/java/ai/elimu/chat/ui/ChatActivity.kt (1)

74-74: Initial state should be disabled when the input is empty.

-        setSendButtonState(false)
+        setSendButtonState(true)

Likely an incorrect or invalid review comment.

app/src/main/java/ai/elimu/chat/di/ServiceLocator.kt (1)

12-46: LGTM! Clean ServiceLocator pattern implementation.

This ServiceLocator provides a solid foundation for dependency management while you await the HILT v2.57.1 release. The initialization-before-use enforcement with clear error messages is well-implemented and aligns with elimu.ai's mission to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

gradle/libs.versions.toml (2)

4-4: LGTM! Clean migration to Room dependencies.

The version catalog updates properly support the GreenDAO to Room migration. All the new dependencies (Room runtime, KTX, compiler, JavaPoet, Activity KTX, and Kotlin KAPT) are using appropriate versions that support elimu.ai's mission to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

Also applies to: 11-11, 13-13, 22-26, 30-30, 35-35


13-13: No update needed: Room 2.7.2 is already the latest stable release
Room 2.7.2 (released June 18, 2025) is the current stable version; no upgrade is necessary.

elimu.ai's mission is to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

Likely an incorrect or invalid review comment.

app/src/main/java/ai/elimu/chat/model/Message.kt (2)

6-13: LGTM! Well-structured domain model.

The Message data class provides a clean domain model that effectively supports chat functionality for elimu.ai's mission to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.


33-38: Emoji selection verified
The EMOJI_UNICODES array contains only standard, child-friendly emoticons (e.g., 😁, 😂, 😊) appropriate for educational content. elimu.ai’s mission is to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

Comment on lines +13 to +16
"SELECT * FROM messages WHERE device_id LIKE :deviceId AND" +
" student_id LIKE :studentId"
)
suspend fun getAllMessages(deviceId: String, studentId: String): List<MessageEntity>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use equality instead of LIKE for exact lookups

LIKE prevents index usage unless callers pass wildcards deliberately. For exact deviceId/studentId retrieval, use = for correctness and performance.

-    @Query(
-        "SELECT * FROM messages WHERE device_id LIKE :deviceId AND" +
-                " student_id LIKE :studentId"
-    )
+    @Query("SELECT * FROM messages WHERE device_id = :deviceId AND student_id = :studentId")

Also ensure indexes on (device_id, student_id) in MessageEntity to keep this query fast.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"SELECT * FROM messages WHERE device_id LIKE :deviceId AND" +
" student_id LIKE :studentId"
)
suspend fun getAllMessages(deviceId: String, studentId: String): List<MessageEntity>
@Query("SELECT * FROM messages WHERE device_id = :deviceId AND student_id = :studentId")
suspend fun getAllMessages(deviceId: String, studentId: String): List<MessageEntity>
🤖 Prompt for AI Agents
In app/src/main/java/ai/elimu/chat/data/local/ChatMessageDao.kt around lines 13
to 16, the DAO query uses LIKE for device_id and student_id which prevents index
usage and is incorrect for exact matches; change the SQL to use '=' for both
device_id and student_id so exact lookups can use indexes, and then add an index
on (device_id, student_id) in the MessageEntity annotation (or the corresponding
Room entity) to ensure the query remains fast.

Comment on lines +22 to +27
val db = Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"chat-db-new"
).build()
chatMessageDao = db.messageDao()
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify Room database migration strategy.

The database name "chat-db-new" suggests this is a migration from the old GreenDAO implementation. Ensure that existing user data is properly migrated or that users are informed about data reset.


🏁 Script executed:

#!/bin/bash
# Description: Search for database migration logic or old GreenDAO references that might need cleanup

# Check for any Room migration classes
fd -t f -e kt | xargs rg -l "Migration|RoomDatabase" | head -5

# Look for any remaining GreenDAO references that might need cleanup
rg -n "greenDao|GreenDAO|DaoSession" --type kotlin

Length of output: 175


Add Room migrations or fallback strategy
No migrations are defined for the new “chat-db-new” schema in ServiceLocator.kt; invoking Room.databaseBuilder(...) without addMigrations() or fallbackToDestructiveMigration() will either break on version mismatch or silently reset user data. Implement a proper migration path between your old GreenDAO schema and the new Room schema—or explicitly call fallbackToDestructiveMigration() with a user warning—to preserve or appropriately reset data in line with elimu.ai’s mission to build innovative learning software that empowers out-of-school children to teach themselves basic reading📖, writing✍🏽 and math🔢 within 6 months.

import android.widget.EditText
import android.widget.ImageButton
import android.widget.ListView
import androidx.core.app.ComponentActivity
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Wrong ComponentActivity import — won’t compile.

Use androidx.activity.ComponentActivity.

-import androidx.core.app.ComponentActivity
+import androidx.activity.ComponentActivity
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import androidx.core.app.ComponentActivity
import androidx.activity.ComponentActivity
🤖 Prompt for AI Agents
In app/src/main/java/ai/elimu/chat/ui/ChatActivity.kt around line 17, the file
imports androidx.core.app.ComponentActivity which is incorrect and will not
compile; replace that import with androidx.activity.ComponentActivity and update
any import grouping if needed so the class references resolve to the correct
AndroidX activity class.

Comment on lines +34 to +35
lateinit var viewModel: ChatViewModel

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Use ViewModel delegation; don’t instantiate ViewModel directly.

Ensures lifecycle scoping and config-change survival.

-    lateinit var viewModel: ChatViewModel
+    private val viewModel: ChatViewModel by viewModels()

Add (outside this hunk):

import androidx.activity.viewModels
🤖 Prompt for AI Agents
In app/src/main/java/ai/elimu/chat/ui/ChatActivity.kt around lines 34 to 35,
replace the lateinit var viewModel: ChatViewModel declaration with a ViewModel
delegate so the ViewModel is lifecycle-scoped and survives config changes;
import androidx.activity.viewModels at the top of the file and declare the
ViewModel as a val using the by viewModels() delegate (e.g., val viewModel:
ChatViewModel by viewModels()) instead of lateinit.

Comment on lines +42 to +43
viewModel = ChatViewModel()

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Remove manual ViewModel construction.

Replaced by by viewModels().

-        viewModel = ChatViewModel()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
viewModel = ChatViewModel()
🤖 Prompt for AI Agents
In app/src/main/java/ai/elimu/chat/ui/ChatActivity.kt around lines 42-43, remove
the manual ViewModel construction "viewModel = ChatViewModel()" and replace it
with a delegated property using the Activity ViewModel delegate: declare a
private val viewModel: ChatViewModel by viewModels() at the class level, remove
the manual instantiation, and add the necessary import
(androidx.activity.viewModels). If ChatViewModel requires constructor
parameters, instead provide a ViewModelProvider.Factory and obtain the ViewModel
via viewModels { yourFactory }.

Comment on lines +68 to +71
override fun afterTextChanged(editable: Editable?) {
Log.i(javaClass.getName(), "afterTextChanged")
setSendButtonState(TextUtils.isEmpty(editable))
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Treat whitespace as empty; disable send accordingly.

-                setSendButtonState(TextUtils.isEmpty(editable))
+                setSendButtonState(editable.isNullOrBlank())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
override fun afterTextChanged(editable: Editable?) {
Log.i(javaClass.getName(), "afterTextChanged")
setSendButtonState(TextUtils.isEmpty(editable))
}
override fun afterTextChanged(editable: Editable?) {
Log.i(javaClass.getName(), "afterTextChanged")
setSendButtonState(editable.isNullOrBlank())
}
🤖 Prompt for AI Agents
In app/src/main/java/ai/elimu/chat/ui/ChatActivity.kt around lines 68 to 71, the
afterTextChanged handler currently treats any non-null Editable as non-empty and
enables the send button even when the text is only whitespace; change the check
to consider trimmed text empty by calling editable?.toString()?.trim() and using
TextUtils.isEmpty(...) on that result (handle null safely) so that
whitespace-only input disables the send button.

Copy link
Member

@tuancoltech tuancoltech left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kiranbollepalli9 The PR is a bit too big.
Would be better if you can split it to smaller PRs, to ease the review & testing.
Also, build was failed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants