Skip to content

Supabase remote config instance loader #1945

Merged
therajanmaurya merged 24 commits intoopenMF:developmentfrom
mobilebytesensei:claude/firebase-remote-config-loader-019GTifqCMdZeH5hUVeB4WKX
Feb 10, 2026
Merged

Supabase remote config instance loader #1945
therajanmaurya merged 24 commits intoopenMF:developmentfrom
mobilebytesensei:claude/firebase-remote-config-loader-019GTifqCMdZeH5hUVeB4WKX

Conversation

@mobilebytesenseicommunity
Copy link
Contributor

@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 choose Main and Interbank servers with grouped lists, previews, and an Update action.
    • Gesture activation: five-tap sequence opens the instance selector.
    • Persistent selections saved across sessions.
    • Cloud-driven instance configuration with built-in default/fallback set.
    • New strings and labels for instance selection, update, and error messages.

…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

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a multi-platform server instance selector: gesture trigger, bottom-sheet UI, ViewModel with remote-config and prefs integration, persistence of main/interbank selections, network DI wiring to use chosen instances, models/resources, and dependency updates.

Changes

Cohort / File(s) Summary
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/InterbankServer.kt
Adds serializable types for ServerInstance, InterbankServer, and InstancesConfig with helpers for defaults and fullUrl composition.
Datastore / Repository
core/datastore/src/commonMain/kotlin/.../UserPreferencesDataSource.kt, core/datastore/src/commonMain/kotlin/.../UserPreferencesRepository.kt, core/datastore/src/commonMain/kotlin/.../UserPreferencesRepositoryImpl.kt
Adds selectedInstance and selectedInterbankInstance state, persistence helpers, and update APIs to store user selections.
Remote config & defaults
core/network/src/commonMain/kotlin/.../config/InstanceConfigLoader.kt, core/network/src/commonMain/composeResources/files/instances_config_default.json, core/network/build.gradle.kts
Introduces InstanceConfigLoader interface and FirebaseInstanceConfigLoader implementation, default JSON config resource, and adds firebase-config dependency.
Config manager & network wiring
core/network/src/commonMain/kotlin/.../config/InstanceConfigManager.kt, core/network/src/commonMain/kotlin/.../utils/BaseURL.kt, core/network/src/commonMain/kotlin/.../utils/KtorInterceptor.kt, core/network/src/commonMain/kotlin/.../di/NetworkModule.kt
Adds InstanceConfigManager, turns BaseURL into a class delegating to configManager, injects configLoader/configManager into DI, and propagates config-driven URLs/headers into Ktor clients and interceptor.
UI, gesture & ViewModel
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/GestureDetector.kt, .../InstanceSelectorScreen.kt, .../InstanceSelectorViewModel.kt
Adds five-tap gesture modifier, InstanceSelectorScreen composable with grouped lists and update flow, and InstanceSelectorViewModel combining remote config + prefs into UI state and persistence actions.
Integration & DI
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 and integrates gesture to show the InstanceSelectorSheet in the app (login graph).
Resources, deps & platform
gradle/libs.versions.toml, cmp-android/dependencies/prodReleaseRuntimeClasspath.txt, cmp-ios/Podfile, cmp-shared/.../strings.xml, cmp-shared/build.gradle.kts, build-logic/.../KotlinAndroid.kt
Version catalog and dependency bumps (adds gitlive-firebase-config), iOS Firebase pods, new strings, and compileSdk bumps.
Analytics import tweak
core/analytics/src/androidMain/kotlin/org/mifospay/core/analytics/di/AnalyticsModule.kt
Adjusted Firebase analytics import paths.

Sequence Diagram(s)

sequenceDiagram
    participant User as User
    participant App as MifosPayApp
    participant Detector as GestureDetector
    participant Screen as InstanceSelectorScreen
    participant VM as InstanceSelectorViewModel
    participant ConfigLoader as FirebaseInstanceConfigLoader
    participant Preferences as UserPreferencesRepository
    participant Network as InstanceConfigManager

    User->>Detector: five quick taps
    Detector->>App: onGestureDetected()
    App->>Screen: show InstanceSelectorScreen
    Screen->>VM: collect state / observe events
    VM->>ConfigLoader: observeInstancesConfig()
    ConfigLoader-->>VM: InstancesConfig (DataState)
    VM->>Preferences: read selectedInstance / selectedInterbankInstance
    Preferences-->>VM: StateFlow values
    VM-->>Screen: render state (instances, selections)
    User->>Screen: select instance + tap Update
    Screen->>VM: handleAction(UpdateInstances)
    VM->>Preferences: updateSelectedInstance/updateSelectedInterbankInstance
    Preferences-->>VM: DataState.Success
    VM-->>Screen: emit DismissSheet
    Screen->>App: onDismiss()
    App->>Network: InstanceConfigManager -> new selected instance used for network calls
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • HekmatullahAmin

Poem

🐰 I tapped five times beneath the moonlit screen,
Firebase whispered lists of servers, bright and lean.
Preferences cradled choices, settled them right,
Network now follows the URL by night.
A joyful hop — instances aligned, code tucked tight!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Firebase remote config instance loader' directly reflects the main feature added: a Firebase-backed instance configuration loader that fetches server instances from remote config.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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
therajanmaurya and others added 2 commits February 6, 2026 18:53
- Upgrade Kotlin to 2.2.21 for gitLive 2.4.0 compatibility
- Update Compose Multiplatform to 1.9.3
- Update Ktor to 3.3.3 and Ktorfit to 2.7.1
- Update Firebase BOM to 34.7.0
- Update Android Gradle Plugin to 8.12.3
- Update Room to 2.8.4
- Add Firebase pods to iOS Podfile for gitlive support

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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: 4

🤖 Fix all issues with AI agents
In `@cmp-ios/Podfile`:
- Around line 12-14: Remove the unused FirebasePerformance pod entry from the
Podfile: delete the line "pod 'FirebasePerformance'" and keep "pod
'FirebaseAnalytics'" and "pod 'FirebaseCrashlytics'"; after removing, run pod
install (or bundle exec pod install) to update the lockfile and ensure there are
no residual references to FirebasePerformance in the iOS build artifacts.
- Around line 9-14: The Podfile currently declares five Firebase pods without
version constraints and likely includes unnecessary pods for this PR; update the
Podfile to (1) remove pod 'FirebaseAnalytics', pod 'FirebaseCrashlytics', and
pod 'FirebasePerformance' unless gitlive/documentation or your app features
require them, keeping only pod 'FirebaseCore' and pod 'FirebaseRemoteConfig' for
Remote Config support, and (2) pin the remaining Firebase pods with a
pessimistic constraint (use ~> to the current major version used in the project)
so both pod 'FirebaseCore' and pod 'FirebaseRemoteConfig' include the same ~>
major.minor constraint to ensure reproducible installs and consistent versions.

In `@gradle/libs.versions.toml`:
- Around line 6-7: The listed versions in libs.versions.toml are invalid and
will cause Gradle sync failures; update androidGradlePlugin and androidTools to
actual published AGP/tool versions (replace androidGradlePlugin = "8.12.3" and
androidTools = "31.12.3" with a real release such as a published 8.1.x / 8.2.x /
9.0.x for androidGradlePlugin and the matching published androidTools
coordinate), verify the chosen versions exist on Maven Central, and run a Gradle
sync to confirm compatibility (look for androidGradlePlugin and androidTools in
the file to make the changes).
- Around line 349-353: Two aliases in gradle/libs.versions.toml point to the
same module: filekit-compose and filekit-dialogs-compose both map to
io.github.vinceglb:filekit-dialogs-compose; update code to use the canonical
alias and remove the duplicate. Replace all usages of libs.filekit.compose with
libs.filekit.dialogs.compose in the build.gradle.kts that currently imports
filekit (where libs.filekit.compose appears), then delete the redundant
filekit-compose entry from gradle/libs.versions.toml so only
filekit-dialogs-compose remains.
🧹 Nitpick comments (2)
gradle/libs.versions.toml (2)

87-93: Kotlin 2.3.0 is now available — consider upgrading.

For the language release 2.2.0, there was only one tooling release 2.2.20 and one bugfix release 2.2.21. This confirms 2.2.21 is valid. However, Kotlin 2.3.0 has been released (Feb 5, 2026). If merging soon, you may want to bump kotlin to 2.3.0 and ksp to a compatible version to stay current. Note that Kotlin 2.3.0 brings Gradle 9.0 compatibility, which may be relevant if you plan to migrate to AGP 9.0.


1-134: Consider splitting the mass dependency upgrade from the feature PR.

This version catalog update touches ~50+ dependency versions (AGP, Kotlin, Ktor, Compose, Firebase, Koin, Room, testing libraries, etc.), but the PR is titled "Firebase remote config instance loader." Bundling sweeping upgrades with a feature change makes it difficult to bisect regressions and complicates the review.

Ideally, the broad version bumps should be a separate, dedicated PR, and this PR should only add the dependencies directly required for the Firebase Remote Config feature (gitlive-firebase-config, gitLive version bump, and Room entries if needed by the feature).

- Update compileSdk from 35 to 36 (required by newer AndroidX libraries)
- Replace deprecated Firebase KTX imports with new Firebase API
- Update Firebase library names from -ktx to non-ktx variants
- Regenerate dependency guard baseline

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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

🤖 Fix all issues with AI agents
In `@cmp-shared/build.gradle.kts`:
- Line 84: Remove the redundant local compileSdk declaration: delete the line
setting compileSdk = 36 from this module so the project uses the centralized
value defined by the convention plugin (KotlinAndroid). Ensure no other local
compileSdk assignments remain in the file so the convention (KotlinAndroid)
controls the SDK version.

- Create InterbankServer model with endpoint, protocol, path, label, isDefault
- Update ServerInstance to include interbankServers list and remove type field
- Simplify InstancesConfig by removing separate main/interbank methods
- Update UserPreferencesDataSource/Repository to use InterbankServer type
- Redesign InstanceSelectorScreen with nested UI showing interbank servers
  below selected main instance
- Add beautified InterbankServerItem with animations and improved styling
- Update InstanceConfigManager to work with new InterbankServer type
- Update fallback JSON config with nested structure
- Update Supabase app_config table with new nested data

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
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

🤖 Fix all issues with AI agents
In
`@cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorScreen.kt`:
- Around line 86-92: InstanceSelectorScreen currently applies the incoming
modifier twice: once to MifosBottomSheet and again to the inner MifosScaffold;
remove the duplicate application by keeping the incoming modifier only on
MifosBottomSheet and replace the MifosScaffold modifier with Modifier (or a new
local innerModifier) so external sizing/padding is not applied twice; update
InstanceSelectorScreen to pass modifier to MifosBottomSheet and use Modifier (or
innerModifier) for the MifosScaffold to avoid double application.
🧹 Nitpick comments (5)
core/network/src/commonMain/kotlin/org/mifospay/core/network/config/InstanceConfigManager.kt (2)

74-77: getSelfServiceUrl() duplicates fullUrl logic — use computed property instead.

fullUrl already computes "$protocol$endpoint$path". This method manually re-concatenates the same pieces.

♻️ Suggested simplification
     fun getSelfServiceUrl(): String {
         val instance = getCurrentInstance()
-        return "${instance.protocol}${instance.endpoint}${instance.path}self/"
+        return "${instance.fullUrl}self/"
     }

20-48: DEFAULT_INTERBANK_INSTANCE duplicates the interbank entry inside DEFAULT_MAIN_INSTANCE.

The same interbank server definition exists in both DEFAULT_MAIN_INSTANCE.interbankServers[0] and DEFAULT_INTERBANK_INSTANCE. Consider defining it once and referencing it in both places to avoid drift.

♻️ Suggested refactor
     companion object {
+        private val DEFAULT_INTERBANK_INSTANCE = InterbankServer(
+            endpoint = "apis.flexcore.mx",
+            protocol = "https://",
+            path = "/v1.0/vnext2/",
+            label = "Default Interbank",
+            isDefault = true,
+        )
+
         private val DEFAULT_MAIN_INSTANCE = ServerInstance(
             endpoint = "mifos-bank-2.mifos.community",
             protocol = "https://",
             path = "/fineract-provider/api/v1/",
             platformTenantId = "mifos-bank-2",
             label = "Default Instance",
             isDefault = true,
-            interbankServers = listOf(
-                InterbankServer(
-                    endpoint = "apis.flexcore.mx",
-                    protocol = "https://",
-                    path = "/v1.0/vnext2/",
-                    label = "Default Interbank",
-                    isDefault = true,
-                ),
-            ),
+            interbankServers = listOf(DEFAULT_INTERBANK_INSTANCE),
         )
-
-        private val DEFAULT_INTERBANK_INSTANCE = InterbankServer(
-            endpoint = "apis.flexcore.mx",
-            protocol = "https://",
-            path = "/v1.0/vnext2/",
-            label = "Default Interbank",
-            isDefault = true,
-        )
     }
cmp-shared/src/commonMain/kotlin/org/mifospay/shared/instance/InstanceSelectorScreen.kt (2)

214-321: Inconsistent theming: MainInstanceItem mixes MaterialTheme and KptTheme.

MainInstanceItem uses MaterialTheme.typography and MaterialTheme.colorScheme (e.g., Lines 256, 272, 276, 290, 293, 301, 304), while InterbankServerItem consistently uses KptTheme.typography and KptTheme.colorScheme. This inconsistency may cause visual discrepancies if MaterialTheme and KptTheme diverge. Consider aligning on one theme system.


513-683: Preview composables use inconsistent theme wrappers.

Some previews wrap in KptTheme { } (Lines 516, 574) while others use MifosTheme { } (Lines 552, 608). Since MifosTheme sets up KptTheme internally (via KptMaterialTheme), using KptTheme directly in previews may skip color scheme and gradient setup from MifosTheme. Standardize on MifosTheme for all previews to ensure they reflect production theming.

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

114-116: ConfigLoaded action is dead code — defined, accepted, but never dispatched or meaningfully handled.

InstanceSelectorAction.ConfigLoaded is declared in the sealed interface and matched in handleAction, but the handler is a no-op comment (// Handled by init block), and config loading is indeed driven entirely by the combine in init. This dead action adds unnecessary surface area to the public API and may confuse consumers.

Remove it unless there's a planned use case.

Suggested fix
 // In handleAction:
-            is InstanceSelectorAction.ConfigLoaded -> {
-                // Handled by init block
-            }

 // In InstanceSelectorAction:
-    data class ConfigLoaded(val config: DataState<InstancesConfig>) : InstanceSelectorAction

Also applies to: 172-172

therajanmaurya and others added 2 commits February 9, 2026 19:57
- Add CocoaPods source
- Add Firebase pods (Core, Analytics, Crashlytics)
- Add post_install hook to disable code signing for Pod targets
- Update dependency guard baseline

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add SupabaseInstanceConfigLoader for fetching instance config from Supabase
- Add SupabaseConfigConventionPlugin for build configuration
- Update InstanceConfigLoader to use Supabase as primary source with fallback
- Update NetworkModule with Supabase dependencies
- Update analytics module to use gitlive firebase-analytics
- Remove deprecated FirebaseAnalyticsHelper
- Update iOS project configuration and Podfile.lock
- Update google-services.json configuration
- Update library versions in libs.versions.toml

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
therajanmaurya and others added 5 commits February 9, 2026 23:40
…ilds

- Added Firebase client entry for org.mifospay.debug package
- Updated prodRelease-badging.txt with compileSdk 36 and version updates

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Added SUPABASECREDS secret documentation to workflow files
- Pass supabase_creds to reusable workflows for Supabase integration
- Secret should be Base64 encoded JSON with url and anonKey fields

Note: mifos-x-actionhub reusable workflows need to be updated to
decode and write the credentials file before Gradle builds.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Updated all workflow references to use v1.0.9 which includes
Supabase credentials support for KMP projects.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Reverted the following files to development branch state:
- cmp-ios/iosApp/iOSApp.swift
- cmp-ios/iosApp.xcodeproj/xcshareddata/xcschemes/cmp-ios.xcscheme
- cmp-ios/Podfile
- core-base/analytics AnalyticsModule.kt files
- Restored FirebaseAnalyticsHelper.kt

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@therajanmaurya therajanmaurya enabled auto-merge (squash) February 9, 2026 19:21
therajanmaurya
therajanmaurya previously approved these changes Feb 9, 2026
therajanmaurya and others added 3 commits February 10, 2026 01:01
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Gradle's inputs.file().optional() doesn't work as expected - it still
requires the file to exist. Fix by only declaring the file as input
when it actually exists.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
Regenerated yarn.lock to fix CI build failure.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@therajanmaurya therajanmaurya merged commit aedc20d into openMF:development Feb 10, 2026
8 checks passed
@therajanmaurya therajanmaurya changed the title Firebase remote config instance loader Supabase remote config instance loader Feb 10, 2026
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