Skip to content

fix(client): Migrate Paging to commonMain from androidMain - #2638

Merged
therajanmaurya merged 3 commits into
openMF:developmentfrom
techsavvy185:pagingMigrateClient
Mar 10, 2026
Merged

fix(client): Migrate Paging to commonMain from androidMain#2638
therajanmaurya merged 3 commits into
openMF:developmentfrom
techsavvy185:pagingMigrateClient

Conversation

@techsavvy185

@techsavvy185 techsavvy185 commented Mar 4, 2026

Copy link
Copy Markdown
Member

Fixes - Jira-#616
Android

Screen_recording_20260309_233957.webm

iOS

Screen.Recording.2026-03-06.at.5.53.06.PM.mov

Desktop

Screen.Recording.2026-03-09.at.11.47.40.PM.mov

Please make sure these boxes are checked before submitting your pull request - thanks!

  • Run the static analysis check ./gradlew check or ci-prepush.sh to make sure you didn't break anything

  • If you have multiple commits please combine them into one commit by squashing them.

Summary by CodeRabbit

  • Refactor
    • Unified client list, client charges, and upcoming charges UI into a single cross-platform implementation for consistent behavior.
  • Bug Fixes / Improvements
    • Paging UI dependency moved to shared code to improve paging behavior across platforms.
  • Chores
    • Removed empty/stub platform-specific UI placeholders.

@techsavvy185
techsavvy185 requested a review from a team March 4, 2026 20:29
@coderabbitai

coderabbitai Bot commented Mar 4, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR moves androidx.paging.compose to commonMain and consolidates multiple platform-specific UI stubs into concrete implementations in commonMain for client lists, client charges, and upcoming charges, removing platform-specific files across android, desktop, js, native, and wasmJs.

Changes

Cohort / File(s) Summary
Build Configuration
feature/client/build.gradle.kts
Moved androidx.paging.compose dependency from androidMain to commonMain.
ShowClientCharge (common + removals)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/charges/ShowClientCharge.kt, feature/client/src/androidMain/.../ShowClientCharge.android.kt, feature/client/src/desktopMain/.../ShowClientCharge.desktop.kt, feature/client/src/jsMain/.../ShowClientCharge.js.kt, feature/client/src/nativeMain/.../ClientChargesScreen.native.kt, feature/client/src/wasmJsMain/.../ShowClientCharge.wasmJs.kt
Added concrete ShowClientCharge in commonMain (collects PagingData<ChargesEntity>, handles LoadState, renders bottom sheet and item actions). Removed platform-specific stub/actual files.
ClientList (selection & paging)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.kt, feature/client/src/androidMain/.../ClientListScreen.android.kt, feature/client/src/desktopMain/.../ClientListScreen.desktop.kt, feature/client/src/jsMain/.../ClientListScreen.js.kt, feature/client/src/nativeMain/.../ClientListScreen.native.kt, feature/client/src/wasmJsMain/.../ClientListScreen.wasmJs.kt
Replaced expect with concrete internal LazyColumnForClientListApi in commonMain (collects paging, selection mode, combinedClickable, load-state handling). Removed platform-specific actual/stub files.
ClientsList (sorted, images, offices)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt, feature/client/src/androidMain/.../ClientListScreen.android.kt, feature/client/src/desktopMain/.../ClientListScreen.desktop.kt, feature/client/src/jsMain/.../ClientListScreen.js.kt, feature/client/src/nativeMain/.../ClientListScreen.native.kt, feature/client/src/wasmJsMain/.../ClientListScreen.wasmJs.kt
Implemented concrete clients-list in commonMain with pull-to-refresh, optional sorting, image prefetching, office extraction, and append-state UI; removed platform-specific stubs.
ClientUpcomingCharges (paging & states)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.kt, feature/client/src/androidMain/.../ClientUpcomingChargesScreen.android.kt, feature/client/src/desktopMain/.../ClientUpcomingChargesScreen.desktop.kt, feature/client/src/jsMain/.../ClientUpcomingChargesScreen.js.kt, feature/client/src/nativeMain/.../ClientUpcomingChargesScreen.native.kt, feature/client/src/wasmJsMain/.../ClientUpcomingChargesScreen.wasmJs.kt
Replaced expect with concrete ChargesListContent in commonMain (collects paging, handles refresh/append load states, empty and end-of-list UI). Removed platform-specific actual/stub files.

Sequence Diagram(s)

sequenceDiagram
    participant UI as Common UI (Compose)
    participant Paging as PagingSource/Flow
    participant Loader as LoadState Handler
    participant Actions as onAction Handler

    UI->>Paging: collectAsLazyPagingItems(flow)
    Paging-->>UI: emit PagingData items
    UI->>Loader: observe items.loadState
    Loader-->>UI: LoadState (Loading / Error / NotLoading)
    UI-->>UI: render Loading/Error/List based on LoadState
    UI->>Actions: onAction(Delete|Edit|Create|Dismiss)
    Actions-->>UI: update state / trigger navigation
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Poem

🐰
Platforms once scattered, now hop as one,
Paging flows gathered beneath a shared sun,
Stubs tuck their tails, real views take the stage,
Code bounces forward, unified on the page.

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly and specifically describes the main change: migrating Paging implementation from androidMain to commonMain, which aligns with the substantial refactoring shown across multiple files moving platform-specific implementations to the common source set.

✏️ 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

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

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 (6)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt (1)

368-373: Potential excessive calls to onUpdateOffices during pagination.

itemSnapshotList.items changes frequently as the user scrolls and more items are loaded. Since offices is derived from these items and used as the LaunchedEffect key, onUpdateOffices will be called repeatedly. Consider using derivedStateOf with a more stable comparison or debouncing the updates.

♻️ Proposed optimization
     val items = clientPagingList.itemSnapshotList.items
     if (items.isNotEmpty()) {
-        val offices = items.map { it.officeName }
-            .distinct()
-        LaunchedEffect(offices) { onUpdateOffices(offices) }
+        val offices = remember(items) {
+            items.map { it.officeName }.distinct()
+        }
+        LaunchedEffect(offices.hashCode()) { onUpdateOffices(offices) }
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt`
around lines 368 - 373, The current LaunchedEffect keyed on a freshly computed
offices list causes onUpdateOffices to run too often; compute a stable derived
state for offices (e.g., wrap items.map { it.officeName }.distinct() in remember
{ derivedStateOf { ... } } or use derivedStateOf with a Set for stable
equality), then key the LaunchedEffect on that derived state's value
(offices.value) so onUpdateOffices is only invoked when the distinct office set
actually changes; update references in ClientListScreen.kt to use the
derivedStateOf result and call onUpdateOffices(offices.value).
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.kt (1)

344-346: Inconsistent theme usage: use KptTheme instead of MaterialTheme.

This code uses MaterialTheme.colorScheme while the rest of the codebase uses KptTheme.colorScheme. The same issue appears on lines 388, 397, 401, and 441 where MaterialTheme.typography is used instead of KptTheme.typography.

🎨 Proposed fix for consistent theming
-                val surfaceColor = MaterialTheme.colorScheme.surface
-                val outlineColor = MaterialTheme.colorScheme.outline
+                val surfaceColor = KptTheme.colorScheme.surface
+                val outlineColor = KptTheme.colorScheme.outline

And for the border (line 388):

-                                .border(1.dp, MaterialTheme.colorScheme.outline, CircleShape),
+                                .border(1.dp, KptTheme.colorScheme.outline, CircleShape),

And for typography (lines 397, 401, 441):

-                                style = MaterialTheme.typography.bodyLarge,
+                                style = KptTheme.typography.bodyLarge,
-                                style = MaterialTheme.typography.bodyMedium,
+                                style = KptTheme.typography.bodyMedium,

Based on learnings: "In Kotlin files under the android-client module, replace MaterialTheme references with the established KptTheme import".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.kt`
around lines 344 - 346, Replace all uses of MaterialTheme with the project's
KptTheme in this file: change the color references that set surfaceColor,
outlineColor and cardColor (currently using MaterialTheme.colorScheme) to use
KptTheme.colorScheme, and replace all MaterialTheme.typography usages with
KptTheme.typography so theming is consistent across ClientListScreen (update the
declarations for surfaceColor, outlineColor, cardColor and the composables that
reference MaterialTheme.typography).
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.kt (4)

292-292: Use MifosTypography for consistency with the rest of the file.

Lines 166 and 171 use MifosTypography for text styling, but this line uses MaterialTheme.typography. Based on learnings, prefer established theme imports (KptTheme or MifosTypography) over MaterialTheme for consistency.

♻️ Suggested fix
-                            style = MaterialTheme.typography.bodyMedium,
+                            style = MifosTypography.bodyMedium,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.kt`
at line 292, The text style usage at the call site using
MaterialTheme.typography.bodyMedium should be replaced with the project-specific
typography to match the rest of the file: change the style reference to
MifosTypography.bodyMedium (or the corresponding MifosTypography token used in
lines 166/171) inside the ClientUpcomingChargesScreen composable so styling is
consistent with other text components.

270-270: Remove unnecessary fully qualified import.

stringResource is already imported at line 61. The fully qualified reference here is inconsistent with the rest of the file.

♻️ Suggested fix
-                    MifosSweetError(message = org.jetbrains.compose.resources.stringResource(Res.string.client_upcoming_charges_failed_message)) {
+                    MifosSweetError(message = stringResource(Res.string.client_upcoming_charges_failed_message)) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.kt`
at line 270, Remove the unnecessary fully-qualified call to
org.jetbrains.compose.resources.stringResource inside the MifosSweetError
invocation and replace it with the already imported stringResource to match the
file's import style; update the call in the MifosSweetError(...) where the
message is Res.string.client_upcoming_charges_failed_message so it uses
stringResource(Res.string.client_upcoming_charges_failed_message) rather than
org.jetbrains.compose.resources.stringResource(...).

239-244: Address or track the TODO comment.

The TODO indicates uncertainty about the "due" calculation logic. If this is a known limitation, consider creating an issue to track it; otherwise, verify the calculation with domain requirements.

Would you like me to open a new issue to track this TODO?

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.kt`
around lines 239 - 244, The TODO about the "due" calculation in
ClientUpcomingChargesScreen (where due is computed from charge.amount and
charge.amountPaid) must be resolved: either confirm the business rule with
product/domain owners and, if correct, remove the TODO and add a short
clarifying comment stating the rule (e.g., due = amount - amountPaid) or, if
uncertain/known limitation, create a tracked issue in the repo referencing
ClientUpcomingChargesScreen and the due calculation, replace the TODO with a
one-line comment linking to that issue ID, and include expected behavior to
verify later.

222-226: Consider wrapping LazyColumn in an else block to avoid rendering both empty card and list.

When refresh completes with zero items, MifosEmptyCard() is shown but the LazyColumn below still renders (albeit empty). This could cause minor layout issues.

♻️ Suggested refactor
-    if (chargesPagingList.loadState.refresh is LoadState.NotLoading && chargesPagingList.itemCount == 0) {
-        MifosEmptyCard()
-    }
-
-    LazyColumn {
+    if (chargesPagingList.loadState.refresh is LoadState.NotLoading && chargesPagingList.itemCount == 0) {
+        MifosEmptyCard()
+    } else {
+        LazyColumn {
             // ... existing content
+        }
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.kt`
around lines 222 - 226, The current code calls MifosEmptyCard() when
chargesPagingList.loadState.refresh is NotLoading and itemCount == 0 but still
proceeds to render LazyColumn; change the control flow so LazyColumn is rendered
only when there are items (i.e. use an else branch) — locate the conditional
using chargesPagingList.loadState.refresh and itemCount and wrap the LazyColumn
block in the corresponding else so that MifosEmptyCard() and LazyColumn are
mutually exclusive.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/charges/ShowClientCharge.kt`:
- Around line 89-92: The code is rendering literal "null" by calling .toString()
on nullable fields; update ShowClientCharge to safely handle nullable values:
replace usages of charge.chargeCalculationType?.value.toString() and
charge.amount.toString() with null-safe formatting (e.g. use the safe-call +
elvis pattern or conditional formatting) so that when
charge.chargeCalculationType or charge.amount is null you return a sensible
default (empty string or formatted zero) instead of "null"; locate these
expressions on the Charge rendering logic in ShowClientCharge and change them to
use charge.chargeCalculationType?.value ?: "" and charge.amount?.let{ /* format
*/ } ?: "" (or your preferred default).

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.kt`:
- Around line 338-341: The key lambda in the items call currently falls back to
the list index (key = { index -> clientPagingList[index]?.id ?: index }), which
can break recomposition when items shift; update the key to avoid using the raw
index as a fallback by either guaranteeing a non-null stable id on each client
or generating a stable surrogate key from the client object (e.g., a persistent
tempId, a stable composite of immutable fields, or the item's hashCode) inside
the key lambda, or skip rendering entries with null id until they resolve;
change the key logic around clientPagingList and its id access so keys remain
stable when items are inserted/removed.
- Around line 417-422: In the LoadState.Error branch (where MifosSweetError is
shown) replace the call to failedRefresh() with a call to
clientPagingList.retry() so that only the failed append page is retried instead
of refreshing the whole list; locate the LoadState.Error handling around
MifosSweetError in ClientListScreen.kt and swap failedRefresh() for
clientPagingList.retry() to perform a targeted retry of the append operation.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt`:
- Around line 387-419: The sorted branch currently renders a sorted snapshot
(using sort, clientPagingList.itemSnapshotList.items, sortedItems, LazyColumn
and items { client -> ... }) but omits append load-state UI (loading spinner,
error retry, end-of-list message) that exists for the unsorted branch; extract
the append-state rendering logic that inspects clientPagingList.loadState.append
(and shows loading, error with retry, or "no more clients") into a small
reusable composable or helper (e.g., renderAppendState or AppendStateItems) that
accepts the PagingData source (clientPagingList), the images map, and
fetchImage/onClientSelect handlers, then invoke that helper inside both the
sorted branch (after items iteration over sortedItems) and the unsorted branch
so additional page loading, errors, and "no more clients" are displayed
consistently.
- Around line 424-427: The key fallback is unsafe: stop using index as a
fallback key in the items { ... key = { ... } } lambda; instead, fetch the item
once (val item = clientPagingList[index]) and only use item.id as the key,
skipping/returning early when the item is null so you never fall back to the
unstable index; update the items block to compute the item, use item.id in the
key lambda (or guard render with if (item == null) return@items) to ensure
stable, unique keys come only from the entity id (referencing clientPagingList
and the items/key lambda).

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.kt`:
- Around line 245-247: In ClientUpcomingChargesScreen.kt the fields `paid`,
`waived`, and `outstanding` are calling `.toString()` on nullable values
(`charge.amountPaid`, `charge.amountWaived`, `charge.amountOutstanding`) which
will render the literal "null"; change each to handle nulls using a safe call +
elvis (e.g., `charge.amountPaid?.toString() ?: ""` or a default like `"0"`) so
the UI shows a sensible default instead of "null" for `paid`, `waived`, and
`outstanding`.

---

Nitpick comments:
In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.kt`:
- Around line 344-346: Replace all uses of MaterialTheme with the project's
KptTheme in this file: change the color references that set surfaceColor,
outlineColor and cardColor (currently using MaterialTheme.colorScheme) to use
KptTheme.colorScheme, and replace all MaterialTheme.typography usages with
KptTheme.typography so theming is consistent across ClientListScreen (update the
declarations for surfaceColor, outlineColor, cardColor and the composables that
reference MaterialTheme.typography).

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt`:
- Around line 368-373: The current LaunchedEffect keyed on a freshly computed
offices list causes onUpdateOffices to run too often; compute a stable derived
state for offices (e.g., wrap items.map { it.officeName }.distinct() in remember
{ derivedStateOf { ... } } or use derivedStateOf with a Set for stable
equality), then key the LaunchedEffect on that derived state's value
(offices.value) so onUpdateOffices is only invoked when the distinct office set
actually changes; update references in ClientListScreen.kt to use the
derivedStateOf result and call onUpdateOffices(offices.value).

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.kt`:
- Line 292: The text style usage at the call site using
MaterialTheme.typography.bodyMedium should be replaced with the project-specific
typography to match the rest of the file: change the style reference to
MifosTypography.bodyMedium (or the corresponding MifosTypography token used in
lines 166/171) inside the ClientUpcomingChargesScreen composable so styling is
consistent with other text components.
- Line 270: Remove the unnecessary fully-qualified call to
org.jetbrains.compose.resources.stringResource inside the MifosSweetError
invocation and replace it with the already imported stringResource to match the
file's import style; update the call in the MifosSweetError(...) where the
message is Res.string.client_upcoming_charges_failed_message so it uses
stringResource(Res.string.client_upcoming_charges_failed_message) rather than
org.jetbrains.compose.resources.stringResource(...).
- Around line 239-244: The TODO about the "due" calculation in
ClientUpcomingChargesScreen (where due is computed from charge.amount and
charge.amountPaid) must be resolved: either confirm the business rule with
product/domain owners and, if correct, remove the TODO and add a short
clarifying comment stating the rule (e.g., due = amount - amountPaid) or, if
uncertain/known limitation, create a tracked issue in the repo referencing
ClientUpcomingChargesScreen and the due calculation, replace the TODO with a
one-line comment linking to that issue ID, and include expected behavior to
verify later.
- Around line 222-226: The current code calls MifosEmptyCard() when
chargesPagingList.loadState.refresh is NotLoading and itemCount == 0 but still
proceeds to render LazyColumn; change the control flow so LazyColumn is rendered
only when there are items (i.e. use an else branch) — locate the conditional
using chargesPagingList.loadState.refresh and itemCount and wrap the LazyColumn
block in the corresponding else so that MifosEmptyCard() and LazyColumn are
mutually exclusive.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 189bb74e-8909-46e8-be9b-4d074026cf1c

📥 Commits

Reviewing files that changed from the base of the PR and between c95348f and 5e27e78.

📒 Files selected for processing (25)
  • feature/client/build.gradle.kts
  • feature/client/src/androidMain/kotlin/com/mifos/feature/client/charges/ShowClientCharge.android.kt
  • feature/client/src/androidMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.android.kt
  • feature/client/src/androidMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.android.kt
  • feature/client/src/androidMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.android.kt
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/charges/ShowClientCharge.kt
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.kt
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.kt
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt
  • feature/client/src/desktopMain/kotlin/com/mifos/feature/client/charges/ShowClientCharge.desktop.kt
  • feature/client/src/desktopMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.desktop.kt
  • feature/client/src/desktopMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.desktop.kt
  • feature/client/src/desktopMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.desktop.kt
  • feature/client/src/jsMain/kotlin/com/mifos/feature/client/charges/ShowClientCharge.js.kt
  • feature/client/src/jsMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.js.kt
  • feature/client/src/jsMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.js.kt
  • feature/client/src/jsMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.js.kt
  • feature/client/src/nativeMain/kotlin/com/mifos/feature/client/charges/ClientChargesScreen.native.kt
  • feature/client/src/nativeMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.native.kt
  • feature/client/src/nativeMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.native.kt
  • feature/client/src/nativeMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.native.kt
  • feature/client/src/wasmJsMain/kotlin/com/mifos/feature/client/charges/ShowClientCharge.wasmJs.kt
  • feature/client/src/wasmJsMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.wasmJs.kt
  • feature/client/src/wasmJsMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.wasmJs.kt
  • feature/client/src/wasmJsMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.wasmJs.kt
💤 Files with no reviewable changes (20)
  • feature/client/src/desktopMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.desktop.kt
  • feature/client/src/jsMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.js.kt
  • feature/client/src/nativeMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.native.kt
  • feature/client/src/desktopMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.desktop.kt
  • feature/client/src/androidMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.android.kt
  • feature/client/src/androidMain/kotlin/com/mifos/feature/client/charges/ShowClientCharge.android.kt
  • feature/client/src/nativeMain/kotlin/com/mifos/feature/client/charges/ClientChargesScreen.native.kt
  • feature/client/src/jsMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.js.kt
  • feature/client/src/wasmJsMain/kotlin/com/mifos/feature/client/clientUpcomingCharges/ClientUpcomingChargesScreen.wasmJs.kt
  • feature/client/src/desktopMain/kotlin/com/mifos/feature/client/charges/ShowClientCharge.desktop.kt
  • feature/client/src/jsMain/kotlin/com/mifos/feature/client/charges/ShowClientCharge.js.kt
  • feature/client/src/desktopMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.desktop.kt
  • feature/client/src/androidMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.android.kt
  • feature/client/src/wasmJsMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.wasmJs.kt
  • feature/client/src/wasmJsMain/kotlin/com/mifos/feature/client/charges/ShowClientCharge.wasmJs.kt
  • feature/client/src/jsMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.js.kt
  • feature/client/src/wasmJsMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.wasmJs.kt
  • feature/client/src/nativeMain/kotlin/com/mifos/feature/client/clientList/ClientListScreen.native.kt
  • feature/client/src/androidMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.android.kt
  • feature/client/src/nativeMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.native.kt

@biplab1

biplab1 commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

@techsavvy185 The screen recording should demonstrate the following actions:

  • Initial loading of the client list when the screen is opened.
  • Scrolling through the client list to trigger pagination and load additional items.
  • Continuous scrolling to verify that multiple pages load correctly without duplication or UI issues.
  • Pull-to-refresh to ensure the list reloads from the first page and updates correctly.
  • Proper display of loading indicators during pagination and refresh.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (1)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt (1)

110-116: ⚠️ Potential issue | 🟠 Major

Pass isRefreshing from state to reflect actual refresh progress.

ClientListContentScreen defines isRefreshing parameter (Line 208) but the call site at Lines 110-116 doesn't pass it, defaulting to false. This means PullToRefreshBox (Line 225) never shows a loading indicator even when RefreshClients is triggered, breaking refresh UX feedback.

Pass the refresh state from state to ClientListContentScreen:

  • Determine if refresh state exists in ClientListState
  • Wire it to the isRefreshing parameter at the call site
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt`
around lines 110 - 116, ClientListContentScreen is not receiving the refresh
flag so PullToRefreshBox never shows loading; update the call site where
ClientListContentScreen(...) is invoked to pass the refresh state from your
screen state (e.g. add isRefreshing = state.isRefreshing or the correct boolean
property on ClientListState) and ensure the ClientListState exposes that boolean
(e.g. refreshInProgress/ isRefreshing) so ClientListContentScreen's isRefreshing
parameter reflects actual refresh progress.
♻️ Duplicate comments (1)
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt (1)

436-439: ⚠️ Potential issue | 🟡 Minor

Use stable paging keys only; avoid index fallback.

Line 438 falls back to index as key when item is null. That key is unstable for paged content and can cause wrong item reuse during list updates.

Suggested fix
+import androidx.paging.compose.itemKey
...
             items(
                 count = clientPagingList.itemCount,
-                key = { index -> clientPagingList[index]?.id ?: index },
+                key = clientPagingList.itemKey { it.id },
             ) { index ->
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt`
around lines 436 - 439, The key fallback to index is unstable; change the items
block so the key uses only the stable item id and do not fall back to index when
the item is null: inside the items(...) lambda, fetch the item once (val client
= clientPagingList[index]), if client is null return@items (skip rendering that
slot), and set key = { index -> clientPagingList[index]!!.id } (or compute the
id from the same non-null client variable) so the key is always a stable item id
and never the index.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt`:
- Around line 261-263: Replace the hardcoded empty-state text in the
ClientListScreen UI with a localized string: instead of MifosEmptyCard("No
clients found"), retrieve the string from composeResources/stringResource (e.g.,
call composeResources.stringResource(R.string.no_clients_found) or equivalent)
and pass that into MifosEmptyCard; ensure you add a corresponding entry
(no_clients_found) to the strings resource so the text is localizable.
- Around line 399-431: The current sorted branch converts Paging3's snapshot to
a static list (sortedItems) and calls items(items=sortedItems), which breaks
Paging3 pagination; revert the UI to always use the count-based API so Paging3
can trigger loads: use LazyColumn { items(count = clientPagingList.itemCount) {
index -> val client = clientPagingList[index]; LaunchedEffect(client?.id) { if
(client != null) fetchImage(client.id) }; ClientItem(client = client, byteArray
= images[client?.id], onClientClick = onClientSelect) } } and remove the
items(items = sortedItems, ...) branch; if true global sorting is required,
implement sort at the Pager/PagingSource level (or via PagingData
transformations) rather than in the composable.

---

Outside diff comments:
In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt`:
- Around line 110-116: ClientListContentScreen is not receiving the refresh flag
so PullToRefreshBox never shows loading; update the call site where
ClientListContentScreen(...) is invoked to pass the refresh state from your
screen state (e.g. add isRefreshing = state.isRefreshing or the correct boolean
property on ClientListState) and ensure the ClientListState exposes that boolean
(e.g. refreshInProgress/ isRefreshing) so ClientListContentScreen's isRefreshing
parameter reflects actual refresh progress.

---

Duplicate comments:
In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt`:
- Around line 436-439: The key fallback to index is unstable; change the items
block so the key uses only the stable item id and do not fall back to index when
the item is null: inside the items(...) lambda, fetch the item once (val client
= clientPagingList[index]), if client is null return@items (skip rendering that
slot), and set key = { index -> clientPagingList[index]!!.id } (or compute the
id from the same non-null client variable) so the key is always a stable item id
and never the index.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 45c1cd57-1c7d-4a96-9396-a3218bbfcbdc

📥 Commits

Reviewing files that changed from the base of the PR and between 5e27e78 and 92097f9.

📒 Files selected for processing (1)
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt

Comment on lines +261 to +263
else -> {
MifosEmptyCard("No clients found")
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Localize the empty-state text instead of hardcoding it.

Line 262 uses a hardcoded user-facing string ("No clients found"). This should come from composeResources via stringResource(...).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt`
around lines 261 - 263, Replace the hardcoded empty-state text in the
ClientListScreen UI with a localized string: instead of MifosEmptyCard("No
clients found"), retrieve the string from composeResources/stringResource (e.g.,
call composeResources.stringResource(R.string.no_clients_found) or equivalent)
and pass that into MifosEmptyCard; ensure you add a corresponding entry
(no_clients_found) to the strings resource so the text is localizable.

Comment on lines +399 to +431
if (sort != null) {
val currentItems = clientPagingList.itemSnapshotList.items

val sortedItems = when (sort) {
SortTypes.NAME -> {
currentItems.sortedBy { it.displayName?.lowercase() }
}
SortTypes.ACCOUNT_NUMBER -> {
currentItems.sortedBy { it.accountNo }
}
SortTypes.EXTERNAL_ID -> {
currentItems.sortedBy { it.externalId }
}
else -> currentItems
}

LazyColumn(
modifier = modifier,
) {
items(
items = sortedItems,
key = { client -> client.id },
) { client ->
LaunchedEffect(client.id) {
fetchImage(client.id)
}
ClientItem(
client = client,
byteArray = images[client.id],
onClientClick = onClientSelect,
)
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt | sed -n '380,450p'

Repository: openMF/mifos-x-field-officer-app

Length of output: 2823


🏁 Script executed:

# Search for the full conditional and both branches
rg -n -B10 "if \(sort != null\)" feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt

Repository: openMF/mifos-x-field-officer-app

Length of output: 408


🏁 Script executed:

# Check if there's an unsorted branch that uses different paging logic
rg -n -A50 "if \(sort != null\)" feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt | head -100

Repository: openMF/mifos-x-field-officer-app

Length of output: 1943


Sorting mode bypasses Paging3's pagination mechanism, stalling item loading when sort is active.

The sorted branch (line 399) uses itemSnapshotList.items and renders via items(items = sortedItems, ...), which provides a static list. The unsorted branch uses items(count = clientPagingList.itemCount, ...) with index-based access (clientPagingList[index]), enabling Paging3 to detect end-of-list and trigger pagination. While sorting, only currently-loaded items are sorted and displayed; new pages will not load as users scroll because the LazyColumn is no longer using Paging3's count-based mechanism to signal pagination.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientsList/ClientListScreen.kt`
around lines 399 - 431, The current sorted branch converts Paging3's snapshot to
a static list (sortedItems) and calls items(items=sortedItems), which breaks
Paging3 pagination; revert the UI to always use the count-based API so Paging3
can trigger loads: use LazyColumn { items(count = clientPagingList.itemCount) {
index -> val client = clientPagingList[index]; LaunchedEffect(client?.id) { if
(client != null) fetchImage(client.id) }; ClientItem(client = client, byteArray
= images[client?.id], onClientClick = onClientSelect) } } and remove the
items(items = sortedItems, ...) branch; if true global sorting is required,
implement sort at the Pager/PagingSource level (or via PagingData
transformations) rather than in the composable.

@biplab1

biplab1 commented Mar 6, 2026

Copy link
Copy Markdown
Contributor

@techsavvy185 Please upload screen recordings for all supported platforms covering all tests mentioned above.

@biplab1 biplab1 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.

Looks good to me. This can be merged.

@sonarqubecloud

Copy link
Copy Markdown

@biplab1 biplab1 changed the title fix(client): Migrated Paging to commonMain from androidMain fix(client): Migrate Paging to commonMain from androidMain Mar 10, 2026
@therajanmaurya
therajanmaurya merged commit 6b0a31f into openMF:development Mar 10, 2026
5 of 10 checks passed
@techsavvy185
techsavvy185 deleted the pagingMigrateClient branch March 11, 2026 08:29
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