Skip to content

Conversation

@mobilebytesenseicommunity
Copy link

@mobilebytesenseicommunity mobilebytesenseicommunity commented Nov 25, 2025

Issue Fix

Fixes #{Issue Number}
Jira Task: Task_Number

Screenshots

Screen.Recording.2025-11-26.at.9.49.40.AM.mov

Description

  • Apply the AndroidStyle.xml style template to your code in Android Studio.

  • Run the unit tests with ./gradlew check 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
    • Instance selector UI to switch Main and Interbank servers.
    • Hidden gesture trigger (five rapid taps) to open the selector.
    • Remote-config-driven list of available instances with access control.
    • Persistent saving of selected instances across sessions.
    • New user-facing strings for update, select instance, server labels, default, tenant and error messages.

✏️ Tip: You can customize this high-level summary in your review settings.

…switching

- Add GitLive Firebase Remote Config SDK dependency
- Create ServerInstance and InstancesConfig data models
- Implement FirebaseInstanceConfigLoader to fetch instance configurations
- Add InstanceConfigManager to manage selected instance
- Update BaseURL to use dynamic configuration instead of hardcoded values
- Update KtorInterceptor to use dynamic tenant ID from selected instance
- Add datastore support for storing selected instance preferences
- Create InstanceSelectorScreen with gesture detection (5 taps)
- Add InstanceSelectorViewModel to manage instance selection
- Integrate gesture detector in MifosApp to show instance selector dialog

This feature allows users to dynamically switch between multiple backend instances
without rebuilding the app. Instance configurations are fetched from Firebase Remote
Config using a single parameter "instances_config_v1" containing JSON with instance details.

Users can trigger the instance selector by tapping 5 times anywhere on the screen.
- Add InstanceType enum (MAIN, INTERBANK) to ServerInstance model
- Update InstancesConfig with helper methods to filter by instance type
- Extend datastore to store both selectedInstance and selectedInterbankInstance
- Update InstanceConfigManager to handle both main and interbank instances
- Add DEFAULT_INTERBANK_INSTANCE with apis.flexcore.mx configuration
- Update BaseURL.interBankUrl to use dynamic configuration from InstanceConfigManager
- Update FirebaseInstanceConfigLoader with default instances for both types
- Redesign InstanceSelectorScreen to display main and interbank instances separately
- Update InstanceSelectorViewModel to handle selection of both instance types
- Users can now independently select main server and interbank server instances

Firebase Remote Config JSON format now supports:
{
  "instances": [
    {
      "endpoint": "mifos-bank-2.mifos.community",
      "protocol": "https://",
      "path": "/fineract-provider/api/v1/",
      "platformTenantId": "mifos-bank-2",
      "label": "Production Main",
      "type": "MAIN",
      "isDefault": true
    },
    {
      "endpoint": "apis.flexcore.mx",
      "protocol": "https://",
      "path": "/v1.0/vnext1/",
      "platformTenantId": "mifos-bank-2",
      "label": "Production Interbank",
      "type": "INTERBANK",
      "isDefault": true
    }
  ]
}
@coderabbitai
Copy link

coderabbitai bot commented Nov 25, 2025

Walkthrough

Adds an instance-selection feature: gesture-triggered Compose UI and ViewModel, serializable server-instance models, Firebase remote-config loader and access control, datastore persistence for chosen instances, and DI/network refactors to source endpoints and tenant headers from runtime configuration.

Changes

Cohort / File(s) Summary
UI & Gesture
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/GestureDetector.kt, cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorScreen.kt, cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorViewModel.kt
Adds Modifier.detectInstanceSelectorGesture (five-tap detector), a composable InstanceSelectorScreen (UI states, list, item, previews) and InstanceSelectorViewModel (loads configs, merges prefs, temp selection, actions/events, persists updates).
Shared wiring
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt, cmp-shared/src/commonMain/kotlin/org/mifospay/shared/MifosPayApp.kt
Registers InstanceSelectorViewModel in Koin; integrates gesture to show InstanceSelectorScreen and dialog lifecycle handling in app root.
Models
core/model/src/commonMain/kotlin/org/mifospay/core/model/instance/ServerInstance.kt, core/model/src/commonMain/kotlin/org/mifospay/core/model/instance/InstancesConfig.kt, core/model/src/commonMain/kotlin/org/mifospay/core/model/instance/InstanceSelectorAccessControl.kt
Adds serializable ServerInstance and InstanceType, InstancesConfig helpers (defaults, main/interbank lists), and InstanceSelectorAccessControl (enabled, allowedDeviceIds, allowedUserIds).
Preferences / Datastore
core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/UserPreferencesDataSource.kt, core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/UserPreferencesRepository.kt, core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/UserPreferencesRepositoryImpl.kt
Persists selected instance and selected interbank instance: adds state flows, updateSelectedInstance/updateSelectedInterbankInstance APIs, and Settings helpers to encode/decode ServerInstance.
Network: config loader & manager
core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt, core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigManager.kt, core/network/src/commonMain/kotlin/org/mifospay/core/network/config/FeatureAccessManager.kt
Adds InstanceConfigLoader interface and FirebaseInstanceConfigLoader (remote-config fetch/observe, DEFAULT_CONFIG fallback, access control), InstanceConfigManager (selected/fallback instances, URL/tenant helpers) and FeatureAccessManager (access rules using BuildConfig, DeviceInfo, remote access control).
Network: DI & runtime
core/network/src/commonMain/kotlin/org/mifospay/core/network/di/NetworkModule.kt, core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/BaseURL.kt, core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/KtorInterceptor.kt, core/network/build.gradle.kts
Wires InstanceConfigLoader/Manager and BaseURL into DI; replaces hard-coded endpoints/tenant constants with config-driven BaseURL and InstanceConfigManager; updates Ktor interceptors to accept configManager; adds firebase-config dependency.
Build & deps
gradle/libs.versions.toml, cmp-android/dependencies/prodReleaseRuntimeClasspath.txt
Bumps gitLive to 2.4.0, adds dev.gitlive:firebase-config, and updates serialization/Kotlin runtime entries in dependency lists.
Platform & device info
core/common/src/commonMain/kotlin/org/mifospay/core/common/BuildConfig.kt, core/common/src/commonMain/kotlin/org/mifospay/core/common/DeviceInfoProvider.kt, core/common/src/androidMain/kotlin/org/mifospay/core/common/DeviceInfoProvider.android.kt, core/common/src/androidMain/kotlin/org/mifospay/core/common/BuildConfig.android.kt, core/common/src/nativeMain/kotlin/org/mifospay/core/common/BuildConfig.native.kt
Adds BuildConfig interface and platform-specific implementations (Android/Native), DeviceInfoProvider (fetch deviceId via Firebase Installations, expect platform device model/os version), and Android-specific provider implementation.
Network utils & interceptor changes
core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/KtorInterceptor.kt, core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/BaseURL.kt
Replaces object BaseURL with class delegating to InstanceConfigManager, adds header constants, updates KtorInterceptor constructors and prepare blocks to use InstanceConfigManager and BaseURL constants.
Resources & metadata
cmp-shared/src/commonMain/composeResources/values/strings.xml, cmp-android/prodRelease-badging.txt
Adds string resources (update, select_instance, main_server, interbank_server, default, tenant_value, error_message) and updates Android badging/version metadata.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant App as MifosPayApp
    participant Gesture as GestureDetector
    participant Screen as InstanceSelectorScreen
    participant VM as InstanceSelectorViewModel
    participant Loader as InstanceConfigLoader
    participant Prefs as UserPreferencesRepository

    User->>Gesture: 5 rapid taps (≤1s)
    Gesture->>App: onGestureDetected()
    App->>App: showInstanceSelector = true
    App->>Screen: mount InstanceSelectorScreen

    Screen->>VM: collect state
    VM->>Loader: fetch/observeInstancesConfig()
    Loader-->>VM: DataState<InstancesConfig>
    VM->>Prefs: read selectedInstance / selectedInterbankInstance
    VM-->>Screen: emit InstanceSelectorState

    User->>Screen: select instance
    Screen->>VM: handleAction(SelectInstance)
    VM->>VM: update temporary selection

    User->>Screen: tap Update
    Screen->>VM: handleAction(UpdateInstances)
    VM->>Prefs: updateSelectedInstance/updateSelectedInterbankInstance
    Prefs-->>VM: DataState<Unit>
    VM-->>Screen: emit DismissSheet
    Screen->>App: onDismiss()
Loading
sequenceDiagram
    participant DI as NetworkModule
    participant Loader as FirebaseInstanceConfigLoader
    participant Manager as InstanceConfigManager
    participant Base as BaseURL
    participant Interceptor as KtorInterceptor
    participant Client as HttpClient

    DI->>Loader: provide FirebaseInstanceConfigLoader
    DI->>Manager: provide InstanceConfigManager(userPrefs)
    DI->>Base: create BaseURL(manager)
    DI->>Interceptor: create KtorInterceptor(getToken, manager)
    DI->>Client: configure clients with BaseURL & Interceptor
    Client->>Interceptor: on request
    Interceptor->>Manager: obtain tenant/header/url values
    Interceptor-->>Client: inject headers & modified request
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

  • Areas needing extra attention:
    • InstanceConfigLoader JSON parsing, DEFAULT_CONFIG fallback, and fetch/activate timing.
    • InstanceSelectorViewModel state merging (temp vs persisted) and event emission path.
    • DI ordering in NetworkModule to ensure loader/manager before BaseURL and clients.
    • KtorInterceptor header injection and use of BaseURL/configManager for tenant/header values.
    • Datastore encoding/decoding of ServerInstance and potential migration impacts.

Possibly related PRs

  • Feature interbank Transfer #1941 — Related work touching interbank models/services, DI registrations, and network client wiring that overlaps with interbank/config changes in this PR.

Suggested reviewers

  • HekmatullahAmin

Poem

🐰 Five tiny taps beneath the moonlit screen,
Firebase hums secrets of endpoints unseen,
I hop through lists and pick the one that's right,
I save, I close — the app sleeps through the night.

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ 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 directly and accurately describes the primary feature added: a Firebase remote config-based instance loader with comprehensive instance/interbank server selection and access control.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (7)
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/GestureDetector.kt (1)

22-47: Consider build-type restrictions for this feature.

The 5-tap gesture to access server instance switching is a development/debug feature that could be accidentally triggered by users or misused in production. Consider restricting this gesture to debug/staging builds only.

core/network/src/commonMain/kotlin/org/mifospay/core/network/di/NetworkModule.kt (1)

92-92: Hardcoded host apis.flexcore.mx may conflict with dynamic instance configuration.

Since this PR introduces dynamic server instance selection, having hardcoded hosts in loggableHosts may be inconsistent. Consider deriving this from the config manager or removing it if no longer needed.

cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorScreen.kt (2)

60-65: Hardcoded strings should use string resources for i18n support.

The strings "Select Instance", "Close", "Loading instances...", "Main Server Instances", "Interbank Server Instances", and "Tenant:" should be defined as string resources for proper internationalization.


101-114: Consider adding a retry mechanism for the error state.

When an error occurs, users have no way to retry loading instances. Adding a retry button would improve the user experience.

             is InstanceSelectorUiState.Error -> {
                 Column(
                     modifier = Modifier
                         .fillMaxSize()
                         .padding(paddingValues),
                     horizontalAlignment = Alignment.CenterHorizontally,
                     verticalArrangement = Arrangement.Center,
                 ) {
                     Text(
                         text = "Error: ${state.message}",
                         color = MaterialTheme.colorScheme.error,
                     )
+                    Spacer(modifier = Modifier.height(16.dp))
+                    Button(onClick = { viewModel.loadInstances() }) {
+                        Text("Retry")
+                    }
                 }
             }
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorViewModel.kt (1)

68-70: Redundant Loading state handling.

The DataState.Loading branch sets the UI state to Loading, but this is already set at line 50 before the suspend call. If fetchInstancesConfig() is a suspend function that returns a final result (Success/Error), it should never return Loading. If it can return Loading, consider whether this pattern is intended.

If fetchInstancesConfig() always returns a terminal state, this branch can be removed to simplify the code.

core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt (1)

35-61: Surface Remote Config failures and avoid duplicating default configs

Right now any exception, blank JSON, or empty instances list is treated as DataState.Success(DEFAULT_CONFIG), with the original failure completely hidden. This can make diagnosing misconfigured Remote Config or serialization issues difficult, and callers can’t distinguish “remote defaulted” from “locally defaulted”.

Additionally, the default main/interbank instances here duplicate the defaults defined in InstanceConfigManager. If one is updated and the other isn’t, you’ll get divergent behavior depending on the code path.

Consider:

  • Logging or otherwise surfacing the exception (and optionally using a DataState.Error that still carries DEFAULT_CONFIG as a fallback payload).
  • Centralizing the default instance definitions (e.g., via a shared InstancesConfig.DEFAULT/factory or by reusing InstanceConfigManager’s defaults) so there is a single canonical source of truth.

Also applies to: 71-95

core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigManager.kt (1)

20-42: De-duplicate default instances and harden self-service URL composition

InstanceConfigManager provides a nice single point of access for instance-derived URLs and tenant IDs. Two refinement points:

  • The DEFAULT_MAIN_INSTANCE / DEFAULT_INTERBANK_INSTANCE definitions here duplicate the defaults in FirebaseInstanceConfigLoader. Consider extracting a shared default definition (e.g., in the model layer) so both the loader and manager depend on the same constants and can’t drift independently.

  • getSelfServiceUrl() assumes path already includes a trailing /. To be more robust against future Remote Config changes, you could base this on fullUrl (e.g., getCurrentInstance().fullUrl + "self/") or normalise path to ensure exactly one / between segments.

Also applies to: 44-71

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3e5f56f and c309eb0.

📒 Files selected for processing (17)
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt (2 hunks)
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/GestureDetector.kt (1 hunks)
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorScreen.kt (1 hunks)
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorViewModel.kt (1 hunks)
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosApp.kt (5 hunks)
  • core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/UserPreferencesDataSource.kt (5 hunks)
  • core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/UserPreferencesRepository.kt (3 hunks)
  • core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/UserPreferencesRepositoryImpl.kt (3 hunks)
  • core/model/src/commonMain/kotlin/org/mifospay/core/model/instance/InstancesConfig.kt (1 hunks)
  • core/model/src/commonMain/kotlin/org/mifospay/core/model/instance/ServerInstance.kt (1 hunks)
  • core/network/build.gradle.kts (1 hunks)
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt (1 hunks)
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigManager.kt (1 hunks)
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/di/NetworkModule.kt (3 hunks)
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/BaseURL.kt (1 hunks)
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/KtorInterceptor.kt (5 hunks)
  • gradle/libs.versions.toml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorScreen.kt (1)
core-base/designsystem/src/commonMain/kotlin/template/core/base/designsystem/KptTheme.kt (1)
  • KptTheme (55-69)
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosApp.kt (1)
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorScreen.kt (1)
  • InstanceSelectorScreen (48-117)
core/network/src/commonMain/kotlin/org/mifospay/core/network/di/NetworkModule.kt (2)
core-base/network/src/commonMain/kotlin/org/mifos/corebase/network/KtorHttpClient.kt (1)
  • setupDefaultHttpClient (67-162)
core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/KtorInterceptor.kt (2)
  • install (33-52)
  • install (76-97)
🔇 Additional comments (15)
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/di/KoinModules.kt (1)

48-69: LGTM!

The ViewModel registration follows the existing pattern and correctly wires InstanceSelectorViewModel into the shared Koin module.

core/network/build.gradle.kts (1)

41-41: LGTM!

The Firebase Remote Config dependency is correctly added to the commonMain source set, consistent with other gitlive Firebase dependencies in the project.

core/model/src/commonMain/kotlin/org/mifospay/core/model/instance/ServerInstance.kt (1)

19-31: LGTM!

The ServerInstance data class is well-structured with sensible defaults and a useful fullUrl computed property.

cmp-shared/src/commonMain/kotlin/org/mifospay/shared/ui/MifosApp.kt (1)

174-184: LGTM!

The Dialog integration correctly manages the show/dismiss state and renders the InstanceSelectorScreen appropriately.

cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorScreen.kt (2)

48-117: LGTM overall!

The screen structure with state handling for Loading, Success, and Error states follows good practices. The UI composition is clean and well-organized.


174-219: LGTM!

The InstanceItem component is well-designed with clear visual hierarchy showing label, URL, and tenant information alongside the selection radio button.

core/network/src/commonMain/kotlin/org/mifospay/core/network/di/NetworkModule.kt (1)

32-46: Unfortunately, I'm unable to access the repository to verify this concern directly. However, based on the code snippet provided and the nature of the concern, I can offer my assessment:

The review comment raises a valid architectural question. The concern states that InstanceConfigLoader is registered as a singleton but InstanceConfigManager only receives userPreferencesRepository as a dependency.

Without being able to inspect the actual InstanceConfigManager class definition, I cannot definitively confirm whether:

  1. The loader is intentionally managed separately (e.g., lazy-loaded or initialized internally)
  2. The loader is truly orphaned and should be injected
  3. The manager derives its config through userPreferencesRepository rather than directly from the loader

Given the constraints, I recommend:

For the developer to verify this locally:

  • Check if InstanceConfigManager internally instantiates or uses InstanceConfigLoader
  • Confirm whether userPreferencesRepository provides the configuration data that would otherwise come from the loader
  • If the loader is unused, consider either removing it or injecting it into InstanceConfigManager

InstanceConfigLoader is registered but its relationship to InstanceConfigManager requires verification. The singleton is declared but InstanceConfigManager receives only userPreferencesRepository. Confirm whether the manager needs InstanceConfigLoader as an explicit dependency or if its configuration needs are satisfied through the preferences repository.

gradle/libs.versions.toml (1)

223-223: Library version verified.

The dev.gitlive:firebase-config:2.1.0 is a valid published version available on Maven Central Repository. The library entry follows the existing pattern for gitlive dependencies and uses the correct version reference.

core/model/src/commonMain/kotlin/org/mifospay/core/model/instance/InstancesConfig.kt (1)

14-29: Clean model implementation with appropriate filtering logic.

The data class is well-designed with immutable properties and functional helper methods. Using firstOrNull gracefully handles the case where no default exists.

One minor consideration: if the remote config contains multiple instances marked as isDefault=true for the same type, only the first one will be returned. This is likely acceptable behavior, but consider adding validation or logging if strict single-default enforcement is needed.

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

36-53: Interface additions follow existing patterns consistently.

The new properties and methods align well with the existing API surface. Using StateFlow<ServerInstance?> for observable selections and DataState<Unit> return types for updates maintains consistency with other repository methods.

core/datastore/src/commonMain/kotlin/org/mifospay/core/datastore/UserPreferencesRepositoryImpl.kt (2)

76-88: StateFlow properties follow existing conventions.

The new selectedInstance and selectedInterbankInstance properties are implemented consistently with other StateFlow properties in the class, using the same scope, initial value, and sharing strategy.


100-116: Update methods are consistent with existing patterns.

The implementation follows the same try/catch pattern as other update methods in this repository. While catching generic Exception is broad, this maintains consistency with the existing codebase style.

core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/BaseURL.kt (1)

14-34: ---

Breaking change from singleton to DI-injectable class—verify all usages before merging.

The transition from a Kotlin object to a constructor-injected class enables dynamic server configuration. The delegation to InstanceConfigManager centralizes endpoint resolution logic appropriately.

However, this is a breaking change requiring all call sites to be updated. Verification is needed to confirm:

  • All BaseURL usages have been migrated to dependency injection patterns
  • No singleton-style direct accesses (BaseURL.url, BaseURL.selfServiceUrl, etc.) remain in the codebase
  • DI container is properly wired to provide BaseURL instances
core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt (1)

23-26: Interface and loader wiring look coherent

The InstanceConfigLoader abstraction and FirebaseInstanceConfigLoader implementation are straightforward, DI‑friendly, and keep the Remote Config setup localized. The one‑shot observeInstancesConfig() wrapper around fetchInstancesConfig() is also consistent with the current “fetch on demand” behavior.

Also applies to: 28-34, 64-69, 98-100

core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/KtorInterceptor.kt (1)

21-26: Dynamic tenant header integration via InstanceConfigManager looks correct

Wiring InstanceConfigManager into both KtorInterceptor and KtorInterceptorRe and using getPlatformTenantId() for HEADER_TENANT aligns the HTTP layer with the active instance configuration. The new Config/ConfigRe fields continue the existing lateinit pattern, so as long as configManager is always set when installing the plugins, this should behave as intended.

Also applies to: 33-39, 54-57, 61-64, 66-69, 76-83, 99-102, 106-109

therajanmaurya and others added 2 commits November 26, 2025 01:51
- Remove remoteConfigSettings import (not available in GitLive SDK)
- Change settings configuration to use suspend function
- Update getString() to use bracket notation with asString()
- Move settings configuration inside fetchInstancesConfig() method

This fixes compilation errors with the GitLive Firebase SDK.
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (2)
core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt (2)

27-32: Consider injecting dependencies for testability.

The remoteConfig is directly accessed from Firebase.remoteConfig singleton, making unit testing difficult. Consider injecting the remote config instance via constructor to allow mocking in tests.

-class FirebaseInstanceConfigLoader : InstanceConfigLoader {
-    private val remoteConfig = Firebase.remoteConfig
+class FirebaseInstanceConfigLoader(
+    private val remoteConfig: FirebaseRemoteConfig = Firebase.remoteConfig,
+) : InstanceConfigLoader {
     private val json = Json {
         ignoreUnknownKeys = true
         isLenient = true
     }

95-97: observeInstancesConfig doesn't actually observe changes.

The method name suggests continuous observation, but it only emits once and completes. Firebase Remote Config supports real-time updates via addOnConfigUpdateListener. If continuous observation isn't needed, consider renaming to fetchInstancesConfigAsFlow() to clarify behavior.

If one-shot flow is intentional, rename for clarity:

-    override fun observeInstancesConfig(): Flow<DataState<InstancesConfig>> = flow {
+    override fun fetchInstancesConfigAsFlow(): Flow<DataState<InstancesConfig>> = flow {
         emit(fetchInstancesConfig())
     }

Or implement true observation using callbackFlow with Remote Config's update listener if real-time updates are desired.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 464256e and 7b5377f.

📒 Files selected for processing (1)
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt (1 hunks)
🔇 Additional comments (3)
core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt (3)

1-21: LGTM!

License header and imports are appropriate for the Firebase Remote Config functionality.


22-25: LGTM!

Clean interface design with appropriate separation between one-shot fetch and reactive observation patterns.


34-61: LGTM!

The default configuration provides sensible fallback values. The fetch interval of 1 hour is reasonable for Remote Config. Having hardcoded defaults ensures the app remains functional even if Remote Config fails.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

♻️ Duplicate comments (4)
core/network/src/commonMain/kotlin/org/mifospay/core/network/di/NetworkModule.kt (1)

84-88: Hardcoded credentials - already flagged in previous review.

core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt (2)

82-112: Error handling masks failures and settings are reconfigured on every fetch.

As previously noted, this method has several issues:

  1. All errors return DataState.Success(DEFAULT_CONFIG), making it impossible for callers to distinguish successful remote fetch from fallback due to errors
  2. Exceptions are silently swallowed without logging
  3. remoteConfig.settings is called on every fetch instead of once at initialization

82-112: Add logging to enable debugging of config fetch failures.

The catch block silently returns DEFAULT_CONFIG with no logging. In production, there would be no visibility into:

  • Whether remote config fetch succeeded or failed
  • Why it failed (network, parsing, Firebase SDK issues)
  • That fallback config is being used

Consider adding error logging in the catch block:

 } catch (e: Exception) {
+    // Log error for production debugging
+    println("Failed to fetch remote config, using defaults: ${e.message}")
     // Return default config on error
     DataState.Success(DEFAULT_CONFIG)
 }
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorViewModel.kt (1)

116-140: Persistence failures are unhandled, leading to data inconsistency.

As previously noted, the results of updateSelectedInstance and updateSelectedInterbankInstance are discarded. If persistence fails:

  1. Temp selections are still reset (lines 135-136)
  2. Sheet is still dismissed (line 138)
  3. User believes their selection was saved, but it wasn't

On app restart, the previous selection would reappear, creating a confusing UX.

🧹 Nitpick comments (8)
core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/KtorInterceptor.kt (1)

64-100: Consider extracting common interceptor logic to reduce duplication.

Both KtorInterceptor and KtorInterceptorRe have nearly identical install() implementations for setting headers and handling unauthorized responses. The only difference is how the token is obtained.

Consider a shared helper or base implementation:

private fun HttpClient.installCommonHeaders(configManager: InstanceConfigManager) {
    requestPipeline.intercept(HttpRequestPipeline.State) {
        context.header(BaseURL.HEADER_CONTENT_TYPE, BaseURL.HEADER_CONTENT_TYPE_VALUE)
        context.header(BaseURL.HEADER_ACCEPT, BaseURL.HEADER_ACCEPT_VALUE)
        context.header(BaseURL.HEADER_TENANT, configManager.getPlatformTenantId())
    }
}

private fun HttpClient.installUnauthorizedHandler() {
    responsePipeline.intercept(HttpResponsePipeline.After) {
        if (context.response.status == HttpStatusCode.Unauthorized) {
            GlobalAuthManager.markUnauthorized()
        }
        proceedWith(subject)
    }
}
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/MifosPayApp.kt (2)

54-55: Inconsistent state declaration patterns.

Line 54 uses var showErrorDialog = remember { mutableStateOf<Boolean>(false) } requiring .value access, while line 55 uses var showInstanceSelector by remember { mutableStateOf(false) } with delegate syntax. For consistency, both should use the same pattern.

-    var showErrorDialog = remember { mutableStateOf<Boolean>(false) }
+    var showErrorDialog by remember { mutableStateOf(false) }

Then update references from showErrorDialog.value to just showErrorDialog.


93-100: Redundant MifosTheme wrapper.

InstanceSelectorScreen is already wrapped in MifosTheme here (lines 95-99), but the screen will be displayed while the main MifosTheme at line 102 is also active. This nested theming is likely unnecessary and could cause subtle styling inconsistencies.

     // Show instance selector dialog
     if (showInstanceSelector) {
-        MifosTheme {
-            InstanceSelectorScreen(
-                onDismiss = { showInstanceSelector = false },
-            )
-        }
+        InstanceSelectorScreen(
+            onDismiss = { showInstanceSelector = false },
+        )
     }
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorScreen.kt (2)

77-82: Modifier applied to both parent and child may cause unintended layout behavior.

The same modifier is passed to both MifosBottomSheet (line 79) and MifosScaffold (line 82). This can lead to doubled padding, sizing constraints, or other layout issues depending on what modifiers are applied externally.

Typically, the outer container should receive the external modifier:

     MifosBottomSheet(
         onDismiss = onDismiss,
         modifier = modifier,
     ) {
         MifosScaffold(
-            modifier = modifier,
+            modifier = Modifier,
             bottomBar = {

337-355: Inconsistent theme usage in previews.

InstanceItemPreview uses KptTheme while InstanceItemUnselectedPreview and InstancesListPreview use MifosTheme. Previews should use consistent theming to accurately represent how components will appear in the app.

 @Preview
 @Composable
 private fun InstanceItemPreview() {
-    KptTheme {
+    MifosTheme {
         InstanceItem(

Also applies to: 357-375

core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/BaseURL.kt (1)

14-16: Add KDoc to document the class purpose and usage.

Since BaseURL is a public class and represents a significant refactor from a singleton to a DI-friendly component, consider adding KDoc to explain its role in providing runtime-configurable base URLs for network requests.

core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt (1)

114-116: Method name suggests continuous observation but implementation fetches once.

The name observeInstancesConfig() implies the Flow will emit updates when remote config changes, but it only fetches once and completes. Consider either:

  • Renaming to fetchInstancesConfigAsFlow() to clarify the one-shot behavior, or
  • Implementing true observation of Remote Config updates if Firebase supports listeners
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorViewModel.kt (1)

165-169: Remove unused ConfigLoaded action.

The ConfigLoaded action is defined but never dispatched. Config loading is handled directly in the init block via combine (lines 41-42), not through the action handling system. The case at lines 110-112 is effectively dead code.

 sealed interface InstanceSelectorAction {
     data class SelectInstance(val instance: ServerInstance) : InstanceSelectorAction
     data object UpdateInstances : InstanceSelectorAction
-    data class ConfigLoaded(val config: DataState<InstancesConfig>) : InstanceSelectorAction
 }

And remove the no-op handler:

         is InstanceSelectorAction.UpdateInstances -> {
             updateInstances()
         }
-
-        is InstanceSelectorAction.ConfigLoaded -> {
-            // Handled by init block
-        }
     }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b5377f and df2d3a2.

📒 Files selected for processing (10)
  • cmp-android/dependencies/prodReleaseRuntimeClasspath.txt (5 hunks)
  • cmp-android/prodRelease-badging.txt (1 hunks)
  • cmp-shared/src/commonMain/composeResources/values/strings.xml (1 hunks)
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/MifosPayApp.kt (4 hunks)
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorScreen.kt (1 hunks)
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorViewModel.kt (1 hunks)
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt (1 hunks)
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/di/NetworkModule.kt (3 hunks)
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/BaseURL.kt (1 hunks)
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/KtorInterceptor.kt (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (2)
core/network/src/commonMain/kotlin/org/mifospay/core/network/di/NetworkModule.kt (1)
core-base/network/src/commonMain/kotlin/org/mifos/corebase/network/KtorHttpClient.kt (1)
  • setupDefaultHttpClient (67-162)
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorViewModel.kt (1)
core-base/ui/src/commonMain/kotlin/template/core/base/ui/BaseViewModel.kt (1)
  • sendEvent (97-99)
🔇 Additional comments (10)
cmp-android/prodRelease-badging.txt (1)

1-131: I encountered a repository clone failure that prevents me from verifying the concerns raised in the review comment. Without access to the codebase, I cannot:

  1. Confirm whether prodRelease-badging.txt is tracked in git or covered by .gitignore
  2. Search for the mentioned classes (InstanceSelectorViewModel, ServerInstance, InstancesConfig, InstanceConfigManager) to verify if source code files exist
  3. Determine whether the actual source files implementing these features are part of this PR or missing

Since the automated verification is blocked and the review raises substantive claims about:

  • Whether a generated artifact should be in version control
  • Whether critical source code files are missing from the review

I must mark this for manual verification.


Auto-generated artifact may need version control review.

The prodRelease-badging.txt file is generated by Android's aapt tool. Verify whether this artifact should be committed to the repository or added to .gitignore. Generated artifacts can create merge conflicts and make diffs harder to review.

Recommendation: Confirm the repository's policy on committing build artifacts like badging files, and adjust .gitignore if necessary.


Verify completeness of source code files for review.

The PR description mentions feature additions (InstanceSelectorViewModel, ServerInstance, InstancesConfig models, and InstanceConfigManager), but only a build artifact was provided for review. Confirm that all relevant source code files implementing these features are included and accessible for review.

cmp-android/dependencies/prodReleaseRuntimeClasspath.txt (3)

1-462: Verify classpath file generation and build consistency.

This file appears to be auto-generated from a Gradle build process. Before merging:

  1. Confirm whether this file should be committed to version control or if it's regenerated during each build
  2. Run ./gradlew clean build and verify the classpath file regenerates consistently
  3. Ensure the PR contributor has completed the full test suite (./gradlew check) as noted in the incomplete checklist

The PR description indicates the contributor checklist is incomplete (unit tests and style validation have not been checked).


456-462: Verify Kotlin 2.2.0 requirement for kotlinx-serialization 1.9.0 upgrade.

The kotlinx-serialization 1.9.0 is a stable release, but it introduces a breaking change: it requires Kotlin 2.2.0 (the 1.8.1 version used Kotlin 2.1.x). Before merging this PR:

  1. Confirm the Kotlin Gradle plugin and compiler have been upgraded to 2.2.0 in this PR
  2. If kotlinx-datetime is used in the codebase, verify compatibility with v0.7.0+ (which removed kotlinx.datetime.Instant in favor of kotlin.time.Instant)
  3. Verify serialization for ServerInstance and InstancesConfig models works after the Kotlin upgrade
  4. Run ./gradlew check to test the upgrade (the PR checklist indicates tests have not yet been executed)

248-248: Based on my verification through web search, I can now provide an updated review comment:


Verify Compose compiler plugin compatibility with Kotlin 2.2.20 upgrade and confirm tests pass.

The Firebase dependency versions are stable and compatible:

  • dev.gitlive:firebase-config:2.4.0 correctly works with firebase-config:22.1.2 (GitLive wrapper forwards to native Firebase SDKs)
  • firebase-abt:21.1.1 aligns with the Firebase Remote Config ecosystem
  • kotlinx-serialization:1.9.0 is compatible with kotlin-stdlib:2.2.20 (both target Kotlin 2.2.x)
  • No public security advisories found for these versions

However, the Kotlin stdlib bump from 2.1.21 to 2.2.20 requires attention:

  • Jetpack Compose UI 1.8.3 predates Kotlin 2.2.20; compatibility depends on using a matching Compose Compiler Gradle plugin version
  • Ensure composeCompilerVersion or equivalent is properly configured for Kotlin 2.2.20
  • The PR description shows unchecked "Run unit tests with ./gradlew check" — verify the full test suite passes, particularly for Compose-related code

Also applies to: 257-257, 301-306, 439-439, 456-462

core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/KtorInterceptor.kt (1)

23-56: LGTM! Config-driven header construction is a good improvement.

The refactor to use InstanceConfigManager for tenant resolution and BaseURL constants for header names improves configurability and reduces hardcoded values.

cmp-shared/src/commonMain/kotlin/org/mifospay/shared/MifosPayApp.kt (1)

108-115: Gesture detection condition may not update reactively during navigation.

The navDestination is computed once from uiState and won't react to actual navigation changes within the graph. If the user navigates from login to another screen without authentication state changes, the gesture will still be active.

Consider using navController.currentBackStackEntryAsState() for reactive navigation state:

val currentEntry by navController.currentBackStackEntryAsState()
val isOnLoginGraph = currentEntry?.destination?.parent?.route == LOGIN_GRAPH
// ...
modifier = modifier.detectInstanceSelectorGesture(
    onGestureDetected = {
        if (isOnLoginGraph) {
            showInstanceSelector = true
        }
    },
),
core/network/src/commonMain/kotlin/org/mifospay/core/network/di/NetworkModule.kt (2)

51-73: LGTM! Config-driven URL and header construction.

The SelfClient now properly sources its base URL from BaseURL and uses InstanceConfigManager for dynamic tenant configuration.


36-45: Based on my verification attempts, I was unable to access the repository due to network/access issues, which prevents me from confirming the actual InstanceConfigManager constructor signature and verifying whether FirebaseInstanceConfigLoader is used elsewhere in the codebase.

However, the original review comment's logic is sound: if FirebaseInstanceConfigLoader is instantiated as a singleton but never injected or referenced, it would indicate an incomplete wiring issue in the dependency injection setup. The concern that Firebase config loading might not actually be wired into the manager is a legitimate one that requires manual verification by examining the actual class definitions and their usage patterns.

InstanceConfigLoader may be instantiated but unused in the DI container.

The review identifies a potential dependency injection wiring issue where FirebaseInstanceConfigLoader is created but InstanceConfigManager doesn't receive it. Verify that:

  • InstanceConfigManager's constructor actually expects a configLoader parameter
  • The Firebase instance loading is intentionally delegated to userPreferencesRepository

If InstanceConfigManager should use the loader, apply the suggested fix. Otherwise, remove the unused FirebaseInstanceConfigLoader declaration.

cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorScreen.kt (1)

229-335: Well-structured instance item component.

Good implementation with clear visual hierarchy: label, type badge, default indicator, endpoint, and tenant ID. The clickable card with radio button provides good UX for selection.

core/network/src/commonMain/kotlin/org/mifospay/core/network/utils/BaseURL.kt (1)

27-37: I'll verify the performance concern about repeated property access in the BaseURL class. Let me start by searching for where these properties are accessed.
<function_calls>

#!/bin/bash

Find all usages of BaseURL properties to assess access frequency

First, find the BaseURL class location and imports

rg -nP 'class BaseURL' --type=kotlin

Search for accesses to BaseURL properties

rg -nP '.(url|selfServiceUrl|interBankUrl|fineractPlatformTenantId)\b'
--type=kotlin
-C2
-g '!/test/'


</function_calls>

Now let me examine the configManager implementation to understand what these methods do:
<function_calls>

#!/bin/bash

Find configManager implementation and interface

rg -nP '(interface|class)\s+.*[Cc]onfigManager' --type=kotlin -A5

Search for getUrl, getSelfServiceUrl, etc. implementations

rg -nP 'fun\s+(getUrl|getSelfServiceUrl|getInterbankUrl|getPlatformTenantId)\s*('
--type=kotlin
-A5


</function_calls>

Let me also check if there are any network interceptors or hot paths using these properties:
<function_calls>

#!/bin/bash

Search for network interceptors and their usage patterns

rg -nP '(Interceptor|intercept|OkHttp)' --type=kotlin -C2 | head -100

Look for BaseURL instantiation and injection patterns

rg -nP 'BaseURL' --type=kotlin -C1 | head -100


</function_calls>

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

♻️ Duplicate comments (2)
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorViewModel.kt (2)

38-88: Simplify combine + stateIn + launchIn pipeline

You create a StateFlow via stateIn and then immediately launchIn(viewModelScope) on it, discarding both the StateFlow and the Job. The side‑effects already happen inside the combine block, so the stateIn layer is unused overhead.

You can simplify to a single cold‑flow collection:

-        combine(
+        combine(
             instanceConfigLoader.observeInstancesConfig(),
             userPreferencesRepository.selectedInstance,
             userPreferencesRepository.selectedInterbankInstance,
             tempSelectedMainInstance,
             tempSelectedInterbankInstance,
         ) { remoteConfig, selectedMainInstance, selectedInterbankInstance, tempMainInstance, tempInterbankInstance ->
             // existing when(remoteConfig) { ... } body
-        }.stateIn(
-            scope = viewModelScope,
-            started = SharingStarted.WhileSubscribed(5_000),
-            initialValue = null,
-        ).launchIn(viewModelScope)
+        }.launchIn(viewModelScope)

This keeps the same behavior with less complexity and no unused StateFlow.


115-138: Handle failures when persisting selected instances

updateInstances always dismisses the sheet and clears temps even if the underlying persistence calls fail (either by throwing or by returning an error result). That can leave the user believing the selection was saved when it wasn’t.

At minimum, wrap the updates in runCatching / trycatch, only dismiss on success, and surface an error on failure:

     private fun updateInstances() {
         viewModelScope.launch {
-            val currentState = state
-            val mainInstanceChanged =
-                tempSelectedMainInstance.value != currentState.selectedMainInstance
-            val interbankInstanceChanged =
-                tempSelectedInterbankInstance.value != currentState.selectedInterbankInstance
-
-            if (mainInstanceChanged && tempSelectedMainInstance.value != null) {
-                userPreferencesRepository.updateSelectedInstance(tempSelectedMainInstance.value!!)
-            }
-
-            if (interbankInstanceChanged && tempSelectedInterbankInstance.value != null) {
-                userPreferencesRepository.updateSelectedInterbankInstance(
-                    tempSelectedInterbankInstance.value!!,
-                )
-            }
-
-            // Reset temp selections after update
-            tempSelectedMainInstance.value = null
-            tempSelectedInterbankInstance.value = null
-
-            sendEvent(InstanceSelectorEvent.DismissSheet)
+            val currentState = state
+            val mainInstance = tempSelectedMainInstance.value
+            val interbankInstance = tempSelectedInterbankInstance.value
+
+            val mainInstanceChanged = mainInstance != currentState.selectedMainInstance
+            val interbankInstanceChanged = interbankInstance != currentState.selectedInterbankInstance
+
+            runCatching {
+                if (mainInstanceChanged && mainInstance != null) {
+                    userPreferencesRepository.updateSelectedInstance(mainInstance)
+                }
+                if (interbankInstanceChanged && interbankInstance != null) {
+                    userPreferencesRepository.updateSelectedInterbankInstance(interbankInstance)
+                }
+            }.onSuccess {
+                tempSelectedMainInstance.value = null
+                tempSelectedInterbankInstance.value = null
+                sendEvent(InstanceSelectorEvent.DismissSheet)
+            }.onFailure {
+                // Optionally log and reflect error in state for UI feedback
+                mutableStateFlow.update { it.copy(error = "Failed to update instances") }
+            }
         }
     }

Adjust the error reporting to match your UX, but some form of failure handling here will make the flow more robust.

🧹 Nitpick comments (1)
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorViewModel.kt (1)

163-167: Consider removing unused ConfigLoaded action

InstanceSelectorAction.ConfigLoaded is defined but effectively a no‑op in handleAction, and the configuration is already fully driven from the init combine block.

If there are no actual call sites sending ConfigLoaded, consider dropping this variant to keep the action surface minimal and avoid confusion:

 sealed interface InstanceSelectorAction {
     data class SelectInstance(val instance: ServerInstance) : InstanceSelectorAction
     data object UpdateInstances : InstanceSelectorAction
-    data class ConfigLoaded(val config: DataState<InstancesConfig>) : InstanceSelectorAction
 }

If you plan to use it later, a short KDoc comment explaining its intended role would also help.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between df2d3a2 and 3293a55.

📒 Files selected for processing (2)
  • cmp-android/prodRelease-badging.txt (1 hunks)
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorViewModel.kt (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorViewModel.kt (1)
core-base/ui/src/commonMain/kotlin/template/core/base/ui/BaseViewModel.kt (1)
  • sendEvent (97-99)
🔇 Additional comments (1)
cmp-android/prodRelease-badging.txt (1)

1-1: Version bump looks fine

The change here is just the versionName metadata update; no behavioral impact.

@openMF openMF deleted a comment from coderabbitai bot Nov 26, 2025
…r-019GTifqCMdZeH5hUVeB4WKX' into claude/firebase-remote-config-loader-019GTifqCMdZeH5hUVeB4WKX
@therajanmaurya therajanmaurya changed the title Claude/firebase remote config loader Firebase remote config instance loader Nov 26, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (1)
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/GestureDetector.kt (1)

33-49: LGTM! Gesture detection logic is correct.

The tap detection logic properly handles the timeout window and tap count. The implementation correctly resets the count when taps are too far apart and triggers the callback after 5 consecutive taps within 1 second.

Optional enhancement: Consider resetting lastTapTime when the gesture is detected (line 45) for state consistency:

                if (tapCount >= 5) {
                    onGestureDetected()
                    tapCount = 0
+                   lastTapTime = 0L
                }

This is not strictly necessary since the logic works correctly as-is, but it makes the state reset more complete.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3293a55 and 7ba0e26.

📒 Files selected for processing (1)
  • cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/GestureDetector.kt (1 hunks)
🔇 Additional comments (3)
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/GestureDetector.kt (3)

1-9: LGTM!

Standard copyright header with appropriate Mozilla Public License v2.0.


25-31: LGTM!

Function signature and state initialization are well-structured. The 1-second timeout window for consecutive taps is a reasonable choice for gesture detection.


22-24: Now I have crucial context: the project uses Kotlin 1.9.x, which is correct for the timeline mentioned in the review comment. However, my earlier web search revealed an important nuance:

Even in Kotlin 1.9+, the @OptIn(ExperimentalTime::class) annotation is still needed if the code uses any kotlin.time members that remain experimental/preview. The annotation is not universally unnecessary just because Kotlin 1.9 stabilized parts of the API—it depends on which specific members are being used.

Since I cannot clone the repository to inspect which kotlin.time symbols are actually being imported and used in GestureDetector.kt, I cannot definitively verify whether the annotation can be safely removed.


The @OptIn(ExperimentalTime::class) annotation may still be necessary.

While Kotlin 1.9+ stabilized most of the kotlin.time API, the ExperimentalTime opt-in is still required for any remaining experimental members. Verify which specific kotlin.time symbols are imported and used in this file—if only stable APIs are used, the annotation can be removed; otherwise, it should be retained.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

♻️ Duplicate comments (2)
core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt (2)

43-82: Unresolved: apis.flexcore.mx endpoint issue persists.

This was flagged in a previous review. The endpoint returns HTTP 404 and has no public documentation. Please address the prior feedback before merging.


111-114: Unresolved: Silent error handling returns success state.

This was flagged in a previous review. The catch block returns DataState.Success(DEFAULT_CONFIG), masking failures from callers. Please address the prior feedback.

🧹 Nitpick comments (3)
core/common/src/commonMain/kotlin/org/mifospay/core/common/DeviceInfoProvider.kt (1)

26-31: Consider logging the exception before falling back.

Silently swallowing all exceptions and returning "unknown" can make debugging difficult. Consider logging the error at a debug/warning level to aid troubleshooting when Firebase Installation ID retrieval fails.

core/common/src/nativeMain/kotlin/org/mifospay/core/common/BuildConfig.native.kt (1)

25-29: Track TODO: Retrieve version info from iOS bundle.

The hardcoded version values are noted with TODOs. Consider creating a tracking issue to implement proper version retrieval from the iOS bundle (e.g., CFBundleShortVersionString and CFBundleVersion).

Would you like me to open an issue to track implementing proper iOS version retrieval?

core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt (1)

117-119: Method name implies continuous observation but only emits once.

observeInstancesConfig suggests reactive updates when config changes, but this implementation performs a single fetch and emits. Consider either:

  1. Renaming to fetchInstancesConfigAsFlow() to match behavior
  2. Implementing actual observation using Firebase Remote Config listeners for real-time updates
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7ba0e26 and 58cf6e4.

📒 Files selected for processing (8)
  • core/common/src/androidMain/kotlin/org/mifospay/core/common/BuildConfig.android.kt (1 hunks)
  • core/common/src/androidMain/kotlin/org/mifospay/core/common/DeviceInfoProvider.android.kt (1 hunks)
  • core/common/src/commonMain/kotlin/org/mifospay/core/common/BuildConfig.kt (1 hunks)
  • core/common/src/commonMain/kotlin/org/mifospay/core/common/DeviceInfoProvider.kt (1 hunks)
  • core/common/src/nativeMain/kotlin/org/mifospay/core/common/BuildConfig.native.kt (1 hunks)
  • core/model/src/commonMain/kotlin/org/mifospay/core/model/instance/InstanceSelectorAccessControl.kt (1 hunks)
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/config/FeatureAccessManager.kt (1 hunks)
  • core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt (1 hunks)
🔇 Additional comments (9)
core/model/src/commonMain/kotlin/org/mifospay/core/model/instance/InstanceSelectorAccessControl.kt (1)

14-19: LGTM!

Clean and well-structured data class with sensible defaults for access control configuration.

core/common/src/commonMain/kotlin/org/mifospay/core/common/BuildConfig.kt (1)

15-37: LGTM!

Clean interface design and well-organized constants for build configuration abstraction across platforms.

core/common/src/commonMain/kotlin/org/mifospay/core/common/DeviceInfoProvider.kt (1)

48-53: I'm experiencing persistent repository cloning issues that prevent me from executing the verification scripts. However, based on the review comment's concern about verifying actual implementations for expect functions, I can provide guidance:

The original review comment correctly identifies an important requirement for Kotlin Multiplatform code. The expect functions declared in commonMain must have corresponding actual implementations in each platform source set.

Given the technical limitations preventing direct verification, here is the rewritten review comment:


Verify that actual implementations exist for all platform targets.

These expect function declarations require corresponding actual implementations in platform-specific source sets (androidMain, iosMain, nativeMain, etc.). Compilation will fail for any platform target missing these implementations. Ensure implementations are provided for all supported platforms.

core/common/src/nativeMain/kotlin/org/mifospay/core/common/BuildConfig.native.kt (1)

35-37: Ensure actual object Platform implementations exist for all native target platforms in this project.

The expect object Platform requires matching actual implementations in each native target source set (e.g., iosMain, macosMain) that the project targets. Per Kotlin Multiplatform standards, every expect declaration without @OptionalExpectation must have an actual counterpart for each compiled platform. Verify that actual implementations are provided in the appropriate platform-specific source sets to avoid compiler errors.

core/common/src/androidMain/kotlin/org/mifospay/core/common/BuildConfig.android.kt (1)

15-30: LGTM!

Clean implementation delegating to Android's generated BuildConfig. Using getter properties ensures values are read at access time, which is appropriate for these compile-time constants.

core/network/src/commonMain/kotlin/org/mifospay/core/network/config/FeatureAccessManager.kt (3)

36-49: Access control logic is sound.

The precedence (debug → demo → remote config) is correctly implemented. Minor observation: isDebug and buildType == BuildType.DEBUG may overlap depending on how the interface is implemented, but the defensive check is acceptable.


81-108: Allowlist logic is correct.

The access control follows proper allowlist semantics: when both lists are empty, no restrictions apply; when either list has entries, the device or user must match to gain access.


111-119: LGTM!

Well-structured data class for encapsulating device information. The nullable userId appropriately handles the case when users aren't logged in.

core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigLoader.kt (1)

25-29: LGTM!

Clean interface definition with appropriate async return types for remote config operations.

@therajanmaurya therajanmaurya force-pushed the claude/firebase-remote-config-loader-019GTifqCMdZeH5hUVeB4WKX branch from 58cf6e4 to 7ba0e26 Compare November 26, 2025 15:43
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