Search code examples
firebaseflutterprovider

how to add CircularProgressIndicator using provider with firebase


i went through google code lab of flutter firebase using provider package every thing working fine as mentioned in codelab i like the way they design Authentication widget passing functions from it, in below code i want to add CircularProgressIndicator on waiting state, i don't understand where do i add condition of isLoading is equals to true then wait otherwise dosomething with functions passed by Authentication widget.

main.dart

import 'package:firebase_core/firebase_core.dart'; // new
import 'package:firebase_auth/firebase_auth.dart'; // new
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:provider/provider.dart'; // new

import 'src/authentication.dart'; // new
import 'src/widgets.dart';

void main() {
  // Modify from here
  runApp(
    ChangeNotifierProvider(
      create: (context) => ApplicationState(),
      builder: (context, _) => App(),
    ),
  );
  // to here.
}

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Firebase Meetup',
      theme: ThemeData(
        buttonTheme: Theme.of(context).buttonTheme.copyWith(
              highlightColor: Colors.deepPurple,
            ),
        primarySwatch: Colors.deepPurple,
        textTheme: GoogleFonts.robotoTextTheme(
          Theme.of(context).textTheme,
        ),
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: HomePage(),
    );
  }
}

class HomePage extends StatelessWidget {
  HomePage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Firebase Meetup'),
      ),
      body: ListView(
        children: <Widget>[
          Image.asset('assets/codelab.png'),
          SizedBox(height: 8),
          IconAndDetail(Icons.calendar_today, 'October 30'),
          IconAndDetail(Icons.location_city, 'San Francisco'),
          // Add from here
          Consumer<ApplicationState>(
            builder: (context, appState, _) => Authentication(
              email: appState.email,
              loginState: appState.loginState,
              startLoginFlow: appState.startLoginFlow,
              verifyEmail: appState.verifyEmail,
              signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
              cancelRegistration: appState.cancelRegistration,
              registerAccount: appState.registerAccount,
              signOut: appState.signOut,
            ),
          ),
          // to here
          Divider(
            height: 8,
            thickness: 1,
            indent: 8,
            endIndent: 8,
            color: Colors.grey,
          ),
          Header("What we'll be doing"),
          Paragraph(
            'Join us for a day full of Firebase Workshops and Pizza!',
          ),
        ],
      ),
    );
  }
}

class ApplicationState extends ChangeNotifier {
  ApplicationState() {
    init();
  }

  Future<void> init() async {
    await Firebase.initializeApp();

    FirebaseAuth.instance.userChanges().listen((user) {
      if (user != null) {
        _loginState = ApplicationLoginState.loggedIn;
      } else {
        _loginState = ApplicationLoginState.loggedOut;
      }
      notifyListeners();
    });
  }

  ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
  ApplicationLoginState get loginState => _loginState;

  String? _email;
  String? get email => _email;

  void startLoginFlow() {
    _loginState = ApplicationLoginState.emailAddress;
    notifyListeners();
  }

  void verifyEmail(
    String email,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      var methods = await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
      if (methods.contains('password')) {
        _loginState = ApplicationLoginState.password;
      } else {
        _loginState = ApplicationLoginState.register;
      }
      _email = email;
      notifyListeners();
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void signInWithEmailAndPassword(
    String email,
    String password,
    void Function(FirebaseAuthException e) errorCallback,
  ) async {
    try {
      await FirebaseAuth.instance.signInWithEmailAndPassword(
        email: email,
        password: password,
      );
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void cancelRegistration() {
    _loginState = ApplicationLoginState.emailAddress;
    notifyListeners();
  }

  void registerAccount(String email, String displayName, String password,
      void Function(FirebaseAuthException e) errorCallback) async {
    try {
      var credential = await FirebaseAuth.instance
          .createUserWithEmailAndPassword(email: email, password: password);
      await credential.user!.updateProfile(displayName: displayName);
    } on FirebaseAuthException catch (e) {
      errorCallback(e);
    }
  }

  void signOut() {
    FirebaseAuth.instance.signOut();

    /// here is not notifylistener();
  }
}

authentication.dart

import 'package:flutter/material.dart';

import 'widgets.dart';

enum ApplicationLoginState {
  loggedOut,
  emailAddress,
  register,
  password,
  loggedIn,
}

class Authentication extends StatelessWidget {
  const Authentication({
    required this.loginState,
    required this.email,
    required this.startLoginFlow,
    required this.verifyEmail,
    required this.signInWithEmailAndPassword,
    required this.cancelRegistration,
    required this.registerAccount,
    required this.signOut,
  });

  final ApplicationLoginState loginState;
  final String? email;
  final void Function() startLoginFlow;
  final void Function(
    String email,
    void Function(Exception e) error,
  ) verifyEmail;
  final void Function(
    String email,
    String password,
    void Function(Exception e) error,
  ) signInWithEmailAndPassword;
  final void Function() cancelRegistration;
  final void Function(
    String email,
    String displayName,
    String password,
    void Function(Exception e) error,
  ) registerAccount;
  final void Function() signOut;

  @override
  Widget build(BuildContext context) {
    switch (loginState) {
      case ApplicationLoginState.loggedOut:
        return Row(
          children: [
            Padding(
              padding: const EdgeInsets.only(left: 24, bottom: 8),
              child: StyledButton(
                onPressed: () {
                  startLoginFlow();
                },
                child: const Text('RSVP'),
              ),
            ),
          ],
        );
      case ApplicationLoginState.emailAddress:
        return EmailForm(
            callback: (email) =>
                verifyEmail(email, (e) => _showErrorDialog(context, 'Invalid email', e)));
      case ApplicationLoginState.password:
        return PasswordForm(
          email: email!,
          login: (email, password) {
            print("CONTEXT $context");
            signInWithEmailAndPassword(
                email, password, (e) => _showErrorDialog(context, 'Failed to sign in', e));
          },
        );
      case ApplicationLoginState.register:
        return RegisterForm(
          email: email!,
          cancel: () {
            cancelRegistration();
          },
          registerAccount: (
            email,
            displayName,
            password,
          ) {
            registerAccount(email, displayName, password,
                (e) => _showErrorDialog(context, 'Failed to create account', e));
          },
        );
      case ApplicationLoginState.loggedIn:
        return Row(
          children: [
            Padding(
              padding: const EdgeInsets.only(left: 24, bottom: 8),
              child: StyledButton(
                onPressed: () {
                  signOut();
                },
                child: const Text('LOGOUT'),
              ),
            ),
          ],
        );
      default:
        return Row(
          children: const [
            Text("Internal error, this shouldn't happen..."),
          ],
        );
    }
  }

  void _showErrorDialog(BuildContext context, String title, Exception e) {
    print("CONTEXT $context");
    showDialog<void>(
      context: context,
      builder: (context) {
        return AlertDialog(
          title: Text(
            title,
            style: const TextStyle(fontSize: 24),
          ),
          content: SingleChildScrollView(
            child: ListBody(
              children: <Widget>[
                Text(
                  '${(e as dynamic).message}',
                  style: const TextStyle(fontSize: 18),
                ),
              ],
            ),
          ),
          actions: <Widget>[
            StyledButton(
              onPressed: () {
                Navigator.of(context).pop();
              },
              child: const Text(
                'OK',
                style: TextStyle(color: Colors.deepPurple),
              ),
            ),
          ],
        );
      },
    );
  }
}

class EmailForm extends StatefulWidget {
  const EmailForm({required this.callback});
  final void Function(String email) callback;
  @override
  _EmailFormState createState() => _EmailFormState();
}

class _EmailFormState extends State<EmailForm> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_EmailFormState');
  final _controller = TextEditingController();

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Header('Sign in with email'),
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Form(
            key: _formKey,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 24),
                  child: TextFormField(
                    controller: _controller,
                    decoration: const InputDecoration(
                      hintText: 'Enter your email',
                    ),
                    validator: (value) {
                      if (value!.isEmpty) {
                        return 'Enter your email address to continue';
                      }
                      return null;
                    },
                  ),
                ),
                Row(
                  mainAxisAlignment: MainAxisAlignment.end,
                  children: [
                    Padding(
                      padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 30),
                      child: StyledButton(
                        onPressed: () async {
                          if (_formKey.currentState!.validate()) {
                            widget.callback(_controller.text);
                          }
                        },
                        child: const Text('NEXT'),
                      ),
                    ),
                  ],
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

class RegisterForm extends StatefulWidget {
  const RegisterForm({
    required this.registerAccount,
    required this.cancel,
    required this.email,
  });
  final String email;
  final void Function(String email, String displayName, String password) registerAccount;
  final void Function() cancel;
  @override
  _RegisterFormState createState() => _RegisterFormState();
}

class _RegisterFormState extends State<RegisterForm> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_RegisterFormState');
  final _emailController = TextEditingController();
  final _displayNameController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _emailController.text = widget.email;
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Header('Create account'),
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Form(
            key: _formKey,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 24),
                  child: TextFormField(
                    controller: _emailController,
                    decoration: const InputDecoration(
                      hintText: 'Enter your email',
                    ),
                    validator: (value) {
                      if (value!.isEmpty) {
                        return 'Enter your email address to continue';
                      }
                      return null;
                    },
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 24),
                  child: TextFormField(
                    controller: _displayNameController,
                    decoration: const InputDecoration(
                      hintText: 'First & last name',
                    ),
                    validator: (value) {
                      if (value!.isEmpty) {
                        return 'Enter your account name';
                      }
                      return null;
                    },
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 24),
                  child: TextFormField(
                    controller: _passwordController,
                    decoration: const InputDecoration(
                      hintText: 'Password',
                    ),
                    obscureText: true,
                    validator: (value) {
                      if (value!.isEmpty) {
                        return 'Enter your password';
                      }
                      return null;
                    },
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                      TextButton(
                        onPressed: widget.cancel,
                        child: const Text('CANCEL'),
                      ),
                      const SizedBox(width: 16),
                      StyledButton(
                        onPressed: () {
                          if (_formKey.currentState!.validate()) {
                            widget.registerAccount(
                              _emailController.text,
                              _displayNameController.text,
                              _passwordController.text,
                            );
                          }
                        },
                        child: const Text('SAVE'),
                      ),
                      const SizedBox(width: 30),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

class PasswordForm extends StatefulWidget {
  const PasswordForm({
    required this.login,
    required this.email,
  });
  final String email;
  final void Function(String email, String password) login;
  @override
  _PasswordFormState createState() => _PasswordFormState();
}

class _PasswordFormState extends State<PasswordForm> {
  final _formKey = GlobalKey<FormState>(debugLabel: '_PasswordFormState');
  final _emailController = TextEditingController();
  final _passwordController = TextEditingController();

  @override
  void initState() {
    super.initState();
    _emailController.text = widget.email;
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Header('Sign in'),
        Padding(
          padding: const EdgeInsets.all(8.0),
          child: Form(
            key: _formKey,
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: <Widget>[
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 24),
                  child: TextFormField(
                    controller: _emailController,
                    decoration: const InputDecoration(
                      hintText: 'Enter your email',
                    ),
                    validator: (value) {
                      if (value!.isEmpty) {
                        return 'Enter your email address to continue';
                      }
                      return null;
                    },
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(horizontal: 24),
                  child: TextFormField(
                    controller: _passwordController,
                    decoration: const InputDecoration(
                      hintText: 'Password',
                    ),
                    obscureText: true,
                    validator: (value) {
                      if (value!.isEmpty) {
                        return 'Enter your password';
                      }
                      return null;
                    },
                  ),
                ),
                Padding(
                  padding: const EdgeInsets.symmetric(vertical: 16),
                  child: Row(
                    mainAxisAlignment: MainAxisAlignment.end,
                    children: [
                      const SizedBox(width: 16),
                      StyledButton(
                        onPressed: () {
                          if (_formKey.currentState!.validate()) {
                            widget.login(
                              _emailController.text,
                              _passwordController.text,
                            );
                          }
                        },
                        child: const Text('SIGN IN'),
                      ),
                      const SizedBox(width: 30),
                    ],
                  ),
                ),
              ],
            ),
          ),
        ),
      ],
    );
  }
}

widgets.dart

import 'package:flutter/material.dart';

class Header extends StatelessWidget {
  const Header(this.heading);
  final String heading;

  @override
  Widget build(BuildContext context) => Padding(
        padding: const EdgeInsets.all(8.0),
        child: Text(
          heading,
          style: const TextStyle(fontSize: 24),
        ),
      );
}

class Paragraph extends StatelessWidget {
  const Paragraph(this.content);
  final String content;
  @override
  Widget build(BuildContext context) => Padding(
        padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
        child: Text(
          content,
          style: const TextStyle(fontSize: 18),
        ),
      );
}

class IconAndDetail extends StatelessWidget {
  const IconAndDetail(this.icon, this.detail);
  final IconData icon;
  final String detail;

  @override
  Widget build(BuildContext context) => Padding(
        padding: const EdgeInsets.all(8.0),
        child: Row(
          children: [
            Icon(icon),
            const SizedBox(width: 8),
            Text(
              detail,
              style: const TextStyle(fontSize: 18),
            )
          ],
        ),
      );
}

class StyledButton extends StatelessWidget {
  const StyledButton({required this.child, required this.onPressed});
  final Widget child;
  final void Function() onPressed;

  @override
  Widget build(BuildContext context) => OutlinedButton(
        style: OutlinedButton.styleFrom(
            side: const BorderSide(color: Colors.deepPurple)),
        onPressed: onPressed,
        child: child,
      );
}

pubspec.yaml (version numbers at time of posting)

 cloud_firestore: ^1.0.0 # new
  firebase_auth: ^1.0.0   # new
  google_fonts: ^2.0.0
  provider: ^5.0.0   

Solution

  • I answer my question, you could do this by adding bool isLoading variable in ApplicationState class and set its value on conditioned base from any method(signInWithEmailAndPassword) present in ApplicationState and pass isLoading to Authentication widget and check condition in build method e.g if isLoading true then show CircularProgressIndicator check below code for more details:

    main.dart

        import 'package:firebase_core/firebase_core.dart'; // new
        import 'package:firebase_auth/firebase_auth.dart'; // new
        import 'package:flutter/material.dart';
        import 'package:google_fonts/google_fonts.dart';
        import 'package:provider/provider.dart'; // new
        
        import 'src/authentication.dart'; // new
        import 'src/widgets.dart';
        
        void main() {
          // Modify from here
          runApp(
            ChangeNotifierProvider(
              create: (context) => ApplicationState(),
              builder: (context, _) => App(),
            ),
          );
          // to here.
        }
        
        class App extends StatelessWidget {
          @override
          Widget build(BuildContext context) {
            return MaterialApp(
              title: 'Firebase Meetup',
              theme: ThemeData(
                buttonTheme: Theme.of(context).buttonTheme.copyWith(
                      highlightColor: Colors.deepPurple,
                    ),
                primarySwatch: Colors.deepPurple,
                textTheme: GoogleFonts.robotoTextTheme(
                  Theme.of(context).textTheme,
                ),
                visualDensity: VisualDensity.adaptivePlatformDensity,
              ),
              home: HomePage(),
            );
          }
        }
        
        class HomePage extends StatelessWidget {
          HomePage({Key? key}) : super(key: key);
        
          @override
          Widget build(BuildContext context) {
            return Scaffold(
              appBar: AppBar(
                title: Text('Firebase Meetup'),
              ),
              body: ListView(
                children: <Widget>[
                  Image.asset('assets/codelab.png'),
                  SizedBox(height: 8),
                  IconAndDetail(Icons.calendar_today, 'October 30'),
                  IconAndDetail(Icons.location_city, 'San Francisco'),
                  // Add from here
                  Consumer<ApplicationState>(
                    builder: (context, appState, _) => Authentication(
    isLoading: appState.isLoading,
                      email: appState.email,
                      loginState: appState.loginState,
                      startLoginFlow: appState.startLoginFlow,
                      verifyEmail: appState.verifyEmail,
                      signInWithEmailAndPassword: appState.signInWithEmailAndPassword,
                      cancelRegistration: appState.cancelRegistration,
                      registerAccount: appState.registerAccount,
                      signOut: appState.signOut,
                    ),
                  ),
                  // to here
                  Divider(
                    height: 8,
                    thickness: 1,
                    indent: 8,
                    endIndent: 8,
                    color: Colors.grey,
                  ),
                  Header("What we'll be doing"),
                  Paragraph(
                    'Join us for a day full of Firebase Workshops and Pizza!',
                  ),
                ],
              ),
            );
          }
        }
        
        class ApplicationState extends ChangeNotifier {
          ApplicationState() {
            init();
          }
        
          Future<void> init() async {
            await Firebase.initializeApp();
        
            FirebaseAuth.instance.userChanges().listen((user) {
              if (user != null) {
                _loginState = ApplicationLoginState.loggedIn;
              } else {
                _loginState = ApplicationLoginState.loggedOut;
              }
              notifyListeners();
            });
          }
        
          ApplicationLoginState _loginState = ApplicationLoginState.loggedOut;
          ApplicationLoginState get loginState => _loginState;
    bool get isLoading => _isLoading;
        
    
          String? _email;
          String? get email => _email;
    bool _isLoading = false;
        
          void startLoginFlow() {
            _loginState = ApplicationLoginState.emailAddress;
            notifyListeners();
          }
        
          void verifyEmail(
            String email,
            void Function(FirebaseAuthException e) errorCallback,
          ) async {
            try {
              var methods = await FirebaseAuth.instance.fetchSignInMethodsForEmail(email);
              if (methods.contains('password')) {
                _loginState = ApplicationLoginState.password;
              } else {
                _loginState = ApplicationLoginState.register;
              }
              _email = email;
              notifyListeners();
            } on FirebaseAuthException catch (e) {
              errorCallback(e);
            }
          }
        
          void signInWithEmailAndPassword(
            String email,
            String password,
            void Function(FirebaseAuthException e) errorCallback,
          ) async {
            try {
    if (!this.isLoading) {
            this._isLoading = true;
            notifyListeners();
          }
              await FirebaseAuth.instance.signInWithEmailAndPassword(
                email: email,
                password: password,
              );
            } on FirebaseAuthException catch (e) {
              errorCallback(e);
    this._isLoading = false;
          notifyListeners();
            }
          }
        
          void cancelRegistration() {
            _loginState = ApplicationLoginState.emailAddress;
            notifyListeners();
          }
        
          void registerAccount(String email, String displayName, String password,
              void Function(FirebaseAuthException e) errorCallback) async {
            try {
              var credential = await FirebaseAuth.instance
                  .createUserWithEmailAndPassword(email: email, password: password);
              await credential.user!.updateProfile(displayName: displayName);
            } on FirebaseAuthException catch (e) {
              errorCallback(e);
            }
          }
        
          void signOut() {
            FirebaseAuth.instance.signOut();
        
            /// here is not notifylistener();
          }
        }
    

    authentication.dart

        import 'package:flutter/material.dart';
        
        import 'widgets.dart';
        
        enum ApplicationLoginState {
          loggedOut,
          emailAddress,
          register,
          password,
          loggedIn,
        }
        
        class Authentication extends StatelessWidget {
          const Authentication({
    required this.isLoading,
            required this.loginState,
            required this.email,
            required this.startLoginFlow,
            required this.verifyEmail,
            required this.signInWithEmailAndPassword,
            required this.cancelRegistration,
            required this.registerAccount,
            required this.signOut,
          });
    
    final bool isLoading;    
          final ApplicationLoginState loginState;
          final String? email;
          final void Function() startLoginFlow;
          final void Function(
            String email,
            void Function(Exception e) error,
          ) verifyEmail;
          final void Function(
            String email,
            String password,
            void Function(Exception e) error,
          ) signInWithEmailAndPassword;
          final void Function() cancelRegistration;
          final void Function(
            String email,
            String displayName,
            String password,
            void Function(Exception e) error,
          ) registerAccount;
          final void Function() signOut;
        
          @override
          Widget build(BuildContext context) {
    if (this.isLoading) {
          return Stack(
            children: [Center(child: CircularProgressIndicator(value: null))],
          );
        }
            switch (loginState) {
              case ApplicationLoginState.loggedOut:
                return Row(
                  children: [
                    Padding(
                      padding: const EdgeInsets.only(left: 24, bottom: 8),
                      child: StyledButton(
                        onPressed: () {
                          startLoginFlow();
                        },
                        child: const Text('RSVP'),
                      ),
                    ),
                  ],
                );
              case ApplicationLoginState.emailAddress:
                return EmailForm(
                    callback: (email) =>
                        verifyEmail(email, (e) => _showErrorDialog(context, 'Invalid email', e)));
              case ApplicationLoginState.password:
                return PasswordForm(
                  email: email!,
                  login: (email, password) {
                    print("CONTEXT $context");
                    signInWithEmailAndPassword(
                        email, password, (e) => _showErrorDialog(context, 'Failed to sign in', e));
                  },
                );
              case ApplicationLoginState.register:
                return RegisterForm(
                  email: email!,
                  cancel: () {
                    cancelRegistration();
                  },
                  registerAccount: (
                    email,
                    displayName,
                    password,
                  ) {
                    registerAccount(email, displayName, password,
                        (e) => _showErrorDialog(context, 'Failed to create account', e));
                  },
                );
              case ApplicationLoginState.loggedIn:
                return Row(
                  children: [
                    Padding(
                      padding: const EdgeInsets.only(left: 24, bottom: 8),
                      child: StyledButton(
                        onPressed: () {
                          signOut();
                        },
                        child: const Text('LOGOUT'),
                      ),
                    ),
                  ],
                );
              default:
                return Row(
                  children: const [
                    Text("Internal error, this shouldn't happen..."),
                  ],
                );
            }
          }
        
          void _showErrorDialog(BuildContext context, String title, Exception e) {
            print("CONTEXT $context");
            showDialog<void>(
              context: context,
              builder: (context) {
                return AlertDialog(
                  title: Text(
                    title,
                    style: const TextStyle(fontSize: 24),
                  ),
                  content: SingleChildScrollView(
                    child: ListBody(
                      children: <Widget>[
                        Text(
                          '${(e as dynamic).message}',
                          style: const TextStyle(fontSize: 18),
                        ),
                      ],
                    ),
                  ),
                  actions: <Widget>[
                    StyledButton(
                      onPressed: () {
                        Navigator.of(context).pop();
                      },
                      child: const Text(
                        'OK',
                        style: TextStyle(color: Colors.deepPurple),
                      ),
                    ),
                  ],
                );
              },
            );
          }
        }
        
        class EmailForm extends StatefulWidget {
          const EmailForm({required this.callback});
          final void Function(String email) callback;
          @override
          _EmailFormState createState() => _EmailFormState();
        }
        
        class _EmailFormState extends State<EmailForm> {
          final _formKey = GlobalKey<FormState>(debugLabel: '_EmailFormState');
          final _controller = TextEditingController();
        
          @override
          Widget build(BuildContext context) {
            return Column(
              children: [
                const Header('Sign in with email'),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Form(
                    key: _formKey,
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 24),
                          child: TextFormField(
                            controller: _controller,
                            decoration: const InputDecoration(
                              hintText: 'Enter your email',
                            ),
                            validator: (value) {
                              if (value!.isEmpty) {
                                return 'Enter your email address to continue';
                              }
                              return null;
                            },
                          ),
                        ),
                        Row(
                          mainAxisAlignment: MainAxisAlignment.end,
                          children: [
                            Padding(
                              padding: const EdgeInsets.symmetric(vertical: 16.0, horizontal: 30),
                              child: StyledButton(
                                onPressed: () async {
                                  if (_formKey.currentState!.validate()) {
                                    widget.callback(_controller.text);
                                  }
                                },
                                child: const Text('NEXT'),
                              ),
                            ),
                          ],
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            );
          }
        }
        
        class RegisterForm extends StatefulWidget {
          const RegisterForm({
            required this.registerAccount,
            required this.cancel,
            required this.email,
          });
          final String email;
          final void Function(String email, String displayName, String password) registerAccount;
          final void Function() cancel;
          @override
          _RegisterFormState createState() => _RegisterFormState();
        }
        
        class _RegisterFormState extends State<RegisterForm> {
          final _formKey = GlobalKey<FormState>(debugLabel: '_RegisterFormState');
          final _emailController = TextEditingController();
          final _displayNameController = TextEditingController();
          final _passwordController = TextEditingController();
        
          @override
          void initState() {
            super.initState();
            _emailController.text = widget.email;
          }
        
          @override
          Widget build(BuildContext context) {
            return Column(
              children: [
                const Header('Create account'),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Form(
                    key: _formKey,
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 24),
                          child: TextFormField(
                            controller: _emailController,
                            decoration: const InputDecoration(
                              hintText: 'Enter your email',
                            ),
                            validator: (value) {
                              if (value!.isEmpty) {
                                return 'Enter your email address to continue';
                              }
                              return null;
                            },
                          ),
                        ),
                        Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 24),
                          child: TextFormField(
                            controller: _displayNameController,
                            decoration: const InputDecoration(
                              hintText: 'First & last name',
                            ),
                            validator: (value) {
                              if (value!.isEmpty) {
                                return 'Enter your account name';
                              }
                              return null;
                            },
                          ),
                        ),
                        Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 24),
                          child: TextFormField(
                            controller: _passwordController,
                            decoration: const InputDecoration(
                              hintText: 'Password',
                            ),
                            obscureText: true,
                            validator: (value) {
                              if (value!.isEmpty) {
                                return 'Enter your password';
                              }
                              return null;
                            },
                          ),
                        ),
                        Padding(
                          padding: const EdgeInsets.symmetric(vertical: 16),
                          child: Row(
                            mainAxisAlignment: MainAxisAlignment.end,
                            children: [
                              TextButton(
                                onPressed: widget.cancel,
                                child: const Text('CANCEL'),
                              ),
                              const SizedBox(width: 16),
                              StyledButton(
                                onPressed: () {
                                  if (_formKey.currentState!.validate()) {
                                    widget.registerAccount(
                                      _emailController.text,
                                      _displayNameController.text,
                                      _passwordController.text,
                                    );
                                  }
                                },
                                child: const Text('SAVE'),
                              ),
                              const SizedBox(width: 30),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            );
          }
        }
        
        class PasswordForm extends StatefulWidget {
          const PasswordForm({
            required this.login,
            required this.email,
          });
          final String email;
          final void Function(String email, String password) login;
          @override
          _PasswordFormState createState() => _PasswordFormState();
        }
        
        class _PasswordFormState extends State<PasswordForm> {
          final _formKey = GlobalKey<FormState>(debugLabel: '_PasswordFormState');
          final _emailController = TextEditingController();
          final _passwordController = TextEditingController();
        
          @override
          void initState() {
            super.initState();
            _emailController.text = widget.email;
          }
        
          @override
          Widget build(BuildContext context) {
            return Column(
              children: [
                const Header('Sign in'),
                Padding(
                  padding: const EdgeInsets.all(8.0),
                  child: Form(
                    key: _formKey,
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: <Widget>[
                        Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 24),
                          child: TextFormField(
                            controller: _emailController,
                            decoration: const InputDecoration(
                              hintText: 'Enter your email',
                            ),
                            validator: (value) {
                              if (value!.isEmpty) {
                                return 'Enter your email address to continue';
                              }
                              return null;
                            },
                          ),
                        ),
                        Padding(
                          padding: const EdgeInsets.symmetric(horizontal: 24),
                          child: TextFormField(
                            controller: _passwordController,
                            decoration: const InputDecoration(
                              hintText: 'Password',
                            ),
                            obscureText: true,
                            validator: (value) {
                              if (value!.isEmpty) {
                                return 'Enter your password';
                              }
                              return null;
                            },
                          ),
                        ),
                        Padding(
                          padding: const EdgeInsets.symmetric(vertical: 16),
                          child: Row(
                            mainAxisAlignment: MainAxisAlignment.end,
                            children: [
                              const SizedBox(width: 16),
                              StyledButton(
                                onPressed: () {
                                  if (_formKey.currentState!.validate()) {
                                    widget.login(
                                      _emailController.text,
                                      _passwordController.text,
                                    );
                                  }
                                },
                                child: const Text('SIGN IN'),
                              ),
                              const SizedBox(width: 30),
                            ],
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ],
            );
          }
        }