Skip to content

feat(pocket): add pocket linked accounts POC#2020

Open
bhuvan-somisetty wants to merge 2 commits into
openMF:developmentfrom
bhuvan-somisetty:poc/pocket-linked-accounts
Open

feat(pocket): add pocket linked accounts POC#2020
bhuvan-somisetty wants to merge 2 commits into
openMF:developmentfrom
bhuvan-somisetty:poc/pocket-linked-accounts

Conversation

@bhuvan-somisetty
Copy link
Copy Markdown

@bhuvan-somisetty bhuvan-somisetty commented May 9, 2026

Proof of concept for the Pocket feature (issue #1997, MW-378). Covers three of the four defined goals: viewing the pocket dashboard, linking accounts, and delinking accounts.

The pocket dashboard shows all linked savings, loan, and share accounts in one view with an aggregated total balance card. A shortcut card on the home screen navigates to it. Linking an account takes an account ID and type; delinking shows a confirmation dialog before removing the account from the list.

The data layer uses an in-memory demo repository so the feature is fully interactive without a running backend. The real PocketRepositoryImpl wired to the Fineract self-service pocket API (GET /self/pockets, POST /self/pockets?command=linkAccounts, POST /self/pockets?command=delinkAccounts) is also included and can be swapped into the DI module once the API is reachable.

Navigation to the individual account detail screen is wired but the destination itself is out of scope for this POC.

Tested manually: dashboard with pre-loaded accounts (savings + loan), link/delink flow, empty state after removing all accounts, and the home screen shortcut.

Closes #1997

Summary by CodeRabbit

  • New Features
    • Added Pocket feature to home screen for managing linked accounts
    • Users can now view total balance across all linked accounts
    • Implemented account linking and unlinking functionality
    • Added dedicated dashboard screen displaying linked accounts with individual balances
    • New account linking workflow with account ID and type selection

Review Change Stack

@bhuvan-somisetty bhuvan-somisetty requested a review from a team May 9, 2026 18:07
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 9, 2026

📝 Walkthrough

Walkthrough

This PR implements the complete Pocket feature, enabling users to view linked financial accounts, link/delink accounts, and access account details through a new dashboard screen. The implementation spans data models, dual repository implementations (demo and API-based), network service definitions, two Compose screens with ViewModels, navigation setup, and home screen integration.

Changes

Pocket Feature Implementation

Layer / File(s) Summary
Data Models & Repository Interface
core/model/src/commonMain/kotlin/org/mifospay/core/model/pocket/Pocket.kt, core/data/src/commonMain/kotlin/org/mifospay/core/data/repository/PocketRepository.kt
Introduces Pocket, PocketAccount, PocketAccountType enum, and API payload models (PocketLinkPayload, PocketDelinkPayload); defines PocketRepository interface with getPocket(), linkAccount(), and delinkAccount() methods returning Flow<DataState<...>>.
Network Layer
core/network/src/commonMain/kotlin/org/mifospay/core/network/services/PocketService.kt, core/network/src/commonMain/kotlin/org/mifospay/core/network/KtorfitClient.kt, core/network/src/commonMain/kotlin/org/mifospay/core/network/SelfServiceApiManager.kt, core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/ApiEndPoints.kt
Defines PocketService with GET/POST endpoints (getPockets, linkAccounts, delinkAccounts); adds pocketApi property to KtorfitClient and SelfServiceApiManager; adds POCKETS endpoint constant.
Repository Implementations
core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/DemoPocketRepository.kt, core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/PocketRepositoryImpl.kt
DemoPocketRepository maintains in-memory pocket state with demo accounts; PocketRepositoryImpl wraps API calls via SelfServiceApiManager; both emit Loading then Success/Error states.
Dependency Injection
core/data/src/commonMain/kotlin/org/mifospay/core/data/di/RepositoryModule.kt, feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/di/PocketModule.kt, cmp-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt, cmp-shared/build.gradle.kts, settings.gradle.kts
Registers PocketRepository with DemoPocketRepository(); creates PocketModule with PocketDashboardViewModel and LinkAccountViewModel; includes module in shared Koin config; adds gradle dependencies.
Build Configuration & Resources
feature/pocket/build.gradle.kts, feature/pocket/src/androidMain/AndroidManifest.xml, feature/pocket/src/commonMain/composeResources/values/strings.xml
Defines feature module build script with plugins and Compose dependencies; empty Android manifest; pocket feature string resources (titles, labels, errors).
Dashboard Screen & ViewModel
feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/dashboard/PocketDashboardScreen.kt, feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/dashboard/PocketDashboardViewModel.kt
PocketDashboardScreen renders loading/empty/error/success states via MifosScaffold; displays total balance and linked account cards with delink dialogs; PocketDashboardViewModel loads pocket data, manages delink flow, and handles navigation.
Link Account Screen & ViewModel
feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/link/LinkAccountScreen.kt, feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/link/LinkAccountViewModel.kt
LinkAccountScreen provides account ID input, account type chip selection, and submit button; LinkAccountViewModel validates numeric input, calls repository linking, and emits LinkSuccess.
Pocket Navigation
feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/navigation/PocketNavigation.kt
Defines POCKET_DASHBOARD_ROUTE and LINK_ACCOUNT_ROUTE constants; provides NavController extension functions to navigate to each route; registers NavGraphBuilder extensions for dashboard and link-account destinations.
Main Navigation Integration
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt
Wires navigateToPocketDashboard callback from homeScreen; registers pocketDashboardScreen and linkAccountScreen destinations with callbacks for back navigation, linking, and account details.
Home Screen Integration
feature/home/src/commonMain/composeResources/values/strings.xml, feature/home/src/commonMain/kotlin/org/mifospay/feature/home/HomeScreen.kt, feature/home/src/commonMain/kotlin/org/mifospay/feature/home/HomeViewModel.kt, feature/home/src/commonMain/kotlin/org/mifospay/feature/home/navigation/HomeNavigation.kt
Adds pocket string resources; inserts PocketShortcutCard into home LazyColumn with wallet icon and title/subtitle; handles PocketClicked action in ViewModel to emit navigation event; extends homeScreen navigation to accept and wire navigateToPocketDashboard callback.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • openMF/mifos-pay#1987: Both PRs register new feature modules and navigation routes in shared app wiring (KoinModules, cmp-shared dependencies, MifosNavHost).

Suggested reviewers

  • niyajali
  • therajanmaurya

Poem

🐰 A pocket full of linked accounts, what joy!
Dashboard, link, delink—no need to deploy,
Home screen shortcuts lead the way,
MVVM flows keep data at bay.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly summarizes the main feature addition: pocket linked accounts proof-of-concept.
Linked Issues check ✅ Passed All four objectives from issue #1997 are implemented or scaffolded: View Dashboard [1], Link Accounts [2], Delink Accounts [3], and Account Details navigation [4] are present.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the Pocket feature implementation. Modifications to home screen, navigation, shared modules, and data layer are necessary for feature integration and follow the PR's declared objectives.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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
Copy Markdown
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: 6

🧹 Nitpick comments (1)
core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/DemoPocketRepository.kt (1)

84-85: ⚡ Quick win

Consider logging or throwing on invalid account type.

The toPocketAccountType() function silently falls back to SAVINGS when it encounters an unrecognized account type. During development and testing, this silent fallback could hide bugs (e.g., typos in account type strings).

🔍 Suggested improvement

For a POC/demo implementation, consider logging the fallback or throwing an exception to catch issues early:

 private fun String.toPocketAccountType(): PocketAccountType =
-    PocketAccountType.entries.firstOrNull { it.name == uppercase() } ?: PocketAccountType.SAVINGS
+    PocketAccountType.entries.firstOrNull { it.name == uppercase() }
+        ?: run {
+            println("Warning: Unknown account type '$this', defaulting to SAVINGS")
+            PocketAccountType.SAVINGS
+        }

Or throw an exception:

 private fun String.toPocketAccountType(): PocketAccountType =
-    PocketAccountType.entries.firstOrNull { it.name == uppercase() } ?: PocketAccountType.SAVINGS
+    PocketAccountType.entries.firstOrNull { it.name == uppercase() }
+        ?: error("Invalid account type: $this")
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/DemoPocketRepository.kt`
around lines 84 - 85, The helper toPocketAccountType() silently maps unknown
strings to PocketAccountType.SAVINGS which can hide bugs; update to detect when
firstOrNull returns null and either (a) log a warning with the original String
and the fallback (include the function name toPocketAccountType and the constant
PocketAccountType.SAVINGS in the message) or (b) throw an explicit exception
(e.g., IllegalArgumentException) with the invalid value so failures are visible
during dev/testing—choose logging for leniency or throwing for strict failure,
and implement the chosen behavior inside toPocketAccountType().
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt`:
- Line 181: The startDestination is currently set to POCKET_DASHBOARD_ROUTE in
MifosNavHost (the assignment of startDestination = POCKET_DASHBOARD_ROUTE),
which makes Pocket Dashboard the app entry point; change this assignment to the
Home route constant (use the HOME_ROUTE / equivalent Home route identifier used
elsewhere in the navigation graph) so the app boots into Home and preserves the
home-first flow and the “home shortcut to Pocket” behavior; update the
startDestination assignment only and verify related navigation graph code in
MifosNavHost and any references to POCKET_DASHBOARD_ROUTE remain unchanged
elsewhere.
- Around line 578-581: The onLinkSuccess callback in the linkAccountScreen
currently calls navController.navigateToPocketDashboard() while navigateBack is
set to navController::navigateUp, causing duplicate Pocket Dashboard entries;
remove the explicit navController.navigateToPocketDashboard() call from the
onLinkSuccess lambda (or replace it with a no-op) so that success handling
relies solely on navigateBack/navController::navigateUp to return to the
dashboard and avoid pushing the dashboard twice.

In
`@core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/PocketRepositoryImpl.kt`:
- Line 35: The Flow.catch blocks in PocketRepositoryImpl that currently do
`.catch { emit(DataState.Error(it)) }` must preserve cooperative cancellation:
change each catch handler to rethrow CancellationException (e.g., if (it is
CancellationException) throw it) and only emit DataState.Error for
non-cancellation exceptions; update all three catch sites in
PocketRepositoryImpl (the catch wrapping Flow emissions that emit
DataState.Error) and ensure kotlin.coroutines.cancellation.CancellationException
is referenced/imported so cancellation is not swallowed.

In
`@core/network/src/commonMain/kotlin/org/mifospay/core/network/services/PocketService.kt`:
- Around line 23-24: Rename the service method getPockets() to getPocket() to
match its return type Flow<Pocket> and the repo interface; update the function
declaration in PocketService (replace fun getPockets(): Flow<Pocket> with fun
getPocket(): Flow<Pocket>) and then update any callers or references to use
getPocket() so signatures remain consistent with the domain model and
repository.

In
`@feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/dashboard/PocketDashboardScreen.kt`:
- Around line 75-97: snackbarHostState is created and used but never passed into
the UI, so MifosScaffold never shows snackbars; update PocketDashboardScreen to
forward snackbarHostState into PocketDashboardScreenContent (add parameter
passing of snackbarHostState) and then inside PocketDashboardScreenContent
supply it to MifosScaffold via snackbarHost = { SnackbarHost(snackbarHostState)
}; ensure the existing LaunchedEffect(state.snackbarMessage) remains to call
snackbarHostState.showSnackbar(...) and adjust function signatures
(PocketDashboardScreenContent) accordingly so the symbol names
snackbarHostState, PocketDashboardScreenContent, MifosScaffold, LaunchedEffect
and state.snackbarMessage are wired end-to-end.

In
`@feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/dashboard/PocketDashboardViewModel.kt`:
- Around line 75-79: The snackbarMessage is left set on error in
PocketDashboardViewModel (mutableStateFlow update in the DataState.Error branch)
so repeated identical errors won't retrigger PocketDashboardScreen's
LaunchedEffect; add a clear action and handler: add
PocketDashboardAction.ClearSnackbar, handle it in handleAction to set
snackbarMessage = null on mutableStateFlow, and after showing the snackbar in
PocketDashboardScreen's LaunchedEffect call
viewModel.trySendAction(PocketDashboardAction.ClearSnackbar) so the message is
cleared and subsequent identical errors will retrigger the effect.

---

Nitpick comments:
In
`@core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/DemoPocketRepository.kt`:
- Around line 84-85: The helper toPocketAccountType() silently maps unknown
strings to PocketAccountType.SAVINGS which can hide bugs; update to detect when
firstOrNull returns null and either (a) log a warning with the original String
and the fallback (include the function name toPocketAccountType and the constant
PocketAccountType.SAVINGS in the message) or (b) throw an explicit exception
(e.g., IllegalArgumentException) with the invalid value so failures are visible
during dev/testing—choose logging for leniency or throwing for strict failure,
and implement the chosen behavior inside toPocketAccountType().
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c9c8c911-99f4-4d01-8dbf-d6425cd9454d

📥 Commits

Reviewing files that changed from the base of the PR and between d399e0a and fe7eb93.

📒 Files selected for processing (27)
  • cmp-shared/build.gradle.kts
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt
  • core/data/src/commonMain/kotlin/org/mifospay/core/data/di/RepositoryModule.kt
  • core/data/src/commonMain/kotlin/org/mifospay/core/data/repository/PocketRepository.kt
  • core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/DemoPocketRepository.kt
  • core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/PocketRepositoryImpl.kt
  • core/model/src/commonMain/kotlin/org/mifospay/core/model/pocket/Pocket.kt
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/KtorfitClient.kt
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/SelfServiceApiManager.kt
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/services/PocketService.kt
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/services/ServiceExtensions.kt
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/ApiEndPoints.kt
  • feature/home/src/commonMain/composeResources/values/strings.xml
  • feature/home/src/commonMain/kotlin/org/mifospay/feature/home/HomeScreen.kt
  • feature/home/src/commonMain/kotlin/org/mifospay/feature/home/HomeViewModel.kt
  • feature/home/src/commonMain/kotlin/org/mifospay/feature/home/navigation/HomeNavigation.kt
  • feature/pocket/build.gradle.kts
  • feature/pocket/src/androidMain/AndroidManifest.xml
  • feature/pocket/src/commonMain/composeResources/values/strings.xml
  • feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/dashboard/PocketDashboardScreen.kt
  • feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/dashboard/PocketDashboardViewModel.kt
  • feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/di/PocketModule.kt
  • feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/link/LinkAccountScreen.kt
  • feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/link/LinkAccountViewModel.kt
  • feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/navigation/PocketNavigation.kt
  • settings.gradle.kts

Comment thread cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt Outdated
Comment on lines +578 to +581
linkAccountScreen(
navigateBack = navController::navigateUp,
onLinkSuccess = { navController.navigateToPocketDashboard() },
)
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid pushing Pocket Dashboard twice after link success.

This callback navigates to dashboard even though success handling already navigates back from the link screen, which can leave duplicate dashboard entries in back stack.

💡 Suggested fix
         linkAccountScreen(
             navigateBack = navController::navigateUp,
-            onLinkSuccess = { navController.navigateToPocketDashboard() },
+            onLinkSuccess = { /* no-op: LinkAccountScreen already navigates back on success */ },
         )
📝 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
linkAccountScreen(
navigateBack = navController::navigateUp,
onLinkSuccess = { navController.navigateToPocketDashboard() },
)
linkAccountScreen(
navigateBack = navController::navigateUp,
onLinkSuccess = { /* no-op: LinkAccountScreen already navigates back on success */ },
)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt`
around lines 578 - 581, The onLinkSuccess callback in the linkAccountScreen
currently calls navController.navigateToPocketDashboard() while navigateBack is
set to navController::navigateUp, causing duplicate Pocket Dashboard entries;
remove the explicit navController.navigateToPocketDashboard() call from the
onLinkSuccess lambda (or replace it with a no-op) so that success handling
relies solely on navigateBack/navController::navigateUp to return to the
dashboard and avoid pushing the dashboard twice.

apiManager.pocketApi.getPockets()
.map<Pocket, DataState<Pocket>> { DataState.Success(it) }
.onStart { emit(DataState.Loading) }
.catch { emit(DataState.Error(it)) }
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify broad exception catches and absence/presence of cancellation handling.
rg -nP --type=kotlin 'catch\s*\(\s*e:\s*Exception\s*\)' core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/PocketRepositoryImpl.kt
rg -n --type=kotlin 'CancellationException' core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/PocketRepositoryImpl.kt

Repository: openMF/mifos-pay

Length of output: 132


🏁 Script executed:

cat -n core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/PocketRepositoryImpl.kt

Repository: openMF/mifos-pay

Length of output: 2625


Rethrow CancellationException to preserve cooperative cancellation.

Line 35 uses Flow.catch which catches all exceptions including CancellationException, converting cancellation into DataState.Error. This breaks cancellation semantics. The .catch block at line 35 must rethrow CancellationException.

Similarly, lines 43 and 53 should explicitly rethrow CancellationException for consistency and defense against any cancellation exceptions that may propagate.

Suggested fix
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@
-            .catch { emit(DataState.Error(it)) }
+            .catch {
+                if (it is CancellationException) throw it
+                emit(DataState.Error(it))
+            }
@@
-        } catch (e: Exception) {
+        } catch (e: Exception) {
+            if (e is CancellationException) throw e
             emit(DataState.Error(e))
         }
@@
-        } catch (e: Exception) {
+        } catch (e: Exception) {
+            if (e is CancellationException) throw e
             emit(DataState.Error(e))
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/data/src/commonMain/kotlin/org/mifospay/core/data/repositoryImpl/PocketRepositoryImpl.kt`
at line 35, The Flow.catch blocks in PocketRepositoryImpl that currently do
`.catch { emit(DataState.Error(it)) }` must preserve cooperative cancellation:
change each catch handler to rethrow CancellationException (e.g., if (it is
CancellationException) throw it) and only emit DataState.Error for
non-cancellation exceptions; update all three catch sites in
PocketRepositoryImpl (the catch wrapping Flow emissions that emit
DataState.Error) and ensure kotlin.coroutines.cancellation.CancellationException
is referenced/imported so cancellation is not swallowed.

Comment on lines +23 to +24
@GET(ApiEndPoints.POCKETS)
fun getPockets(): Flow<Pocket>
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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix method name to match return type.

The method name getPockets() is plural, but it returns Flow<Pocket> (singular). This creates a naming inconsistency. Based on the repository interface using getPocket() (singular) and the domain model structure (a single Pocket contains multiple linkedAccounts), the service method should be named getPocket() to match.

📝 Proposed fix
 `@GET`(ApiEndPoints.POCKETS)
-fun getPockets(): Flow<Pocket>
+fun getPocket(): Flow<Pocket>
📝 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
@GET(ApiEndPoints.POCKETS)
fun getPockets(): Flow<Pocket>
`@GET`(ApiEndPoints.POCKETS)
fun getPocket(): Flow<Pocket>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@core/network/src/commonMain/kotlin/org/mifospay/core/network/services/PocketService.kt`
around lines 23 - 24, Rename the service method getPockets() to getPocket() to
match its return type Flow<Pocket> and the repo interface; update the function
declaration in PocketService (replace fun getPockets(): Flow<Pocket> with fun
getPocket(): Flow<Pocket>) and then update any callers or references to use
getPocket() so signatures remain consistent with the domain model and
repository.

Comment on lines +75 to +97
val snackbarHostState = remember { SnackbarHostState() }

org.mifospay.core.ui.utils.EventsEffect(viewModel) { event ->
when (event) {
PocketDashboardEvent.NavigateBack -> navigateBack()
PocketDashboardEvent.NavigateToLinkAccount -> navigateToLinkAccount()
is PocketDashboardEvent.NavigateToAccountDetails -> navigateToAccountDetails(event.accountId)
}
}

LaunchedEffect(state.snackbarMessage) {
state.snackbarMessage?.let { snackbarHostState.showSnackbar(it) }
}

PocketDashboardScreenContent(
state = state,
onNavigateBack = { viewModel.trySendAction(PocketDashboardAction.NavigateBack) },
onLinkAccount = { viewModel.trySendAction(PocketDashboardAction.NavigateToLinkAccount) },
onDelinkAccount = { viewModel.trySendAction(PocketDashboardAction.DelinkAccount(it)) },
onViewDetails = { viewModel.trySendAction(PocketDashboardAction.NavigateToAccountDetails(it)) },
onRetry = { viewModel.trySendAction(PocketDashboardAction.Retry) },
modifier = modifier,
)
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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check MifosScaffold signature for snackbarHost parameter

ast-grep --pattern $'@Composable
fun MifosScaffold($$$) {
  $$$
}'

Repository: openMF/mifos-pay

Length of output: 42


🏁 Script executed:

# Find MifosScaffold definition
fd -t f -i "*scaffold*" -e kt -e compose | head -20

Repository: openMF/mifos-pay

Length of output: 350


🏁 Script executed:

# Search for MifosScaffold in the codebase
rg "fun MifosScaffold" -t kt -A 10

Repository: openMF/mifos-pay

Length of output: 87


🏁 Script executed:

# Check the full PocketDashboardScreen file
cat -n feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/dashboard/PocketDashboardScreen.kt | head -120

Repository: openMF/mifos-pay

Length of output: 6417


🏁 Script executed:

# Search for MifosScaffold definition
rg "fun MifosScaffold" --type-list | head

Repository: openMF/mifos-pay

Length of output: 450


🏁 Script executed:

# Try broader search for MifosScaffold
rg "MifosScaffold" -l | grep -i scaffold

Repository: openMF/mifos-pay

Length of output: 158


🏁 Script executed:

# Search in core designsystem
fd MifosScaffold -e kt

Repository: openMF/mifos-pay

Length of output: 158


🏁 Script executed:

cat -n core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt | head -100

Repository: openMF/mifos-pay

Length of output: 4873


🏁 Script executed:

sed -n '100,125p' feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/dashboard/PocketDashboardScreen.kt

Repository: openMF/mifos-pay

Length of output: 964


Wire the snackbar state to MifosScaffold to display error messages.

The snackbarHostState is created and messages are queued via LaunchedEffect, but the state is never passed to MifosScaffold. Since MifosScaffold accepts a snackbarHost parameter (defined in core/designsystem/src/commonMain/kotlin/org/mifospay/core/designsystem/component/MifosScaffold.kt), error messages—including delink failures—won't display to users without wiring this parameter.

Pass snackbarHostState from PocketDashboardScreen to PocketDashboardScreenContent, then add snackbarHost = { SnackbarHost(snackbarHostState) } to the MifosScaffold call.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/dashboard/PocketDashboardScreen.kt`
around lines 75 - 97, snackbarHostState is created and used but never passed
into the UI, so MifosScaffold never shows snackbars; update
PocketDashboardScreen to forward snackbarHostState into
PocketDashboardScreenContent (add parameter passing of snackbarHostState) and
then inside PocketDashboardScreenContent supply it to MifosScaffold via
snackbarHost = { SnackbarHost(snackbarHostState) }; ensure the existing
LaunchedEffect(state.snackbarMessage) remains to call
snackbarHostState.showSnackbar(...) and adjust function signatures
(PocketDashboardScreenContent) accordingly so the symbol names
snackbarHostState, PocketDashboardScreenContent, MifosScaffold, LaunchedEffect
and state.snackbarMessage are wired end-to-end.

Comment on lines +75 to +79
is DataState.Error -> mutableStateFlow.update {
it.copy(
isDelinking = false,
snackbarMessage = result.exception.message ?: "Failed to remove account",
)
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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Clear snackbar message after displaying to allow repeated errors.

The snackbarMessage is set on error but never cleared. Because the LaunchedEffect in PocketDashboardScreen (line 85) is keyed on state.snackbarMessage, if two consecutive delink operations fail with the same error text, the second error won't trigger a new snackbar because the key hasn't changed.

🔧 Proposed fix to clear the message after setting

Option 1: Add a timestamp to make each error unique:

                     is DataState.Error -> mutableStateFlow.update {
                         it.copy(
                             isDelinking = false,
-                            snackbarMessage = result.exception.message ?: "Failed to remove account",
+                            snackbarMessage = "${result.exception.message ?: "Failed to remove account"} [${System.currentTimeMillis()}]",
                         )
                     }

Option 2: Add a separate action to clear the message (cleaner approach):

Add to PocketDashboardAction:

data object ClearSnackbar : PocketDashboardAction

Handle in handleAction:

is PocketDashboardAction.ClearSnackbar -> mutableStateFlow.update { it.copy(snackbarMessage = null) }

Then in the Screen, after showing the snackbar:

LaunchedEffect(state.snackbarMessage) {
    state.snackbarMessage?.let { 
        snackbarHostState.showSnackbar(it)
        viewModel.trySendAction(PocketDashboardAction.ClearSnackbar)
    }
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@feature/pocket/src/commonMain/kotlin/org/mifospay/feature/pocket/dashboard/PocketDashboardViewModel.kt`
around lines 75 - 79, The snackbarMessage is left set on error in
PocketDashboardViewModel (mutableStateFlow update in the DataState.Error branch)
so repeated identical errors won't retrigger PocketDashboardScreen's
LaunchedEffect; add a clear action and handler: add
PocketDashboardAction.ClearSnackbar, handle it in handleAction to set
snackbarMessage = null on mutableStateFlow, and after showing the snackbar in
PocketDashboardScreen's LaunchedEffect call
viewModel.trySendAction(PocketDashboardAction.ClearSnackbar) so the message is
cleared and subsequent identical errors will retrigger the effect.

Signed-off-by: bhuvan-somisetty <somisettybhuvan5@gmail.com>
@bhuvan-somisetty bhuvan-somisetty changed the title [POC] MW-378: Pocket Linked Accounts Management feat(pocket): add pocket linked accounts POC May 11, 2026
Copy link
Copy Markdown
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.

♻️ Duplicate comments (1)
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt (1)

577-580: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Avoid pushing Pocket Dashboard twice on link success.

navigateBack already handles returning from the link screen; the extra navigate at Line 579 can duplicate dashboard in the back stack.

💡 Minimal fix
         linkAccountScreen(
             navigateBack = navController::navigateUp,
-            onLinkSuccess = { navController.navigateToPocketDashboard() },
+            onLinkSuccess = { navController.navigateUp() },
         )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt`
around lines 577 - 580, The linkAccountScreen call is causing the Pocket
Dashboard to be pushed twice because navigateBack (navController::navigateUp)
already returns from the link screen; remove the extra navigation in the
onLinkSuccess callback (the navController.navigateToPocketDashboard()
invocation) or change onLinkSuccess to a no-op so only navigateBack is used;
update the linkAccountScreen invocation (referencing linkAccountScreen,
navigateBack, onLinkSuccess, and navController.navigateToPocketDashboard)
accordingly to avoid duplicating the dashboard in the back stack.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In
`@cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt`:
- Around line 577-580: The linkAccountScreen call is causing the Pocket
Dashboard to be pushed twice because navigateBack (navController::navigateUp)
already returns from the link screen; remove the extra navigation in the
onLinkSuccess callback (the navController.navigateToPocketDashboard()
invocation) or change onLinkSuccess to a no-op so only navigateBack is used;
update the linkAccountScreen invocation (referencing linkAccountScreen,
navigateBack, onLinkSuccess, and navController.navigateToPocketDashboard)
accordingly to avoid duplicating the dashboard in the back stack.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 72c5546b-263f-4a18-ae40-9f2e73488817

📥 Commits

Reviewing files that changed from the base of the PR and between fe7eb93 and f6efcc2.

📒 Files selected for processing (3)
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/navigation/MifosNavHost.kt
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/KtorfitClient.kt
🚧 Files skipped from review as they are similar to previous changes (2)
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/KtorfitClient.kt
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt

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.

[DMP 2026]: [Flow] Pocket - Linked Accounts Management - JIRA MR-16

1 participant