Skip to content

fix(groups): Migrate Paging to commonMain from androidMain - #2636

Merged
therajanmaurya merged 5 commits into
openMF:developmentfrom
techsavvy185:pagingMigrateGroups
Mar 10, 2026
Merged

fix(groups): Migrate Paging to commonMain from androidMain#2636
therajanmaurya merged 5 commits into
openMF:developmentfrom
techsavvy185:pagingMigrateGroups

Conversation

@techsavvy185

@techsavvy185 techsavvy185 commented Mar 4, 2026

Copy link
Copy Markdown
Member

Fixes - Jira-#616
Android

Screen_recording_20260309_235730.webm

iOS

Screen.Recording.2026-03-05.at.10.58.00.PM.mov

Desktop

Screen.Recording.2026-03-10.at.12.01.25.AM.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

  • New Features
    • Unified Groups list UI with pull-to-refresh, paging list, item selection with contextual top bar, add button, and sync dialog.
  • Refactor
    • Consolidated platform-specific implementations into a single cross-platform Groups list, improving consistency and behavior across targets.
  • Platform
    • Paging and back-handler behavior now available consistently across platforms.

@techsavvy185
techsavvy185 requested a review from a team March 4, 2026 19:48
@coderabbitai

coderabbitai Bot commented Mar 4, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Consolidates the GroupsList UI into commonMain by adding a concrete GroupsListScreen and GroupsListRoute, moves paging and backhandler dependencies to commonMain, and removes platform-specific actual/stub implementations (android, desktop, js, native, wasmJs).

Changes

Cohort / File(s) Summary
Build / Dependencies
feature/groups/build.gradle.kts
Moved libs.androidx.paging.compose and libs.ui.backhandler into commonMain; removed libs.androidx.paging.compose from androidMain.
Common Implementation Added
feature/groups/src/commonMain/kotlin/.../GroupsListScreen.kt
Replaced expect route with a concrete GroupsListRoute and added GroupsListScreen composable (Scaffold, FAB, contextual top bar, pull-to-refresh, LazyColumn with Paging, selection management, BackHandler, sync dialog).
Android Implementation Removed
feature/groups/src/androidMain/kotlin/.../GroupsListScreen.android.kt
Deleted full Android-specific GroupsListScreen implementation and associated helper functions.
Platform Stubs Removed
feature/groups/src/desktopMain/.../GroupsListScreen.desktop.kt, feature/groups/src/jsMain/.../GroupsListScreen.js.kt, feature/groups/src/nativeMain/.../GroupsListScreen.native.kt, feature/groups/src/wasmJsMain/.../GroupsListScreen.wasmJs.kt
Removed platform-specific actual stubs/placeholders for GroupsListRoute (desktop, js, native, wasmJs).

Sequence Diagram(s)

sequenceDiagram
  participant UI as "Compose UI\n(GroupsListScreen)"
  participant VM as "GroupsListViewModel"
  participant Paging as "PagingData\n/ Pager"
  participant Nav as "Navigation"
  rect rgba(135,206,235,0.5)
    UI->>VM: request paged items / refresh
    VM->>Paging: load page
    Paging-->>VM: PagingData<PagedItem>
    VM-->>UI: provide PagingData (collectAsLazyPagingItems)
  end
  rect rgba(144,238,144,0.5)
    UI->>UI: user selects / long-press item
    UI->>VM: notify selection or action (delete/sync)
    UI->>Nav: onGroupClick -> navigate to detail
  end
  rect rgba(255,182,193,0.5)
    UI->>UI: BackHandler clears selection
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested reviewers

  • biplab1

Poem

🐰
I hopped through modules, soft and spry,
Merged stubs to share one glowing sky,
Pages now dance in common land,
One screen to hold them, hand in hand,
Hooray — a rabbit's multiplatform sigh!

🚥 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 title accurately describes the main change: migrating Paging to commonMain from androidMain, which is reflected in the build.gradle.kts and the platform-specific file consolidations.

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

🧹 Nitpick comments (1)
feature/groups/src/commonMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.kt (1)

114-119: Prefer stable-id selection checks over full-entity equality.

Current selection toggling and isSelected checks rely on GroupEntity equality. With paging updates, new instances for the same group can break selection continuity.

Proposed refactor
         onSelectItem = {
-            if (selectedItems.contains(it)) {
-                selectedItems.remove(it)
-            } else {
-                selectedItems.add(it)
-            }
+            it.id?.let { candidateId ->
+                val selectedIndex = selectedItems.indexOfFirst { selected -> selected.id == candidateId }
+                if (selectedIndex >= 0) {
+                    selectedItems.removeAt(selectedIndex)
+                } else {
+                    selectedItems.add(it)
+                }
+            }
         },
@@
                     successState(
                         pagingItems = data,
                         isInSelectionMode = selectedItems.isNotEmpty(),
-                        isSelected = selectedItems::contains,
+                        isSelected = { item ->
+                            item.id?.let { id -> selectedItems.any { selected -> selected.id == id } } == true
+                        },
                         onGroupClick = onGroupClick,
                         onSelectItem = onSelectItem,
                     )

Also applies to: 215-217

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

In
`@feature/groups/src/commonMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.kt`
around lines 114 - 119, The selection toggle currently compares full GroupEntity
instances (selectedItems.contains(it)) which breaks when paging creates new
instances; change selection to use the group's stable id instead: store and
check IDs (e.g., selectedItemIds) rather than GroupEntity objects, update the
onSelectItem lambda to add/remove the group's id (use the unique id property on
GroupEntity) and update all isSelected checks to reference that id-set (also
apply the same change for the other occurrence around lines 215-217). Ensure
helper names referenced (selectedItems → selectedItemIds, onSelectItem, any
isSelected or selection-check logic) are updated consistently so selection
survives entity instance changes.
🤖 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/groups/build.gradle.kts`:
- Line 35: The dependency implementation(libs.androidx.paging.compose) is
declared in commonMain but it’s Android-only; move the
implementation(libs.androidx.paging.compose) declaration out of commonMain and
add it under the androidMain source set (where platform-specific deps belong)
while keeping libs.androidx.paging.common in commonMain; update the
multiplatform sourceSets block to place paging-compose under androidMain
(reference the existing implementation(libs.androidx.paging.compose) token and
the sourceSets/androidMain block) so non-Android targets (desktop/iOS/web) no
longer depend on an Android-only artifact.

In
`@feature/groups/src/commonMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.kt`:
- Around line 105-124: The pull-to-refresh UI isn't tied to the Paging refresh
state: update calls to GroupsListScreen so its isRefreshing parameter is set
from the PagingData's load state (e.g., isRefreshing = data.loadState.refresh is
LoadState.Loading) instead of the default false; inside GroupsListRoute locate
the data variable (PagingData/LazyPagingItems) and pass isRefreshing as
described to GroupsListScreen (and make the same change for the other
GroupsListScreen invocations referenced around the other call sites), ensuring
you import androidx.paging.LoadState and use the Paging loadState.refresh to
drive the refresh indicator and swipe-to-refresh behavior.
- Around line 147-149: The hide callback for the dialog in GroupsListScreen (the
hide = { ... } block) is a no-op and must be implemented to perform the intended
"hide" behavior distinct from dismiss; update the hide lambda to call the same
state-change or helper used by dismiss (e.g., the dialogVisible/dialogShown
state setter or the existing dismiss handler) or invoke the ViewModel action
that hides the dialog without clearing selection, ensuring it uses the same
unique symbols used elsewhere (hide lambda, dismiss handler, and the ViewModel
method that toggles dialog visibility) so both dialog paths perform the correct
state transition.
- Around line 190-195: The Column currently only applies a conditional top inset
via padding(top = if (selectedItems.isNotEmpty())
paddingValues.calculateTopPadding() else 0.dp), which drops start/bottom/end
scaffold insets; replace that conditional top-only padding with applying the
full Scaffold content insets using padding(paddingValues) (or combine
padding(paddingValues) with any additional conditional top offset if needed) on
the Column modifier so the list respects start/bottom/end insets and avoids
being clipped by FAB/system bars; look for the Column, selectedItems, and
paddingValues.calculateTopPadding() usage in GroupsListScreen to make this
change.

---

Nitpick comments:
In
`@feature/groups/src/commonMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.kt`:
- Around line 114-119: The selection toggle currently compares full GroupEntity
instances (selectedItems.contains(it)) which breaks when paging creates new
instances; change selection to use the group's stable id instead: store and
check IDs (e.g., selectedItemIds) rather than GroupEntity objects, update the
onSelectItem lambda to add/remove the group's id (use the unique id property on
GroupEntity) and update all isSelected checks to reference that id-set (also
apply the same change for the other occurrence around lines 215-217). Ensure
helper names referenced (selectedItems → selectedItemIds, onSelectItem, any
isSelected or selection-check logic) are updated consistently so selection
survives entity instance changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee461ccc-c6e2-4ae0-8e3d-83dc5f5cc6cf

📥 Commits

Reviewing files that changed from the base of the PR and between c95348f and 34cd070.

📒 Files selected for processing (7)
  • feature/groups/build.gradle.kts
  • feature/groups/src/androidMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.android.kt
  • feature/groups/src/commonMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.kt
  • feature/groups/src/desktopMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.desktop.kt
  • feature/groups/src/jsMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.js.kt
  • feature/groups/src/nativeMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.native.kt
  • feature/groups/src/wasmJsMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.wasmJs.kt
💤 Files with no reviewable changes (5)
  • feature/groups/src/wasmJsMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.wasmJs.kt
  • feature/groups/src/jsMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.js.kt
  • feature/groups/src/nativeMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.native.kt
  • feature/groups/src/desktopMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.desktop.kt
  • feature/groups/src/androidMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.android.kt

Comment thread feature/groups/build.gradle.kts
@biplab1

biplab1 commented Mar 5, 2026

Copy link
Copy Markdown
Contributor

@techsavvy185 The screen recording should demonstrate the following actions:

  • Initial loading of the group list when the screen is opened.
  • Scrolling through the group 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.

@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 changed the title fix(groups): Migrated Paging to commonMain from androidMain fix(groups): Migrate Paging to commonMain from androidMain Mar 10, 2026
@biplab1

biplab1 commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

@techsavvy185 Can you please address the @coderabbitai reviews?

@coderabbitai

coderabbitai Bot commented Mar 10, 2026

Copy link
Copy Markdown

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Fetch all review comments by coderabbitai on PR `#2636`
gh pr view 2636 --repo openMF/mifos-x-field-officer-app --json reviews,comments

echo "--- REVIEW COMMENTS ---"
gh api repos/openMF/mifos-x-field-officer-app/pulls/2636/comments --paginate | jq '[.[] | select(.user.login == "coderabbitai") | {path: .path, line: .original_line, body: .body}]'

echo "--- ISSUE COMMENTS ---"
gh api repos/openMF/mifos-x-field-officer-app/issues/2636/comments --paginate | jq '[.[] | select(.user.login == "coderabbitai") | {id: .id, body: .body}]'

Length of output: 26068


@techsavvy185, here is a summary of all the outstanding CodeRabbit review comments that need to be addressed:


❌ Actionable Comments (4)

1. feature/groups/build.gradle.kts — Line 35
libs.androidx.paging.compose is an Android-only artifact but it has been placed in the commonMain source set. Non-Android targets (desktop/iOS/web) cannot use it. Move it back to androidMain, and keep only libs.androidx.paging.common (if needed) in commonMain.


2. GroupsListScreen.kt — ~Lines 105–124
The pull-to-refresh indicator (isRefreshing) is not connected to the Paging load state, so the refresh spinner will never show automatically. It should be driven by the paging data's load state:

isRefreshing = data.loadState.refresh is LoadState.Loading

3. GroupsListScreen.kt — ~Lines 147–149
The hide lambda passed to SyncGroupDialogScreen is a no-op and doesn't actually hide the dialog. It must be implemented to perform the correct state transition (e.g., calling the same state setter or ViewModel action used by dismiss).


4. GroupsListScreen.kt — ~Lines 190–195
The Column only applies a conditional top inset (paddingValues.calculateTopPadding()), silently dropping the start, bottom, and end scaffold insets. This can cause content to be clipped by the FAB or system bars. Replace with:

Modifier.padding(paddingValues) // Apply full scaffold insets

💡 Nitpick Comment (1)

5. GroupsListScreen.kt — Lines 114–119 & 215–217
Selection toggling uses full GroupEntity equality (selectedItems.contains(it) / selectedItems::contains). When paging emits new instances for the same group, selections will be lost. Use stable IDs instead:

// Toggle by ID
it.id?.let { candidateId ->
    val selectedIndex = selectedItems.indexOfFirst { selected -> selected.id == candidateId }
    if (selectedIndex >= 0) selectedItems.removeAt(selectedIndex)
    else selectedItems.add(it)
}

// Check selection by ID
isSelected = { item ->
    item.id?.let { id -> selectedItems.any { selected -> selected.id == id } } == true
},

Please address items 1–4 (actionable) as a priority, and consider item 5 (nitpick) as well since it can cause subtle bugs during pagination. Let me know if you need more details on any of these!

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

🧹 Nitpick comments (1)
feature/groups/src/commonMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.kt (1)

95-97: Consider selection by stable ID instead of full entity equality.

Storing full GroupEntity objects and using contains/remove/add relies on object equality. When paging emits new instances after refresh or page loads, the same logical group may have a different object reference, causing selection state to be lost.

♻️ Proposed refactor to use ID-based selection
-    val selectedItems = remember {
-        mutableStateListOf<GroupEntity>()
-    }
+    val selectedItemIds = remember {
+        mutableStateListOf<Int>()
+    }

Then update the toggle logic:

         onSelectItem = {
-            if (selectedItems.contains(it)) {
-                selectedItems.remove(it)
+            it.id?.let { id ->
+                if (selectedItemIds.contains(id)) {
+                    selectedItemIds.remove(id)
+                } else {
+                    selectedItemIds.add(id)
+                }
-            } else {
-                selectedItems.add(it)
             }
         },

And update isSelected check accordingly:

-        isSelected = selectedItems::contains,
+        isSelected = { group -> group.id in selectedItemIds },

Also applies to: 114-119

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

In
`@feature/groups/src/commonMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.kt`
around lines 95 - 97, Currently selectedItems stores GroupEntity instances which
breaks when paging emits new instances; change selection to store stable IDs
(e.g., Long or String) instead of full GroupEntity objects: replace
mutableStateListOf<GroupEntity>() with a mutableStateListOf<IdType>() or a
MutableStateSet of IDs (referencing selectedItems), update any toggleSelection
logic that previously added/removed GroupEntity to add/remove group.id (use the
group.id value from the UI item and functions like toggleSelection(groupId)),
and update isSelected checks to compare against IDs (e.g., isSelected =
selectedItems.contains(group.id)). Also update any other uses around the toggle
and selection checks (the code referenced near lines 114-119) to operate on IDs
instead of object equality.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@feature/groups/src/commonMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.kt`:
- Around line 95-97: Currently selectedItems stores GroupEntity instances which
breaks when paging emits new instances; change selection to store stable IDs
(e.g., Long or String) instead of full GroupEntity objects: replace
mutableStateListOf<GroupEntity>() with a mutableStateListOf<IdType>() or a
MutableStateSet of IDs (referencing selectedItems), update any toggleSelection
logic that previously added/removed GroupEntity to add/remove group.id (use the
group.id value from the UI item and functions like toggleSelection(groupId)),
and update isSelected checks to compare against IDs (e.g., isSelected =
selectedItems.contains(group.id)). Also update any other uses around the toggle
and selection checks (the code referenced near lines 114-119) to operate on IDs
instead of object equality.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 53de6707-b3d3-4150-b101-3eb8ba222f04

📥 Commits

Reviewing files that changed from the base of the PR and between 34cd070 and 15829dc.

📒 Files selected for processing (1)
  • feature/groups/src/commonMain/kotlin/com/mifos/feature/groups/groupList/GroupsListScreen.kt

@sonarqubecloud

Copy link
Copy Markdown

@biplab1

biplab1 commented Mar 10, 2026

Copy link
Copy Markdown
Contributor

@techsavvy185 Have you tested the screen after the recent updates were pushed?

@techsavvy185

Copy link
Copy Markdown
Member Author

@biplab1 Yes, I've tested it. It looks exactly the same as before.

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

@therajanmaurya
therajanmaurya merged commit a86f9ce into openMF:development Mar 10, 2026
5 of 10 checks passed
@techsavvy185
techsavvy185 deleted the pagingMigrateGroups 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