aws-amplify / amplify-flutter

A declarative library with an easy-to-use interface for building Flutter applications on AWS.
https://docs.amplify.aws
Apache License 2.0
1.33k stars 248 forks source link

Prefill Username and Email field for sign up #3806

Open denxorz opened 1 year ago

denxorz commented 1 year ago

Description

In our app users are added by invites. To simply the Sign up process we want to prefill certain fields with data from the invite, like username and email. Is it possbile to do this somehow?

It would also be nice to have the ability to disable the fields that are already filled.

Categories

Steps to Reproduce

No response

Screenshots

No response

Platforms

Flutter Version

3.13.1

Amplify Flutter Version

1.4.0

Deployment Method

Custom Pipeline

Schema

No response

Jordan-Nelson commented 1 year ago

Hi @denxorz - There currently isn't a great way to achieve this. It can be achieved with Full UI Customization, but this is not trivial. You would need to build a completely custom sign up form with a custom username/email field and a custom sign up button. I will include an example of what that might look like below.

I will label this as a feature request.

Example

This example is a copy of [the example from the docs](https://ui.docs.amplify.aws/flutter/connected-components/authenticator/customization#full-ui-customization) for UI customization, with the addition of a fully custom sign up form. ```dart import 'package:amplify_auth_cognito/amplify_auth_cognito.dart'; import 'package:amplify_authenticator/amplify_authenticator.dart'; import 'package:amplify_flutter/amplify_flutter.dart'; import 'package:flutter/material.dart'; import 'amplifyconfiguration.dart'; void main() { runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => _MyAppState(); } class _MyAppState extends State { @override void initState() { super.initState(); _configureAmplify(); } void _configureAmplify() async { try { await Amplify.addPlugin(AmplifyAuthCognito()); await Amplify.configure(amplifyconfig); safePrint('Successfully configured'); } on Exception catch (e) { safePrint('Error configuring Amplify: $e'); } } @override Widget build(BuildContext context) { return Authenticator( // `authenticatorBuilder` is used to customize the UI for one or more steps authenticatorBuilder: (BuildContext context, AuthenticatorState state) { switch (state.currentStep) { case AuthenticatorStep.signIn: return CustomScaffold( state: state, // A prebuilt Sign In form from amplify_authenticator body: SignInForm(), // A custom footer with a button to take the user to sign up footer: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('Don\'t have an account?'), TextButton( onPressed: () => state.changeStep( AuthenticatorStep.signUp, ), child: const Text('Sign Up'), ), ], ), ); case AuthenticatorStep.signUp: const initialUsernameValue = 'User123'; const initialEmailValue = 'user123@example.com'; return CustomScaffold( state: state, body: AuthenticatorForm( child: Column( children: [ // username field TextFormField( decoration: const InputDecoration( label: Text('Username'), ), initialValue: initialUsernameValue, enabled: false, ), const SizedBox(height: 16), // username field TextFormField( decoration: const InputDecoration( label: Text('Email'), ), initialValue: initialEmailValue, enabled: false, ), const SizedBox(height: 16), SignUpFormField.password(), SignUpFormField.passwordConfirmation(), FilledButton( onPressed: () async { state.username = initialUsernameValue; state.username = initialEmailValue; await state.signUp(); }, child: const Row( mainAxisAlignment: MainAxisAlignment.center, children: [ Text('Sign Up'), ], ), ), ], ), ), // A custom footer with a button to take the user to sign in footer: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('Already have an account?'), TextButton( onPressed: () => state.changeStep( AuthenticatorStep.signIn, ), child: const Text('Sign In'), ), ], ), ); case AuthenticatorStep.confirmSignUp: return CustomScaffold( state: state, // A prebuilt Confirm Sign Up form from amplify_authenticator body: ConfirmSignUpForm(), ); case AuthenticatorStep.resetPassword: return CustomScaffold( state: state, // A prebuilt Reset Password form from amplify_authenticator body: ResetPasswordForm(), ); case AuthenticatorStep.confirmResetPassword: return CustomScaffold( state: state, // A prebuilt Confirm Reset Password form from amplify_authenticator body: const ConfirmResetPasswordForm(), ); default: // Returning null defaults to the prebuilt authenticator for all other steps return null; } }, child: MaterialApp( builder: Authenticator.builder(), theme: ThemeData.light(useMaterial3: true), home: const Scaffold( body: Center( child: Text('You are logged in!'), ), ), ), ); } } /// A widget that displays a logo, a body, and an optional footer. class CustomScaffold extends StatelessWidget { const CustomScaffold({ super.key, required this.state, required this.body, this.footer, }); final AuthenticatorState state; final Widget body; final Widget? footer; @override Widget build(BuildContext context) { return Scaffold( body: Padding( padding: const EdgeInsets.all(16), child: SingleChildScrollView( child: Column( children: [ // App logo const Padding( padding: EdgeInsets.only(top: 32), child: Center(child: FlutterLogo(size: 100)), ), Container( constraints: const BoxConstraints(maxWidth: 600), child: body, ), ], ), ), ), persistentFooterButtons: footer != null ? [footer!] : null, ); } } ```
denxorz commented 1 year ago

@Jordan-Nelson thanks for the help. I will try out the workaround.

denxorz commented 1 year ago

Replaced the FilledButton by the following, so that the visuals/translations/isBusy-behavior is the same.

CustomSignUpButton((context, state) {
  state.username = initialUsernameValue;
  state.email = initialEmailValue;
}),
class CustomSignUpButton extends SignUpButton {
  const CustomSignUpButton(this.customOnPressed, {Key? key}) : super(key: key);

  final void Function(
    BuildContext context,
    AuthenticatorState state,
  ) customOnPressed;

  @override
  void onPressed(BuildContext context, AuthenticatorState state) {
    customOnPressed(context, state);
    super.onPressed(context, state);
  }
}

Couldn't do the same for the field, because most of it is 'private'.