Skip to content

Fixed login Issue using mifos/password(Login credentials)#3071

Open
Aditya002500 wants to merge 17 commits intoopenMF:developmentfrom
Aditya002500:devx
Open

Fixed login Issue using mifos/password(Login credentials)#3071
Aditya002500 wants to merge 17 commits intoopenMF:developmentfrom
Aditya002500:devx

Conversation

@Aditya002500
Copy link
Contributor

@Aditya002500 Aditya002500 commented Jan 14, 2026

Fixes - MM-352

Shows "unexpected error occurred" because client data fetches fail from the server

Both storage AND in-memory state are cleared immediately, so auth check fails correctly

Login behavior

Before fix After fix
BeforeFix.mp4

|

WhatsApp.Video.2026-02-02.at.23.21.12.mp4

| |

Changes in -
cmp-android\src\main\AndroidManifest.xml
core\datastore\src\commonMain\kotlin\org\mifos\mobile\core\datastore\UserPreferencesDataSource.kt
feature\auth\src\commonMain\kotlin\org\mifos\mobile\feature\auth\login\LogInNavigation.kt

Summary by CodeRabbit

  • Bug Fixes
    • User data now properly clears from memory when storage is cleared.
    • Error messages in login flow now display localized text correctly.
    • Login flow improved to handle users with no associated clients.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

Warning

Rate limit exceeded

@Aditya002500 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 54 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📝 Walkthrough

Walkthrough

The changes introduce localization support for login error messages via StringResource, add validation to handle users without clients, and fix a state synchronization issue in the datastore where the in-memory cache wasn't being reset alongside persistent storage.

Changes

Cohort / File(s) Summary
Login Error Handling
feature/auth/src/commonMain/kotlin/org/mifos/mobile/feature/auth/login/LoginViewModel.kt, feature/auth/src/commonMain/kotlin/org/mifos/mobile/feature/auth/login/LoginScreen.kt
Updated error dialog to use StringResource instead of plain String. Added early validation to detect users without clients and emit error dialog before proceeding. LoginScreen wraps error message retrieval in stringResource() call.
State Persistence Fix
core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesDataSource.kt
Fixed clearInfo() method to reset in-memory state _userInfo to UserData.DEFAULT in addition to persisting to storage, ensuring cache-storage consistency.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Suggested reviewers

  • biplab1
  • markrizkalla

Poem

🐰 Strings now resourceful, localized with care,
Users with no clients—we catch them right there!
Memory and storage, in sync once again,
Clear info flows cleanly, no stale data to maintain!

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning The title mentions fixing a login issue with credentials but doesn't accurately reflect the actual changes: resetting in-memory state, changing error payload types to StringResource, and handling the no-clients scenario. Revise the title to more accurately describe the main changes, such as 'Reset in-memory auth state and improve error handling in login flow' or 'Update login error handling with StringResource and clear in-memory state on logout'.
Docstring Coverage ⚠️ Warning Docstring coverage is 12.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
Contributor

@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: 1

🤖 Fix all issues with AI agents
In
`@feature/auth/src/commonMain/kotlin/org/mifos/mobile/feature/auth/login/LoginViewModel.kt`:
- Around line 112-122: Replace the hardcoded error message in LoginViewModel
(inside the user.clients.isEmpty() branch) with a string resource: add
feature_sign_in_no_linked_client_error to your resources and pass a Res.string
reference (consistent with other messages that use Res.string.*) into
DialogState.Error instead of the literal String; if DialogState.Error currently
only accepts String, update its constructor/type to accept a StringResource/Res
or resolve the resource before calling updateState, and also remove the extra
leading whitespace/indentation in that block to match surrounding formatting.
🧹 Nitpick comments (2)
core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesDataSource.kt (1)

160-170: Correct fix - now aligns with the update pattern used elsewhere in the class.

The addition of _userInfo.value = UserData.DEFAULT ensures the in-memory state is synchronized with persistent storage, matching the pattern used in updateUserInfo, updateToken, updateClientId, etc. This is the key fix for the auth state inconsistency.

Minor: The indentation on line 163 appears to have extra leading whitespace compared to the surrounding code.

🔧 Suggested indentation fix
     suspend fun clearInfo() {
         withContext(dispatcher) {
             settings.putUserPreference(UserData.DEFAULT)
-                _userInfo.value = UserData.DEFAULT
+            _userInfo.value = UserData.DEFAULT
             val cleared = settings.getSettingsPreference().copy(
                 isAuthenticated = false,
             )
feature/auth/src/commonMain/kotlin/org/mifos/mobile/feature/auth/login/LoginViewModel.kt (1)

123-135: Simplify redundant isNotEmpty() check.

Since the early return at line 121-122 guarantees user.clients is not empty at this point, the conditional on lines 126-130 is redundant. You can safely use user.clients[0] directly.

♻️ Suggested simplification
                 val userData = UserData(
                     userId = user.userId,
                     userName = user.username.orEmpty(),
-                    clientId = if (user.clients.isNotEmpty()) {
-                        user.clients[0]
-                    } else {
-                        user.userId
-                    },
+                    clientId = user.clients[0],
                     isAuthenticated = user.isAuthenticated,
                     base64EncodedAuthenticationKey = user.base64EncodedAuthenticationKey.orEmpty(),
                     officeName = user.officeName.orEmpty(),
                     password = state.password,
                 )
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5c961c6 and de482a7.

📒 Files selected for processing (3)
  • cmp-android/src/main/AndroidManifest.xml
  • core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesDataSource.kt
  • feature/auth/src/commonMain/kotlin/org/mifos/mobile/feature/auth/login/LoginViewModel.kt
🧰 Additional context used
🧬 Code graph analysis (1)
feature/auth/src/commonMain/kotlin/org/mifos/mobile/feature/auth/login/LoginViewModel.kt (10)
feature/home/src/commonMain/kotlin/org/mifos/mobile/feature/home/HomeViewModel.kt (1)
  • updateState (211-213)
feature/auth/src/commonMain/kotlin/org/mifos/mobile/feature/auth/registration/RegistrationViewModel.kt (1)
  • updateState (61-63)
feature/beneficiary/src/commonMain/kotlin/org/mifos/mobile/feature/beneficiary/beneficiaryDetail/BeneficiaryDetailViewModel.kt (1)
  • updateState (100-102)
feature/accounts/src/commonMain/kotlin/org/mifos/mobile/feature/accounts/accountTransactions/TransactionViewModel.kt (1)
  • updateState (116-118)
feature/savings-application/src/commonMain/kotlin/org/mifos/mobile/feature/savings/application/savingsApplication/SavingsApplyViewModel.kt (1)
  • updateState (167-169)
feature/beneficiary/src/commonMain/kotlin/org/mifos/mobile/feature/beneficiary/beneficiaryApplication/BeneficiaryApplicationViewModel.kt (1)
  • updateState (96-98)
feature/beneficiary/src/commonMain/kotlin/org/mifos/mobile/feature/beneficiary/beneficiaryList/BeneficiaryListViewModel.kt (1)
  • updateState (131-133)
feature/qr/src/commonMain/kotlin/org/mifos/mobile/feature/qr/qr/QrCodeReaderViewModel.kt (1)
  • updateState (63-65)
feature/qr/src/commonMain/kotlin/org/mifos/mobile/feature/qr/qrCodeDisplay/QrCodeDisplayViewModel.kt (1)
  • updateState (91-93)
feature/qr/src/commonMain/kotlin/org/mifos/mobile/feature/qr/qrCodeImport/QrCodeImportViewModel.kt (1)
  • updateState (59-61)
🔇 Additional comments (1)
cmp-android/src/main/AndroidManifest.xml (1)

37-37: Good security and reliability improvement.

Disabling android:allowBackup prevents Android from restoring stale authentication credentials after app reinstall or device migration, which aligns with the PR's goal of fixing auth state inconsistencies. This is a best practice for apps handling sensitive user credentials.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 112 to 122
if (user.clients.isEmpty()) {
updateState {
it.copy(
isError = true,
dialogState = LoginState.DialogState.Error(
"This account is not linked to any client. Please contact your administrator.",
),
)
}
return
}
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 14, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Hardcoded string should be a string resource for localization.

The error message is hardcoded while other error strings in this file use Res.string.* resources (see lines 99-100). For consistency and proper i18n support, extract this to a string resource.

Also, the indentation on these lines appears to have extra leading whitespace.

🔧 Suggested fix

Add a string resource in the appropriate resources file:

<string name="feature_sign_in_no_linked_client_error">This account is not linked to any client. Please contact your administrator.</string>

Then update the code:

-                   if (user.clients.isEmpty()) {
-                    updateState {
-                        it.copy(
-                            isError = true,
-                            dialogState = LoginState.DialogState.Error(
-                                "This account is not linked to any client. Please contact your administrator.",
-                            ),
-                        )
-                    }
-                    return
-                }
+                if (user.clients.isEmpty()) {
+                    updateState {
+                        it.copy(
+                            isError = true,
+                            dialogState = LoginState.DialogState.Error(
+                                Res.string.feature_sign_in_no_linked_client_error,
+                            ),
+                        )
+                    }
+                    return
+                }

Note: This would require changing DialogState.Error to accept a StringResource instead of String, or resolving the resource at this point.

🤖 Prompt for AI Agents
In
`@feature/auth/src/commonMain/kotlin/org/mifos/mobile/feature/auth/login/LoginViewModel.kt`
around lines 112 - 122, Replace the hardcoded error message in LoginViewModel
(inside the user.clients.isEmpty() branch) with a string resource: add
feature_sign_in_no_linked_client_error to your resources and pass a Res.string
reference (consistent with other messages that use Res.string.*) into
DialogState.Error instead of the literal String; if DialogState.Error currently
only accepts String, update its constructor/type to accept a StringResource/Res
or resolve the resource before calling updateState, and also remove the extra
leading whitespace/indentation in that block to match surrounding formatting.

Copy link
Contributor

@amanna13 amanna13 Jan 16, 2026

Choose a reason for hiding this comment

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

@Aditya002500 Please address the suggested changes requested by coderabbitai

Copy link
Contributor

Choose a reason for hiding this comment

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

@amanna13, thank you for following up on this!

To summarize what needs to be addressed in this code block:

  1. Add a string resource for the error message in the appropriate resources file (likely feature/auth/src/commonMain/composeResources/values/strings.xml):

    <string name="feature_sign_in_no_linked_client_error">This account is not linked to any client. Please contact your administrator.</string>
  2. Update the code to use the string resource and fix the indentation:

    • Remove the extra leading whitespace (3 spaces) before if on line 112
    • Replace the hardcoded string with a reference to the string resource
  3. Handle the type mismatch: Since DialogState.Error currently accepts String, you'll need to either:

    • Change DialogState.Error to accept StringResource (recommended for consistency), OR
    • Resolve the string resource before passing it to DialogState.Error

The first approach would maintain better type safety and consistency with other error handling patterns in the codebase, where string resources are resolved at the UI layer rather than in the ViewModel.

Copy link
Contributor Author

@Aditya002500 Aditya002500 left a comment

Choose a reason for hiding this comment

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

Fixed indentation

@biplab1
Copy link
Contributor

biplab1 commented Jan 15, 2026

@Aditya002500 The ticket number seems to be incorrect. Please check.

@biplab1
Copy link
Contributor

biplab1 commented Jan 15, 2026

https://mifosforge.jira.com/browse/MM-352

Please update the ticket number and its corresponding URL in the PR description.

Copy link
Contributor

@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: 1

Caution

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

⚠️ Outside diff range comments (4)
feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/di/SettingsModule.kt (1)

27-35: Duplicate ViewModel registration: UpdatePasscodeViewModel is registered twice.

viewModelOf(::UpdatePasscodeViewModel) appears at both line 29 and line 31. Remove the duplicate registration.

🐛 Proposed fix
 val SettingsModule = module {
     viewModelOf(::SettingsViewModel)
     viewModelOf(::UpdatePasscodeViewModel)
     viewModelOf(::ChangePasswordViewModel)
-    viewModelOf(::UpdatePasscodeViewModel)
     viewModelOf(::FaqViewModel)
     viewModelOf(::LanguageViewModel)
     viewModelOf(::ChangeThemeViewModel)
     viewModelOf(::SelectedServicesViewModel)
 }
core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesRepositoryImpl.kt (1)

139-146: Pre-existing null safety issue: clientId!! will throw NPE if null is passed.

The method signature accepts Long? but immediately force-unwraps it. Consider handling the null case gracefully or changing the parameter type to non-nullable Long.

Suggested fix
     override suspend fun updateClientId(clientId: Long?): DataState<Unit> {
         return try {
-            val result = preferenceManager.updateClientId(clientId!!)
+            val id = clientId ?: return DataState.Error(IllegalArgumentException("clientId cannot be null"))
+            val result = preferenceManager.updateClientId(id)
             DataState.Success(result)
         } catch (e: Exception) {
             DataState.Error(e)
         }
     }
feature/home/src/commonMain/kotlin/org/mifos/mobile/feature/home/HomeViewModel.kt (1)

317-329: Pre-existing null safety issue: force unwrap on nullable parameter.

savingAccountList is typed as List<SavingAccount>? but is force-unwrapped with !! at line 319. While the current call site at line 257 guards this with isNotEmpty(), the function signature doesn't guarantee a non-null list.

Consider making the parameter non-nullable or using safe iteration
-    private fun getSavingAccountDetails(savingAccountList: List<SavingAccount>?) {
+    private fun getSavingAccountDetails(savingAccountList: List<SavingAccount>) {
         var totalAmount = 0.0
-        for (savingAccount in savingAccountList!!) {
+        for (savingAccount in savingAccountList) {
             totalAmount += savingAccount.accountBalance
         }

Or if the nullable type must be retained:

-        for (savingAccount in savingAccountList!!) {
+        for (savingAccount in savingAccountList.orEmpty()) {
core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesDataSource.kt (1)

160-170: clearInfo() should also reset selected services to defaults.

When a user logs out, the _selectedServices state flow and the persisted SELECTED_SERVICES preference retain their values. Since selected services are user-specific (as shown by their use in HomeViewModel to filter service cards per user), the next user logging in would initially see the previous user's service selections. Reset both _selectedServices and the stored preference to DEFAULT_SELECTED_SERVICES in clearInfo() to maintain user data separation.

🤖 Fix all issues with AI agents
In
`@core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesDataSource.kt`:
- Around line 239-246: getSelectedServices() currently treats an empty stored
string the same as "not configured" because
settings.getString(SELECTED_SERVICES, "") returns "" for both cases; change the
logic to distinguish absent vs explicit empty by either (A) checking presence
via settings.contains(SELECTED_SERVICES) (or using getString with a nullable
default) and returning DEFAULT_SELECTED_SERVICES only when the key is missing,
while returning emptySet() when the stored value is "" — or (B) introduce a
separate boolean flag (e.g., SELECTED_SERVICES_CONFIGURED) that you set when the
user saves selections and check in getSelectedServices(); update the code paths
that write the preference (the setter that calls settings.putString or writes
SELECTED_SERVICES_CONFIGURED) and modify getSelectedServices() accordingly so
DEFAULT_SELECTED_SERVICES is returned only for "not yet configured" and an
explicit empty string yields an empty set.
🧹 Nitpick comments (3)
feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/componenets/SettingsItems.kt (1)

108-116: Verify icon choice: MifosIcons.AppRecent is also used by AppInfo.

The SelectedServices item uses MifosIcons.AppRecent, which is the same icon used by AppInfo (line 168). Consider using a distinct icon for better visual differentiation in the settings list.

feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/selectedservices/SelectedServicesViewModel.kt (1)

57-64: Use a stable snapshot for apply; verify failure handling.
Capturing pendingSelectedServices before launching avoids stale reads. Also, if persistence can fail, consider surfacing that instead of navigating back.

♻️ Suggested adjustment
 private fun handleApplyChanges() {
-    viewModelScope.launch {
-        userPreferencesRepository.setSelectedServices(state.pendingSelectedServices)
-        mutableStateFlow.update {
-            it.copy(currentSelectedServices = state.pendingSelectedServices)
-        }
+    val pending = state.pendingSelectedServices
+    viewModelScope.launch {
+        userPreferencesRepository.setSelectedServices(pending)
+        mutableStateFlow.update {
+            it.copy(currentSelectedServices = pending)
+        }
         sendEvent(SelectedServicesEvent.NavigateBack)
     }
 }
feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/selectedservices/SelectedServicesScreen.kt (1)

98-129: Apply the modifier to the scaffold instead of the button.
Right now the caller’s modifier only affects the bottom button, which is surprising for a screen-level modifier.

♻️ Suggested adjustment
     MifosElevatedScaffold(
+        modifier = modifier,
         onNavigateBack = { onAction(SelectedServicesAction.OnNavigateBack) },
         topBarTitle = stringResource(Res.string.feature_settings_selected_services_topbar),
         bottomBar = {
             MifosButton(
-                modifier = modifier
+                modifier = Modifier
                     .fillMaxWidth()
                     .height(DesignToken.sizes.buttonHeight)
                     .padding(horizontal = KptTheme.spacing.md),
                 shape = KptTheme.shapes.medium,
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3fdf202 and 129e53a.

📒 Files selected for processing (12)
  • core/common/src/commonMain/kotlin/org/mifos/mobile/core/common/Constants.kt
  • core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesDataSource.kt
  • core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesRepository.kt
  • core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesRepositoryImpl.kt
  • feature/home/src/commonMain/kotlin/org/mifos/mobile/feature/home/HomeViewModel.kt
  • feature/settings/src/commonMain/composeResources/values/strings.xml
  • feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/componenets/SettingsItems.kt
  • feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/di/SettingsModule.kt
  • feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/navigation/SettingsNavGraphRoute.kt
  • feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/selectedservices/SelectedServicesRoute.kt
  • feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/selectedservices/SelectedServicesScreen.kt
  • feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/selectedservices/SelectedServicesViewModel.kt
🧰 Additional context used
📓 Path-based instructions (1)
**/{src,}/**/di/**/*.kt

📄 CodeRabbit inference engine (CLAUDE.md)

Define Koin modules in the di/ package for each module

Files:

  • feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/di/SettingsModule.kt
🧠 Learnings (2)
📚 Learning: 2026-01-07T10:05:07.700Z
Learnt from: CR
Repo: openMF/mifos-mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-07T10:05:07.700Z
Learning: Use ViewModels with StateFlow/SharedFlow for state management in feature modules

Applied to files:

  • feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/selectedservices/SelectedServicesViewModel.kt
  • feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/di/SettingsModule.kt
  • feature/home/src/commonMain/kotlin/org/mifos/mobile/feature/home/HomeViewModel.kt
📚 Learning: 2026-01-07T10:05:07.700Z
Learnt from: CR
Repo: openMF/mifos-mobile PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-07T10:05:07.700Z
Learning: Use the `ScreenState<T>` pattern for loading/success/error states in feature modules

Applied to files:

  • feature/home/src/commonMain/kotlin/org/mifos/mobile/feature/home/HomeViewModel.kt
🧬 Code graph analysis (2)
feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/selectedservices/SelectedServicesScreen.kt (2)
core/designsystem/src/commonMain/kotlin/org/mifos/mobile/core/designsystem/component/MifosScaffold.kt (1)
  • MifosElevatedScaffold (209-284)
core/designsystem/src/commonMain/kotlin/org/mifos/mobile/core/designsystem/theme/Theme.kt (1)
  • MifosMobileTheme (267-291)
feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/navigation/SettingsNavGraphRoute.kt (1)
feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/selectedservices/SelectedServicesRoute.kt (1)
  • selectedServicesDestination (22-30)
🔇 Additional comments (23)
core/common/src/commonMain/kotlin/org/mifos/mobile/core/common/Constants.kt (1)

111-111: LGTM!

The constant follows the existing naming conventions and is appropriately placed in the settings constants section.

feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/componenets/SettingsItems.kt (2)

36-37: LGTM!

Imports follow the established pattern for other settings items.


192-192: LGTM!

The new item is properly added to the settings list in a logical position.

feature/settings/src/commonMain/composeResources/values/strings.xml (1)

71-85: LGTM!

String resources follow the established naming conventions and are well-organized. The "A/c" abbreviation for "Account" is used consistently across related entries.

feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/navigation/SettingsNavGraphRoute.kt (2)

25-25: LGTM!

Import follows the established pattern for other destination imports.


90-92: LGTM!

The selectedServicesDestination is wired consistently with other destinations in the settings graph, using popBackStack for the back navigation action.

core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesRepository.kt (1)

78-80: LGTM!

The new interface members for observing and setting selected services are well-defined and consistent with the existing API patterns in this repository interface.

feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/selectedservices/SelectedServicesRoute.kt (1)

18-30: LGTM!

The navigation helpers follow the established pattern for settings routes. Using composableWithPushTransitions ensures consistent transition animations, and the internal visibility is appropriate for feature-module navigation.

core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesRepositoryImpl.kt (1)

190-195: LGTM!

The implementation correctly delegates to preferenceManager for both observing and setting selected services, following the established delegation pattern used throughout this repository.

core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesDataSource.kt (2)

160-170: Critical fix: Resetting in-memory state after clearing persistent storage.

This change correctly addresses the root cause of MM-352. Previously, only the persistent storage was cleared but the in-memory _userInfo state retained stale values, causing authentication checks to fail and show "unexpected error occurred".


265-276: LGTM on the default services configuration.

The default set of services is well-defined and covers the expected feature set. Using a companion object constant makes it reusable and testable.

feature/home/src/commonMain/kotlin/org/mifos/mobile/feature/home/HomeViewModel.kt (1)

66-77: LGTM!

The observer correctly:

  • Uses distinctUntilChanged() to prevent redundant state updates
  • Filters from the original serviceCards list (not the current state) ensuring consistent behavior
  • Converts to PersistentList for Compose immutability requirements

Based on learnings, this follows the StateFlow/SharedFlow pattern recommended for ViewModels.

feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/selectedservices/SelectedServicesViewModel.kt (6)

22-35: Clean initial state + observation wiring.
Initialization seeds defaults and streams repository updates into state actions clearly.


37-43: Action routing is straightforward.
The handler cleanly dispatches all action types without gaps.


46-55: Toggle logic is clear and immutable.
Set updates are concise and safe.


67-73: Load handler keeps current/pending in sync.
This makes state hydration predictable.


77-83: State model and hasChanges derivation look good.
Clear and minimal.


85-96: Event/action modeling is tidy.
Sealed interfaces keep the action space explicit.

feature/settings/src/commonMain/kotlin/org/mifos/mobile/feature/settings/selectedservices/SelectedServicesScreen.kt (5)

57-73: Service catalog structure is clear.
The model and list are straightforward and readable.


75-96: Screen wiring to state + events looks solid.
The event bridge is clean and concise.


131-149: List rendering is straightforward.
The list composition reads cleanly.


151-179: Checkbox row composition looks good.
Layout and styling choices are consistent with the design system.


182-193: Preview mirrors default state nicely.
Helpful for quick UI checks.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Comment on lines 239 to 246
fun getSelectedServices(): Set<String> {
val stored = settings.getString(SELECTED_SERVICES, "")
return if (stored.isEmpty()) {
DEFAULT_SELECTED_SERVICES
} else {
stored.split(",").toSet()
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Empty set cannot be distinguished from "not yet configured".

If a user explicitly deselects all services, getSelectedServices() returns DEFAULT_SELECTED_SERVICES instead of an empty set. This may not be the intended behavior.

Consider using a sentinel value or separate "configured" flag
     fun getSelectedServices(): Set<String> {
-        val stored = settings.getString(SELECTED_SERVICES, "")
-        return if (stored.isEmpty()) {
+        val stored = settings.getStringOrNull(SELECTED_SERVICES)
+        return if (stored == null) {
             DEFAULT_SELECTED_SERVICES
         } else {
-            stored.split(",").toSet()
+            if (stored.isEmpty()) emptySet() else stored.split(",").toSet()
         }
     }

This allows distinguishing between "never configured" (null → defaults) and "explicitly empty" (empty string → empty set).

📝 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
fun getSelectedServices(): Set<String> {
val stored = settings.getString(SELECTED_SERVICES, "")
return if (stored.isEmpty()) {
DEFAULT_SELECTED_SERVICES
} else {
stored.split(",").toSet()
}
}
fun getSelectedServices(): Set<String> {
val stored = settings.getStringOrNull(SELECTED_SERVICES)
return if (stored == null) {
DEFAULT_SELECTED_SERVICES
} else {
if (stored.isEmpty()) emptySet() else stored.split(",").toSet()
}
}
🤖 Prompt for AI Agents
In
`@core/datastore/src/commonMain/kotlin/org/mifos/mobile/core/datastore/UserPreferencesDataSource.kt`
around lines 239 - 246, getSelectedServices() currently treats an empty stored
string the same as "not configured" because
settings.getString(SELECTED_SERVICES, "") returns "" for both cases; change the
logic to distinguish absent vs explicit empty by either (A) checking presence
via settings.contains(SELECTED_SERVICES) (or using getString with a nullable
default) and returning DEFAULT_SELECTED_SERVICES only when the key is missing,
while returning emptySet() when the stored value is "" — or (B) introduce a
separate boolean flag (e.g., SELECTED_SERVICES_CONFIGURED) that you set when the
user saves selections and check in getSelectedServices(); update the code paths
that write the preference (the setter that calls settings.putString or writes
SELECTED_SERVICES_CONFIGURED) and modify getSelectedServices() accordingly so
DEFAULT_SELECTED_SERVICES is returned only for "not yet configured" and an
explicit empty string yields an empty set.

@biplab1
Copy link
Contributor

biplab1 commented Jan 22, 2026

@Aditya002500 Is the screenshot after the changes in this PR?

@Aditya002500
Copy link
Contributor Author

@Aditya002500 Is the screenshot after the changes in this PR?

No sir but, I have updated now

@biplab1
Copy link
Contributor

biplab1 commented Jan 23, 2026

@Aditya002500 Can you please upload the screen recording before and after flow?

@piyushs-05
Copy link
Contributor

Looks good to me

@biplab1
Copy link
Contributor

biplab1 commented Feb 4, 2026

@Aditya002500 Please fix the spotless issue by running ./ci-prepush.sh and push the changes.

<application
android:name=".AndroidApp"
android:allowBackup="true"
android:allowBackup="false"
Copy link
Contributor

Choose a reason for hiding this comment

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

@Aditya002500 If this is set to false then no backups or restore will be available for this app. Was this the cause of the issue you are trying to fix in this PR?

Ref: https://developer.android.com/reference/android/R.attr.html#allowBackup

Copy link
Contributor Author

@Aditya002500 Aditya002500 Feb 5, 2026

Choose a reason for hiding this comment

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

It didnt fixed it, initially it was guided by the Rajan sir to start with this. And sir I asked some stuff to ai it seems that it is safer to keep it false for a banking app and keep things at server side, kindly let me know if needed to be changed.

@Aditya002500 Aditya002500 requested a review from biplab1 February 5, 2026 13:55
Copy link
Contributor

@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: 1

🤖 Fix all issues with AI agents
In
`@feature/auth/src/commonMain/kotlin/org/mifos/mobile/feature/auth/login/LoginViewModel.kt`:
- Around line 109-122: When handling the DataState.Success branch in
LoginViewModel where you currently early-return on user.clients.isEmpty(),
revoke/cleanup the server session and local credentials before returning: call
the appropriate logout/revoke method on the auth repository (e.g.,
authRepository.logout() or authRepository.revokeToken()) and clear any stored
auth state via userPreferencesRepositoryImpl (e.g., clear saved token/user info)
so no orphaned session remains; place these calls just before the existing
updateState error block’s return inside the is DataState.Success branch that
checks user.clients.isEmpty().
🧹 Nitpick comments (1)
feature/auth/src/commonMain/kotlin/org/mifos/mobile/feature/auth/login/LoginViewModel.kt (1)

123-131: Dead else-branch: user.clients.isNotEmpty() is always true here.

The early return on line 121 guarantees clients is non-empty at this point. The conditional and its fallback to user.userId are now dead code.

Suggested simplification
                 val userData = UserData(
                     userId = user.userId,
                     userName = user.username.orEmpty(),
-                    clientId = if (user.clients.isNotEmpty()) {
-                        user.clients[0]
-                    } else {
-                        user.userId
-                    },
+                    clientId = user.clients[0],
                     isAuthenticated = user.isAuthenticated,

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.

4 participants