Flutter 在 Google 登录后将页面推送到上下文中

问题描述 投票:0回答:1

刚刚开始一个 flutter 个人项目。我正在开发 Flutter 应用程序中的入职页面,该页面将在创建新用户后出现。目前有两种方式创建用户:电子邮件/密码或 Google 登录。我的入门页面可以通过

Navigator.pushNamed(context, '/onboarding');
正确推送。但是,当我尝试使用 GoogleSignin 执行相同的流程时,它决定在推送新页面之前更改页面。这使它失去上下文并给出此错误。

[ERROR:flutter/runtime/dart_vm_initializer.cc(41)] Unhandled Exception: This widget has been unmounted, so the State no longer has a context (and should be considered defunct).
Consider canceling any active work during "dispose" or using the "mounted" getter to determine if the State is still active.
#0      State.context.<anonymous closure> (package:flutter/src/widgets/framework.dart:951:9)
#1      State.context (package:flutter/src/widgets/framework.dart:957:6)
#2      _LoginPageState._handleGoogleSignIn (package:silo_app/pages/auth/login_page.dart:64:29)
<asynchronous suspension>

这是我如何处理谷歌登录以及如何为我的主页或authpages渲染页面的代码。


  void _handleGoogleSignIn() async {
    bool isNewUser = false;

    try {
      isNewUser = await GoogleSignInService().signInWithGoogle(context);
    } catch (error) {
      print("Google sign-in failed: $error");
    } finally {
      if (mounted) {
        Navigator.pop(context);
      } else {
        print("not mounted so it can't be popped");
      }
      if (isNewUser) {
        Navigator.pushNamed(context, '/onboarding');
      } else {
        Navigator.pushReplacementNamed(context, '/home');
      }
    }
  }
class GoogleSignInService {
  final GoogleSignIn _googleSignIn = GoogleSignIn();

  Future<bool> signInWithGoogle(BuildContext context) async {
    print("Start of 3rd party signin");
    try {
      print('Signing in with Google');
      final GoogleSignInAccount? googleSignInAccount =
          await _googleSignIn.signIn();

      if (googleSignInAccount == null) {
        print('Google sign-in was cancelled.');
        return false;
      }

      final GoogleSignInAuthentication googleSignInAuthentication =
          await googleSignInAccount.authentication;

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

      final UserCredential credentials =
          await FirebaseAuth.instance.signInWithCredential(authCredential);

      User? user = credentials.user;

      if (user != null) {
        print('User signed in successfully: ${user.uid}');
        DocumentSnapshot userDoc = await FirebaseFirestore.instance
            .collection("Users")
            .doc(user.uid)
            .get();

        if (!userDoc.exists) {
          print('User does not exist in Firestore, creating user...');
          await FirebaseFirestore.instance
              .collection("Users")
              .doc(user.uid)
              .set({
            "email": user.email,
            "username": user.displayName ?? user.email!.split("@")[0],
            "dateCreated": DateTime.now(),
            "uid": user.uid,
          });

          print("User created successfully");
          return true;
        } else {
          print('User already exists, navigating to home...');
          return false;
        }
      }
      return false;
    } catch (error) {
      print(error.toString());
      return false;
    }
  }
}
class AuthPage extends StatelessWidget {
  const AuthPage({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color(0xFF010922),
      body: StreamBuilder<User?>(
          stream: FirebaseAuth.instance.authStateChanges(),
          builder: (context, snapshot) {
            if (snapshot.connectionState == ConnectionState.waiting) {
              return const Center(
                child: CircularProgressIndicator(),
              );
            } else if (snapshot.hasData) {
              return HomePage();
            } else {
              return LoginOrRegisterPage();
            }
          }),
    );
  }
}

我怀疑问题在于上下文和小部件如何根据streamBuilder 进行更改。因为当它得到谷歌的签名时,它会自动改变。但是,为什么它适用于普通的 firebase 寄存器呢?以及阻止小部件在我的代码执行完成之前退出的解决方案是什么。

我让服务返回一个变量,判断它是否是新用户,然后处理调用 Google 登录服务的页面中的路由。我认为这会让小部件保持安装状态,但当异步signInWithCredential通过时,它似乎立即切换到主页。我想知道最好的方法是什么。

感谢您的帮助!

flutter firebase dart google-cloud-platform google-signin
1个回答
0
投票

原因

state
FirebaseAuth
更改时,您的
StreamBuilder
函数会触发,它会删除
LoginOrRegisterPage
,并且
context
会更改为
mounted = false
。在这种情况下,您无法使用它的上下文导航到其他屏幕。

您正确地认识到,当身份验证状态发生变化时,

StreamBuilder
会重建小部件,从而导致小部件丢失上下文。发生这种情况是因为
authStateChanges()
流在导航逻辑完成之前触发了重建,从而导致错误。

要正确处理此问题,您可以使用

StatefulWidget
更有效地管理状态并单独控制导航。

解决方案:使用
StatefulWidget
进行身份验证和导航

通过在

StatefulWidget
内处理导航,您可以更好地管理状态并在
StreamBuilder
外执行导航。

以下是修改代码的方法:

class AuthPage extends StatefulWidget {
  const AuthPage({Key? key}) : super(key: key);

  @override
  _AuthPageState createState() => _AuthPageState();
}

class _AuthPageState extends State<AuthPage> {
  bool _isNewUser = false; // To track if the user is new
  bool _isLoading = true; // To handle loading state
  bool _isNavigating = false; // To prevent multiple navigations

  @override
  void initState() {
    super.initState();
    _checkUserStatus(); // Check if the user is new or existing
  }

  Future<void> _checkUserStatus() async {
    setState(() {
      _isLoading = true;
    });

    User? user = FirebaseAuth.instance.currentUser;

    if (user != null) {
      DocumentSnapshot userDoc = await FirebaseFirestore.instance
          .collection("Users")
          .doc(user.uid)
          .get();

      if (!userDoc.exists) {
        _isNewUser = true;
      }
    }

    setState(() {
      _isLoading = false;
    });

    _navigateUser();
  }

  void _navigateUser() {
    if (_isNavigating) return; // Prevent multiple navigations
    _isNavigating = true;

    if (_isNewUser) {
      Navigator.pushNamed(context, '/onboarding');
    } else {
      Navigator.pushReplacementNamed(context, '/home');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Color(0xFF010922),
      body: _isLoading
          ? Center(
              child: CircularProgressIndicator(),
            )
          : StreamBuilder<User?>(
              stream: FirebaseAuth.instance.authStateChanges(),
              builder: (context, snapshot) {
                if (snapshot.connectionState == ConnectionState.waiting) {
                  return const Center(
                    child: CircularProgressIndicator(),
                  );
                } else if (snapshot.hasData) {
                  return HomePage(); // Show the home page if a user is authenticated
                } else {
                  return LoginOrRegisterPage(); // Show the login page if no user is authenticated
                }
              },
            ),
    );
  }
}

StatefulWidget 变更说明:

使用

StatefulWidget
管理
state
并在检查其
status
后处理用户导航。

_checkUserStatus
方法:此方法通过检查
Firestore
中的数据来确定用户是新用户还是现有用户。

_navigateUser
方法:根据用户是新用户还是现有用户来管理导航逻辑。

状态管理标志:

_isNewUser
:确定用户是否应该进行入职培训。
_isLoading
:检查用户时显示加载指示器
status
_isNavigating
:在
StreamBuilder
重建时防止多次导航。

为什么这种方法有效

Better State Control
:使用
StatefulWidget
可以让您更好地控制
navigation
流,将其与对
StreamBuilder
变化做出反应的
authentication state
分开。
Avoid Context Loss
:只有在确定用户状态后才会触发导航逻辑,防止上下文丢失和相关错误。

通过遵循此方法,您可以确保您的入职流程对于

Google Sign-In
email/password
身份验证都能正确运行。

© www.soinside.com 2019 - 2024. All rights reserved.