feat(kyc): implement KYC Level 3 review and submit screen#2017
feat(kyc): implement KYC Level 3 review and submit screen#2017IITI-tushar wants to merge 1 commit into
Conversation
Signed-off-by: Tushar Saxena <019saxenatushar@gmail.com>
📝 WalkthroughWalkthroughThis PR implements the complete KYC Level 3 "Review & Submit" screen by replacing placeholder code with a fully functional Compose UI and business logic ViewModel. The ViewModel fetches KYC Level 1 details on init, manages loading/error dialog states, and persists KYC Level 3 completion to storage on user confirmation. The screen displays fetched details in grouped review rows and provides a bottom submit button with dialog feedback. ChangesKYC Level 3 Feature Implementation
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In
`@feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3Screen.kt`:
- Around line 40-43: The current onDismiss for KycLevel3Dialogs calls
viewModel.trySendAction(KycLevel3Action.NavigateBack) which navigates away on
any dialog dismissal; add a new action (e.g., KycLevel3Action.DismissDialog or
ClearDialog) to the KycLevel3Action sealed class, handle it in the ViewModel to
clear state.dialogState, and change the KycLevel3Dialogs call to use onDismiss =
{ viewModel.trySendAction(KycLevel3Action.DismissDialog) } so dismissing the
error simply clears the dialog without navigating away.
In
`@feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt`:
- Around line 44-45: In KYCLevel3ViewModel, the early return when
state.kycDetails is null (the line referencing state.kycDetails?.copy(...))
leaves dialogState stuck in loading; update the null branch to first set
dialogState to a non-loading state (e.g., dismiss the dialog or set an error
state/message) before returning so the UI stops showing the spinner—locate the
submit/launch block that references state.kycDetails and ensure dialogState is
updated (cleared or set to an error) whenever state.kycDetails is null instead
of simply returning.
- Around line 22-24: The ViewModel currently force-unwraps clientId in
KYCLevel3ViewModel initialization (initialState = KycLevel3State(clientId =
requireNotNull(userPreferencesRepository.clientId.value))), which can crash if
clientId is not set; change this to a safe initialization by not using
requireNotNull — set KycLevel3State.clientId to a nullable or default value
(e.g., null or empty string) and defer real clientId usage to when it becomes
available (subscribe/observe userPreferencesRepository.clientId and update the
state), and handle null/default in any methods that use clientId (refer to
KYCLevel3ViewModel, KycLevel3State, and userPreferencesRepository.clientId).
- Line 43: Add the missing imports required for using viewModelScope.launch and
the KycLevel enum: import the coroutine extension (kotlinx.coroutines.launch) so
viewModelScope.launch resolves, and import the KycLevel enum/type that defines
KYC_LEVEL_3 (the package where KycLevel is declared in this module) so
KycLevel.KYC_LEVEL_3 compiles; update the KYCLevel3ViewModel imports to include
these symbols.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 47f28755-b8e0-4d0e-b166-9ace191a3ddf
📒 Files selected for processing (2)
feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3Screen.ktfeature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt
| KycLevel3Dialogs( | ||
| dialogState = state.dialogState, | ||
| onDismiss = { viewModel.trySendAction(KycLevel3Action.NavigateBack) }, | ||
| ) |
There was a problem hiding this comment.
UX concern: Error dialog dismissal navigates away from screen.
When the user dismisses an error dialog, they're navigated back to the previous screen. This prevents retry attempts and may frustrate users who want to fix the issue and resubmit. Typically, dismissing an error dialog should just close the dialog while keeping the user on the current screen.
💡 Suggested UX improvement
Add a dedicated dismiss action to clear the dialog state:
+// In KycLevel3Action
+sealed interface KycLevel3Action {
+ data object NavigateBack : KycLevel3Action
+ data object ConfirmAndSubmit : KycLevel3Action
+ data object DismissDialog : KycLevel3Action
+ sealed interface Internal : KycLevel3Action {
+ data class HandleLevel1Result(val result: DataState<KYCLevel1Details?>) : Internal
+ }
+}
+
+// In KYCLevel3ViewModel.handleAction
+when (action) {
+ KycLevel3Action.NavigateBack -> sendEvent(KycLevel3Event.OnNavigateBack)
+ KycLevel3Action.ConfirmAndSubmit -> handleConfirmSubmit()
+ KycLevel3Action.DismissDialog -> mutableStateFlow.update { it.copy(dialogState = null) }
+ is HandleLevel1Result -> handleLevel1Result(action)
+}Then in the screen:
KycLevel3Dialogs(
dialogState = state.dialogState,
- onDismiss = { viewModel.trySendAction(KycLevel3Action.NavigateBack) },
+ onDismiss = { viewModel.trySendAction(KycLevel3Action.DismissDialog) },
)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3Screen.kt`
around lines 40 - 43, The current onDismiss for KycLevel3Dialogs calls
viewModel.trySendAction(KycLevel3Action.NavigateBack) which navigates away on
any dialog dismissal; add a new action (e.g., KycLevel3Action.DismissDialog or
ClearDialog) to the KycLevel3Action sealed class, handle it in the ViewModel to
clear state.dialogState, and change the KycLevel3Dialogs call to use onDismiss =
{ viewModel.trySendAction(KycLevel3Action.DismissDialog) } so dismissing the
error simply clears the dialog without navigating away.
| initialState = KycLevel3State( | ||
| clientId = requireNotNull(userPreferencesRepository.clientId.value), | ||
| ), |
There was a problem hiding this comment.
Critical: Potential crash if clientId is null at ViewModel initialization.
Accessing userPreferencesRepository.clientId.value with requireNotNull during ViewModel construction will crash the app if the client ID hasn't been set yet. This creates a fragile initialization dependency.
Consider deferring clientId access or handling the null case gracefully.
🛡️ Proposed safer initialization
-) : BaseViewModel<KycLevel3State, KycLevel3Event, KycLevel3Action>(
- initialState = KycLevel3State(
- clientId = requireNotNull(userPreferencesRepository.clientId.value),
- ),
-) {
+) : BaseViewModel<KycLevel3State, KycLevel3Event, KycLevel3Action>(
+ initialState = KycLevel3State(
+ clientId = userPreferencesRepository.clientId.value ?: 0L,
+ ),
+) {
init {
+ if (state.clientId == 0L) {
+ mutableStateFlow.update {
+ it.copy(
+ isLoading = false,
+ dialogState = KycLevel3State.DialogState.Error("Client ID not found")
+ )
+ }
+ return
+ }
kycLevelRepository.fetchKYCLevel1Details(state.clientId)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt`
around lines 22 - 24, The ViewModel currently force-unwraps clientId in
KYCLevel3ViewModel initialization (initialState = KycLevel3State(clientId =
requireNotNull(userPreferencesRepository.clientId.value))), which can crash if
clientId is not set; change this to a safe initialization by not using
requireNotNull — set KycLevel3State.clientId to a nullable or default value
(e.g., null or empty string) and defer real clientId usage to when it becomes
available (subscribe/observe userPreferencesRepository.clientId and update the
state), and handle null/default in any methods that use clientId (refer to
KYCLevel3ViewModel, KycLevel3State, and userPreferencesRepository.clientId).
| private fun handleConfirmSubmit() { | ||
| mutableStateFlow.update { it.copy(dialogState = KycLevel3State.DialogState.Loading) } | ||
| // KYC Level 3 marks the current level as complete — update level field | ||
| viewModelScope.launch { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify the missing import and check if launch is used elsewhere in the codebase
# Check current imports in the file
echo "=== Current imports in KYCLevel3ViewModel.kt ==="
rg -n "^import" feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt
# Search for correct import pattern in other ViewModels
echo -e "\n=== Examples of launch usage in other ViewModels ==="
rg -n "import kotlinx.coroutines.launch" --type=kt -g '*ViewModel*' -C 1Repository: openMF/mifos-pay
Length of output: 731
🏁 Script executed:
cat -n feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt | sed -n '40,50p'Repository: openMF/mifos-pay
Length of output: 805
🏁 Script executed:
cat -n feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt | sed -n '1,30p'Repository: openMF/mifos-pay
Length of output: 1389
🏁 Script executed:
rg "KycLevel" feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt -n | head -20Repository: openMF/mifos-pay
Length of output: 1291
🏁 Script executed:
rg "^import.*KycLevel" feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.ktRepository: openMF/mifos-pay
Length of output: 196
🏁 Script executed:
fd "KycLevel" --type f -e kt | head -20Repository: openMF/mifos-pay
Length of output: 244
🏁 Script executed:
rg "enum class KycLevel|object KycLevel" --type-list | head -20Repository: openMF/mifos-pay
Length of output: 450
🏁 Script executed:
rg "enum class KycLevel|object KycLevel" -A 2Repository: openMF/mifos-pay
Length of output: 382
🏁 Script executed:
rg "import.*KycLevel" feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCDescriptionViewModel.ktRepository: openMF/mifos-pay
Length of output: 120
🏁 Script executed:
cat -n feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCDescriptionViewModel.kt | grep -A 5 "enum class KycLevel"Repository: openMF/mifos-pay
Length of output: 276
🏁 Script executed:
rg "KycLevel\." -n feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/ | grep -v "KycLevel3\|KycLevelRepository"Repository: openMF/mifos-pay
Length of output: 660
Multiple missing imports cause compilation errors.
The code uses viewModelScope.launch (line 43) and KycLevel.KYC_LEVEL_3 (line 44) but neither launch nor KycLevel are imported. Both are required for compilation.
🔧 Add missing imports
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.update
+import kotlinx.coroutines.launch
import org.mifospay.core.common.DataState
import org.mifospay.core.data.repository.KycLevelRepository
import org.mifospay.core.datastore.UserPreferencesRepository
import org.mifospay.core.model.kyc.KYCLevel1Details
import org.mifospay.core.ui.utils.BaseViewModel
+import org.mifospay.feature.kyc.KycLevel
import org.mifospay.feature.kyc.KycLevel3Action.Internal.HandleLevel1Result🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt`
at line 43, Add the missing imports required for using viewModelScope.launch and
the KycLevel enum: import the coroutine extension (kotlinx.coroutines.launch) so
viewModelScope.launch resolves, and import the KycLevel enum/type that defines
KYC_LEVEL_3 (the package where KycLevel is declared in this module) so
KycLevel.KYC_LEVEL_3 compiles; update the KYCLevel3ViewModel imports to include
these symbols.
| val details = state.kycDetails?.copy(currentLevel = KycLevel.KYC_LEVEL_3.name) | ||
| ?: return@launch |
There was a problem hiding this comment.
Major: Dialog stuck in loading state if kycDetails is null.
If kycDetails is null when the user clicks submit, the function returns early without updating dialogState, leaving the loading dialog visible indefinitely. This creates a poor user experience where the UI appears frozen.
🔧 Handle the null case properly
private fun handleConfirmSubmit() {
mutableStateFlow.update { it.copy(dialogState = KycLevel3State.DialogState.Loading) }
// KYC Level 3 marks the current level as complete — update level field
viewModelScope.launch {
val details = state.kycDetails?.copy(currentLevel = KycLevel.KYC_LEVEL_3.name)
- ?: return@launch
+ ?: run {
+ mutableStateFlow.update {
+ it.copy(dialogState = KycLevel3State.DialogState.Error(
+ "KYC details not available. Please try again."
+ ))
+ }
+ return@launch
+ }
val result = kycLevelRepository.updateKYCLevel1Details(state.clientId, details)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| val details = state.kycDetails?.copy(currentLevel = KycLevel.KYC_LEVEL_3.name) | |
| ?: return@launch | |
| val details = state.kycDetails?.copy(currentLevel = KycLevel.KYC_LEVEL_3.name) | |
| ?: run { | |
| mutableStateFlow.update { | |
| it.copy(dialogState = KycLevel3State.DialogState.Error( | |
| "KYC details not available. Please try again." | |
| )) | |
| } | |
| return@launch | |
| } |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In
`@feature/kyc/src/commonMain/kotlin/org/mifospay/feature/kyc/KYCLevel3ViewModel.kt`
around lines 44 - 45, In KYCLevel3ViewModel, the early return when
state.kycDetails is null (the line referencing state.kycDetails?.copy(...))
leaves dialogState stuck in loading; update the null branch to first set
dialogState to a non-loading state (e.g., dismiss the dialog or set an error
state/message) before returning so the UI stops showing the spinner—locate the
submit/launch block that references state.kycDetails and ensure dialogState is
updated (cleared or set to an error) whenever state.kycDetails is null instead
of simply returning.
Issue Fix
Fixes #2016
Jira Task: Task_Number
Description
This pull request implements the complete KYC Level 3 screen and its supporting view model, enabling users to review their details and submit for KYC Level 3 verification. The changes introduce state management, UI composition, event handling, and dialog support for loading and error states.
KYC Level 3 Screen Implementation and State Management:
UI and User Flow:
KYCLevel3Screenthat displays user KYC details in a review format, allows confirmation and submission, and handles navigation and completion events.ViewModel and State Logic:
KYCLevel3ViewModelto manage screen state, fetch KYC details, handle user actions (confirm/submit, navigate back), and update the UI based on repository responses.KycLevel3State,KycLevel3Event, andKycLevel3Actionto structure state, events, and actions for the screen.Repository Integration:
KycLevelRepositoryandUserPreferencesRepositoryto fetch and update KYC details, marking the KYC process as complete on successful submission.UI Feedback:
Apply the
AndroidStyle.xmlstyle template to your code in Android Studio.Run the unit tests with
./gradlew checkto make sure you didn't break anythingIf you have multiple commits please combine them into one commit by squashing them.
Summary by CodeRabbit