Skip to content

refactor(loan): centralize loan status mapping across screens - #2697

Merged
niyajali merged 3 commits into
openMF:devfrom
sahilshivekar:fix-loan-status
Jun 30, 2026
Merged

refactor(loan): centralize loan status mapping across screens#2697
niyajali merged 3 commits into
openMF:devfrom
sahilshivekar:fix-loan-status

Conversation

@sahilshivekar

@sahilshivekar sahilshivekar commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Fixes - Jira-#783

Client Loan Accounts Screen

Before After
image image

Loan Account Profile Screen

Background color for the loan status is same as it's in the web app now

Pending Approval

Before After
image image

Approved

Before After
image image

Active

Before After
image image

Closed (Overpaid)

Before After
App crashes with exception because the send_money.xml file was using @android:color/white. Exception java.lang.IllegalArgumentException: Invalid color value @android:color/white image

Closed (Obligations Met)

Before After
image image

Rejected

Before After
image image

Loan accounts with following loan status not found

Closed (Written Off)

Before After

Closed (Rescheduled)

Before After

Withdrawn by Applicant

Before After

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8c35c903-b451-42da-a72e-30151ff892a9

📥 Commits

Reviewing files that changed from the base of the PR and between 81275fe and 3b74add.

📒 Files selected for processing (2)
  • feature/loan/src/commonMain/composeResources/values/strings.xml
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/utils/LoanStatus.kt
🚧 Files skipped from review as they are similar to previous changes (1)
  • feature/loan/src/commonMain/composeResources/values/strings.xml
📜 Recent review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: PR Checks / Static Analysis Check
🧰 Additional context used
📓 Path-based instructions (1)
**/*.kt

⚙️ CodeRabbit configuration file

**/*.kt: Additional Code Review Guidelines:

  1. Null Safety & Stability
  • Avoid using !! operator
  • Handle null cases explicitly using safe calls or proper state handling
  • Do not assume values are always non-null without guarantees
  1. Architecture Boundaries
  • ViewModel must not depend on specific network/library implementations
  • Ensure proper separation between data, domain, and presentation layers
  • Do not format data (currency, dates, calculations) inside the UI layer
  • All formatting must be handled in the ViewModel and exposed via state (e.g., StateFlow)
  1. Performance Considerations
  • Avoid unnecessary recompositions in Compose
  • Do not attach heavy logic to frequently changing states (e.g., scrollState)
  • Prefer lifting state up instead of recomputing in child composables
  1. Compose & Navigation Best Practices
  • NEVER trigger navigation functions or side-effects directly during composition
  • Always wrap navigation calls inside LaunchedEffect or EventsEffect to avoid repeated execution on recomposition
  • Avoid triggering intensive side-effects during recomposition
  • Navigation routes must be type-safe.
  • Ensure all route classes or objects used for navigation are annotated with @Serializable.
  1. UI Structure
  • Dialogs must be separated into their own composables
  • Do not embed dialogs inline within complex main screens
  1. Localization Consistency
  • Ensure all supported languages are updated consistently across modules
  • Verify translations exist for newly added UI strings
  1. Code Cleanliness
  • Avoid unnecessary inline comments unless critical
  • Remove leftover debug or commented code
  1. Focus on correctness, readability, and maintainability over cosmetic nitpicks.
  • Avoid reviewing README, config, or asset files.
  • Prioritize identifying bugs, performance issues, and architectural concerns.
  1. Naming & Intent Rules:

Files:

  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/utils/LoanStatus.kt
🧠 Learnings (2)
📚 Learning: 2026-02-06T13:15:16.968Z
Learnt from: sahilshivekar
Repo: openMF/android-client PR: 2603
File: feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanTransaction/LoanTransactionsViewModel.kt:43-106
Timestamp: 2026-02-06T13:15:16.968Z
Learning: Guideline: When a Kotlin function parameter is nullable (e.g., balance: Double?, currencyCode: String?, maximumFractionDigits: Int?) and downstream calls require a non-null value, add null-safety handling in all implementations. Specifically, avoid calling Currency.getInstance(currencyCode) with a possibly null currencyCode; provide a safe default (e.g., currencyCode ?: "$") or validate before use. Ensure all platform targets (Android/Desktop/Native) follow consistent null handling, and consider centralizing currencyCode normalization in the common layer if feasible. Add tests covering null currencyCode to prevent NPEs.

Applied to files:

  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/utils/LoanStatus.kt
📚 Learning: 2026-04-01T05:03:14.323Z
Learnt from: kartikey004
Repo: openMF/mifos-x-field-officer-app PR: 2659
File: feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanDisbursement/LoanAccountDisbursementScreen.kt:190-195
Timestamp: 2026-04-01T05:03:14.323Z
Learning: In this repo, existing `SelectableDates` / `SelectableDates.isSelectableDate(utcTimeMillis: Long)` implementations use `Clock.System.now().toEpochMilliseconds()` (UTC epoch millis) for date-boundary checks. During PR reviews, do not flag these checks for not using a timezone-aware `LocalDate` start-of-day approach; treat it as an established project-wide pattern. If a change is desired, handle it as a coordinated project-wide improvement rather than as a per-PR review issue.

Applied to files:

  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/utils/LoanStatus.kt
🔇 Additional comments (2)
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/utils/LoanStatus.kt (2)

68-72: LGTM!


74-85: 🎯 Functional Correctness

Verify whether closed also needs an explicit branch.

The upstream status contract still includes a separate closed flag, but this mapper never reads it. If the backend can return closed == true without one of the more specific closed variants, these accounts will now fall through to UNKNOWN, and the downstream screens will render the gray unknown badge/action set instead of a closed state.


Summary by CodeRabbit

  • New Features

    • Improved loan status display with consistent, localized labels and matching colors across loan screens.
    • Added/updated loan status variants (approved, pending approval, active, closed variants, rejected, withdrawn by applicant).
    • Updated next-action behavior to reflect the current loan status (approve, repay, transfer, or view details).
  • Bug Fixes

    • Fixed inconsistent status rendering between loan summary and profile views by using a shared status mapping.
    • Improved loan status badge behavior to always render the correct label and color.
  • Style

    • Updated a drawable icon to use a fixed white fill color for more reliable rendering.

Walkthrough

Loan status handling now uses a shared LoanStatus model across loan profile, summary, and client loan account screens. Loan status strings and theme colors were updated, and the send-money icon fill color changed to white.

Changes

Loan status normalization

Loan status handling is centralized across the loan feature.

Review checkpoint table
Layer / File(s) Summary
Status model and resources
feature/loan/src/commonMain/composeResources/values/strings.xml, core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/theme/Color.kt, feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/utils/LoanStatus.kt
Loan status strings and theme colors are defined for the new status set, and LoanStatus maps entity flags to those resources.
Profile state and actions
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileViewModel.kt, feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileScreen.kt
LoanAccountProfileViewModel now derives status and next actions from getLoanStatus(), and the profile screen consumes the updated state and badge rendering.
Summary and client screens
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreen.kt, feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt
LoanAccountSummaryScreen and ClientLoanAccountsScreen now render loan statuses through getLoanStatus().

The send-money icon change is separate.

Send money drawable
Layer / File(s) Summary
Icon fill color
core/ui/src/commonMain/composeResources/drawable/send_money.xml
The drawable path fill color changes from the Android white resource to #FFFFFF.

Sequence Diagram(s)

The shared status mapper now feeds profile, summary, and client loan account rendering.

Loan status flow
sequenceDiagram
  participant LoanStatusEntity
  participant LoanAccountProfileViewModel
  participant LoanAccountProfileScreen
  participant LoanAccountSummaryScreen
  participant ClientLoanAccountsScreen

  LoanStatusEntity->>LoanAccountProfileViewModel: loan.status.getLoanStatus()
  LoanAccountProfileViewModel->>LoanAccountProfileScreen: currentStatus and nextActionButtonRes
  LoanStatusEntity->>LoanAccountSummaryScreen: loanWithAssociations.status.getLoanStatus()
  LoanStatusEntity->>ClientLoanAccountsScreen: loan.status.getLoanStatus().label
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 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 (3 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the PR's main refactor of centralizing loan status mapping across loan screens.
Description check ✅ Passed The description matches the PR by describing the loan status UI refactor and the send_money.xml fix.
Jira Link And Before/After Sections ✅ Passed The PR description has a valid Jira link and multiple non-empty Before/After sections with image media.
✨ 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.

@sahilshivekar
sahilshivekar marked this pull request as ready for review June 26, 2026 13:38
@sahilshivekar
sahilshivekar requested a review from a team June 26, 2026 13:38

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

🧹 Nitpick comments (5)
core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/theme/Color.kt (1)

259-267: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Shared foundational module touched + naming inconsistency on the rescheduled token.

Heads up: Color.kt lives in the core design-system module, so these token additions are high-impact and ripple into every consumer — worth an extra careful pass to confirm the new tokens are minimal and necessary.

Separately, loanClosedRescheduled breaks the loan*Status naming pattern used by its siblings (loanClosedOverpaidStatus, loanClosedObligationsMetStatus, loanClosedWrittenOffStatus). Rename for consistency.

♻️ Suggested rename
-    val loanClosedRescheduled = Color(0xFF000AAD)
+    val loanClosedRescheduledStatus = Color(0xFF000AAD)

Update the reference in LoanStatus.kt accordingly (AppColors.loanClosedRescheduledAppColors.loanClosedRescheduledStatus).

As per path instructions: "core-base is a shared foundational module and requires extra review attention" (applies to core/** here), and Kotlin naming should "Ensure consistency".

🤖 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/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/theme/Color.kt`
around lines 259 - 267, The new color token in AppColors/Color.kt should follow
the existing loan*Status naming pattern, so rename loanClosedRescheduled to
loanClosedRescheduledStatus for consistency with the other loan status tokens.
Update any consumer references, especially in LoanStatus.kt, to use the new
AppColors.loanClosedRescheduledStatus name, and keep the shared core
design-system additions minimal and necessary.

Source: Path instructions

feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileScreen.kt (1)

289-304: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Compute the status once instead of mapping twice.

loanAccount.status.getLoanStatus() is evaluated twice here (color on Line 292, label on Line 300), recomputed on each recomposition. Hoist it to a single val.

♻️ Suggested change
+            val loanStatus = loanAccount.status.getLoanStatus()
             Box(
                 modifier = Modifier
                     .clip(KptTheme.shapes.large)
-                    .background(loanAccount.status.getLoanStatus().color)
+                    .background(loanStatus.color)
                     .padding(
                         horizontal = DesignToken.padding.medium,
                         vertical = KptTheme.spacing.xs,
                     ),
                 contentAlignment = Alignment.Center,
             ) {
                 Text(
-                    text = stringResource(loanAccount.status.getLoanStatus().label).uppercase(),
+                    text = stringResource(loanStatus.label).uppercase(),

As per path instructions: "Prefer lifting state up instead of recomputing in child composables".

🤖 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/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileScreen.kt`
around lines 289 - 304, The loan status is being mapped twice in the status
badge block inside LoanAccountProfileScreen, once for the color and once for the
label, which recomputes it on each recomposition. Hoist
loanAccount.status.getLoanStatus() into a single local val in that composable
section and reuse it for both the background color and the stringResource label
lookup to keep the status mapping computed once.

Source: Path instructions

feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileViewModel.kt (1)

108-142: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Two parallel status→action maps can silently diverge.

calculateNextActionResource (button label) and handleNextAction (navigation target) both switch over the same LoanStatus, but as separate when blocks. If one is updated without the other, the button could display "Make Repayment" while navigating elsewhere. Consider a single source of truth mapping LoanStatus → (label resource, action) to keep them in lockstep.

Not blocking, but it's a real maintenance hazard given the two lists already enumerate the same three cases.

🤖 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/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileViewModel.kt`
around lines 108 - 142, The button-label mapping in calculateNextActionResource
and the navigation mapping in handleNextAction can drift because they duplicate
the same LoanStatus cases in separate when blocks. Refactor
LoanAccountProfileViewModel to use a single source of truth for
status-to-next-action behavior, so both the StringResource returned for the CTA
and the LoanProfileAction sent by NavigateToAction come from the same mapping.
Keep the existing symbols calculateNextActionResource, handleNextAction, and
LoanStatus as the main lookup points while consolidating the three shared cases
and the fallback branch.
feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreen.kt (1)

273-283: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Reuse a single getLoanStatus() result for label and color.

getLoanStatus() runs on Line 273 (label) and again on Line 282 (circle color) inside the composable. Hoist it to one val before the Canvas.

♻️ Suggested change
-                    val statusDescription = stringResource(loanWithAssociations.status.getLoanStatus().label)
+                    val loanStatus = loanWithAssociations.status.getLoanStatus()
+                    val statusDescription = stringResource(loanStatus.label)
                     Canvas(
                         modifier = Modifier
                             .size(DesignToken.sizes.iconMedium)
                             .semantics {
                                 contentDescription = "Loan status: $statusDescription"
                             },
                         onDraw = {
                             drawCircle(
-                                color = loanWithAssociations.status.getLoanStatus().color,
+                                color = loanStatus.color,
                             )
                         },
                     )
🤖 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/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreen.kt`
around lines 273 - 283, Reuse a single LoanStatus value in
LoanAccountSummaryScreen: the current code calls
loanWithAssociations.status.getLoanStatus() twice for the label and circle color
inside the composable. Hoist the result into one val before the Canvas, then use
that same value for both stringResource(...label) and drawCircle(...color) to
avoid duplicate computation.
feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt (1)

78-78: 📐 Maintainability & Code Quality | 🔵 Trivial

feature:client directly depends on feature:loan to access getLoanStatus

The Gradle configuration in feature/client/build.gradle.kts explicitly includes implementation(projects.feature.loan), making the import valid.

However, coupling feature modules together is generally an anti-pattern. Shared utilities like getLoanStatus should reside in a common core module to prevent circular or meshed dependencies between features.

Dependency Analysis

feature/client currently depends on multiple feature modules:

  • feature.savings
  • feature.loan (used for getLoanStatus)
  • feature.document
  • feature.recurringDeposit
  • feature.groups
  • feature.dataTable
  • feature.note

This mesh of inter-feature dependencies simplifies refactoring if shared logic moves to a central core module.

🤖 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/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt`
at line 78, Move the getLoanStatus dependency out of feature:client and into a
shared core/common module so ClientLoanAccountsScreen no longer imports from
feature:loan. Update the LoanStatus utility location and expose it from a
neutral shared module, then change the import in ClientLoanAccountsScreen to the
new shared symbol. Keep the feature module boundaries clean by avoiding direct
feature-to-feature references while preserving the same status mapping behavior.
🤖 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
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/utils/LoanStatus.kt`:
- Around line 69-81: The fallback in LoanStatusEntity.getLoanStatus() is
incorrectly mapping every unrecognized state to LoanStatus.REJECTED. Update the
status mapping to use a neutral UNKNOWN outcome for unmatched/unsupported flags,
and only return REJECTED when LoanStatusEntity exposes an explicit rejected
indicator. If the enum currently lacks UNKNOWN, add it and wire it to the
existing loanUnknownStatus color/token so LoanStatusEntity.getLoanStatus() and
the related UI stay aligned.

---

Nitpick comments:
In
`@core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/theme/Color.kt`:
- Around line 259-267: The new color token in AppColors/Color.kt should follow
the existing loan*Status naming pattern, so rename loanClosedRescheduled to
loanClosedRescheduledStatus for consistency with the other loan status tokens.
Update any consumer references, especially in LoanStatus.kt, to use the new
AppColors.loanClosedRescheduledStatus name, and keep the shared core
design-system additions minimal and necessary.

In
`@feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt`:
- Line 78: Move the getLoanStatus dependency out of feature:client and into a
shared core/common module so ClientLoanAccountsScreen no longer imports from
feature:loan. Update the LoanStatus utility location and expose it from a
neutral shared module, then change the import in ClientLoanAccountsScreen to the
new shared symbol. Keep the feature module boundaries clean by avoiding direct
feature-to-feature references while preserving the same status mapping behavior.

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileScreen.kt`:
- Around line 289-304: The loan status is being mapped twice in the status badge
block inside LoanAccountProfileScreen, once for the color and once for the
label, which recomputes it on each recomposition. Hoist
loanAccount.status.getLoanStatus() into a single local val in that composable
section and reuse it for both the background color and the stringResource label
lookup to keep the status mapping computed once.

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileViewModel.kt`:
- Around line 108-142: The button-label mapping in calculateNextActionResource
and the navigation mapping in handleNextAction can drift because they duplicate
the same LoanStatus cases in separate when blocks. Refactor
LoanAccountProfileViewModel to use a single source of truth for
status-to-next-action behavior, so both the StringResource returned for the CTA
and the LoanProfileAction sent by NavigateToAction come from the same mapping.
Keep the existing symbols calculateNextActionResource, handleNextAction, and
LoanStatus as the main lookup points while consolidating the three shared cases
and the fallback branch.

In
`@feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreen.kt`:
- Around line 273-283: Reuse a single LoanStatus value in
LoanAccountSummaryScreen: the current code calls
loanWithAssociations.status.getLoanStatus() twice for the label and circle color
inside the composable. Hoist the result into one val before the Canvas, then use
that same value for both stringResource(...label) and drawCircle(...color) to
avoid duplicate computation.
🪄 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: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 91fa9885-17e0-4ad8-b7cd-907ae0e9db79

📥 Commits

Reviewing files that changed from the base of the PR and between c997b69 and 81275fe.

📒 Files selected for processing (8)
  • core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/theme/Color.kt
  • core/ui/src/commonMain/composeResources/drawable/send_money.xml
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt
  • feature/loan/src/commonMain/composeResources/values/strings.xml
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileViewModel.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/utils/LoanStatus.kt
📜 Review details
⏰ Context from checks skipped due to timeout. (1)
  • GitHub Check: PR Checks / Build Web Application
🧰 Additional context used
📓 Path-based instructions (5)
**/*.kt

⚙️ CodeRabbit configuration file

**/*.kt: Additional Code Review Guidelines:

  1. Null Safety & Stability
  • Avoid using !! operator
  • Handle null cases explicitly using safe calls or proper state handling
  • Do not assume values are always non-null without guarantees
  1. Architecture Boundaries
  • ViewModel must not depend on specific network/library implementations
  • Ensure proper separation between data, domain, and presentation layers
  • Do not format data (currency, dates, calculations) inside the UI layer
  • All formatting must be handled in the ViewModel and exposed via state (e.g., StateFlow)
  1. Performance Considerations
  • Avoid unnecessary recompositions in Compose
  • Do not attach heavy logic to frequently changing states (e.g., scrollState)
  • Prefer lifting state up instead of recomputing in child composables
  1. Compose & Navigation Best Practices
  • NEVER trigger navigation functions or side-effects directly during composition
  • Always wrap navigation calls inside LaunchedEffect or EventsEffect to avoid repeated execution on recomposition
  • Avoid triggering intensive side-effects during recomposition
  • Navigation routes must be type-safe.
  • Ensure all route classes or objects used for navigation are annotated with @Serializable.
  1. UI Structure
  • Dialogs must be separated into their own composables
  • Do not embed dialogs inline within complex main screens
  1. Localization Consistency
  • Ensure all supported languages are updated consistently across modules
  • Verify translations exist for newly added UI strings
  1. Code Cleanliness
  • Avoid unnecessary inline comments unless critical
  • Remove leftover debug or commented code
  1. Focus on correctness, readability, and maintainability over cosmetic nitpicks.
  • Avoid reviewing README, config, or asset files.
  • Prioritize identifying bugs, performance issues, and architectural concerns.
  1. Naming & Intent Rules:

Files:

  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/utils/LoanStatus.kt
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreen.kt
  • core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/theme/Color.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileViewModel.kt
**/*Screen.kt

⚙️ CodeRabbit configuration file

**/*Screen.kt: Screen architecture rules:

Each screen must follow a 2-layer structure:

  1. Layer 1 (Entry/Route Composable):

    • Function Name: Typically *ScreenRoute or the entry-point composable.
    • Responsibilities: Inject ViewModel (e.g., koinViewModel), take navigation lambdas, collect the StateFlow, and handle ViewModel events.
    • Logic: Should only handle ViewModel interaction, state collection, and triggering navigation in response to ViewModel events.
  2. Layer 2 (Stateful/Content Composable — *Screen or *ScreenContent):

    • Parameters: MUST only take state (the UI state object) and a single onAction lambda (e.g., onAction: (FeatureAction) -> Unit).
    • Responsibilities: Render the UI based strictly on the provided state.
    • Rule: MUST NOT pass multiple separate lambda functions for different UI interactions; consolidate them into the single onAction.
    • Constraint: Must NOT contain any business logic or ViewModel/Navigation references.

Internal/private helper composables (e.g., dialogs, sections, sub-components):

  • These are NOT subject to the single onAction rule.
  • They may accept specific, focused lambdas (e.g., onRetry: () -> Unit) or a narrowed onAction as appropriate.
  • However, they must NOT be passed the ViewModel or navigation controllers directly.

UI consistency:

  • Avoid hardcoded values (dp, sp, padding, fontSize, colors)
  • Use DesignToken, KptTheme, AppColors, MifosTypography for spacing, typography, and colors

Code quality:

  • Keep Composables small and readable
  • Avoid deeply nested UI

Flag:

  • Missing Layer 2 (*Screen/*ScreenContent) separation from the entry-point composable
  • Layer 2 (*Screen/*ScreenContent) receiving multiple separate lambdas instead of a single onAction
  • UI logic inside the entry-point composable
  • Business logic inside any Composable
  • Hardcoded strings instead of using string resources
  • Hardcoded dp/sp values
  • Direct styling instead of using DesignToken or KptTheme

Files:

  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileScreen.kt
**/{core-base,core}/**/*.kt

⚙️ CodeRabbit configuration file

**/{core-base,core}/**/*.kt: Critical Module Change Detection:

  • Changes in core-base module must be treated as high-impact.
  • Flag any PR that modifies files inside core-base for careful review.
  • Verify that modifications in core-base are necessary and minimal.
    Output:
  • Clearly highlight that core-base is a shared foundational module and requires extra review attention.

Files:

  • core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/theme/Color.kt
**/composeResources/values*/strings.xml

⚙️ CodeRabbit configuration file

**/composeResources/values*/strings.xml: String resource conventions:

Naming:

  • All keys must follow:
    feature_{feature_name}_{ui_text_in_snake_case}

Examples:
"In Advance" → feature_loan_in_advance
"Outstanding" → feature_loan_outstanding

  • Avoid generic names like: title, text1, label
  • The suffix should be a short, readable representation of the UI text
  • Avoid multiple keys representing the same UI text
  • Keys must be lowercase and use snake_case

Flag:

  • Incorrect naming pattern
  • Generic or unclear key names
  • Duplicate keys for same UI text

Files:

  • feature/loan/src/commonMain/composeResources/values/strings.xml
**/*ViewModel.kt

⚙️ CodeRabbit configuration file

**/*ViewModel.kt: MVI architecture rules:

  • All new features must follow MVI
  • ViewModel must extend BaseViewModel
  • The ViewModel MUST maintain a single UI state (e.g., a single StateFlow)
    instead of multiple separate state variables.

Required:

  • Use *State, *Event, *Action
  • Naming must be consistent (FeatureViewModel, FeatureState, FeatureEvent, FeatureAction)
  • Follow unidirectional flow:
    Action → ViewModel → State → UI

Internal reducer/action architecture rules:

  • Async operations MUST NOT directly mutate UI state repeatedly inside
    Flow collectors, suspend callbacks, or repository result handlers.

  • Repository/network/database results MUST be converted into internal
    actions using sendAction(...).

  • handleAction(...) must act as the primary reducer responsible for:

    • state mutation
    • reducer-style state transitions
    • triggering follow-up actions
  • Large async methods must be split into:

    1. async collection layer
    2. internal action dispatching
    3. reducer/state handling
  • Avoid directly calling another business/data-loading method from
    repository collectors or async callbacks.
    Prefer dispatching follow-up internal actions instead.

Preferred pattern:
repository result
-> sendAction(...)
-> handleAction(...)
-> mutableStateFlow.update { ... }

Anti-pattern examples:
mutableStateFlow.update { ... } inside collect { }
fetchX() -> directly calls fetchY() inside async collector
large methods mixing:
- collection
- state mutation
- navigation
- business chaining

Flag:

  • Multiple mutableStateFlow.update {} calls inside collect { }
  • Direct state mutation inside async repository callbacks
  • Async methods performing both collection and reducer logic
  • Direct business-flow chaining from async collectors
  • Missing internal reducer actions for async results
  • Missing *State / *Event / *Action
  • ViewModel not...

Files:

  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileViewModel.kt
🧠 Learnings (4)
📚 Learning: 2026-02-06T13:15:16.968Z
Learnt from: sahilshivekar
Repo: openMF/android-client PR: 2603
File: feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanTransaction/LoanTransactionsViewModel.kt:43-106
Timestamp: 2026-02-06T13:15:16.968Z
Learning: Guideline: When a Kotlin function parameter is nullable (e.g., balance: Double?, currencyCode: String?, maximumFractionDigits: Int?) and downstream calls require a non-null value, add null-safety handling in all implementations. Specifically, avoid calling Currency.getInstance(currencyCode) with a possibly null currencyCode; provide a safe default (e.g., currencyCode ?: "$") or validate before use. Ensure all platform targets (Android/Desktop/Native) follow consistent null handling, and consider centralizing currencyCode normalization in the common layer if feasible. Add tests covering null currencyCode to prevent NPEs.

Applied to files:

  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/utils/LoanStatus.kt
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreen.kt
  • core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/theme/Color.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileViewModel.kt
📚 Learning: 2026-04-01T05:03:14.323Z
Learnt from: kartikey004
Repo: openMF/mifos-x-field-officer-app PR: 2659
File: feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanDisbursement/LoanAccountDisbursementScreen.kt:190-195
Timestamp: 2026-04-01T05:03:14.323Z
Learning: In this repo, existing `SelectableDates` / `SelectableDates.isSelectableDate(utcTimeMillis: Long)` implementations use `Clock.System.now().toEpochMilliseconds()` (UTC epoch millis) for date-boundary checks. During PR reviews, do not flag these checks for not using a timezone-aware `LocalDate` start-of-day approach; treat it as an established project-wide pattern. If a change is desired, handle it as a coordinated project-wide improvement rather than as a per-PR review issue.

Applied to files:

  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/utils/LoanStatus.kt
  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountSummary/LoanAccountSummaryScreen.kt
  • core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/theme/Color.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileScreen.kt
  • feature/loan/src/commonMain/kotlin/com/mifos/feature/loan/loanAccountProfile/LoanAccountProfileViewModel.kt
📚 Learning: 2026-02-16T08:37:28.351Z
Learnt from: kartikey004
Repo: openMF/android-client PR: 2610
File: feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientIdentifiersList/ClientIdentifiersListScreen.kt:60-60
Timestamp: 2026-02-16T08:37:28.351Z
Learning: In Kotlin files under the android-client module, replace MaterialTheme references with the established KptTheme import: import template.core.base.designsystem.theme.KptTheme. This should be applied consistently across files where KptTheme is used, replacing any MaterialTheme imports with the correct KptTheme import path.

Applied to files:

  • feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt
📚 Learning: 2026-02-16T08:26:18.398Z
Learnt from: kartikey004
Repo: openMF/android-client PR: 2610
File: core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/theme/DesignToken.kt:328-337
Timestamp: 2026-02-16T08:26:18.398Z
Learning: In core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/theme/DesignToken.kt, ensure size tokens in AppSizes use raw dp-based names like dp5, dp18, dp20, dp42, dp48, dp72, dp100, dp120, dp128, etc., rather than relying solely on semantic names. This naming convention should be accepted for design token definitions in this file and should guide future token definitions without requiring semantic aliases.

Applied to files:

  • core/designsystem/src/commonMain/kotlin/com/mifos/core/designsystem/theme/Color.kt
🔇 Additional comments (3)
core/ui/src/commonMain/composeResources/drawable/send_money.xml (1)

19-19: LGTM!

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

476-484: LGTM!

feature/client/src/commonMain/kotlin/com/mifos/feature/client/clientLoanAccounts/ClientLoanAccountsScreen.kt (1)

256-256: LGTM!

@sonarqubecloud

Copy link
Copy Markdown

@Nagarjuna0033

Copy link
Copy Markdown
Contributor

LGTM

@sahilshivekar
sahilshivekar requested a review from niyajali June 30, 2026 06:38
@niyajali
niyajali merged commit e7d68c5 into openMF:dev Jun 30, 2026
10 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants