Improve User Experience in Flutter With Snack Bars, Progress Indicators, and Alert Dialog
Tags: flutter, blog, tutorial, firebase
Published: 2022-05-20 08:00:49

[Snack Bars](https://api.flutter.dev/flutter/material/SnackBar-class.html), [Alert Dialogs](https://api.flutter.dev/flutter/material/AlertDialog-class.html), [ProgressIndicators](https://api.flutter.dev/flutter/material/CircularProgressIndicator-class.html), and similar items are very essential tools to enhance the user experience on an application regardless of platform. 


## Intro 
Welcome to another exciting blog, this is 8<sup>th</sup> part of the series __Flutter App Development Tutorial__. So, far we made [Splash Screen](https://dev.to/kcl/create-splash-screen-and-launch-icon-in-flutter-1kdg), some [global widgets](https://dev.to/kcl/how-to-create-custom-widgets-app-bar-drawer-and-bottom-navigation-bar-in-flutter-4kkl) like the app bar and bottom nav bar, and also implemented a global [theme](https://dev.to/kcl/how-to-define-flutter-theme-c26). In the last two sections, we created [UI](https://dev.to/kcl/authentication-in-flutter-user-interface-design-1d9l) and wrote [backend](https://dev.to/kcl/flutter-firebase-authentication-email-and-password-1g1p) for registration and sign-in methods to use with the Firebase project. In this section, we'll use features from flutters to give visual feedback to users.

You can find the source code from [here](https://github.com/nibukdk/astha-tutorial/tree/master/part_seven).

### Show Progress Indicator During Registration/Sign In 

Head over to __auth_state_provider.dart__ class and make some changes.

__Create an enum outside of class, to toggle the Application Process State State.__

```
// Outside of any class or function
// Make an enum to togggle progrss indicator
enum ProcessingState {
  done,
  waiting,
}
``` 
__Inside of Provider class let's create a field of type *ProcessingState* and a  function that switches these values/states.__

```
  ProcessingState _processingState = ProcessingState.done;
  // getter
  ProcessingState get processingState => _processingState;

  void setPrcState(ProcessingState prcsState) {
    _processingState = prcsState;
    notifyListeners();
  }
``` 
We'll display the CircularProgressIndicator whenever the application is busy and remove it when done. For instance, after pressing the register/sign-in button we can display the progress indicator in place of the button and then remove it when firebase sends a response. 

So, let's first start by adding the function inside the register function. We'll make changes inside auth_form_widget.dart files after this.

__Update Processing State In Register Function__

```
// Our Function will take email,password, username and buildcontext
  void register(String email, String password, String username,
      BuildContext context) async {
    // Start loading progress indicator once submit button is hit
    // #1
    setPrcState(ProcessingState.waiting);
    try {
      // Get back usercredential future from createUserWithEmailAndPassword method
      UserCredential userCred = await authInstance
          .createUserWithEmailAndPassword(email: email, password: password);
      // Save username name
      await userCred.user!.updateDisplayName(username);

      // After that access "users" Firestore in firestore and save username, email and userLocation
      await FirebaseFirestore.instance
          .collection('users')
          .doc(userCred.user!.uid)
          .set(
        {
          'username': username,
          'email': email,
          'userLocation': null,
        },
      );
      // if everything goes well user will be registered and logged in
      // now go to the homepage
      GoRouter.of(context).goNamed(APP_PAGE.home.routeName);
    } on FirebaseAuthException catch (e) {
      // In case of error
      // if email already exists
      if (e.code == "email-already-in-use") {
        print("The account with this email already exists.");
      }
      if (e.code == 'weak-password') {
        // If password is too weak
        print("Password is too weak.");
      }

    // #2
      setPrcState(ProcessingState.done);
    } catch (e) {
      // For anything else
      print("Something went wrong please try again.");
      // #3
      setPrcState(ProcessingState.done);
    }
    // notify listeneres
    notifyListeners();
  }
``` 
1. In the registration function, we'll start to toggle the ProcessState to *waiting*(which's *done* by default) right before the Try block. 
2. (#2&3) We'll only switch it back to *done* once the servers send an error response, at the end of the Catch block. We'll not switch it back at the end of the try block because, in case of success, we'll move on to the next page as well as display a Snackbar which we'll implement after this section. 

Please make similar changes to the __Sign In__ and the __Log Out__ functions by yourself. 

Now, we'll need to tell the application, when the processing state is in *waiting*, display a progress indicator, and then remove the indicator once the processing state is done. To do so let's head over to __auth_form_widget.dart__. Right after we instantiate an AuthStateProvider, create a new variable of the type ProcessState whose value is equal to that of AuthStateProvider's process state.


```
 Widget build(BuildContext context) {
    final AuthStateProvider authStateProvider =
        Provider.of<AuthStateProvider>(context);

    // make new ProcessState var
    ProcessingState prcState = authStateProvider.processingState;

``` 
After that, down where we have our elevation button, let's make it a conditionally rendering widget.


```
  if (prcState == ProcessingState.waiting) const CircularProgressIndicator(),
  if (prcState == ProcessingState.done)
              Row(
                mainAxisAlignment: MainAxisAlignment.end,
                children: [
                  TextButton(
                    onPressed: () {},
                    child: const Text('Cancel'),
                  ),
                  const SizedBox(
                    width: 20,
                  ),
                  ElevatedButton(
                    onPressed: () {
                      // call _submitForm on tap
                      _submitForm(authStateProvider, context);
                    },
                    child: Text(registerAuthMode ? 'Register' : 'Sign In'),
                    style: ButtonStyle(
                      elevation: MaterialStateProperty.all(8.0),
                    ),
                  ),
                ],
              ),
``` 
 With this, the progress indicator will activate and deactivate at the right time. 

### Display Information on Snackbar 

As mentioned earlier, we'll be using [Snackbar](https://api.flutter.dev/flutter/material/SnackBar-class.html) to display information like "Authentication Successful", "Welcome Back", etc. To do so let's again create a simple function on AuthStateProvider class.


```
// Right after setPrcState function
 //  create function to handle popups
  SnackBar msgPopUp(msg) {
    return SnackBar(
        content: Text(
      msg,
      textAlign: TextAlign.center,
    ));
  }

``` 
This function will take a custom message and return a SnackBar to display on the screen. Snackbar works in conjunction with [ScaffoldMessenger](https://api.flutter.dev/flutter/material/ScaffoldMessenger-class.html) class. So, we'll pass this msgPopUp method in [ScaffoldMessenger.of(context)](https://api.flutter.dev/flutter/material/ScaffoldMessenger/of.html) at the end of the __try__ block operations, and just before we call GoRouter.

*Inside Registration Function*

```
ScaffoldMessenger.of(context)
          .showSnackBar(msgPopUp("The account has been registered."));
// Before GoRouter
GoRouter.of(context).goNamed(APP_PAGE.home.routeName);
``` 


*Inside Login Function*


```
    ScaffoldMessenger.of(context).showSnackBar(msgPopUp("Welcome Back"));
``` 

With this when the authentication operation succeeds user will see a snack bar at the bottom of the application.

![Snack Bar Msg](https://cdn.hashnode.com/res/hashnode/image/upload/v1648870054137/s6hpGbo40.png)

## Alert Error on Authentication With Alert Dialog

The snack bar is useful because it subtly displays short messages. However, because of its subtlety, it is not very appropriate to be used in case of errors. In such cases, it's better to alert users using a dialog pop-up such as the [AlertDialog](https://api.flutter.dev/flutter/material/AlertDialog-class.html#:~:text=An%20alert%20dialog%20informs%20the,are%20displayed%20below%20the%20content.) widget. So, right after the msgPopUp function, let's create another function.


```
  // #1
  AlertDialog errorDialog(BuildContext context, String errMsg) {
    return AlertDialog(
      title: Text("Error",
          style: TextStyle(
            //text color will be red
            // #2
            color: Theme.of(context).colorScheme.error,
          )),
      content: Text(errMsg,
          style: TextStyle(
            //text color will be red
             // #3
            color: Theme.of(context).colorScheme.error,
          )),
      actions: [
        TextButton(
          // On button click remove the dialog box
           // #2
          onPressed: () => Navigator.of(context).pop(),
          child: const Text('OK'),
        ),
      ],
    );
  }
``` 
1. Our method will take two arguments: an error message and a build context. 
2. Our alert box will have texts with the color red.
3. On click, the dialog box will be removed from the screen. The build context is required here to call [Navigator.pop](https://api.flutter.dev/flutter/widgets/Navigator/pop.html) method.

On both registration and sign-in methods, we are only printing the error messages. Now, let's replace those print statements with errorDialog() and pass the same messages into the function.

__Inside Register Function's Catch Blocks__


```
on FirebaseAuthException catch (e) {
      // In case of error
      // if email already exists
      if (e.code == "email-already-in-use") {
        showDialog(
            context: context,
            builder: (context) => errorDialog(
                context, "The account with this email already exists."));
      }
      if (e.code == 'weak-password') {
        // If password is too weak
        showDialog(
            context: context,
            builder: (context) =>
                errorDialog(context, "Password is too weak."));
      }
      setPrcState(ProcessingState.done);
    } catch (e) {
      // For anything else
      showDialog(
          context: context,
          builder: (context) =>
              errorDialog(context, "Something went wrong please try again."));
      setPrcState(ProcessingState.done);
    }
``` 
This is what the box will look like. 
![Alert Dialog Msg](https://cdn.hashnode.com/res/hashnode/image/upload/v1648870070921/m2ZJeH45L.png)

*Please, add these alerts to your sign-in method by yourself.*


![Khadka's Coding Lounge](https://cdn.hashnode.com/res/hashnode/image/upload/v1648871044555/ypdcGIn2r.png)

## Summary
Few changes were made in AuthStateProvider and AuthFormWidget classes. 
1. We created an Enum to address process states.
2. Progress indicators were implemented based on Processing Status.
3. A simple snack bar function was built to display information for users when authentication succeeds.
4. An error handler was created to display error messages on an alert dialog box.

## Support Us
Was this an informative blog? If so, hit the like button, ask us some questions and share this with your colleagues. The next part is going to be on App Permissions and Google Places API. So, __subscribe and follow us__ to get notifications.

This is Nibesh Khadka from Khadka's Coding Lounge. We are a freelancing agency that makes websites and mobile applications.

![Like and Subscribe](https://cdn.hashnode.com/res/hashnode/image/upload/v1648871261807/PtdB2Zi15.png)