Onboarding with Flutter and AWS Amplify

Onboarding with Flutter and AWS Amplify

How to easily customise Authenticator to add onboarding

Dzień dobry 👋👋. Today I'll show how to easily implement onboarding by customizing AWS Amplify's Authenticator widget I introducted you recently and by using the Flutter package flutter_onboarding_slider.

Animated GIF with screen recoding showing the implemented onboarding slider and the sign-up screen.

Prerequisites

You configured Amplify Auth in your Flutter project. If you haven't done it yet follow the official documentation or jump into my recent article showing how to initialize a Fluttter project, add Amplify Auth and customise sign-up fields build by Authenticator.

Before starting with the onboarding implementation wrap your MaterialApp widget with Authenticator.

return Authenticator(
  child: MaterialApp(
    // Material App props
  ),
);

Authenticator builder

To build onboarding we will use Authenticator's property authenticatorBuilder. It provides us a possibility to overwrite the auth screens depending on AuthenticatorState state and which step needs to get build (state.currentStep).

By setting the prop initialStep to AuthenticatorStep.onboarding we tell the Authenticator widget to start with onboarding for unauthenticated users.

return Authenticator(
  initialStep: AuthenticatorStep.onboarding,
  authenticatorBuilder:
      (BuildContext context, AuthenticatorState state) {
    // If current step equals to onboarding,
    // return onboarding widget that we implement in the next step..
    //
    // state.changeStep calls affect change in the authenticator state
    // so users get navigated to the sign-in and sign-up screens.
    if (state.currentStep == AuthenticatorStep.onboarding) {
      return OnboardingSlider(
        signUpButtonFunction: () => state.changeStep(
          AuthenticatorStep.signUp,
        ),
        signInButtonFunction: () => state.changeStep(
          AuthenticatorStep.signIn,
        ),
      );
    }

    return null;
  },
  child: MaterialApp(
    // Material App props
  ),
);

Onboarding slider widget

Finally let's add the widget OnboardingSlider awaiting functions which are going to be executed as users tap on the sign-in and sign-up buttons in the onboarding.

💡
As the included code snippets have been created on purpose of this publication the onboarding images are build by including assets with plain strings. Use flutter_gen to prevent typos in images' paths and changing multiple places in the codebase as a single image changes.
import 'package:flutter/material.dart';
import 'package:flutter_onboarding_slider/flutter_onboarding_slider.dart';

class OnboardingSlider extends StatelessWidget {
  const OnboardingSlider({
    required this.signUpButtonFunction,
    required this.signInButtonFunction,
    super.key,
  });

  final Function signUpButtonFunction;
  final Function signInButtonFunction;

  static const double verticalPaddingSmall = 5;
  static const double verticalPaddingLarge = 15;
  static const double viewPaddingHorizontal = 25;

  @override
  Widget build(BuildContext context) {
    final mediaQuery = MediaQuery.of(context);
    final screenPadding = mediaQuery.padding;
    final screenHeight =
        mediaQuery.size.height - screenPadding.top - screenPadding.bottom;

    final backgroundImageHeight = screenHeight / 2;

    return OnBoardingSlider(
      imageVerticalOffset: verticalPaddingLarge,
      trailingFunction: signInButtonFunction,
      onFinish: signUpButtonFunction,
      headerBackgroundColor: Colors.white,
      finishButtonText: 'Register',
      finishButtonStyle: const FinishButtonStyle(
        backgroundColor: Colors.blue,
        shape: RoundedRectangleBorder(
          borderRadius: BorderRadius.all(
            Radius.circular(10),
          ),
        ),
      ),
      skipTextButton: const Text('Skip'),
      trailing: const Text('Login'),
      background: [
        _OnboardingSliderBackground(
          key: const ValueKey('onboarding-slider-bg-1'),
          assetName: 'assets/images/onboarding_namaste.png',
          height: backgroundImageHeight,
        ),
        _OnboardingSliderBackground(
          key: const ValueKey('onboarding-slider-bg-2'),
          assetName: 'assets/images/onboarding_inhale_exhale.png',
          height: backgroundImageHeight,
        ),
        _OnboardingSliderBackground(
          key: const ValueKey('onboarding-slider-bg-3'),
          assetName: 'assets/images/onboarding_present_moment.png',
          height: backgroundImageHeight,
        ),
      ],
      totalPage: 3,
      speed: 1.8,
      pageBodies: [
        _OnboardingSliderPageBody(
          key: const ValueKey('onboarding-slider-body-1'),
          offset: backgroundImageHeight,
          title: 'Discover a New You',
          subtitle: 'Explore mood tracking and estimate your '
              'mood on daily basis.',
        ),
        _OnboardingSliderPageBody(
          key: const ValueKey('onboarding-slider-body-2'),
          offset: backgroundImageHeight,
          title: 'Cultivate Gratitude, Record Life',
          subtitle: 'Capture gratitude and emotions daily. Show gratitude and '
              "write down what you're grateful for.",
        ),
        _OnboardingSliderPageBody(
          key: const ValueKey('onboarding-slider-body-3'),
          offset: backgroundImageHeight,
          title: 'Empower Your Mind, Find Clarity',
          subtitle: 'Unlock inner peace and mental clarity by journaling '
              'your thoughts in a diary.',
        ),
      ],
    );
  }
}

class _OnboardingSliderPageBody extends StatelessWidget {
  const _OnboardingSliderPageBody({
    required this.offset,
    required this.title,
    required this.subtitle,
    super.key,
  });

  final double offset;
  final String title;
  final String subtitle;

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(
        horizontal: OnboardingSlider.viewPaddingHorizontal,
      ),
      child: Column(
        children: <Widget>[
          SizedBox(
            height: offset,
          ),
          Expanded(
            child: Column(
              children: [
                const SizedBox(
                  height: OnboardingSlider.verticalPaddingLarge,
                ),
                Text(
                  title,
                  style: Theme.of(context).textTheme.headlineSmall,
                  textAlign: TextAlign.center,
                ),
                const SizedBox(
                  height: OnboardingSlider.verticalPaddingSmall,
                ),
                Text(
                  subtitle,
                  style: Theme.of(context).textTheme.bodyLarge,
                  textAlign: TextAlign.center,
                ),
              ],
            ),
          ),
        ],
      ),
    );
  }
}

class _OnboardingSliderBackground extends StatelessWidget {
  const _OnboardingSliderBackground({
    required this.height,
    required this.assetName,
    super.key,
  });

  final double height;
  final String assetName;

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: MediaQuery.of(context).size.width,
      height: height,
      child: Padding(
        padding: const EdgeInsets.symmetric(
          horizontal: OnboardingSlider.viewPaddingHorizontal,
        ),
        child: Image.asset(
          assetName,
        ),
      ),
    );
  }
}

Outro

More Flutter and Amplify content is coming soon. 🤫


Credits for pikisuperstar for the vector images