Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

State management changes - MBway #1975

Draft
wants to merge 33 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
26e5f78
Remove input data
araratthehero Jan 14, 2025
f847490
Remove outputData and use MBWayViewState instead
araratthehero Jan 15, 2025
96f5d91
Separate view state from view model state
araratthehero Jan 16, 2025
885dfda
Rename state files to delegateState to keep them separate from viewState
araratthehero Jan 17, 2025
4ee4bba
Observe submitHandler to validate all fields when Submit button is cl…
araratthehero Jan 17, 2025
133bee0
Optimise updating field values in delegate
araratthehero Jan 17, 2025
396e1ce
Update viewState every time delegateState updates, instead of trigger…
araratthehero Jan 17, 2025
201920d
Add isValidationErrorCheckForced flag, to show validation error messa…
araratthehero Jan 20, 2025
915871a
Provide countries and initiallySelectedCountry from the delegate
araratthehero Jan 20, 2025
9fc1a8f
Do minor optimizations
araratthehero Jan 21, 2025
4514c3f
Generate component state when delegate state gets updated
araratthehero Jan 21, 2025
750e340
Focus the first invalid field when Submit button is clicked and make …
araratthehero Jan 23, 2025
b0ea4d8
Implement FieldTransformerRegistry, which will transform field values…
araratthehero Jan 24, 2025
6a25504
Update phone number text when received from the delegate
araratthehero Jan 24, 2025
f6dbd17
Improve MBWayValidatorRegistry logic where not implementing a new Fie…
araratthehero Jan 24, 2025
117ead2
Add todos
araratthehero Jan 24, 2025
779265c
Support different types for updateField function
araratthehero Jan 28, 2025
30a4e91
Remove unnecessary flag for keeping the error message visible and ins…
araratthehero Jan 30, 2025
e398b08
Improve onSubmit() function by highlighting validation errors if the …
araratthehero Jan 30, 2025
0cf2491
Move each state related logic to their own files
araratthehero Jan 30, 2025
dd42832
Create UI event delegate for delegates
araratthehero Jan 30, 2025
ff0a0f5
Improve MBWayTransformerRegistry to show compile time error if a new …
araratthehero Feb 4, 2025
2ea4b09
Create StateUpdaterRegistry and StateManager to handle all state updates
araratthehero Feb 4, 2025
e3d713d
Move state to the StateManager
araratthehero Feb 4, 2025
06f7367
Fix MBWayComponentProvider
araratthehero Feb 4, 2025
30d6f57
Move highlightAllFieldValidationErrors to StateManager
araratthehero Feb 4, 2025
2c55566
Fix onFieldValueChanged function to adopt generics
araratthehero Feb 4, 2025
84b1f5b
Use stateManager directly instead of a helper function
araratthehero Feb 4, 2025
171b3b0
Set default value of shouldHighlightValidationError to false, to make…
araratthehero Feb 4, 2025
b2249f5
When isValid is requested, validate all unvalidated fields to give a …
araratthehero Feb 4, 2025
41ffbb9
Create DefaultValidator for all fields which are always valid
araratthehero Feb 4, 2025
ee23e33
Create DefaultTransformer for all fields which do not need transforma…
araratthehero Feb 5, 2025
ee0a7df
Make DelegateStateManager generic for all delegates and create a fact…
araratthehero Feb 11, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 14/1/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model

import androidx.annotation.RestrictTo

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class ComponentFieldDelegateState<T>(
val value: T,
val validation: Validation? = null,
val hasFocus: Boolean = false,
val shouldHighlightValidationError: Boolean = false,
)

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
fun <T> ComponentFieldDelegateState<T>.updateFieldState(
value: T? = null,
validation: Validation? = null,
hasFocus: Boolean? = null,
shouldHighlightValidationError: Boolean? = null,
): ComponentFieldDelegateState<T> = copy(
value = value ?: this.value,
validation = validation ?: this.validation,
hasFocus = hasFocus ?: this.hasFocus,
shouldHighlightValidationError = shouldHighlightValidationError ?: this.shouldHighlightValidationError,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 14/1/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model

import androidx.annotation.RestrictTo
import androidx.annotation.StringRes

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
data class ComponentFieldViewState<T>(
val value: T,
val hasFocus: Boolean = false,
@StringRes val errorMessageId: Int? = null,
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 6/2/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model.state

import androidx.annotation.RestrictTo
import com.adyen.checkout.components.core.internal.ui.model.Validation
import com.adyen.checkout.components.core.internal.ui.model.transformer.FieldTransformerRegistry
import com.adyen.checkout.components.core.internal.ui.model.updateFieldState
import com.adyen.checkout.components.core.internal.ui.model.validation.FieldValidatorRegistry
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class DefaultDelegateStateManager<S : DelegateState, FI>(
private val factory: DelegateStateFactory<S, FI>,
private val transformerRegistry: FieldTransformerRegistry<FI>,
private val validationRegistry: FieldValidatorRegistry<FI>,
private val stateUpdaterRegistry: StateUpdaterRegistry<FI, S>,
) : DelegateStateManager<S, FI> {

private val _state = MutableStateFlow(factory.createDefaultDelegateState())
override val state: StateFlow<S> = _state.asStateFlow()

override val isValid
get() = run {
validateNonValidatedFields()
_state.value.isValid
}

private fun validateNonValidatedFields() {
factory.getFieldIds().forEach { fieldId ->
val fieldState = stateUpdaterRegistry.getFieldState<Any>(fieldId, _state.value)

if (fieldState.validation == null) {
updateField(
fieldId = fieldId,
value = fieldState.value, // Ensure the current value is validated
)
}
}
}

// A list can be added, which will show which other fields need to be validated or updated when a specific field is updated
override fun <T> updateField(
fieldId: FI,
value: T?,
hasFocus: Boolean?,
shouldHighlightValidationError: Boolean?,
) {
val validation = value?.let {
validationRegistry.validate(
fieldId,
transformerRegistry.transform(fieldId, value),
)
}

val fieldState = stateUpdaterRegistry.getFieldState<T>(fieldId, _state.value)
val updatedFieldState = fieldState.updateFieldState(
value = value,
validation = validation,
hasFocus = hasFocus,
shouldHighlightValidationError = shouldHighlightValidationError,
)

_state.update {
stateUpdaterRegistry.updateFieldState(fieldId, _state.value, updatedFieldState)
}
}

override fun highlightAllFieldValidationErrors() {
// Flag to focus only the first invalid field
var isErrorFieldFocused = false

factory.getFieldIds().forEach { fieldId ->
val fieldState = stateUpdaterRegistry.getFieldState<Any>(fieldId, _state.value)

val shouldFocus = !isErrorFieldFocused && fieldState.validation is Validation.Invalid
if (shouldFocus) {
isErrorFieldFocused = true
}

updateField(
fieldId = fieldId,
value = fieldState.value, // Ensure the current value is validated
hasFocus = shouldFocus,
shouldHighlightValidationError = true,
)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 6/2/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model.state

import androidx.annotation.RestrictTo

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface DelegateState {
val isValid: Boolean
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 6/2/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model.state

import androidx.annotation.RestrictTo

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface DelegateStateFactory<S, FI> {
fun createDefaultDelegateState(): S

fun getFieldIds(): List<FI>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 4/2/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model.state

import androidx.annotation.RestrictTo
import kotlinx.coroutines.flow.StateFlow

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface DelegateStateManager<S, FI> {

val state: StateFlow<S>

val isValid: Boolean

fun <T> updateField(
fieldId: FI,
value: T? = null,
hasFocus: Boolean? = null,
// Default value is false, to make sure that errors are cleared when field is updated
shouldHighlightValidationError: Boolean? = false,
)

fun highlightAllFieldValidationErrors()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 4/2/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model.state

import androidx.annotation.RestrictTo

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface StateUpdater<S, FS> {
fun getFieldState(state: S): FS

fun updateFieldState(state: S, fieldState: FS): S
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 4/2/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model.state

import androidx.annotation.RestrictTo
import com.adyen.checkout.components.core.internal.ui.model.ComponentFieldDelegateState

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface StateUpdaterRegistry<K, S> {
fun <T> getFieldState(key: K, state: S): ComponentFieldDelegateState<T>

fun <T> updateFieldState(key: K, state: S, fieldState: ComponentFieldDelegateState<T>): S
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 5/2/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model.transformer

import androidx.annotation.RestrictTo

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class DefaultTransformer : FieldTransformer<Any> {
override fun transform(value: Any) = value
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 24/1/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model.transformer

import androidx.annotation.RestrictTo

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface FieldTransformer<T> {
fun transform(value: T): T
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 24/1/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model.transformer

import androidx.annotation.RestrictTo

/**
* A [FieldTransformerRegistry] is being used to implement value transformation for fields. This is useful when the
* value should get transformed before validation is performed or before it is being processed further.
* State should hold the original value, while transformed value will be used for other operations.
*/
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface FieldTransformerRegistry<K> {
fun <T> transform(key: K, value: T): T
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 4/2/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model.validation

import androidx.annotation.RestrictTo
import com.adyen.checkout.components.core.internal.ui.model.Validation

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
class DefaultValidator : FieldValidator<Any> {
override fun validate(input: Any) = Validation.Valid
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 15/1/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model.validation

import androidx.annotation.RestrictTo
import com.adyen.checkout.components.core.internal.ui.model.Validation

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface FieldValidator<T> {
fun validate(input: T): Validation
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/*
* Copyright (c) 2025 Adyen N.V.
*
* This file is open source and available under the MIT license. See the LICENSE file for more info.
*
* Created by ararat on 15/1/2025.
*/

package com.adyen.checkout.components.core.internal.ui.model.validation

import androidx.annotation.RestrictTo
import com.adyen.checkout.components.core.internal.ui.model.Validation

@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
interface FieldValidatorRegistry<K> {
fun <T> validate(key: K, value: T): Validation
}
Loading
Loading