扑火firebase谷歌退出不工作

Posted

技术标签:

【中文标题】扑火firebase谷歌退出不工作【英文标题】:flutter firebase google sign out not working 【发布时间】:2020-04-01 13:15:24 【问题描述】:

我正在尝试实现 google 登录/注销,但我的注销不起作用。我得到一个错误 signOut() was called on null 。当我在用户登录后打印出用户时,我确实得到了所有正确的信息,但是当我在我的注销功能中打印出来时,它说它是空的。自定义 firebase 用户确实有效。这是我的 auth.dart 文件:

import 'dart:async';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';

abstract class BaseAuth 
  Future<String> signIn(String email, String password);

  Future<String> signUp(String email, String password);

  Future<FirebaseUser> getCurrentUser();

  Future<void> sendEmailVerification();

  Future<void> signOut();

  Future<bool> isEmailVerified();

  Future<String> signInWithGoogle();


class Auth implements BaseAuth 
  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
  final GoogleSignIn googleSignIn = GoogleSignIn();
  FirebaseUser user;

  Future<String> signIn(String email, String password) async 

    user = (await _firebaseAuth.signInWithEmailAndPassword(email: email, password: password)).user;
    return user.email;
  

  Future<String> signUp(String email, String password) async 
    FirebaseUser user = (await _firebaseAuth.createUserWithEmailAndPassword(
        email: email, password: password)).user;
    return user.uid;
  

  Future<FirebaseUser> getCurrentUser() async 
    user = await _firebaseAuth.currentUser();
    return user;
  
  signOut() async 
    //print("signed in user: $authService.user");
    await _firebaseAuth.signOut();
  

  Future<void> sendEmailVerification() async 
    FirebaseUser user = await _firebaseAuth.currentUser();
    user.sendEmailVerification();
  

  Future<bool> isEmailVerified() async 
    FirebaseUser user = await _firebaseAuth.currentUser();
    return user.isEmailVerified;
  

  Future<String> signInWithGoogle() async 
    final GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn();
    final GoogleSignInAuthentication googleSignInAuthentication =
    await googleSignInAccount.authentication;

    final AuthCredential credential = GoogleAuthProvider.getCredential(
      accessToken: googleSignInAuthentication.accessToken,
      idToken: googleSignInAuthentication.idToken,
    );

   user = (await _firebaseAuth.signInWithCredential(credential)).user;

    assert(!user.isAnonymous);
    assert(await user.getIdToken() != null);

    final FirebaseUser currentUser = await _firebaseAuth.currentUser();
    assert(user.uid == currentUser.uid);
    return 'signInWithGoogle succeeded: $user';
  




另外一些奇怪的事情是,如果我启动应用程序并且已经登录(作为谷歌),我点击退出并且它似乎可以工作。我的控制台中没有任何内容,但它会返回登录屏幕。然后,如果我以 google 身份重新登录并注销,就会开始发生错误。另一个奇怪的事情是,如果我碰巧已经登录并且单击后将其注销,而如果我在 android studio 中重新启动我的应用程序而不更改任何内容,它会将我带回到我应该已经登录的屏幕。这仅发生在 Google 注销(而不是 firebase 注销)时。 知道我可能做错了什么吗?谢谢

pubspec.yaml

dependencies:
  firebase_auth: ^0.14.0+5

  firebase_database: ^3.0.7
  google_sign_in: ^4.0.7
  firebase_storage:
  image_picker:
  cloud_firestore:
  shared_preferences:
  fluttertoast:
  cached_network_image:
  intl:

我使用它的一个页面:(我有很多,但实现类似于此,它在我的任何页面中都不起作用)

import 'package:flutter/material.dart';
import 'package:pet_helper/chat.dart';
import 'package:pet_helper/lost_and_found.dart';
import 'package:pet_helper/pet_adoption.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();


class _HomePageState extends State<HomePage> 

  final GlobalKey<FormState> formKey = GlobalKey<FormState>();
  int _currentIndex = 0;

  @override
  void initState() 
    super.initState();
  
  final List<Widget> _children = [
    new LostAndFoundPage(),
    new PetAdoptionPage(),
    new ChatPage(),
  ];
  _signOut() async 
    try 
      await widget.auth.signOut();
      widget.onSignedOut();
     catch (e) 

      print(e);
    
  

  onTabTapped(int index) 
    setState(() 
      _currentIndex = index;
    );
  

  @override
  Widget build(BuildContext context) 
    return new Scaffold(
        appBar: new AppBar(
          automaticallyImplyLeading:false,
          title: new Text('Pet Helper'),
          actions: <Widget>[
            new FlatButton(
                child: new Text('Logout',
                    style: new TextStyle(fontSize: 17.0, color: Colors.white)),
                onPressed: _signOut)
          ],
        ),
        body: _children[_currentIndex], // new
        bottomNavigationBar: BottomNavigationBar(
          onTap: onTabTapped, // new
          currentIndex: _currentIndex, // new
          items: [
            new BottomNavigationBarItem(
              icon: Icon(Icons.home),
              title: Text('Lost & Found'),
            ),
            new BottomNavigationBarItem(
              icon: Icon(Icons.pets),
              title: Text('Pet Adoption'),
            ),
            new BottomNavigationBarItem(
                icon: Icon(Icons.person), title: Text('Chat'))
          ],
        ));
  

为了完成,这是我的登录页面:


import 'package:flutter/material.dart';
import 'package:pet_helper/home_page.dart';
import 'authentication.dart';

class LoginSignUpPage extends StatefulWidget 
  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> 
  final _formKey = new GlobalKey<FormState>();
  String _email;
  String _password;
  String _errorMessage;

  // Initial form is login form
  FormMode _formMode = FormMode.LOGIN;
  bool _isios;
  bool _isLoading;

  // Check if form is valid before perform login or signup
  bool _validateAndSave() 
    final form = _formKey.currentState;
    if (form.validate()) 
      form.save();
      return true;
    
    return false;
  

  // Perform login or signup
  void _validateAndSubmit() async 
    setState(() 
      _errorMessage = "";
      _isLoading = true;
    );
    if (_validateAndSave()) 
      String userId = "";
      try 
        if (_formMode == FormMode.LOGIN) 
          userId = await widget.auth.signIn(_email, _password);
          print('Signed in: $userId');
         else 
          userId = await widget.auth.signUp(_email, _password);
          widget.auth.sendEmailVerification();
          _showVerifyEmailSentDialog();
          print('Signed up user: $userId');
        
        setState(() 
          _isLoading = false;
        );

        if (userId.length > 0 && userId != null && _formMode == FormMode.LOGIN) 
          widget.onSignedIn();
        

       catch (e) 
        print('Error: $e');
        setState(() 
          _isLoading = false;
          if (_isIos) 
            _errorMessage = e.details;
           else
            _errorMessage = 'Incorrect user or password';
        );
      
    
  


  @override
  void initState() 
    _errorMessage = "";
    _isLoading = false;
    super.initState();
  

  void _changeFormToSignUp() 
    _formKey.currentState.reset();
    _errorMessage = "";
    setState(() 
      _formMode = FormMode.SIGNUP;
    );
  

  void _changeFormToLogin() 
    _formKey.currentState.reset();
    _errorMessage = "";
    setState(() 
      _formMode = FormMode.LOGIN;
    );
  

  @override
  Widget build(BuildContext context) 
    _isIos = Theme.of(context).platform == TargetPlatform.iOS;
    return new Scaffold(
        appBar: new AppBar(
          title: new Text('Pet Helper'),
        ),
        body: Stack(
          children: <Widget>[
            _showBody(),
            _showCircularProgress(),
          ],
        ));
  

  Widget _showCircularProgress()
    if (_isLoading) 
      return Center(child: CircularProgressIndicator());
     return Container(height: 0.0, width: 0.0,);

  

  void _showVerifyEmailSentDialog() 
    showDialog(
      context: context,
      builder: (BuildContext context) 
        // return object of type Dialog
        return AlertDialog(
          title: new Text("Verify your account"),
          content: new Text("Link to verify account has been sent to your email"),
          actions: <Widget>[
            new FlatButton(
              child: new Text("Dismiss"),
              onPressed: () 
                _changeFormToLogin();
                Navigator.of(context).pop();
              ,
            ),
          ],
        );
      ,
    );
  

  Widget _showBody()
    return new Container(
        padding: EdgeInsets.all(16.0),
        child: new Form(
          key: _formKey,
          child: new ListView(
            shrinkWrap: true,
            children: <Widget>[
              _showLogo(),
              _showEmailInput(),
              _showPasswordInput(),
              _showErrorMessage(),
              _showPrimaryButton(),
              _showSecondaryButton(),
              _googleSignInButton(),

            ],
          ),
        ));
  

  Widget _showErrorMessage() 
    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,
      );
    
  

  Widget _showLogo() 
    return new Hero(
      tag: 'hero',
      child: Padding(
        padding: EdgeInsets.fromLTRB(0.0, 30.0, 0.0, 0.0),
        child: CircleAvatar(
          backgroundColor: Colors.transparent,
          radius: 120.0,
          child: Image.asset('assets/babies.png'),
        ),
      ),
    );
  

  Widget _showEmailInput() 
    return Padding(
      padding: const EdgeInsets.fromLTRB(0.0, 25.0, 0.0, 0.0),
      child: new TextFormField(
        maxLines: 1,
        keyboardType: TextInputType.emailAddress,
        autofocus: false,
        decoration: new InputDecoration(
            hintText: 'Email',
            icon: new Icon(
              Icons.mail,
              color: Colors.grey,
            )),
        validator: (String value) 
        if (value.isEmpty) 
          _isLoading = false;
          return 'Email can\'t be empty';
        
        else
          return null;
        
      ,
        onSaved: (value) => _email = value.trim(),
      ),
    );
  

  Widget _showPasswordInput() 
    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: (String value) 
          if (value.isEmpty) 
            _isLoading = false;
            return 'Password can\'t be empty';
          
          else
            return null;
          
        ,
        onSaved: (value) => _password = value.trim(),
      ),
    );
  

  Widget _showSecondaryButton() 
    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
          ? _changeFormToSignUp
          : _changeFormToLogin,
    );
  

  Widget _showPrimaryButton() 
    return new Padding(
        padding: EdgeInsets.fromLTRB(0.0, 35.0, 0.0, 0.0),
        child: SizedBox(
          height: 40.0,
          child: new RaisedButton(
            elevation: 5.0,
            shape: new RoundedRectangleBorder(borderRadius: new BorderRadius.circular(30.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,
          ),
        ));
  

void submitGoogleLogin() async
  setState(() 
    _errorMessage = "";
    _isLoading = true;
  );
  String userId = "";
  userId = await widget.auth.signInWithGoogle().whenComplete(() 
    widget.onSignedIn();
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (context) 
          return new HomePage();
        ,
      ),
    );
  );
  print('Signed in: $userId');


  Widget _googleSignInButton() 
    return OutlineButton(
      splashColor: Colors.grey,
      onPressed: submitGoogleLogin,
      shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(40)),
      highlightElevation: 0,
      borderSide: BorderSide(color: Colors.grey),
      child: Padding(
        padding: const EdgeInsets.fromLTRB(0, 10, 0, 10),
        child: Row(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Image(image: AssetImage("assets/google_logo.png"), height: 30.0),
            Padding(
              padding: const EdgeInsets.only(left: 10),
              child: Text(
                'Sign in with Google',
                style: TextStyle(
                  fontSize: 15,
                  color: Colors.grey,
                ),
              ),
            )
          ],
        ),
      ),
    );
  




哦,根页面改变了用户的状态:

import 'package:flutter/material.dart';
import 'login_signup_page.dart';
import 'authentication.dart';
import 'home_page.dart';

class RootPage extends StatefulWidget 
  RootPage(this.auth);

  final BaseAuth auth;

  @override
  State<StatefulWidget> createState() => new _RootPageState();


enum AuthStatus 
  NOT_DETERMINED,
  NOT_LOGGED_IN,
  LOGGED_IN,


class _RootPageState 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.NOT_LOGGED_IN : 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.NOT_LOGGED_IN;
      _userId = "";
    );
  

  Widget _buildWaitingScreen() 
    return Scaffold(
      body: Container(
        alignment: Alignment.center,
        child: CircularProgressIndicator(),
      ),
    );
  

  @override
  Widget build(BuildContext context) 
    switch (authStatus) 
      case AuthStatus.NOT_DETERMINED:
        return _buildWaitingScreen();
        break;
      case AuthStatus.NOT_LOGGED_IN:
        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 _buildWaitingScreen();
        break;
      default:
        return _buildWaitingScreen();
    
  



初始化根页面 Main.dart

void main() async
  runApp(new MyApp());


class MyApp extends StatelessWidget 
  @override
  Widget build(BuildContext context) 
    return new MaterialApp(
        title: 'Pet Helper',
        debugShowCheckedModeBanner: false,
        theme: new ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: new RootPage(auth: new Auth()));
  

【问题讨论】:

初始化RootPage的代码在哪里?如果你没有写这个,你可能想从你看到的指南中从 github 下载完整的源代码,然后从那里开始。当这些文件集中发生很多事情时,您似乎走得太远了。 对不起,刚刚添加了初始化根页面的main.dart 我修改了所有内容以避免重复代码并清理了我项目中多余的不必要代码。 顺便说一句,我确实按照教程创建了这个抽象类,并且一切都只使用登录/注册页面。该教程不包括谷歌登录,我必须弄清楚它是如何工作的,以使其与我的特定项目修改一起工作,以使其专门与我的应用程序一起工作。 我的意思是建议大家先充分了解现有的Firebase登录在Flutter中是如何工作的(状态管理、初始化、Statefulness、Event Listener),然后一步步加Google授权。 【参考方案1】:
abstract class BaseAuth 
  Future<String> signIn(String email, String password);

  Future<String> signUp(String email, String password);

  Future<FirebaseUser> getCurrentUser();

  Future<void> sendEmailVerification();

  Future<void> signOut();

  Future<bool> isEmailVerified();

  Future<String> signInWithGoogle();

  void signOutGoogle();


class Auth implements BaseAuth 
  final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
  final GoogleSignIn googleSignIn = GoogleSignIn();
  FirebaseUser user;

  Future<String> signIn(String email, String password) async 

    user = (await _firebaseAuth.signInWithEmailAndPassword(email: email, password: password)).user;
    return user.email;
  

  Future<String> signUp(String email, String password) async 
    FirebaseUser user = (await _firebaseAuth.createUserWithEmailAndPassword(
        email: email, password: password)).user;
    return user.uid;
  

  Future<FirebaseUser> getCurrentUser() async 
    user = await _firebaseAuth.currentUser();
    return user;
  
  signOut() async 
    print("signed in user: $user");
    await _firebaseAuth.signOut();
  

  Future<void> sendEmailVerification() async 
    FirebaseUser user = await _firebaseAuth.currentUser();
    user.sendEmailVerification();
  

  Future<bool> isEmailVerified() async 
    FirebaseUser user = await _firebaseAuth.currentUser();
    return user.isEmailVerified;
  

  Future<String> signInWithGoogle() async 
    final GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn();
    final GoogleSignInAuthentication googleSignInAuthentication =
    await googleSignInAccount.authentication;

    final AuthCredential credential = GoogleAuthProvider.getCredential(
      accessToken: googleSignInAuthentication.accessToken,
      idToken: googleSignInAuthentication.idToken,
    );

   user = (await _firebaseAuth.signInWithCredential(credential)).user;

    assert(!user.isAnonymous);
    assert(await user.getIdToken() != null);

    final FirebaseUser currentUser = await _firebaseAuth.currentUser();
    assert(user.uid == currentUser.uid);
    return 'signInWithGoogle succeeded: $user';
  


final Auth authService = Auth(); // add this to the bottom outside the class

使用全局变量

所以让我解释一下。每次调用Auth() 时,它都会创建该类的一个新实例。因此,在一种情况下,您可以让用户登录。在另一种情况下,您可以让用户退出。变量将包含变量的不同值。因此,如果您使用与登录用户不同的实例来注销用户,则用户变量将为空,因此不允许您注销并打印空值。

解决方案

解决方案是使用全局变量访问 Auth 类一次。前任。 var authService = Auth();

将变量放在 Auth 类之外,这样它就可以在任何地方的任何类中访问

【讨论】:

嗯,这很有道理。不过还是不行。使用 ``` _firebaseAuth.signOut()``` 时它的用户为空,它实际上甚至没有打印“登录用户:....”它只是在说 NoSuchMethodError: The method 'signOut' was called on null. Receiver: null Tried calling: signOut() 只有谷歌退出有问题。登录我将正确的用户信息打印到控制台,但是当我尝试使用它注销时它为空。适用于常规 Firebase 帐户 如果将_firebaseAuth 替换为FirebaseAuth.instance,是否有效? 该死。没有不同。我想知道它是否是我正在使用的 google_sign_in 包的版本。我正在使用 ^4.0.7。我不这么认为,因为我只使用 google_sign_in 登录哪个有效。但我正在使用身份验证来注销。也许错误版本的 firebase_auth 不适用于退出谷歌?仍然没有解释为什么在我调用它之前,打印的用户是空的。 你能用你的 pubspec 依赖更新你的问题吗?【参考方案2】:

感谢大家的回答和支持。我发现了问题。登录谷歌时,widget.onSignedIn 将我重定向到主页(它在根页面中)。在调用 SignedIn 后,我在谷歌登录按钮中重定向到主页,这就是我失去用户变量范围的原因。谢谢大家!

【讨论】:

以上是关于扑火firebase谷歌退出不工作的主要内容,如果未能解决你的问题,请参考以下文章

Swift:不退出 Firebase 观察功能块 [重复]

没有firebase的谷歌登录

如何在不退出的情况下使用 Firebase API 创建新用户? [复制]

使用 gatsby 解决 netlify 上的 firebase 身份验证(谷歌登录)部署错误

如何使用 Flutter 成功登录和退出 Firebase?

Flutter + Firebase:如何实现“谷歌登录”