An interactive 2FA screen is table stakes with the SignUp/SignIn flow these days especially if you’re building an app that requires high levels of security.
Note: If you came for the code, skip to the bottom of this post for a link to my GitHub repo.
In this post, I will walk you through the process of building a screen that looks like the one above. So let’s get started.
For the animations part, we are going to use a third party library called animated widgets which you can find here: https://pub.dev/packages/animated_widgets
So, let’s go ahead and declare it in our pub_spec.yaml file and do a ‘flutter packages get’
Since, this is clearly a stateful widget, let’s go ahead and declare a stateful widget,
We have created a basic stateful widget that has a TextEditingController so that we can use it for a TextField for the user to type the code, a FocusNode that can be attached to the TextField, a ‘code’ string which will hold the value of the code, a ‘loaded’, ‘shake’ and ‘valid’ boolean variables for toggling the state based on whether the backend returned after validating the code, the text fields must shake(animate with a red) for invalid code and if the code is valid or not respectively.
Now, for the onChange() callback of the TextField, let’s define a method that takes the current value of the code and sets the state of the code variable to that value.
For the onClick() callback of the Verify button, let’s go ahead and define a function where we will make the backend call, validate2FaCode(code) which is asynchronous.
The logic for this method goes likes this,
- Set loaded to false, so that you can use this variable to display a spinner/activity indicator while the backend call is processing
- Make the backend call asynchronously
- Now set loaded back to true and set valid to the result of the call
- If it’s valid, move on to the next screen or sign in according to the flow
- If it’s not valid, set shake to true which will trigger the text field to shake and change colour to red for 300 milliseconds and then set shake back to false so that it stops.
Now, let’s move on to the design:
There are two problems to solve here:
- Render a text field that looks like 6 dashed lines for the 6 characters of the code.
- The dashed lines behaves like a TextField widget so that we can leverage the onChange() property and also attach a TextEditingController and FocusNode.
For the first part, we can design it using a Column of code[n](nth character of the code) and a container(for the dashed line holding that character). We have 6 of them, so we could render it using a for loop by using the index of the for loop on the code string for each character. Each one of these Column widget’s can then be rendered inside a Row widget so that it looks like the one on the gif above.
In a nutshell, we will be rendering a Row of (Column of Text and Container) of size 6.
For the Column widget, I have created a re usable function which returns a List of 6 Column widgets which can then be spread inside a Row for rendering it. Notice how I have wrapped it inside a ShakedAnimatedWidget so that we can apply the animations.
Now, for solving the second problem, I have taken an unconventional approach where I am going to render an actual TextField stacked on top of our custom text field, but hidden using the opaque property. In theory, the user types on the TextField but doesn’t actually see the TextField. The custom text field reflects what the user types by reading from the state variable, code and the onCodeInput callback attached to the TextField.
As you can see from the above code, we are wrapping the TextFormField and the custom field (getField()) inside a Stack widget which will basically super impose these two field. Also, we are hiding the TextFormField using a Opacity widget by setting the opacity property to 0.0. You can actually see the TextFormField by setting the opacity to 1.0.
That’s it! Go ahead and add some creativity to your 2FA Verification screen and make it look lively with colours and animations.
You can find the entire code for the screen here,
Please don’t forget to star the repo so that you can have a point of reference in the future. Thanks for reading this post and feel free to leave your feedback/concerns.