aws-amplify / amplify-ui-android

Amplify UI for Android is a collection of accessible, themeable, performant Android components that can connect directly to the cloud.
https://ui.docs.amplify.aws
Apache License 2.0
15 stars 7 forks source link

Improve documentation regarding "Full UI custimization" and custom validators #155

Open pmellaaho opened 5 months ago

pmellaaho commented 5 months ago

Before creating a new issue, please confirm:

Which UI component?

Authenticator

Gradle script dependencies

// Put output below this line
authenticatorVersion = "1.1.0"

Environment information

# Put output below this line
Gradle 8.6

Please include any relevant guides or documentation you're referencing

https://ui.docs.amplify.aws/android/connected-components/authenticator/customization#full-ui-customization

Describe the bug

I'm trying to implement the custom UI for the Authenticator steps (e.g. SignUp) but still depend on the State instance provided by Authenticator. Lets say that in my case the username is always email so I would like to replace the orig username validator with email validator. I'm able to replace the UI components (e.g. username input) without problems but I'm having hard time trying to replace the validator used for my custom input (TextInputField). This because the validator inside fieldData is a val not var. Can you please provide more documentation on how this can be done with simple code example. Furthermore, I don't quite get why validator can be found from both fieldData and fieldConfig.

Reproduction steps (if applicable)

No response

Code Snippet

// Trying to replace the orig username validator with email validator

val userName = state.form.fields[FieldKey.Username]!!
    val userNameData = object : MutableFieldData {
        override val config: FieldConfig = userName.config
        override val state = userName.state
        override val validator: FieldValidator = {
            if (content.isNotBlank() && !Patterns.EMAIL_ADDRESS.matcher(content)
                    .matches()
            ) FieldError.InvalidFormat else null
        }
    }

// And then passing the userNameData to custom TextInputField

TextInputField(
                fieldConfig = userNameData.config as FieldConfig.Text,
                fieldState = userNameData.state,
                enabled = formStateEnabled
            )

// The above code does really replace the username's MutablefieldData and the original validator is still in use.

Log output

// Put your logs below this line

amplifyconfiguration.json

No response

Additional information and screenshots

No response

mattcreaser commented 5 months ago

Hi @pmellaaho - we can look at improving this documentation/DX. Quick question though: if your username is always email, is there a reason that you don't have email set as the cognito sign in attribute? That will "just work" and use an email validator.

If you're not able to change the user pool, the way to set the validator for a field is to use the signUpForm builder. Specifically to add email validation to a username field it would look something like this:

val authenticatorState = rememberAuthenticatorState(
    signUpForm = {
        text(
            key = FieldKey.Username, // the key is important here. This replaces the standard username field.
            label = "Username",
            required = true,
            validator = {
                if (!Patterns.EMAIL_ADDRESS.matcher(content).matches()) FieldError.Custom("Should be an email address") else null
            }
        )
        password()
        confirmPassword()
    }
)

It's not currently possible to change the validator from within the composable.

pmellaaho commented 5 months ago

Thanks for fast reply! I experimented with the signUpForm builder at some some point but unfortunately found out that only the 'Full UI Customization' -approach is flexible enough to fill our UX needs. I think It's worth considering the cognito sign in attribute, thanks for bringing it out, but it might not be possible since it would limit our options in the future. Do you think we could change this 'Question' into 'Feature request'?

mattcreaser commented 5 months ago

@pmellaaho you can combine the signUpForm with a full-ui customization - the values in the signUpForm builder will set up the field states and validators that will be used, and then you can use signUpContent to change the composables used to collect the data. That should address your use case.

A good way to think of it is signUpForm defines what will be collected, and signUpContent can override how it is collected.

In terms of a feature request, we would want to clarify the ask a little bit further. Making the validator be mutable is contrary to Compose guidelines (you'd be introducing side effects to your composable), so we would probably want to consider an alternate approach.

pmellaaho commented 5 months ago

Thank's for the info, I didn't realize that you could combine the signUpForm with a full-ui customization, maybe worth mentioning in the docs?

There's a drawback though that it's only possible to replace the validator for SignUpForm and not e.g. to 'Password reset'. I think it's totally up to you to decide how to enable replacing the validators!

And finally, there's one issue related to SignUp logic. The confirmPassword seems to be mandatory part of the SignUp i.e. even if I used the SignUpFormBuilder without calling confirmPassword() it's still required. The same applies also when signUpForm is not used at all. The way I got around this to do something like this in custom SignUpScreen:

AuthenticatorButton( onClick = { scope.launch { // just copy the password to confirm password to pass the validation val confPasswordFieldState = confPassword.state as MutablePasswordFieldState confPasswordFieldState.content = passwordFieldState.content state.signUp() } },

What do you think?

mattcreaser commented 4 months ago

That's right, the confirm password field is required. Feel free to open another feature request to make that optional!