Skip to content

feat(android): migrate XML layouts to Jetpack Compose#943

Open
AchoArnold wants to merge 14 commits into
mainfrom
feature/migrate-xml-to-compose
Open

feat(android): migrate XML layouts to Jetpack Compose#943
AchoArnold wants to merge 14 commits into
mainfrom
feature/migrate-xml-to-compose

Conversation

@AchoArnold

@AchoArnold AchoArnold commented Jul 7, 2026

Copy link
Copy Markdown
Member

This PR migrates the httpSMS Android application's UI from legacy XML layouts to Jetpack Compose.

Key Changes

  • Build System: Enabled Compose and added Material3 dependencies.
  • Theming: Created a new Material3 theme (\HttpSmsTheme) and typography.
  • Architecture: Introduced ViewModels (\MainViewModel, \LoginViewModel, \SettingsViewModel) for reactive state management.
  • UI Migration: Re-implemented Login, Main, and Settings screens as Composables.
  • Cleanup: Removed legacy XML layouts and refactored Activities to use \setContent.

Verification

  • Project builds successfully with ./gradlew assembleDebug.
  • All features (Login, Main dashboard, Settings) are functional in the new Compose UI.

@codacy-production

codacy-production Bot commented Jul 7, 2026

Copy link
Copy Markdown

Not up to standards ⛔

🔴 Issues 4 high · 8 medium · 33 minor

Alerts:
⚠ 45 issues (≤ 0 issues of at least minor severity)

Results:
45 new issues

Category Results
Documentation 33 minor
ErrorProne 4 high
Complexity 8 medium

View in Codacy

🟢 Metrics 79 complexity · -1 duplication

Metric Results
Complexity 79
Duplication -1

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@AchoArnold AchoArnold changed the title \feat: migrate XML layouts to Jetpack Compose" --body-file \C:/Users/achoa/AppData/Local/Google/AndroidStudio2026.1.1/projects/android.6b880862/.artifacts/20260625-115653-3d264f9e-e99a-4263-a680-2bde6150421d/pr_body.txt\ --base main --head feature/migrate-xml-to-compose feat(android): migrate XML layouts to Jetpack Compose Jul 7, 2026
@greptile-apps

greptile-apps Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR migrates the httpSMS Android app's three Activities (Login, Main, Settings) from XML-based views to Jetpack Compose, introducing dedicated LoginViewModel, MainViewModel, and SettingsViewModel for reactive state management with StateFlow.

  • Build & Theme: Adds Compose BOM 2024.10.01, Material3, and a new HttpSmsTheme with dynamic colour support (Android 12+).
  • Login screen: Validation logic (URL check, phone format, FCM token presence) and the network call to updateFcmToken are moved into LoginViewModel.login(), with proper try/catch around the URI constructor and IO work, restoring isLoading = false on error.
  • Main & Settings screens: State reads from Settings.* are consolidated into ViewModel updateState/initialize calls; heartbeat sending is moved to MainViewModel.sendHeartbeat; logout to SettingsViewModel.logout.

Confidence Score: 5/5

Safe to merge — all three screens function correctly with the new Compose implementation, the previous login-freeze regression has been addressed, and no blocking defects were introduced.

The core migration is solid: validation logic is preserved, the URI constructor and network calls are properly wrapped in try/catch with isLoading reset on errors, and each ViewModel correctly manages its own StateFlow. The only findings are minor style issues — dead code in LoginActivity, redundant SideEffect status-bar writes in SettingsActivityContent, and a slightly misleading permission guard in autoDetectPhoneNumbers.

No files require special attention; the three items flagged are all low-impact style clean-ups.

Important Files Changed

Filename Overview
android/app/build.gradle.kts Adds Compose BOM, Material3, and activity-compose dependencies. composeOptions block and navigation-compose dependency (unused) are still present despite previous review feedback.
android/app/src/main/java/com/httpsms/LoginActivity.kt Activity slimmed down to delegate to LoginViewModel. The private getPhoneNumber() method is retained but its only caller (setPhoneNumber()) was removed, leaving dead code.
android/app/src/main/java/com/httpsms/ui/login/LoginViewModel.kt Proper ViewModel with StateFlow. URL validation with URLUtil, phone-number validation, and IO-wrapped network call with try/catch all look correct.
android/app/src/main/java/com/httpsms/ui/login/LoginActivityContent.kt Compose login screen with controlled text fields, QR scan icon, and loading indicator. Clean implementation.
android/app/src/main/java/com/httpsms/ui/main/MainViewModel.kt Clean ViewModel for main dashboard. Heartbeat coroutine properly resets loading state on completion and updates the displayed timestamp.
android/app/src/main/java/com/httpsms/ui/main/MainActivityContent.kt Main dashboard Composable with phone cards, permission buttons, heartbeat button, and settings navigation.
android/app/src/main/java/com/httpsms/ui/settings/SettingsViewModel.kt Settings ViewModel reads/writes Settings preferences and mirrors state into StateFlow correctly. Logout clears all expected keys.
android/app/src/main/java/com/httpsms/ui/settings/SettingsActivityContent.kt Settings Composable with Scaffold+TopAppBar. Contains a SideEffect that sets window.statusBarColor, duplicating what HttpSmsTheme already does on every recomposition.
android/app/src/main/java/com/httpsms/ui/theme/Theme.kt Material3 theme with dynamic colour support. SideEffect sets status bar colour; dynamic colours will override the hand-picked palette on Android 12+.
android/app/src/main/java/com/httpsms/ui/theme/Typography.kt Minimal typography definition; only bodyLarge is customised.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant A as LoginActivity
    participant LVM as LoginViewModel
    participant MVM as MainViewModel
    participant SVM as SettingsViewModel

    A->>LVM: initialize(context, defaultServerUrl)
    LVM->>LVM: autoDetectPhoneNumbers()
    A->>A: "setContent { HttpSmsTheme { LoginScreen } }"

    Note over A,LVM: User taps Login
    A->>LVM: login(context, countryCode, ...)
    LVM->>LVM: validate (phone, URL)
    LVM->>LVM: "withContext(IO) { updateFcmToken }"
    LVM-->>A: "uiState.loginSuccess = true"
    A->>A: LaunchedEffect → redirectToMain()

    Note over A,MVM: MainActivity opened
    A->>MVM: initialize(context, appVersion)
    MVM->>MVM: updateState() – SMS perms, battery, heartbeat ts
    A->>A: "setContent { HttpSmsTheme { MainScreen } }"

    Note over A,MVM: User taps Heartbeat
    A->>MVM: sendHeartbeat(context, onComplete)
    MVM->>MVM: "withContext(IO) { storeHeartbeat }"
    MVM-->>A: onComplete(error?)

    Note over A,SVM: SettingsActivity opened
    A->>SVM: initialize(context)
    A->>A: "setContent { HttpSmsTheme { SettingsScreen } }"
    Note over A,SVM: Toggle / logout delegates to SettingsViewModel
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant A as LoginActivity
    participant LVM as LoginViewModel
    participant MVM as MainViewModel
    participant SVM as SettingsViewModel

    A->>LVM: initialize(context, defaultServerUrl)
    LVM->>LVM: autoDetectPhoneNumbers()
    A->>A: "setContent { HttpSmsTheme { LoginScreen } }"

    Note over A,LVM: User taps Login
    A->>LVM: login(context, countryCode, ...)
    LVM->>LVM: validate (phone, URL)
    LVM->>LVM: "withContext(IO) { updateFcmToken }"
    LVM-->>A: "uiState.loginSuccess = true"
    A->>A: LaunchedEffect → redirectToMain()

    Note over A,MVM: MainActivity opened
    A->>MVM: initialize(context, appVersion)
    MVM->>MVM: updateState() – SMS perms, battery, heartbeat ts
    A->>A: "setContent { HttpSmsTheme { MainScreen } }"

    Note over A,MVM: User taps Heartbeat
    A->>MVM: sendHeartbeat(context, onComplete)
    MVM->>MVM: "withContext(IO) { storeHeartbeat }"
    MVM-->>A: onComplete(error?)

    Note over A,SVM: SettingsActivity opened
    A->>SVM: initialize(context)
    A->>A: "setContent { HttpSmsTheme { SettingsScreen } }"
    Note over A,SVM: Toggle / logout delegates to SettingsViewModel
Loading

Reviews (2): Last reviewed commit: "fix: address PR review comments\n\n- Imp..." | Re-trigger Greptile

Comment thread android/app/src/main/java/com/httpsms/ui/login/LoginViewModel.kt Outdated
Comment thread android/app/src/main/java/com/httpsms/ui/settings/SettingsActivityContent.kt Outdated
Comment thread android/app/build.gradle.kts Outdated
Comment on lines 45 to 47
composeOptions {
kotlinCompilerExtensionVersion = "1.5.15"
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 When using the org.jetbrains.kotlin.plugin.compose Gradle plugin (Kotlin 2.0+), the compose compiler is bundled with the Kotlin plugin and the composeOptions { kotlinCompilerExtensionVersion } block is no longer needed. Including version 1.5.15 here alongside the 2.0.21 plugin can produce a build warning and is at minimum confusing.

Suggested change
composeOptions {
kotlinCompilerExtensionVersion = "1.5.15"
}

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

implementation("androidx.compose.material3:material3")
implementation("androidx.activity:activity-compose:1.9.3")
implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.8.7")
implementation("androidx.navigation:navigation-compose:2.8.3")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 navigation-compose is added as a dependency but is never used in this PR — all three activities still navigate via explicit Intents. This unused artifact increases build time and APK size needlessly. Remove it if Compose Navigation is not yet planned, or leave a comment explaining its intent.

Suggested change
implementation("androidx.navigation:navigation-compose:2.8.3")

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed! Removed the unused dependency.

Comment thread android/app/src/main/java/com/httpsms/ui/login/LoginActivityContent.kt Outdated
@AchoArnold

Copy link
Copy Markdown
Member Author

I've addressed the review comments:

  1. Improved URL Validation: Restored the structural URL checks using URLUtil.isValidUrl and URLUtil.isHttpsUrl in LoginViewModel to ensure robust validation before processing.
  2. Enhanced Error Handling: Added ry-catch blocks around the URI constructor and network service calls. This prevents the coroutine from failing silently and ensures isLoading is correctly reset to alse if an error occurs, keeping the UI responsive.
  3. Cleaned Up Imports: Removed duplicate wildcard imports in LoginActivityContent.kt, SettingsActivityContent.kt, and MainActivityContent.kt to improve code clarity and comply with standards.
  4. Verified Build: Confirmed that the project builds successfully with these improvements.

@AchoArnold

Copy link
Copy Markdown
Member Author

@greptile-apps Thanks for the thorough review! I've fixed the issues you pointed out:

  • URL Validation Regression: I've restored the robust validation using \URLUtil.isValidUrl\ and \URLUtil.isHttpsUrl. I've also wrapped the \URI\ constructor and API calls in a \ ry-catch\ block to ensure \isLoading\ is correctly reset to \ alse\ if an error occurs, preventing the UI from getting stuck.
  • Redundant Config: Cleaned up the redundant \composeOptions\ in \�uild.gradle.kts.
  • Duplicate Imports: Fixed the duplicate and deprecated imports in \SettingsActivityContent.kt\ and removed wildcard imports in other UI files.
  • Unused Dependency: Removed the unused
    avigation-compose\ dependency.

The PR is now more stable and adheres to the project's coding standards.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR migrates the Android app UI from legacy XML layouts to Jetpack Compose + Material3, introducing ViewModels to drive reactive UI state and updating Activities to render via setContent.

Changes:

  • Enabled Compose in the Android app module, added Compose/Material3 dependencies, and introduced a Compose theme/typography layer.
  • Rebuilt Login, Main, and Settings screens as composables backed by new ViewModels (LoginViewModel, MainViewModel, SettingsViewModel).
  • Removed legacy XML activity layouts and refactored Activities to use Compose content instead of setContentView(...).

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
android/build.gradle.kts Adds Kotlin + Compose compiler plugins at the Android root level.
android/app/build.gradle.kts Enables Compose and adds Material3/Compose dependencies for the app module.
android/app/src/main/res/layout/activity_settings.xml Removes legacy Settings XML layout (Compose replacement).
android/app/src/main/res/layout/activity_main.xml Removes legacy Main XML layout (Compose replacement).
android/app/src/main/res/layout/activity_login.xml Removes legacy Login XML layout (Compose replacement).
android/app/src/main/java/com/httpsms/ui/theme/Typography.kt Adds Material3 typography definition used by the Compose theme.
android/app/src/main/java/com/httpsms/ui/theme/Theme.kt Introduces HttpSmsTheme and color schemes for Compose screens.
android/app/src/main/java/com/httpsms/ui/settings/SettingsViewModel.kt Adds a Settings ViewModel + state model backed by Settings.
android/app/src/main/java/com/httpsms/ui/settings/SettingsActivityContent.kt Implements the Settings UI in Compose.
android/app/src/main/java/com/httpsms/ui/main/MainViewModel.kt Adds a Main ViewModel handling permissions/battery state and heartbeat sending.
android/app/src/main/java/com/httpsms/ui/main/MainActivityContent.kt Implements the Main dashboard UI in Compose.
android/app/src/main/java/com/httpsms/ui/login/LoginViewModel.kt Adds a Login ViewModel handling validation, auto-detection, and login flow.
android/app/src/main/java/com/httpsms/ui/login/LoginActivityContent.kt Implements the Login UI in Compose.
android/app/src/main/java/com/httpsms/SettingsActivity.kt Refactors SettingsActivity to use Compose + SettingsViewModel.
android/app/src/main/java/com/httpsms/MainActivity.kt Refactors MainActivity to use Compose + MainViewModel and removes XML wiring.
android/app/src/main/java/com/httpsms/LoginActivity.kt Refactors LoginActivity to use Compose + LoginViewModel and updates permission flow.

Comment on lines 1 to 6
plugins {
id("com.android.application")
id("com.google.gms.google-services")
id("io.sentry.android.gradle") version "6.2.0"
id("org.jetbrains.kotlin.plugin.compose")
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Adding \org.jetbrains.kotlin.android\ here actually causes a build conflict: 'Cannot add extension with name \kotlin, as there is an extension already registered'. It seems the plugin is already being applied or configured by the root build file or the Android application plugin. I'll keep it as is since it builds successfully.

Comment thread android/app/build.gradle.kts Outdated
Comment on lines 45 to 47
composeOptions {
kotlinCompilerExtensionVersion = "1.5.15"
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed! Removed the redundant \composeOptions\ block as the Kotlin 2.0 Compose plugin handles this automatically.

Comment on lines +57 to +59
val uiState by viewModel.uiState.collectAsState()
val context = LocalContext.current

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed! Removed the unused \context\ variable.

Comment on lines +64 to +68
if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_PHONE_STATE) != PackageManager.PERMISSION_GRANTED &&
ContextCompat.checkSelfPermission(context, Manifest.permission.READ_SMS) != PackageManager.PERMISSION_GRANTED) {
Timber.d("Permissions not granted for auto-detecting phone numbers")
return
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Fixed! Simplified the permission check to \READ_PHONE_STATE\ as suggested to avoid unnecessary security exceptions.

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.

2 participants