精华内容
下载资源
问答
  • firebase_auth_oauth Flutter插件,可轻松使用FirebaseAuth执行OAuth登录流程。 它还包括对Apple for Firebase登录的支持。 该插件支持Android,iOS和Web。 OAuth流程是通过在应用程序顶部打开弹出窗口来执行的,以...
  • lit_firebase_auth:点亮Firebase身份验证可简化向Flutter应用程序添加Firebase身份验证的过程
  • 使用Riverpod的Flutter Firebase电话验证 随时在问我您的问题。
  • Flutter中的简单Firebase登录流程 点击此处查看逐步阅读说明 源代码在 源代码在 这里有一个更完整的有关登录和帐户创建的示例 入门 该项目是Flutter应用程序的起点。 如果这是您的第一个Flutter项目,那么有一些...
  • java开发短信验证firebase_auth 插件 一个 Flutter 插件,用于使用 . 有关其他 Firebase 产品的 Flutter 插件,请参阅 。 注意:此插件仍在开发中,某些 API 可能尚未可用。 并且非常受欢迎! 用法 配置 Google 登录...
  • firebase_auth_test
  • Flutter –使用Firebase进行登录和注册身份验证。 , , , , ,
  • 应用15 应用简介以使用BaaS。 使用Firebase进行身份验证 项目配置 社交网络身份验证 匿名用户 Firebase Firestore NoSQL Firebase存储(文件) 入门 请记住,克隆项目后,请在项目...flutter packages get 应用截图
  • firebase_auth_test-源码

    2021-02-15 14:19:01
    firebase_auth_test 一个新的Flutter项目。 入门 该项目是Flutter应用程序的起点。 如果这是您的第一个Flutter项目,那么有一些资源可以帮助您入门: 要获得Flutter入门方面的帮助,请查看我们的,其中提供了教程...
  • Flutter Firebase Starter项目 用 :beating_heart: 来自尼泊尔 您可以克隆一个项目,以使用带有Firebase服务的Flutter构建您的下一个项目。 实施了哪些Firebase功能? Firebase身份验证 基于电子邮件的注册/登录 ...
  • Flutter_Firebase_Demo-源码

    2021-03-28 12:27:13
    wastegram_extended ... 此外,使用Firebase Auth来验证和验证用户以进行基于角色的显示,使用Firebase Analytics记录用户登录/注册/后期修改,以及共享首选项以更新主题。 注意:必须为Firebase使用自己的Goog
  • flutter_firebase_CRUD_app A new Flutter-Firebase "CRUD" application with Firebase Cloud Firestore. 入门 要获得Flutter入门方面的帮助,请查看我们的,其中提供了教程,示例,有关移动开发的指南以及完整的...
  • 使用Flutter的Google Firebase电子邮件和Google Plus登录。 介绍: 如今,Google Firebase越来越流行。 Google Firebase大约有10种登录方法,包括电子邮件,谷歌,facebook,电话,twitter,yahoo等登录。 本文仅...
  • 投票-完整的Flutter应用程序 ... 对于状态管理,我们使用 。 如果您需要知道如何使用 ,请 演示2.0 功能和更新 ...有时firebase和google auth在Android模拟器上不起作用,因此是在真实设备中进行测试的首
  • Flutter Firebase身份验证的这篇文章中,我们将了解如何使用Firebaseflutter插件向我们的Firebase应用程序验证用户身份。 如果您不熟悉颤动,建议您先阅读此内容,然后再继续。 它将为您提供有关抖动的基本...

    flutter 图形验证

    在Flutter Firebase身份验证的这篇文章中,我们将了解如何使用Firebase的flutter插件向我们的Firebase应用程序验证用户身份。

    如果您不熟悉颤动,建议您先阅读此内容,然后再继续。 它将为您提供有关抖动的基本概念,并帮助您学习本教程。

    https://ayusch.com/getting-started-with-flutter-app-development/

    我们将创建一个具有登录屏幕和主页的基本应用程序。 它可以让用户登录注销应用程序。 我们还将具有允许用户注册到我们的Firebase应用程序的功能。

    这是应用程序的基本流程:

    所以,让我们开始吧!

    创建Flutter应用程序

    转到Android Studio并通过单击“ 新建”->“ Flutter项目”并按照向导进行操作,创建Flutter应用程序。

    删除默认计数器应用程序的代码,并添加以下行:

     import 'package:flutter/material.dart' ;  import 'package:flutter_firebase_auth/root_page.dart' ;  import 'LoginSignupPage.dart' ;  import 'authentication.dart' ;  void main() => runApp(MyApp());  MyApp class extends StatelessWidget { 
       // This widget is the root of your application. 
       @override 
       Widget build(BuildContext context) { 
         return MaterialApp( 
           title: 'Flutter Authentication AndroidVille' , 
           theme: ThemeData( 
             primarySwatch: Colors.blue, 
           ), 
           home: RootPage( 
             auth: new Auth(), 
           ), 
         ); 
       }  } 

    我们将很快创建RootPage。

    注意 :请记住不要使用AndroidX工件。 Firebase的flutter插件包含一些AndroidX不支持的注释。 在升级之前,我们不要使用AndroidX。

    添加依赖项

    我们需要在android / flutter中添加一些依赖关系,以便Flutter使用Firebase身份验证。

    首先,将此添加到您的项目级别的build.gradle文件。 对于扑扑的项目,这可以在android / build.gradle中找到

     classpath 'com.google.gms:google-services:4.3.2' 

    接下来,我们需要将google-services插件应用于应用程序级别build.gradle 。 这可以在android / app / build.gradle中找到。 将此行添加到文件末尾。

     apply plugin: 'com.google.gms.google-services' 

    最后,我们需要为flutter添加firebase插件。 打开pubspec.yaml并在依赖项下添加以下行:

     firebase_auth: ^ 0.6 . 6 

    创建Flutter Firebase身份验证服务

    接下来,我们需要为flutter的firebase登录系统创建身份验证服务。 所有页面(或android中的活动)都将使用它与Firebase进行通信。

    创建一个新的dart文件,名称为: authentication.dart

    我们将首先添加一个抽象的BaseAuth类,该类将由Auth类实现。 它包含登录,注册,获取用户信息和注销用户的基本方法。

     import 'dart:async' ;  import 'package:firebase_auth/firebase_auth.dart' ;  abstract class BaseAuth { 
       Future<String> signIn(String email, String password); 
       Future<String> signUp(String email, String password); 
       Future<FirebaseUser> getCurrentUser(); 
       Future< void > signOut();  } 

    在同一文件中,创建一个名为Auth的类,并实现BaseAuth类。 覆盖所有方法,如下所示:

     import 'dart:async' ;  import 'package:firebase_auth/firebase_auth.dart' ;  abstract class BaseAuth { 
       Future<String> signIn(String email, String password); 
       Future<String> signUp(String email, String password); 
       Future<FirebaseUser> getCurrentUser(); 
       Future< void > signOut();  }  Auth class implements BaseAuth { 
       final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; 
       Future<String> signIn(String email, String password) async { 
         FirebaseUser user = await _firebaseAuth.signInWithEmailAndPassword(email: email, password: password); 
         return user.uid; 
       } 
       Future<String> signUp(String email, String password) async { 
         FirebaseUser user = await _firebaseAuth.createUserWithEmailAndPassword(email: email, password: password); 
         return user.uid; 
       } 
       Future<FirebaseUser> getCurrentUser() async { 
         FirebaseUser user = await _firebaseAuth.currentUser(); 
         return user; 
       } 
       Future< void > signOut() async { 
         return _firebaseAuth.signOut(); 
       }  } 

    我们在这里所做的是创建一个全局Firebase实例,并使用它来为用户执行登录/注销。

    创建根页面

    我们需要创建一个根目录来标识用户是否已登录。 如果用户已登录 ,我们会将其定向到主页,否则将向他显示登录/注册屏幕。

    创建一个root_page.dart ,如下所示:

     import 'package:flutter/material.dart' ;  import 'authentication.dart' ;  import 'LoginSignupPage.dart' ;  import 'home_page.dart' ;  RootPage class extends StatefulWidget { 
       RootPage({ this .auth}); 
       final BaseAuth auth; 
       @override 
       State<StatefulWidget> createState() => new _RootPageState();  }  enum AuthStatus { 
       NOT_DETERMINED, 
       LOGGED_OUT, 
       LOGGED_IN,  }  _RootPageState class extends State<RootPage> { 
       AuthStatus authStatus = AuthStatus.NOT_DETERMINED; 
       String _userId = "" ; 
       @override 
       void initState() { 
         super .initState(); 
         widget.auth.getCurrentUser().then((user) { 
           setState(() { 
             if (user != null ) { 
               _userId = user?.uid; 
             } 
             authStatus = 
                 user?.uid == null ? AuthStatus.LOGGED_OUT : AuthStatus.LOGGED_IN; ? AuthStatus.LOGGED_OUT : AuthStatus.LOGGED_IN; 
           }); 
         }); 
       } 
       void _onLoggedIn() { 
         widget.auth.getCurrentUser().then((user) { 
           setState(() { 
             _userId = user.uid.toString(); 
           }); 
         }); 
         setState(() { 
           authStatus = AuthStatus.LOGGED_IN; 
         }); 
       } 
       void _onSignedOut() { 
         setState(() { 
           authStatus = AuthStatus.LOGGED_OUT; 
           _userId = "" ; 
         }); 
       } 
       Widget progressScreenWidget() { 
         return Scaffold( 
           body: Container( 
             alignment: Alignment.center, 
             child: CircularProgressIndicator(), 
           ), 
         ); 
       } 
       @override 
       Widget build(BuildContext context) { 
         switch (authStatus) { 
           case AuthStatus.NOT_DETERMINED: 
             return progressScreenWidget(); 
             break ; 
           case AuthStatus.LOGGED_OUT: 
             return new LoginSignupPage( 
               auth: widget.auth, 
               onSignedIn: _onLoggedIn, 
             ); 
             break ; 
           case AuthStatus.LOGGED_IN: 
             if (_userId.length > 0 && _userId != null ) { 
               return new HomePage( 
                 userId: _userId, 
                 auth: widget.auth, 
                 onSignedOut: _onSignedOut, 
               ); 
             } else 
               return progressScreenWidget(); 
             break ; 
           default : 
             return progressScreenWidget(); 
         } 
       }  } 

    这里要注意的主要是构建方法。 对于不同的用户状态,我们返回不同的小部件。 我们将很快创建LoginSignupPageHomePage

    为了显示进度条,我们使用Flutter提供的默认CircularProgressIndicator

    另外,请注意,我们在构造函数中传递了auth对象。 这是从main.dart传递的

    创建LoginSignupPage

    这是最重要的部分,我们将创建一个登录/注册表单。 我们将基于formMode区分这些形式。

    我们总共有6个小部件:

    1. 电子邮件字段。
    2. 密码字段。
    3. 登录按钮
    4. 在登录和注册表格之间切换的按钮。
    5. 进度条
    6. 错误消息小部件。

    这是LoginSignupPage的代码:

     import 'package:flutter/material.dart' ;  import 'package:firebase_auth/firebase_auth.dart' ;  import 'authentication.dart' ;  class LoginSignupPage extends StatefulWidget { 
       .auth, LoginSignupPage({ this .auth, this .onSignedIn}); 
       final BaseAuth auth; 
       final VoidCallback onSignedIn; 
       @override 
       State<StatefulWidget> createState() => new _LoginSignupPageState();  }  enum FormMode { LOGIN, SIGNUP }  _LoginSignupPageState class extends State<LoginSignupPage> { _LoginSignupPageState State<LoginSignupPage> { 
       final _formKey = new GlobalKey<FormState>(); 
       String _email; 
       String _password; 
       String _errorMessage = "" ; 
       // this will be used to identify the form to show 
       FormMode _formMode = FormMode.LOGIN; 
       bool _isIos = false ; 
       bool _isLoading = false ; 
       @override 
       Widget build(BuildContext context) { 
         return new Scaffold( 
           appBar: new AppBar( 
             title: new Text( "Flutter login demo" ), 
           ), 
           body: Column( 
             children: <Widget>[ 
               formWidget(), 
               loginButtonWidget(), 
               secondaryButton(), 
               errorWidget(), 
               progressWidget() 
             ], 
           ), 
         ); 
       } 
       Widget progressWidget() { 
         if (_isLoading) { 
           return Center(child: CircularProgressIndicator()); 
         } 
         return Container( 
           height: 0.0 , 
           width: 0.0 , 
         ); 
       } 
       Widget formWidget() { 
         return Form( 
           key: _formKey, 
           child: Column( 
             children: <Widget>[ 
               _emailWidget(), 
               _passwordWidget(), 
             ], 
           ), 
         ); 
       } 
       Widget _emailWidget() { 
         return Padding( 
           padding: const EdgeInsets.fromLTRB( 0.0 , 100.0 , 0.0 , 0.0 ), 
           child: TextFormField( 
             maxLines: 1 , 
             keyboardType: TextInputType.emailAddress, 
             autofocus: false , 
             decoration: new InputDecoration( 
                 hintText: 'Enter Email' , 
                 icon: new Icon( 
                   Icons.mail, 
                   color: Colors.grey, 
                 )), 
             validator: (value) => value.isEmpty ? 'Email cannot be empty' : null , 
             onSaved: (value) => _email = value.trim(), 
           ), 
         ); 
       } 
       Widget _passwordWidget() { 
         return Padding( 
           padding: const EdgeInsets.fromLTRB( 0.0 , 15.0 , 0.0 , 0.0 ), 
           child: new TextFormField( 
             maxLines: 1 , 
             obscureText: true , 
             autofocus: false , 
             decoration: new InputDecoration( 
                 hintText: 'Password' , 
                 icon: new Icon( 
                   Icons.lock, 
                   color: Colors.grey, 
                 )), 
             validator: (value) => value.isEmpty ? 'Password cannot be empty' : null , 
             onSaved: (value) => _password = value.trim(), 
           ), 
         ); 
       } 
       Widget loginButtonWidget() { 
         return new Padding( 
             padding: EdgeInsets.fromLTRB( 0.0 , 45.0 , 0.0 , 0.0 ), 
             child: new MaterialButton( 
               elevation: 5.0 , 
               minWidth: 200.0 , 
               height: 42.0 , 
               color: Colors.blue, 
               child: _formMode == FormMode.LOGIN 
                   ? new Text( 'Login' , 
                       style: new TextStyle(fontSize: 20.0 , color: Colors.white)) 
                   : new Text( 'Create account' , 
                       style: new TextStyle(fontSize: 20.0 , color: Colors.white)), 
               onPressed: _validateAndSubmit, 
             )); 
       } 
       Widget secondaryButton() { 
         return new FlatButton( 
           child: _formMode == FormMode.LOGIN 
               ? new Text( 'Create an account' , 
                   style: new TextStyle(fontSize: 18.0 , fontWeight: FontWeight.w300)) 
               : new Text( 'Have an account? Sign in' , 
                   style: 
                       new TextStyle(fontSize: 18.0 , fontWeight: FontWeight.w300)), 
           onPressed: _formMode == FormMode.LOGIN ? showSignupForm : showLoginForm, 
         ); 
       } 
       void showSignupForm() { 
         _formKey.currentState.reset(); 
         _errorMessage = "" ; 
         setState(() { 
           _formMode = FormMode.SIGNUP; 
         }); 
       } 
       void showLoginForm() { 
         _formKey.currentState.reset(); 
         _errorMessage = "" ; 
         setState(() { 
           _formMode = FormMode.LOGIN; 
         }); 
       } 
       Widget errorWidget() { 
         if (_errorMessage.length > 0 && _errorMessage != null ) { 
           return new Text( 
             _errorMessage, 
             style: TextStyle( 
                 fontSize: 13.0 , 
                 color: Colors.red, 
                 height: 1.0 , 
                 fontWeight: FontWeight.w300), 
           ); 
         } else { 
           return new Container( 
             height: 0.0 , 
           ); 
         } 
       } 
       bool _validateAndSave() { 
         final form = _formKey.currentState; 
         if (form.validate()) { 
           form.save(); 
           return true ; 
         } 
         return false ; 
       } 
       _validateAndSubmit() async { 
         setState(() { 
           _errorMessage = "" ; 
           _isLoading = true ; 
         }); 
         if (_validateAndSave()) { 
           String userId = "" ; 
           try { 
             if (_formMode == FormMode.LOGIN) { 
               userId = await widget.auth.signIn(_email, _password); 
             } else { 
               userId = await widget.auth.signUp(_email, _password); 
             } 
             setState(() { 
               _isLoading = false ; 
             }); 
             if (userId.length > 0 && userId != null ) { 
               widget.onSignedIn(); 
             } 
           } catch (e) { 
             setState(() { 
               _isLoading = false ; 
               if (_isIos) { 
                 _errorMessage = e.details; 
               } else 
                 _errorMessage = e.message; 
             }); 
           } 
         } else { 
           setState(() { 
             _isLoading = false ; 
           }); 
         } 
       }  } 

    注意方法validateAndSubmit() ,当人们按下登录按钮时会调用该方法。 首先,我们将加载的初始状态设置为true和空错误。

    同样,我建议您查阅文章知道的setState方法做。

    之后,我们验证表单,然后在formMode的基础上,登录用户或注册用户。 在这两种情况下,我们都将获得userId。

    最后,我们在小部件上调用onSignedIn方法。 此方法是VoidCallback ,由根页面在LoginSignupPage的构造函数中提供。

    最终,根页面调用它的onLoggedIn方法,该方法设置userId并最终调用setState() 。 这将导致重建,然后转到主屏幕。

    这样就完成了LoginSignupPage 。 现在,是时候在我们的主页中添加注销功能。

    要完成此Flutter firebase身份验证教程,我们必须添加注销功能。 注销只是意味着设置空用户ID并重定向到LoginSignupPage。

    这是HomePage的代码:

     import 'package:flutter/material.dart' ;  import 'authentication.dart' ;  class HomePage extends StatefulWidget { 
       HomePage({Key key, this .auth, this .userId, this .onSignedOut}) 
           : super (key: key); 
       final BaseAuth auth; 
       final VoidCallback onSignedOut; 
       final String userId; 
       @override 
       State<StatefulWidget> createState() => new _HomePageState();  }  _HomePageState class extends State<HomePage> { 
       _signOut() async { 
         try { 
           await widget.auth.signOut(); 
           widget.onSignedOut(); 
         } catch (e) { 
           print(e); 
         } 
       } 
       @override 
       Widget build(BuildContext context) { 
         return new Scaffold( 
           appBar: new AppBar( 
             title: new Text( 'Flutter login demo' ), 
             actions: <Widget>[ 
               new FlatButton( 
                   child: new Text( 'Logout' , 
                       style: new TextStyle(fontSize: 17.0 , color: Colors.white)), 
                   onPressed: _signOut) 
             ], 
           ), 
           body: Center( 
             child: Text( "hello" ), 
           ), 
         ); 
       }  } 

    我们将退出按钮保留在应用程序栏中。 当用户点击,我们调用由root_page提供的onSignedOut方法。

    根页面简单地设置用户LoggedOut用户id空字符串authState。

    最终结果如下所示:

    结论

    希望您觉得本文有用。 如果您愿意,请在下面的评论部分中告诉我,我将喜欢写更多这样的概念性文章。

    您可以在以下位置找到本文的完整代码: https : //github.com/Ayusch/Flutter-Firebase-Authentication

    如果您有任何疑问,请在下面的评论中告诉我,我们将竭诚为您服务!

    翻译自: https://www.javacodegeeks.com/2019/09/flutter-firebase-authentication-tutorial.html

    flutter 图形验证

    展开全文
  • registeration_login_firebase_auth 一个新的Flutter项目。 入门 该项目是Flutter应用程序的起点。 如果这是您的第一个Flutter项目,那么有一些资源可以帮助您入门: 要获得Flutter入门方面的帮助,请查看我们的,...
  • firebase_ui-源码

    2021-02-12 06:45:17
    firebase_ui 没有维护旧软件包,所以我创建了这个要... 注意:此插件使用firebase_auth,您必须按照配置。 显示示例。 入门 要获得Flutter入门方面的帮助,请查看我们的在线。 有关编辑程序包代码的帮助,请参阅。
  • Flutter Firebase身份验证的这篇文章中,我们将了解如何使用Firebaseflutter插件向我们的Firebase应用程序验证用户身份。 如果您不熟悉颤动,建议您先阅读此内容,然后再继续。 它将为您提供有关抖动的基本...

    在Flutter Firebase身份验证的这篇文章中,我们将了解如何使用Firebase的flutter插件向我们的Firebase应用程序验证用户身份。

    如果您不熟悉颤动,建议您先阅读此内容,然后再继续。 它将为您提供有关抖动的基本概念,并帮助您完成本教程。

    https://ayusch.com/getting-started-with-flutter-app-development/

    我们将创建一个具有登录屏幕和主页的基本应用程序。 它可以让用户登录注销应用程序。 我们还将具有允许用户注册到我们的Firebase应用程序的功能。

    这是应用程序的基本流程:

    所以,让我们开始吧!

    创建Flutter应用程序

    转到Android Studio并通过单击“ 新建”->“ Flutter项目”并遵循从那里开始的向导来创建Flutter应用程序。

    删除默认计数器应用程序的代码,并添加以下行:

     import 'package:flutter/material.dart' ;  import 'package:flutter_firebase_auth/root_page.dart' ;  import 'LoginSignupPage.dart' ;  import 'authentication.dart' ;  void main() => runApp(MyApp());  MyApp class extends StatelessWidget { 
       // This widget is the root of your application. 
       @override 
       Widget build(BuildContext context) { 
         return MaterialApp( 
           title: 'Flutter Authentication AndroidVille' , 
           theme: ThemeData( 
             primarySwatch: Colors.blue, 
           ), 
           home: RootPage( 
             auth: new Auth(), 
           ), 
         ); 
       }  } 

    我们将很快创建RootPage。

    注意 :请记住不要使用AndroidX工件。 Firebase的Flutter插件包含一些AndroidX不支持的注释。 在升级之前,我们不要使用AndroidX。

    添加依赖项

    我们需要在android / flutter中添加一些依赖关系,以便Flutter使用Firebase身份验证。

    首先,将其添加到项目级别的build.gradle文件。 对于扑扑的项目,这可以在android / build.gradle中找到

     classpath 'com.google.gms:google-services:4.3.2' 

    接下来,我们需要将google-services插件应用于应用程序级别build.gradle 。 这可以在android / app / build.gradle中找到。 将此行添加到文件末尾。

     apply plugin: 'com.google.gms.google-services' 

    最后,我们需要为flutter添加firebase插件。 打开pubspec.yaml并在依赖项下添加以下行:

     firebase_auth: ^ 0.6 . 6 

    创建Flutter Firebase身份验证服务

    接下来,我们需要为flutter的firebase登录系统创建身份验证服务。 所有页面(或android中的活动)都将使用它与Firebase进行通信。

    创建一个新的dart文件,名称为: authentication.dart

    我们将首先添加一个抽象的BaseAuth类,该类将由Auth类实现。 这包含登录,注册,获取用户信息和注销用户的基本方法。

     import 'dart:async' ;  import 'package:firebase_auth/firebase_auth.dart' ;  abstract class BaseAuth { 
       Future<String> signIn(String email, String password); 
       Future<String> signUp(String email, String password); 
       Future<FirebaseUser> getCurrentUser(); 
       Future< void > signOut();  } 

    在同一文件中,创建一个名为Auth的类,并实现BaseAuth类。 覆盖所有方法,如下所示:

     import 'dart:async' ;  import 'package:firebase_auth/firebase_auth.dart' ;  abstract class BaseAuth { 
       Future<String> signIn(String email, String password); 
       Future<String> signUp(String email, String password); 
       Future<FirebaseUser> getCurrentUser(); 
       Future< void > signOut();  }  Auth class implements BaseAuth { 
       final FirebaseAuth _firebaseAuth = FirebaseAuth.instance; 
       Future<String> signIn(String email, String password) async { 
         FirebaseUser user = await _firebaseAuth.signInWithEmailAndPassword(email: email, password: password); 
         return user.uid; 
       } 
       Future<String> signUp(String email, String password) async { 
         FirebaseUser user = await _firebaseAuth.createUserWithEmailAndPassword(email: email, password: password); 
         return user.uid; 
       } 
       Future<FirebaseUser> getCurrentUser() async { 
         FirebaseUser user = await _firebaseAuth.currentUser(); 
         return user; 
       } 
       Future< void > signOut() async { 
         return _firebaseAuth.signOut(); 
       }  } 

    我们在这里所做的是创建一个全局Firebase实例,并使用它来为用户执行登录/注销。

    创建根页面

    我们需要创建一个根目录来标识用户是否已登录。 如果用户已登录 ,我们会将其定向到主页,否则将向他显示登录/注册屏幕。

    创建一个root_page.dart ,如下所示:

     import 'package:flutter/material.dart' ;  import 'authentication.dart' ;  import 'LoginSignupPage.dart' ;  import 'home_page.dart' ;  RootPage class extends StatefulWidget { 
       RootPage({ this .auth}); 
       final BaseAuth auth; 
       @override 
       State<StatefulWidget> createState() => new _RootPageState();  }  enum AuthStatus { 
       NOT_DETERMINED, 
       LOGGED_OUT, 
       LOGGED_IN,  }  _RootPageState class extends State<RootPage> { 
       AuthStatus authStatus = AuthStatus.NOT_DETERMINED; 
       String _userId = "" ; 
       @override 
       void initState() { 
         super .initState(); 
         widget.auth.getCurrentUser().then((user) { 
           setState(() { 
             if (user != null ) { 
               _userId = user?.uid; 
             } 
             authStatus = 
                 user?.uid == null ? AuthStatus.LOGGED_OUT : AuthStatus.LOGGED_IN; ? AuthStatus.LOGGED_OUT : AuthStatus.LOGGED_IN; 
           }); 
         }); 
       } 
       void _onLoggedIn() { 
         widget.auth.getCurrentUser().then((user) { 
           setState(() { 
             _userId = user.uid.toString(); 
           }); 
         }); 
         setState(() { 
           authStatus = AuthStatus.LOGGED_IN; 
         }); 
       } 
       void _onSignedOut() { 
         setState(() { 
           authStatus = AuthStatus.LOGGED_OUT; 
           _userId = "" ; 
         }); 
       } 
       Widget progressScreenWidget() { 
         return Scaffold( 
           body: Container( 
             alignment: Alignment.center, 
             child: CircularProgressIndicator(), 
           ), 
         ); 
       } 
       @override 
       Widget build(BuildContext context) { 
         switch (authStatus) { 
           case AuthStatus.NOT_DETERMINED: 
             return progressScreenWidget(); 
             break ; 
           case AuthStatus.LOGGED_OUT: 
             return new LoginSignupPage( 
               auth: widget.auth, 
               onSignedIn: _onLoggedIn, 
             ); 
             break ; 
           case AuthStatus.LOGGED_IN: 
             if (_userId.length > 0 && _userId != null ) { 
               return new HomePage( 
                 userId: _userId, 
                 auth: widget.auth, 
                 onSignedOut: _onSignedOut, 
               ); 
             } else 
               return progressScreenWidget(); 
             break ; 
           default : 
             return progressScreenWidget(); 
         } 
       }  } 

    这里要注意的主要是构建方法。 对于不同的用户状态,我们返回不同的小部件。 我们将很快创建LoginSignupPageHomePage

    为了显示进度条,我们使用Flutter提供的默认CircularProgressIndicator

    另外,请注意,我们在构造函数中传递了auth对象。 这是从main.dart传递的

    创建LoginSignupPage

    这是最重要的部分,我们将创建一个登录/注册表单。 我们将基于formMode区分这些形式。

    我们总共有6个小部件:

    1. 电子邮件字段。
    2. 密码字段。
    3. 登录按钮
    4. 在登录和注册表格之间切换的按钮。
    5. 进度条
    6. 错误消息小部件。

    这是LoginSignupPage的代码:

     import 'package:flutter/material.dart' ;  import 'package:firebase_auth/firebase_auth.dart' ;  import 'authentication.dart' ;  class LoginSignupPage extends StatefulWidget { 
       .auth, LoginSignupPage({ this .auth, this .onSignedIn}); 
       final BaseAuth auth; 
       final VoidCallback onSignedIn; 
       @override 
       State<StatefulWidget> createState() => new _LoginSignupPageState();  }  enum FormMode { LOGIN, SIGNUP }  class _LoginSignupPageState extends State<LoginSignupPage> { _LoginSignupPageState State<LoginSignupPage> { 
       final _formKey = new GlobalKey<FormState>(); 
       String _email; 
       String _password; 
       String _errorMessage = "" ; 
       // this will be used to identify the form to show 
       FormMode _formMode = FormMode.LOGIN; 
       bool _isIos = false ; 
       bool _isLoading = false ; 
       @override 
       Widget build(BuildContext context) { 
         return new Scaffold( 
           appBar: new AppBar( 
             title: new Text( "Flutter login demo" ), 
           ), 
           body: Column( 
             children: <Widget>[ 
               formWidget(), 
               loginButtonWidget(), 
               secondaryButton(), 
               errorWidget(), 
               progressWidget() 
             ], 
           ), 
         ); 
       } 
       Widget progressWidget() { 
         if (_isLoading) { 
           return Center(child: CircularProgressIndicator()); 
         } 
         return Container( 
           height: 0.0 , 
           width: 0.0 , 
         ); 
       } 
       Widget formWidget() { 
         return Form( 
           key: _formKey, 
           child: Column( 
             children: <Widget>[ 
               _emailWidget(), 
               _passwordWidget(), 
             ], 
           ), 
         ); 
       } 
       Widget _emailWidget() { 
         return Padding( 
           padding: const EdgeInsets.fromLTRB( 0.0 , 100.0 , 0.0 , 0.0 ), 
           child: TextFormField( 
             maxLines: 1 , 
             keyboardType: TextInputType.emailAddress, 
             autofocus: false , 
             decoration: new InputDecoration( 
                 hintText: 'Enter Email' , 
                 icon: new Icon( 
                   Icons.mail, 
                   color: Colors.grey, 
                 )), 
             validator: (value) => value.isEmpty ? 'Email cannot be empty' : null , 
             onSaved: (value) => _email = value.trim(), 
           ), 
         ); 
       } 
       Widget _passwordWidget() { 
         return Padding( 
           padding: const EdgeInsets.fromLTRB( 0.0 , 15.0 , 0.0 , 0.0 ), 
           child: new TextFormField( 
             maxLines: 1 , 
             obscureText: true , 
             autofocus: false , 
             decoration: new InputDecoration( 
                 hintText: 'Password' , 
                 icon: new Icon( 
                   Icons.lock, 
                   color: Colors.grey, 
                 )), 
             validator: (value) => value.isEmpty ? 'Password cannot be empty' : null , 
             onSaved: (value) => _password = value.trim(), 
           ), 
         ); 
       } 
       Widget loginButtonWidget() { 
         return new Padding( 
             padding: EdgeInsets.fromLTRB( 0.0 , 45.0 , 0.0 , 0.0 ), 
             child: new MaterialButton( 
               elevation: 5.0 , 
               minWidth: 200.0 , 
               height: 42.0 , 
               color: Colors.blue, 
               child: _formMode == FormMode.LOGIN 
                   ? new Text( 'Login' , 
                       style: new TextStyle(fontSize: 20.0 , color: Colors.white)) 
                   : new Text( 'Create account' , 
                       style: new TextStyle(fontSize: 20.0 , color: Colors.white)), 
               onPressed: _validateAndSubmit, 
             )); 
       } 
       Widget secondaryButton() { 
         return new FlatButton( 
           child: _formMode == FormMode.LOGIN 
               ? new Text( 'Create an account' , 
                   style: new TextStyle(fontSize: 18.0 , fontWeight: FontWeight.w300)) 
               : new Text( 'Have an account? Sign in' , 
                   style: 
                       new TextStyle(fontSize: 18.0 , fontWeight: FontWeight.w300)), 
           onPressed: _formMode == FormMode.LOGIN ? showSignupForm : showLoginForm, 
         ); 
       } 
       void showSignupForm() { 
         _formKey.currentState.reset(); 
         _errorMessage = "" ; 
         setState(() { 
           _formMode = FormMode.SIGNUP; 
         }); 
       } 
       void showLoginForm() { 
         _formKey.currentState.reset(); 
         _errorMessage = "" ; 
         setState(() { 
           _formMode = FormMode.LOGIN; 
         }); 
       } 
       Widget errorWidget() { 
         if (_errorMessage.length > 0 && _errorMessage != null ) { 
           return new Text( 
             _errorMessage, 
             style: TextStyle( 
                 fontSize: 13.0 , 
                 color: Colors.red, 
                 height: 1.0 , 
                 fontWeight: FontWeight.w300), 
           ); 
         } else { 
           return new Container( 
             height: 0.0 , 
           ); 
         } 
       } 
       bool _validateAndSave() { 
         final form = _formKey.currentState; 
         if (form.validate()) { 
           form.save(); 
           return true ; 
         } 
         return false ; 
       } 
       _validateAndSubmit() async { 
         setState(() { 
           _errorMessage = "" ; 
           _isLoading = true ; 
         }); 
         if (_validateAndSave()) { 
           String userId = "" ; 
           try { 
             if (_formMode == FormMode.LOGIN) { 
               userId = await widget.auth.signIn(_email, _password); 
             } else { 
               userId = await widget.auth.signUp(_email, _password); 
             } 
             setState(() { 
               _isLoading = false ; 
             }); 
             if (userId.length > 0 && userId != null ) { 
               widget.onSignedIn(); 
             } 
           } catch (e) { 
             setState(() { 
               _isLoading = false ; 
               if (_isIos) { 
                 _errorMessage = e.details; 
               } else 
                 _errorMessage = e.message; 
             }); 
           } 
         } else { 
           setState(() { 
             _isLoading = false ; 
           }); 
         } 
       }  } 

    注意方法validateAndSubmit() ,当人们按下登录按钮时会调用该方法。 首先,我们将加载的初始状态设置为true和空错误。

    同样,我建议您查阅文章知道的setState方法做。

    之后,我们验证表单,然后在formMode的基础上,登录用户或注册用户。 在这两种情况下,我们都将获得userId。

    最后,我们在小部件上调用onSignedIn方法。 此方法是VoidCallback ,由根页在LoginSignupPage的构造函数中提供。

    最终,根页面调用它的onLoggedIn方法,该方法设置userId并最终调用setState() 。 这将导致重建,然后我们转到主屏幕。

    这样就完成了LoginSignupPage 。 现在,是时候在我们的主页中添加注销功能。

    要完成此Flutter firebase身份验证教程,我们必须添加注销功能。 注销仅表示设置空用户ID并重定向到LoginSignupPage。

    这是HomePage的代码:

     import 'package:flutter/material.dart' ;  import 'authentication.dart' ;  class HomePage extends StatefulWidget { 
       HomePage({Key key, this .auth, this .userId, this .onSignedOut}) 
           : super (key: key); 
       final BaseAuth auth; 
       final VoidCallback onSignedOut; 
       final String userId; 
       @override 
       State<StatefulWidget> createState() => new _HomePageState();  }  _HomePageState class extends State<HomePage> { 
       _signOut() async { 
         try { 
           await widget.auth.signOut(); 
           widget.onSignedOut(); 
         } catch (e) { 
           print(e); 
         } 
       } 
       @override 
       Widget build(BuildContext context) { 
         return new Scaffold( 
           appBar: new AppBar( 
             title: new Text( 'Flutter login demo' ), 
             actions: <Widget>[ 
               new FlatButton( 
                   child: new Text( 'Logout' , 
                       style: new TextStyle(fontSize: 17.0 , color: Colors.white)), 
                   onPressed: _signOut) 
             ], 
           ), 
           body: Center( 
             child: Text( "hello" ), 
           ), 
         ); 
       }  } 

    我们将退出按钮保留在应用程序栏中。 当用户点击,我们调用由root_page提供的onSignedOut方法。

    根页面简单地设置用户LoggedOut用户id空字符串authState。

    最终结果如下所示:

    结论

    希望您觉得本文有用。 如果您愿意,请在下面的评论部分中告诉我,我很乐意写更多这样的概念文章。

    您可以在以下位置找到本文的完整代码: https : //github.com/Ayusch/Flutter-Firebase-Authentication

    如果您有任何疑问,请在下面的评论中告诉我,我们将竭诚为您服务!

    翻译自: https://www.javacodegeeks.com/2019/09/flutter-firebase-authentication-tutorial.html

    展开全文
  • flutter_firebase_auth_mvvm AZ Ackmatoff编写的Flutter Firebase Auth MVVM。 电子邮件登录并创建Google登录Facebook登入简单的Firestore CRUD 入门 克隆项目后需要做的事情: Android设置 创建一个firebase项目 ...
  • ns_firebase_utils :mechanical_arm: Firebase的方法/扩展集可以使使用框架更加轻松和简洁,同时还增加了其他功能... firebase_core:^ 0.7.0 firebase_auth:^ 0.20.1 firebase_storage:^ 7.0.0 cloud_firestore:^
  • Firebase-Phone-Auth-Demo:演示项目,展示如何在带有Firebase后端的Flutter中使用电话身份验证
  • 我们使用 flutter_bloc[21] 进行状态管理,因为它支持我们轻松测试业务逻辑,并观察应用中的所有状态变化。这对于生成开发者日志和确保可追溯性特别有用,因为我们可以准确地观察到从一个状态到另一个状态的变化,并...

    作者 / Very Good Ventures Team

    我们 (Very Good Ventures 团队) 与 Google 合作,在今年的 Google I/O 大会上推出了 照相亭互动体验[1] (I/O Photo Booth)。您可以与深受喜爱的 Google 吉祥物合影: Flutter 的 Dash[2]、Android Jetpack、Chrome 的 Dino 和 Firebase 的 Sparky,并用各种贴纸装饰照片,包括派对帽、披萨、时髦眼镜等。当然,您也可以通过社交媒体下载并分享,或者用作您的个人头像!

    △ Flutter 的 Dash、Firebase 的 Sparky、Android Jetpack 和 Chrome 的 Dino

    我们使用 Flutter web[3] 和 Firebase[4] 构建了 I/O 照相亭。因为 Flutter 现在支持打造 Web 应用[5],我们认为这将是一个很好的方式,可以让世界各地的与会者在今年的线上 Google I/O 大会上轻松访问这一应用。Flutter web 消除了必须通过应用商店安装应用的障碍,同时用户还可以灵活选择运行应用的设备: 移动设备、桌面设备或平板电脑。因此,只要能使用浏览器,用户便可无需下载直接使用 I/O 照相亭。

    尽管 I/O 照相亭旨在提供 Web 体验,但所有代码均采用与平台无关的架构编写而成。当相机插件等原生功能的支持在各个平台就绪后,这套代码即可在所有平台 (桌面、Web 和移动设备) 通用。

    使用 Flutter 构建虚拟照相亭

    构建 Web 版 Flutter 相机插件

    第一个挑战即在 Web 上为 Flutter 构建摄像头插件。最初,我们联系了 Baseflow[6] 团队,因为他们负责维护现有的开源 Flutter 摄像头插件[7]。Baseflow 致力于构建适用于 iOS 和 Android 的一流摄像头插件支持,我们也很乐于与其合作,使用 联合插件[8] 方法为插件提供 Web 支持。我们尽可能符合官方插件接口,以便我们可以在准备就绪时将其合并回官方插件。

    我们确定了两个对于在 Flutter 中构建 I/O 照相亭相机体验至关重要的 API。

    • 初始化摄像头: 应用首先需要访问您的设备摄像头。对于桌面设备,访问的可能是网络摄像头,而对于移动设备,我们选择了访问前置摄像头。我们还提供了 1080p 的预期分辨率,以根据用户设备类型充分提高拍摄质量。

    • 拍照: 我们使用了内置的 HtmlElementView[9],该控件使用平台视图将原生 Web 元素渲染为 Flutter widget。在此项目中,我们将 VideoElement[10] 渲染为原生 HTML 元素,这便是您在拍照前会在屏幕上看到的内容。我们还使用了一个 CanvasElement[11],用于在您点击拍照按钮时从媒体流中捕获图像。

    Future<CameraImage> takePicture() async {
     final videoWidth = videoElement.videoWidth;
     final videoHeight = videoElement.videoHeight;
     final canvas = html.CanvasElement(
       width: videoWidth,
       height: videoHeight,
     );
     canvas.context2D
       ..translate(videoWidth, 0)
       ..scale(-1, 1)
       ..drawImageScaled(videoElement, 0, 0, videoWidth, videoHeight);
     final blob = await canvas.toBlob();
     return CameraImage(
       data: html.Url.createObjectUrl(blob),
       width: videoWidth,
       height: videoHeight,
     );
    }
    

    摄像头权限

    在 Web 上完成 Flutter 摄像头插件后,我们创建了一个抽象布局,以根据相机权限显示不同的界面。例如,在等待您允许或拒绝使用浏览器摄像头时,或者如果没有可供访问的摄像头时,我们可以显示一条说明性消息。

    Camera(
     controller: _controller,
     placeholder: (_) => const SizedBox(),
     preview: (context, preview) => PhotoboothPreview(
       preview: preview,
       onSnapPressed: _onSnapPressed,
     ),
     error: (context, error) => PhotoboothError(error: error),
    )
    

    在上面的抽象布局中,placeholder 会在应用等待您授予摄像头权限时返回初始界面。Preview 则会在您授予权限后返回真实的界面,并显示摄像头的实时视频流。结尾的 Error 构造语句则可以在错误发生时捕获错误并显示相应的消息。

    生成镜像照片

    我们的下一个挑战是生成镜像照片。如果我们照原样使用摄像头拍摄的照片,那么您看到的内容将与您在照镜子时所看到的内容不一样。某些设备会提供专门处理这一问题的设置选项[12],所以,如果您用前置摄像头拍照,您看到的其实是照片的镜像版本。

    在我们的第一种方法中,我们尝试捕捉默认的摄像头视图,然后围绕 y 轴对其进行 180 度翻转。这种方法似乎有效,但后来我们遇到了 一个问题[13],即 Flutter 偶尔会覆盖这个翻转,导致视频恢复到未镜像的版本。

    在 Flutter 团队的帮助下,我们将 VideoElement 放在 DivElement[14] 中,并更新 VideoElement 以填充 DivElement 的宽度和高度,解决了这个问题。这样一来,我们能够为视频元素应用镜像,同时因为父元素是 div,所以不会被 Flutter 覆盖翻转效果。如此一来,我们便获得了所需的镜像摄像头视图!

    △ 未镜像的视图
    △ 镜像视图

    保持宽高比

    在大屏幕上保持 4:3 宽高比,以及在小屏幕上保持 3:4 宽高比,这个操作起来比看起来更难!保持宽高比非常重要,既要符合 Web 应用的整体设计,又要确保在社交媒体上分享照片时,令其中的像素呈现出清晰的本色效果。这是一项具有挑战性的任务,因为不同设备上内置摄像头的宽高比差异很大。

    为了强制保持宽高比,应用首先使用 JavaScript getUserMedia API[15] 从设备摄像头请求可能的最大分辨率。随后,我们将此 API 传递到 VideoElement 流中,这便是您在摄像头视图中看到的内容 (当然是已镜像的版本)。我们还应用了 object-fit[16] CSS 属性来确保视频元素能盖住其父级容器。我们使用 Flutter 自带的 AspectRatio widget 来设置宽高比。因此,摄像头不会对显示的宽高比做出任何假设;它始终返回支持的最大分辨率,然后遵守 Flutter 提供的约束条件 (在本例中为 4:3 或 3:4)。

    final orientation = MediaQuery.of(context).orientation;
    final aspectRatio = orientation == Orientation.portrait
       ? PhotoboothAspectRatio.portrait
       : PhotoboothAspectRatio.landscape;
    return Scaffold(
     body: _PhotoboothBackground(
       aspectRatio: aspectRatio,
       child: Camera(
         controller: _controller,
         placeholder: (_) => const SizedBox(),
         preview: (context, preview) => PhotoboothPreview(
           preview: preview,
           onSnapPressed: () => _onSnapPressed(
             aspectRatio: aspectRatio,
           ),
         ),
         error: (context, error) => PhotoboothError(error: error),
       ),
     ),
    );
    

    通过拖放添加贴纸

    I/O 照相亭的一大重要体验在于与您最喜欢的 Google 吉祥物合影并添加道具。您能够在照片中拖放吉祥物和道具,以及调整大小和旋转,直到获得您喜欢的图像。您也会发现,在将吉祥物添加到屏幕上时,您可以拖动它们并调整其大小。吉祥物们还是有动画效果的——这种效果由 sprite sheet 来实现。

    for (final character in state.characters)
     DraggableResizable(   
       canTransform: character.id == state.selectedAssetId,
       onUpdate: (update) {
         context.read<PhotoboothBloc>().add(
           PhotoCharacterDragged(
             character: character, 
             update: update,
           ),
         );
       },
       child: _AnimatedCharacter(name: character.asset.name),
     ),
    

    为调整对象的大小,我们创建了可拖动、可调整大小且可以容纳其他 Flutter widget 的 widget,在本例中,即为吉祥物和道具。该 widget 会使用 LayoutBuilder[17],根据窗口的约束条件来处理 widget 的缩放。在内部,我们使用 GestureDetector[18] 以挂接到 onScaleStart、onScaleUpdate 和 onScaleEnd 事件。这些回调提供了必要的手势详细信息,以反映用户对吉祥物和道具的操作。

    通过多个 GestureDetector 回馈的数据,Transform[19] widget 和 4D 矩阵变换即可根据用户所做的各种手势处理缩放,以及旋转吉祥物和道具。

    Transform(
     alignment: Alignment.center,
     transform: Matrix4.identity()
       ..scale(scale)
       ..rotateZ(angle),
     child: _DraggablePoint(...),
    )
    

    最后,我们创建了单独的 package 来确定您的设备是否支持触摸输入。可拖动、可调整大小的 widget 会根据触摸功能做出相应的调整。在具有触摸输入功能的设备上,您并不能看到调整大小的锚点和旋转图标,因为您可以通过双指张合和平移手势来直接操纵图像;而在不支持触摸输入的设备 (例如您的桌面设备) 上,我们则添加了锚点和旋转图标,以适应单击和拖动操作。

    针对 Web 优化 Flutter

    使用 Flutter 针对 Web 进行开发

    这是我们使用 Flutter 构建的首批纯 Web 项目之一,其与移动应用具有不同的特征。

    我们需要确保该应用对任何设备上的任何浏览器都具有 响应性和自适应性[20]。也就是说,我们必须确保 I/O 照相亭可以根据浏览器大小进行缩放,并且能够处理移动设备和 Web 端的输入。我们通过以下几种方式做到了这一点:

    • 响应式调整大小: 用户能够随意调整浏览器的大小,并且界面能做出响应。如果您的浏览器窗口为纵向,则相机会从 4:3 的横向视图翻转为 3:4 的纵向视图。

    • 响应式设计: 针对桌面浏览器,我们设计为在右侧显示 Dash、Android Jetpack、Dino 和 Sparky,而对于移动设备,这些要素则会显示在顶部。我们针对桌面设备,在摄像头右侧设计使用了抽屉式导航栏,而对于移动设备,则使用了 BottomSheet 类。

    • 自适应输入: 如果您使用桌面设备访问 I/O 照相亭,则鼠标点击操作将被视为输入,如果您使用的是平板电脑或手机,则使用触摸输入。在调整贴纸大小并将其放置在照片中时,这一点尤其重要。移动设备支持双指张合和平移手势,桌面设备支持点击和拖动操作。

    可扩展架构

    我们还为此应用构建了可扩展的移动应用。我们的 I/O 照相亭在创建之初就具有稳固的基础,包括良好的空安全性、国际化,以及从第一次提交开始就做到的 100% 单元和 widget 测试覆盖率。我们使用 flutter_bloc[21] 进行状态管理,因为它支持我们轻松测试业务逻辑,并观察应用中的所有状态变化。这对于生成开发者日志和确保可追溯性特别有用,因为我们可以准确地观察到从一个状态到另一个状态的变化,并更快地隔离问题。

    我们还实现了由功能驱动的单一代码库结构。例如,贴纸、分享和实时相机预览,均在各自的文件夹中得到实现,其中每个文件夹包含其各自的界面组件和业务逻辑。这些功能也会用到外部依赖,例如位于 package 子目录中的相机插件。利用这种架构,我们的团队能够在互不干扰的情况下并行处理多个功能,最大限度地减少合并冲突,并有效地重用代码。例如,界面组件库是名为 photobooth_ui[22] 的单独 package,相机插件也是单独的。

    通过将组件分成独立的 package,我们可以提取未与此特定项目绑定的各个组件,并将其开源。与 Material[23] 和 Cupertino[24] 组件库类似,我们甚至可以将界面组件库 package 做开源处理,以供 Flutter 社区使用。

    Firebase + Flutter = 完美组合

    Firebase Auth、存储、托管等

    照相亭利用 Firebase 生态系统进行各种后端集成。firebase_auth[25] package 支持用户在应用启动后立即匿名登录。每个会话都使用 Firebase Auth 创建具有唯一 ID 的匿名用户。

    当您来到共享页面时,此设置即会开始发挥作用。您可以下载照片以保存为个人头像,也可以直接将其分享到社交媒体。如果您下载照片,则该照片将存储在您的本地设备上。如果您分享照片,我们会使用 firebase_storage[26] package 将照片存储在 Firebase 中,以便稍后检索并生成帖子通过社交媒体发布。

    我们在 Firebase 的存储分区上定义了 Firebase 安全规则[27],确保照片在创建后不可变。这可以防止其他用户修改或删除存储分区中的照片。此外,我们使用 Google Cloud 提供的 对象生命周期管理[28],定义了一个删除 30 天前所有对象的规则,但您可以按照应用中列出的说明请求尽快删除您的照片。

    此应用还使用 Firebase Hosting[29] 快速安全地进行托管。我们可以借助 action-hosting-deploy[30] GitHub Action,根据目标分支,将应用自动部署到 Firebase Hosting。当我们将变更合并到主分支时,该操作会触发一个工作流,用于构建应用的特定开发版本,并将其部署到 Firebase Hosting。同样,当我们将变更合并到发布分支时,该操作也会触发部署生产版本。通过结合使用 GitHub Action 与 Firebase Hosting,我们的团队能够快速迭代,并始终得到最新版本的预览。

    最后,我们使用 Firebase 性能监测[31] 来监控主要的 Web 性能指标。

    使用 Cloud Functions 进行社交

    在生成您的社交帖子之前,我们首先会确保照片内容是像素级完美的。最终图像包含漂亮的边框,以呈现 I/O 照相亭特色,并按 4:3 或 3:4 的宽高比进行裁剪,以便在社交帖子上呈现出色的效果。

    我们使用 OffscreenCanvas[32] API 或 CanvasElement[33] 来合成原始照片、吉祥物和道具的图层,并生成您可以下载的单个图像。这个处理步骤由 image_compositor[34] package 负责执行。

    然后,我们利用 Firebase 强大的 Cloud Functions[35],来将照片分享到社交媒体。当您点击分享按钮时,系统会带您前往新标签页,并在所选的社交平台上自动生成待发布的帖子。该帖子还包含一个链接,连接到我们编写的 Cloud Functions。浏览器在分析网址时,会检测 Cloud Functions 生成的动态元数据,并据此在您的社交帖子中显示照片的精美预览,以及一个指向分享页面的链接,您的粉丝们可以在该页面上查看照片,并导航回 I/O 照相亭应用,以获取他们自己的照片。

    function renderSharePage(imageFileName: string, baseUrl: string): string {
     const context = Object.assign({}, BaseHTMLContext, {
       appUrl: baseUrl,
       shareUrl: `${baseUrl}/share/${imageFileName}`,
       shareImageUrl: bucketPathForFile(`${UPLOAD_PATH}/${imageFileName}`),
     });
     return renderTemplate(shareTmpl, context);
    }
    

    成品如下所示:

    有关如何在 Flutter 项目中使用 Firebase 的更多信息,请查看 此 Codelab[36]

    最终成果

    本项目详细地示范了如何针对 Web 来构建应用的方法。令我们感到惊喜的是,与使用 Flutter 构建移动应用的体验相比,这个 Web 应用的构建工作流与之非常相似。我们必须考虑窗口大小、自适应、触摸与鼠标输入、图像加载时间、浏览器兼容性等元素,以及在构建 Web 应用时所必需考虑的其他所有因素。但是,我们仍然可以使用相同的模式、架构和编码标准来编写 Flutter 代码,这让我们在构建 Web 应用时感到非常自在。Flutter package 提供的工具和不断发展的生态系统,包括 Firebase 工具套件,帮助我们实现了 I/O 照相亭。

    △ 打造 I/O 照相亭的 Very Good Ventures 团队

    我们已经开放了所有源代码,欢迎大家前往 GitHub 查看 photo_booth[37] 项目,也别忘了多多拍照秀出来哦!

    参考资料

    [1]

    照相亭互动体验: https://photobooth.flutter.cn/

    [2]

    Flutter 的 Dash: https://flutter.cn/dash

    [3]

    Flutter web: https://flutter.cn/web

    [4]

    Firebase: https://firebase.google.com/

    [5]

    Flutter 现在支持打造 Web 应用: https://flutter.cn/posts/whats-new-in-flutter-2-0

    [6]

    Baseflow: https://www.baseflow.com/

    [7]

    Flutter 摄像头插件: https://github.com/Baseflow/flutter-plugins

    [8]

    联合插件: https://flutter.cn/docs/development/packages-and-plugins/developing-packages#federated-plugins

    [9]

    HtmlElementView: https://api.flutter.cn/flutter/widgets/HtmlElementView-class.html

    [10]

    VideoElement: https://api.flutter.cn/flutter/dart-html/VideoElement-class.html

    [11]

    CanvasElement: https://api.flutter.cn/flutter/dart-html/CanvasElement-class.html

    [12]

    某些设备会提供专门处理这一问题的设置选项: https://9to5mac.com/2020/07/09/iphone-mirror-selfie-photos/

    [13]

    一个问题: https://github.com/flutter/flutter/issues/79519

    [14]

    DivElement: https://api.flutter.cn/flutter/dart-html/DivElement-class.html

    [15]

    getUserMedia API: https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia

    [16]

    object-fit: https://developer.mozilla.org/en-US/docs/Web/CSS/object-fit

    [17]

    LayoutBuilder: https://api.flutter.cn/flutter/widgets/LayoutBuilder-class.html

    [18]

    GestureDetector: https://api.flutter.cn/flutter/widgets/GestureDetector-class.html

    [19]

    Transform: https://api.flutter.cn/flutter/widgets/Transform-class.html

    [20]

    响应性和自适应性: https://flutter.cn/docs/development/ui/layout/adaptive-responsive

    [21]

    flutter_bloc: https://pub.flutter-io.cn/packages/flutter_bloc

    [22]

    photobooth_ui: https://github.com/flutter/photobooth/tree/main/packages/photobooth_ui

    [23]

    Material: https://flutter.cn/docs/development/ui/widgets/material

    [24]

    Cupertino: https://flutter.cn/docs/development/ui/widgets/cupertino

    [25]

    firebase_auth: https://pub.flutter-io.cn/packages/firebase_auth

    [26]

    firebase_storage: https://pub.flutter-io.cn/packages/firebase_storage

    [27]

    Firebase 安全规则: https://firebase.google.cn/docs/rules

    [28]

    对象生命周期管理: https://cloud.google.com/storage/docs/lifecycle

    [29]

    Firebase Hosting: https://firebase.google.cn/docs/hosting

    [30]

    action-hosting-deploy: https://github.com/FirebaseExtended/action-hosting-deploy

    [31]

    Firebase 性能监测: https://firebase.google.cn/products/performance

    [32]

    OffscreenCanvas: https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas

    [33]

    CanvasElement: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/canvas

    [34]

    image_compositor: https://github.com/flutter/photobooth/tree/main/packages/image_compositor

    [35]

    Cloud Functions: https://firebase.google.cn/docs/functions

    [36]

    此 Codelab: https://firebase.google.cn/codelabs/firebase-get-to-know-flutter#0

    [37]

    photo_booth: https://github.com/flutter/photobooth

    展开全文
  • firebase_sign_in ...flutter_spinkit: ^4.1.2 创建项目并连接您的应用不要忘记下载适用于iOS的GoogleService-Info.plist和适用于Android的google-services.json ,然后分别使用XCode和android/app目录将它们移至R
  • template_firebase_app-源码

    2021-02-21 04:58:51
    Firebase Auth集成 用Facebook登录 用电子邮件和密码登录 永久登录凭证 视觉错误处理 Firebase Firestore集成 登出功能 功能齐全的屏幕 演练流程 欢迎屏幕 登入画面 注册屏幕 主屏幕 导航抽屉 其他优点 美丽的用户...
  • firebase_app 一个新的Flutter项目。 入门 该项目是Flutter应用程序的起点。 如果这是您的第一个Flutter项目,那么有一些资源可以帮助您入门: 要获得Flutter入门方面的帮助,请查看我们的,其中提供了教程,示例,...
  • flutter-auth-ui:flutter-auth-ui是Flutter Web应用程序的身份验证库。 它使用Firebase身份验证作为安全性即服务(SECaaS)提供程序。 它实现了UI来注册用户,验证电子邮件,登录,注销,恢复密码,访问Firestore。
  • 我这里贴一个官方地址教你如何进行配置: https://firebase.google.com/docs/flutter/setup?authuser=0 2、本次需要使用的插件 1、firebase_storage: ^1.0.4 //firebase对象存储插件,核心插件 2、flutter_image_...

    用苹果的捷径直接视频转gif的,所以清晰度可能有点差,大家见谅

     

    IMG_0516.GIF

     

    最后结束的时候可能你发现突然少了一张,那是我点击的右上角X,想演示删除操作的。

    一、图片云端存储方案

    1、需求分析

    老婆要数据不丢失(这次我们先解决图片的问题)

    2、原来图片存储方式

    之前是通过两种方式存储

    1、每次复制一张图片到app文档目录,然后把地址存入本地数据库文件。
    2、直接把图片原始数据uint8List数据存入数据库
    这里不讨论这两种方式的好坏,关键问题是一旦手机丢或坏,那么图片数据就都没了。

    3、为什么我选择Firebase_storage对象存储

    网上查了很多资料,国内的诸如阿里云、腾讯云虽然都有对象存储,但是都要依赖后端,是的,我就是一个自学flutter,不会后端的菜鸡,而Firebase的对象存储和数据库都是帮你把后端服务给做好了的,你直接使用就可以,只要简单看下api就可以上手使用。

    二、app添加Firebase以及使用的相关插件

    (往下的内容可能都需要科学上网)
    1、应用配置
    要在自己的app里应用firebase体系的东西需要先进行配置,配置方法我这里就不说了,我推荐去firebase官方学习文档里学习一下,都是中文说明,所以应该没有困难。我这里贴一个官方地址教你如何进行配置:
    https://firebase.google.com/docs/flutter/setup?authuser=0
    2、本次需要使用的插件

    1、firebase_storage: ^1.0.4 //firebase对象存储插件,核心插件
    2、flutter_image_compress: ^0.2.3 //图片压缩插件。非必选
    3、cached_network_image: ^0.5.1 //网络图片加载插件,支持缓存
    4、multi_image_picker: ^2.2.55 //图片多选插件
    5、path_provider: ^0.4.0 //提供获得应用路径
    6、path: ^1.6.2 //路径操作

    三、撸码

    撸码前我建议大家去看下firebase_storage的帮助文档,了解使用方法后理解更加深刻。
    在开撸前我还是习惯先梳理业务流程,分为以下几步:

    1、获得你自己的对象存储空间实例
    2、获得一个对象存储空间的位置引用,用来存储图片,注意这个引用的位置需要包含你的文件完整名字。
    3、本地相册选择图片,然后你可以选择压缩后上传,或者不压缩直接上传到对象存储空间
    4、上传完成后获得上传图片的下载地址(当然还提供了获得诸如文件位置、名称、大小等各类信息的方法)
    5、将获得图片下载地址存入数据库

    业务核心逻辑代码:

      //图片上传任务列表
      List<StorageUploadTask> _upLoadTask = [];
      //存放firestorage返回的图片下载地址
      List<String> _imageUrl = [];
      //存放压缩后的图片数据路径
      List<String> _imagePath = [];
      //选择图片并上传
      _pickImageUpLoad() async {
        //通过MultiImagePicker插件从本地相册选取图片,配置一次最多选择12张,禁止摄像头拍照
        var requestList = await MultiImagePicker.pickImages(
          maxImages: 12,
          enableCamera: false,
        );
        if (!mounted) return;
        //获得目前上传任务数量
        int _taskNum = _upLoadTask.length;
        //这里进行一下判断,是否选择了图片,如果没有选择图片不进行任何操作。
        if (requestList.length != 0) {
          for (int i = 0; i < requestList.length; i++) {
            //获得一个uuud码用于给图片命名
            final String uuid = Uuid().v1();
            //请求原始图片数据
            await requestList[i].requestOriginal();
            //获取图片数据,并转换成uint8List类型
            Uint8List imageData = requestList[i].imageData.buffer.asUint8List();
            print('开始压缩第$i张图片');
            //通过图片压缩插件进行图片压缩
            var result = await FlutterImageCompress.compressWithList(imageData,
                quality: 100);
            //获得应用临时目录路径
            final Directory _directory = await getTemporaryDirectory();
            final Directory _imageDirectory =
                await new Directory('${_directory.path}/image/')
                    .create(recursive: true);
            _path = _imageDirectory.path;
            print('本次获得路径:${_imageDirectory.path}');
            //将压缩的图片暂时存入应用缓存目录
            File imageFile = new File('${_path}originalImage_$uuid.png')
              ..writeAsBytesSync(result);
            _imagePath.add(imageFile.path);
            print('图片$i的 本地路径是:${imageFile.path}');
            print('开始创建第$i个上传任务');
            //获得对象存储控件实例后获得图片引用地址
            StorageReference storageReference =
                FirebaseStorage.instance.ref().child('image').child('image_${uuid}.jpg');
            //将图片上传至对象存储空间
            StorageUploadTask storageUploadTask =
                storageReference.putFile(imageFile);
            setState(() {
              _upLoadTask.add(storageUploadTask);
            });
            //释放图片原始数据资源
            requestList[i].releaseOriginal();
          }
          //根据上传任务数量获得上传成功后的图片下载地址
          for (int i = _taskNum == 0 ? 0 : (_upLoadTask.length - _taskNum);
              i < _upLoadTask.length;
              i++) {
            StorageTaskSnapshot snapshot = await _upLoadTask[i].onComplete;
            String uri = await snapshot.ref.getDownloadURL();
            _imageUrl.add(uri);
            print('上传图片返回的url:$uri');
          }
        }
      }
    

    以上代码是业务流程的核心逻辑代码
    我基本都写了注释,在这里我再说几点注意点:

    1、上传图片的方法目前支持上传file文件putFile(File file)和内存数据putData(Uint8List data),建议使用上传文件的方式,因为上传内存数据的话需要图片数据先在内存中编译完成,可能导致内存占用方面的问题。
    2、我用的这个多图选择插件个人感觉是目前最好的一个,但是他返回的是图片的内存数据,所以记得一旦不用的时候要释放。
    3、图片上传的时候是否可以对过程进行操作和监控呢?当然可以。请往下看。

    UI代码

    刚才上面的是逻辑层面的,我们需要继续编写UI内容用于展示选择图片后的可以看到的界面变化。就好像开头效果展示一样,下面会显示上传任务的容器,容器底下还会显示上传状态和上传进度的变化。

    分析:无论有多少个上传任务,每个任务展示的UI其实是一样的,如何展示只是形式的问题,可能你喜欢竖着排,他可能喜欢横着排,这个看个人喜好。我这里主要放上单个上传任务的UI
    upload_task.dart

    import 'package:firebase_storage/firebase_storage.dart';
    import 'package:flutter/material.dart';
    
    import 'package:transparent_image/transparent_image.dart';
    
    class UpLoadTaskContainer extends StatelessWidget {
      UpLoadTaskContainer({
        this.task,
    //    this.onDismissed,
        this.onCancel,
        this.imagePath,
    //    this.index,
    //    this.url
    //    this.getUrl,
      });
    
      final StorageUploadTask task;
    //  final VoidCallback onDismissed;
      final VoidCallback onCancel;
    //  final String url;
      final String imagePath;
    //  final int index;
    //  final VoidCallback getUrl;
    
      get status {
        String result;
        if (task.isComplete) {
          if (task.isSuccessful) {
            result = '成功';
          } else if (task.isCanceled) {
            result = '取消';
          } else
            result = '失败:${task.lastSnapshot.error}';
        } else if (task.isPaused) {
          result = '暂停';
        } else if (task.isInProgress) {
          result = '上传中';
        }
        return result;
      }
    
      _bytePercent(StorageTaskSnapshot snapshot) {
        return '${((snapshot.bytesTransferred / snapshot.totalByteCount) * 100).toInt()}%';
      }
    
      @override
      Widget build(BuildContext context) {
        return new StreamBuilder<StorageTaskEvent>(
          stream: task.events,
          builder: (BuildContext context,
              AsyncSnapshot<StorageTaskEvent> asyncSnapShot) {
            Widget subtitle;
            if (asyncSnapShot.hasData) {
              StorageTaskEvent event = asyncSnapShot.data;
              StorageTaskSnapshot snapshot = event.snapshot;
              subtitle = new Text(
                '$status:${_bytePercent(snapshot)}',
                style: new TextStyle(color: Colors.white),
              );
            } else
              subtitle = new Text('准备上传');
            return new Stack(
              alignment: Alignment.bottomCenter,
              children: <Widget>[
                new FadeInImage(
                  placeholder: new MemoryImage(kTransparentImage),
                  image: AssetImage(
                    imagePath,
                  ),
                  fit: BoxFit.cover,
                  width: (MediaQuery.of(context).size.width - 32.0 - 8.0) / 3,
                  height: (MediaQuery.of(context).size.width - 32.0 - 8.0) / 3,
                ),
                new Positioned(
                  right: 0.08,
                  top: 0.08,
                  child: new GestureDetector(
                    onTap: onCancel,
                    child: new Container(
                      decoration: new BoxDecoration(
                        color: Colors.black45,
                        shape: BoxShape.circle,
                      ),
                      child: new Icon(
                        Icons.close,
                        color: Colors.white,
                        size: 20.0,
                      ),
                    ),
                  ),
                ),
                new Positioned(
                    child: new Container(
                  alignment: Alignment.center,
                  height: 20.0,
                  width: (MediaQuery.of(context).size.width - 32.0 - 8.0) / 3,
                  color: Colors.black45,
                  child: subtitle,
                )),
              ],
            );
          },
        );
      }
    }
    

    代码简析:

    1、该类构造函数接收三个参数,上传任务task,回调函数onCancel用于删除上传任务,imagePath用于临时显示上传任务的展示
    2、get status方法用于获取上传任务的状态
    3、StreamBuilder<StorageTaskEvent>是一个上传任务事件类型的数据流构造方法,stream数据流就是任务事件。通过判断任务快照是否在进行数据传输显示任务状态。
    4、最上层的ui就是一个stack层叠控件,包裹着图片显示,右上角的删除任务按钮和底下的带有上传状态和进度的控件。

    总结

    至此基本上上传多个图片到Firebase_storage就实现了,上传后你可以在你的存储空间看到上传的图片。当然了后续业务还包括下载图片、删除图片等,原理差不多,都是先获得图片在对象控件的引用,然后进行相关操作,这里我就不多介绍,感兴趣的朋友可以自己操作一下。

    本文毕竟贴了核心代码,所以如果你在操作的过程出现问题,可以去我项目地址查看全部源码。地址是:
    https://gitee.com/xusujun33/activity_record_jia
    本人是一个产品经理自学开发的小学生,所以代码简陋、错误难免,还望各位海涵。下个目标是把diy活动信息部分也搬到firebase上,这样就实现了所有数据的云端化,再也不怕丢数据了。

    不聊了,老婆又提新需求了,我赶紧溜了。

    展开全文
  • Firebase Auth集成 用Facebook登录 用电子邮件和密码登录 永久登录凭证 视觉错误处理 Firebase Firestore集成 登出功能 个人资料照片上传 Firebase存储集成 功能齐全的屏幕 演练流程 欢迎屏幕 登入画面 注册屏幕 主...
  • 该项目由Flutter Framework , Firebase身份验证, Cloud Firestore和Adobe XD Flutter插件组成。 如果这是您的第一个Flutter项目,那么有一些资源可以帮助您入门: 实验室:编写您的第一个Flutter应用 食谱:有
  • auth_services_module 一个新的Flutter软件包项目。 入门 该项目是Dart的起点,Dart是一个库模块,其中包含可以在多个Flutter或Dart项目之间轻松共享的代码。 要获得Flutter入门方面的帮助,请查看我们的,其中...

空空如也

空空如也

1 2 3 4 5 ... 9
收藏数 169
精华内容 67
关键字:

firebase_authflutter