Flutter and Rive: Adding Life to the Authentication Process— Episode 2

The Flutter Way
Flutter Community
Published in
8 min readJan 10, 2023

--

Welcome to the second episode of Rive and Flutter: A Match Made in Animation Heaven. In this episode, we will be building a sign-in model and custom text fields with icons that are functional. We will also learn how to create custom model transitions on the sign-in view. Once the button animation is complete, the sign-in dialog will slide down from the top. Lastly, we will create a loading animation that will be displayed during the sign-in process. If there are any errors, an error animation will be shown. If the sign-in is successful, a success animation with confetti will be displayed.

Episode 2 Preview

Here is the link to Episode 1 in case you missed it

🎬 Video tutorial

Sign in dialog

We can use showGeneralDialog to create such sing in dialog. Let’s create a function called customSigninDialog

Future<Object?> customSigninDialog(BuildContext context,
{required ValueChanged onCLosed}) {
return showGeneralDialog(
barrierDismissible: true,
barrierLabel: "Sign In",
context: context,
// TODO: Custom transition
pageBuilder: (context, _, __) => Center(
child: Container(
height: 620,
margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(vertical: 32, horizontal: 24),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.94),
borderRadius: const BorderRadius.all(Radius.circular(40)),
),
child: Scaffold(
backgroundColor: Colors.transparent,
body: Stack(
clipBehavior: Clip.none,
children: [
Column(
children: [
// TODO: Sign in text and decsription

// TODO: Sign in form

// TODO: Divider

// TODO: Socal login
],
),
// TODO: Close button
],
),
),
),
),
).then(onCLosed);
}

When the button is pressed, we will wait for a short delay before calling the customSigninDialog function to allow the button to display its animation. Later, I will explain why we need the onClosed event.

Future.delayed(
const Duration(milliseconds: 800),
() {
// TODO: Update dialog status

customSigninDialog(context, onCLosed: (_) {});
},
);

To include the large sign in text and description, replace Sign in text and description with the following code.

const Text(
"Sign In",
style: TextStyle(fontSize: 34, fontFamily: "Poppins"),
),
const Padding(
padding: EdgeInsets.symmetric(vertical: 16),
child: Text(
"Access to 240+ hours of content. Learn design and code, by building real apps with Flutter and Swift.",
textAlign: TextAlign.center,
),
),
Dialog preview with Sign in text

To add the divider, replace TODO: Divider with the following code.

Row(
children: const [
Expanded(child: Divider()),
Padding(
padding: EdgeInsets.symmetric(horizontal: 16),
child: Text(
"OR",
style: TextStyle(
color: Colors.black26,
),
),
),
Expanded(child: Divider()),
],
),

To add the social buttons, replace TODO: Social login with the following code.

Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
IconButton(
padding: EdgeInsets.zero,
onPressed: () {},
icon: SvgPicture.asset(
"assets/icons/email_box.svg",
height: 64,
width: 64,
),
),
IconButton(
padding: EdgeInsets.zero,
onPressed: () {},
icon: SvgPicture.asset(
"assets/icons/apple_box.svg",
height: 64,
width: 64,
),
),
IconButton(
padding: EdgeInsets.zero,
onPressed: () {},
icon: SvgPicture.asset(
"assets/icons/google_box.svg",
height: 64,
width: 64,
),
),
],
)

We are almost finished. All that is left to do is create a separate widget for the sign in form and Sign in button which called SignInForm.

class SignInForm extends StatefulWidget {
const SignInForm({
Key? key,
}) : super(key: key);

@override
State<SignInForm> createState() => _SignInFormState();
}

class _SignInFormState extends State<SignInForm> {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();

@override
Widget build(BuildContext context) {
return Stack(
children: [
Form(
key: _formKey,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const Text(
"Email",
style: TextStyle(color: Colors.black54),
),
Padding(
padding: const EdgeInsets.only(top: 8, bottom: 16),
child: TextFormField(
validator: (value) {
if (value!.isEmpty) {
return "";
}
return null;
},
onSaved: (email) {},
decoration: InputDecoration(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: SvgPicture.asset("assets/icons/email.svg"),
),
),
),
),
const Text(
"Password",
style: TextStyle(color: Colors.black54),
),
Padding(
padding: const EdgeInsets.only(top: 8, bottom: 16),
child: TextFormField(
validator: (value) {
if (value!.isEmpty) {
return "";
}
return null;
},
onSaved: (password) {},
obscureText: true,
decoration: InputDecoration(
prefixIcon: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: SvgPicture.asset("assets/icons/password.svg"),
),
),
),
),
Padding(
padding: const EdgeInsets.only(top: 8, bottom: 24),
child: ElevatedButton.icon(
onPressed: () {
// TODO: Show the loading animation
},
style: ElevatedButton.styleFrom(
backgroundColor: const Color(0xFFF77D8E),
minimumSize: const Size(double.infinity, 56),
shape: const RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(10),
topRight: Radius.circular(25),
bottomRight: Radius.circular(25),
bottomLeft: Radius.circular(25),
),
),
),
icon: const Icon(
CupertinoIcons.arrow_right,
color: Color(0xFFFE0037),
),
label: const Text("Sign In"),
),
),
],
),
),

// TODO: Loading animation

// TODO: Confetti animation
],
);
}
}

And replace TODO: Sign in form with SignInForm() .

SignInForm()
Preview Sign in model

Check/Error Animation

We have completed the Sign in dialog and can now display the animation. The animation we are using can be found for free at this link: Check/Error. It has already been added to the asset file as check.rive

Inputs

The animation has 3 inputs, which allow us to control which animation is displayed at a given time.

We have created two Booleans to show and hide the Check/Error and Confetti animations, as well as three variables of type SMITrigger, which are late variables, to control the Check, Error, and Reset inputs.

bool isShowLoading = false;
bool isShowConfetti = false;

late SMITrigger check;
late SMITrigger error;
late SMITrigger reset;

// TODO: SMITrigger for confetti

It’s time to add the Check/Error animation. I want to display it above the Sign in button and set the height and width to 100. I also want to do the same for the confetti animation, so I have created a new component/widget called CustomPositioned, which will allow us to align it as desired.

class CustomPositioned extends StatelessWidget {
const CustomPositioned({super.key, required this.child, this.size = 100});

final Widget child;
final double size;

@override
Widget build(BuildContext context) {
return Positioned.fill(
child: Column(
children: [
const Spacer(),
SizedBox(
height: size,
width: size,
child: child,
),
const Spacer(flex: 2),
],
),
);
}
}

It’s time to add the animation. Adding it is as simple as adding an image.

CustomPositioned(
child: RiveAnimation.asset(
"assets/RiveAssets/check.riv",
onInit: (artboard) {
// TODO: Set the trigger
},
),
),

Replace the TODO: Set the trigger with the following code

StateMachineController? controller = StateMachineController.fromArtboard(artboard, "State Machine 1");
artboard.addController(controller!);
check = controller.findSMI("Check") as SMITrigger;
error = controller.findSMI("Error") as SMITrigger;
reset = controller.findSMI("Reset") as SMITrigger;
Preview with Check/Error animation

As you can see, the loading animation is currently always displayed. However, we only want it to show when the Sign in button is pressed. To fix this, we can use the isShowLoading variable that we created earlier.

isShowLoading
? CustomPositioned(
child: RiveAnimation.asset( ... ),
)
: const SizedBox(),

The plan for the Sign in button is as follows: when the user clicks the button, we will set isShowLoading to true. Then, we will validate the text fields and display the success animation if they are valid, or the error animation if they are not. To implement this, we are going to create a function called signIn and call it when the button is pressed.

void signIn(BuildContext context) {
setState(() {
isShowLoading = true;
// TODO: Show confetti aniamton
});
Future.delayed(
const Duration(seconds: 1),
() {
if (_formKey.currentState!.validate()) {

check.fire();
Future.delayed(
const Duration(seconds: 2),
() {
setState(() {
isShowLoading = false;
});
// TODO: Fire confetti animation

// TODO: Navigate to next screen
},
);
} else {
error.fire();
Future.delayed(
const Duration(seconds: 2),
() {
setState(() {
isShowLoading = false;
});
},
);
}
},
);
}

To do this, replace the TODO: Show the loading animation with a call to signIn(context).

signIn(context);
Preview with check & error

Great, this is what we wanted. If the TextField is empty, it shows the error animation; if all the fields are valid, it shows the success animation. For simplicity, I have not implemented any authentication here, but the process would be similar.

The last thing we need to do is show the confetti animation after a successful check. To do this, we should first create a variable called isShowConfetti that we can use to control the display of the confetti animation also a trigger.

bool isShowConfetti = false;
late SMITrigger confetti;

Replace the TODO: Confetti animation with the following code.

isShowConfetti
? CustomPositioned(
child: Transform.scale(
scale: 7,
child: RiveAnimation.asset(
"assets/RiveAssets/confetti.riv",
onInit: (artboard) {
StateMachineController? controller = StateMachineController.fromArtboard(artboard, "State Machine 1");
artboard.addController(controller!);
confetti = controller.findSMI("Trigger explosion") as SMITrigger;
},
),
),
)
: const SizedBox(),

We have added the animation and set the confetti trigger. It’s now time to activate it. To do this, go to the signIn function and replace the TODO: Fire confetti animation with

confetti.fire();

There is just one more thing we need to do. At first, we need to set isShowConfetti to true. Replace the TODO: Show confetti animation with the following code to do this

isShowConfetti = true;
This is the result of our efforts! 😍

🔥 Bonus — Shop UI Kit

Looking to create a stunning online store? Look no further than FlutterShop, the premium UI kit for Flutter that lets you build beautiful, intuitive, and professional-grade e-commerce app with ease.

We have completed all the necessary steps. Once the sign in is successful, you will probably want to navigate to the next page. To do this, replace the TODO: Navigate to next screen with the destination screen you want to navigate to. Check the example, I have added a 1 second delay because the confetti animation takes 1 second to complete. Once it is done, we will navigate to the new page.

Future.delayed(
Duration(seconds: 1),
() {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => // destination screen
),
);
},
);

We have finished episode 2. Thank you for reading this article. In the next episode, I will show you how to create an animated bottom navigation bar in Flutter.

🔥 Complete source code https://cutt.ly/b14ZAuh (Patreon)

Preview of Episode 3

Working on the Episode 3…

I would love to hear your thoughts on this tutorial and how you found it helpful. If you enjoyed it and found it useful, please don’t forget to give it a clap 👏. Your feedback is important to me and helps me to create content that is valuable and useful to you. Thank you for taking the time to read this tutorial, and I hope that it has been helpful in your animation journey with Rive and Flutter.

Thum

--

--

The Flutter Way
Flutter Community

Want to improve your flutter skill? Join our channel, learn how to become an expert flutter developer and land your next dream job!