iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 26
0
自我挑戰組

從零開始的Flutter世界系列 第 26

Day26 Flutter 的狀態管理 Provider (五) Firebase Login

  • 分享至 

  • xImage
  •  

main,dart

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

import 'components/auth_widget.dart';
import 'components/auth_widget_builder.dart';
import 'services/apple_sign_in_available.dart';
import 'services/auth_service.dart';
import 'services/auth_service_adapter.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized();
  await Firebase.initializeApp();
  final appleSignInAvailable = await AppleSignInAvailable.check();
  runApp(MyApp(appleSignInAvailable: appleSignInAvailable));
}

class MyApp extends StatelessWidget {
  const MyApp(
      {this.initialAuthServiceType = AuthServiceType.firebase,
      this.appleSignInAvailable});

  final AuthServiceType initialAuthServiceType;
  final AppleSignInAvailable appleSignInAvailable;

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        Provider<AppleSignInAvailable>.value(value: appleSignInAvailable),
        Provider<AuthService>(
          create: (_) => AuthServiceAdapter(
            initialAuthServiceType: initialAuthServiceType,
          ),
          dispose: (_, AuthService authService) => authService.dispose(),
        ),
      ],
      child: AuthWidgetBuilder(
          builder: (BuildContext context, AsyncSnapshot<User> userSnapshot) {
        return MaterialApp(
          theme: ThemeData(primarySwatch: Colors.indigo),
          home: AuthWidget(userSnapshot: userSnapshot),
        );
      }),
    );
  }
}

login_manager.dart

import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:meta/meta.dart';
import 'package:travel_note/services/auth_service.dart';

class LoginManager {
  LoginManager({@required this.auth, @required this.isLoading});

  final AuthService auth;
  final ValueNotifier<bool> isLoading;

  Future<void> loginWithEmailAndPassword(
      {@required String email, @required String password}) async {
    try {
      isLoading.value = true;
      return await auth.loginWithEmailAndPassword(email, password);
    } catch (e) {
      isLoading.value = false;
      rethrow;
    }
  }
}

body.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:travel_note/components/no_account_text.dart';
import 'package:travel_note/components/social_media.dart';
import 'package:travel_note/components/social_media_manager.dart';
import 'package:travel_note/services/auth_service.dart';

import '../../../constants.dart';
import '../../../size_config.dart';
import '../login_manager.dart';
import 'login_form.dart';

class Body extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final AuthService auth = Provider.of<AuthService>(context, listen: false);
    return ChangeNotifierProvider<ValueNotifier<bool>>(
      create: (_) => ValueNotifier<bool>(false),
      child: Consumer<ValueNotifier<bool>>(
        builder: (_, ValueNotifier<bool> isLoading, __) => MultiProvider(
          providers: [
            Provider<LoginManager>(
              create: (_) => LoginManager(auth: auth, isLoading: isLoading),
            ),
            Provider<SocialMediaManager>(
              create: (_) =>
                  SocialMediaManager(auth: auth, isLoading: isLoading),
            ),
          ],
          child: SingleChildScrollView(
            child: Padding(
              padding: EdgeInsets.symmetric(
                  horizontal: getProportionateScreenWidth(25)),
              child: Column(
                children: [
                  Text(
                    "Welcome to Travel Note",
                    style: headingStyle,
                  ),
                  VerticalSpacing(of: 16),
                  Text(
                    'Log in with your email and password \nor continue with social media',
                    textAlign: TextAlign.left,
                    style: contentStyle,
                  ),
                  VerticalSpacing(of: 25),
                  Consumer<LoginManager>(
                    builder: (_, LoginManager manager, __) => LoginForm.set(
                      isLoading: isLoading.value,
                      manager: manager,
                    ),
                  ),
                  VerticalSpacing(of: 25),
                  Consumer<SocialMediaManager>(
                    builder: (_, SocialMediaManager manager, __) =>
                        SocialMedia.set(
                      isLoading: isLoading.value,
                      manager: manager,
                    ),
                  ),
                  VerticalSpacing(of: 25),
                  NoAccountText(),
                  VerticalSpacing(of: 25),
                ],
              ),
            ),
          ),
        ),
      ),
    );
  }
}

login_form.dart

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:material_design_icons_flutter/material_design_icons_flutter.dart';
import 'package:travel_note/components/default_button.dart';
import 'package:travel_note/components/form_error.dart';
import 'package:travel_note/components/platform_exception_alert_dialog.dart';
import 'package:travel_note/screens/forgot_password/forgot_password_screen.dart';
import 'package:travel_note/screens/login/login_manager.dart';

import '../../../constants.dart';
import '../../../size_config.dart';

class LoginForm extends StatefulWidget {
  final LoginManager manager;
  final bool isLoading;

  LoginForm.set({Key key, this.isLoading, this.manager}) : super(key: key);

  @override
  _LoginFormState createState() => _LoginFormState.set(isLoading, manager);
}

class _LoginFormState extends State<LoginForm> {
  _LoginFormState.set(this.isLoading, this.manager);

  final LoginManager manager;
  final bool isLoading;
  final _formKey = GlobalKey<FormState>();
  String email;
  String password;
  bool remember = false;
  final List<String> errors = [];

  void addError({String error}) {
    if (!errors.contains(error))
      setState(() {
        errors.add(error);
      });
  }

  void removeError({String error}) {
    if (errors.contains(error))
      setState(() {
        errors.remove(error);
      });
  }

  @override
  Widget build(BuildContext context) {
    return Form(
      key: _formKey,
      child: Column(
        children: [
          buildEmailFormField(),
          VerticalSpacing(of: 25),
          buildPasswordFormField(),
          VerticalSpacing(of: 25),
          Row(
            children: [
              Checkbox(
                value: remember,
                activeColor: kPrimaryColor,
                onChanged: (value) {
                  setState(() {
                    remember = value;
                  });
                },
              ),
              Text("Remember me"),
              Spacer(),
              GestureDetector(
                onTap: () {
                  Navigator.pushNamed(context, ForgotPasswordScreen.routeName);
                },
                child: Text(
                  "Forgot Password",
                  style: TextStyle(decoration: TextDecoration.underline),
                ),
              )
            ],
          ),
          FormError(errors: errors),
          VerticalSpacing(of: 25),
          DefaultButton(
            text: "Login",
            press: isLoading
                ? null
                : () {
                    if (_formKey.currentState.validate()) {
                      _formKey.currentState.save();
                      _loginWithEmailAndPassword(context, email, password);
                    }
                  },
          ),
        ],
      ),
    );
  }

  TextFormField buildPasswordFormField() {
    return TextFormField(
      obscureText: true,
      onSaved: (newValue) => password = newValue,
      onChanged: (value) {
        if (value.isNotEmpty) {
          removeError(error: kPasswordNullError);
        }
        if (value.length >= 8) {
          removeError(error: kShortPasswordError);
        }
      },
      validator: (value) {
        if (value.isEmpty) {
          addError(error: kPasswordNullError);
          removeError(error: kShortPasswordError);
          return "";
        } else if (value.length < 8) {
          addError(error: kShortPasswordError);
          return "";
        }
        return null;
      },
      decoration: InputDecoration(
        labelText: "Password",
        hintText: "Enter your password",
        floatingLabelBehavior: FloatingLabelBehavior.always,
        suffixIcon: Icon(
          MdiIcons.fromString("lock-outline"),
        ),
      ),
    );
  }

  TextFormField buildEmailFormField() {
    return TextFormField(
      keyboardType: TextInputType.emailAddress,
      onSaved: (newValue) => email = newValue,
      onChanged: (value) {
        if (value.isNotEmpty) {
          removeError(error: kEmailNullError);
        }
        if (emailValidatorRegExp.hasMatch(value)) {
          removeError(error: kInvalidEmailError);
        }
      },
      validator: (value) {
        if (value.isEmpty) {
          addError(error: kEmailNullError);
          removeError(error: kInvalidEmailError);
          return "";
        } else if (!emailValidatorRegExp.hasMatch(value)) {
          addError(error: kInvalidEmailError);
          return "";
        }
        return null;
      },
      decoration: InputDecoration(
        labelText: "Email",
        hintText: "Enter your email",
        floatingLabelBehavior: FloatingLabelBehavior.always,
        suffixIcon: Icon(
          MdiIcons.fromString("email-outline"),
        ),
      ),
    );
  }

  Future<void> _loginWithEmailAndPassword(
      BuildContext context, String email, String password) async {
    try {
      await manager.loginWithEmailAndPassword(email: email, password: password);
    } on PlatformException catch (e) {
      if (e.code != 'ERROR_ABORTED_BY_USER') {
        _showLoginError(context, e);
      }
    }
  }

  Future<void> _showLoginError(
      BuildContext context, PlatformException exception) async {
    await PlatformExceptionAlertDialog(
      title: 'Login failed',
      exception: exception,
    ).show(context);
  }
}

其他註冊頁、忘記密碼頁,動作都類似,接下來可以先自己嘗試看看


上一篇
Day25 Flutter 的狀態管理 Provider (四) Firebase Login
下一篇
Day27 JSON and serialization
系列文
從零開始的Flutter世界30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言