在Flutter中的小部件外部管理提供者模型的状态

时间:2020-10-25 08:47:02

标签: firebase flutter dart graphql hasura

我有一个使用Firebase Auth和Hasura作为后端api的应用程序。我能够将所有内容连接在一起,并设置了身份验证流程。本质上,在称为Auth的dart类中有一个基于authstatechange的firebase,它包含所有auth逻辑,并且用户对象上有一个valuenotifier,这样,当用户的值更改时,将在main内部调用一个回调函数。 .dart会触发一些逻辑。

假设用户注销,则将通知authstatechange,并依次通知值通知程序和回调,将用户推入登录页面。或者,当用户登录或返回已经登录的用户时,将显示一个初始页面,同时auth状态更改,并使用令牌设置graphqlclient,然后调用回调并将用户推送到主页或入职。 >

我遇到的问题是,我需要发出一个api请求来获取客户模型的客户数据,该数据要在小部件树中进行访问,以用于主页,入门,个人资料等,通常这是提供者是好的。问题是,我想在回调函数中发出请求,因为那是当我知道用户已登录时,最重要的是,graphql客户端具有用于授权请求的令牌。我还将使用这些数据来决定是将其发送到首页还是入职(已经包含在代码中了)

但是,如果我在其中发出请求,并且该回调函数在小部件树之外并且没有构建上下文,我应该如何与小部件共享该请求?此外,我希望能够在小部件树中更新该模型的值,这将再次与提供程序一起使用。但是我不知道如何最初在小部件树之外获取数据,然后与小部件树共享数据?

这是我的auth.dart类和main.dart类

class Auth {
  final _googleSignIn = GoogleSignIn();
  final _firebaseAuth = FirebaseAuth.instance;
  final ValueNotifier<AppUser> currentUser;
  IdTokenResult tokenResult;
  StreamSubscription<User> _onAuthChange;
  bool firebaseRegistered = false;

  //will be called to create an Auth instance.
  static Auth create() {
    final currentUser = FirebaseAuth.instance.currentUser;
    return Auth._(currentUser);
  }

  //Used to refer to the created Auth instance from anywhere in the widget tree.
  static Auth of(BuildContext context) {
    return Provider.of<Auth>(context, listen: false);
  }

  //Internal constructor which set the currentUser field.
  Auth._(
    User currentUser,
  ) : this.currentUser = ValueNotifier<AppUser>(
          currentUser != null ? AppUser(currentUser) : AppUser.empty(),
        );

  //Add a listener on the currentUser ValueNotifier.
  //Also listens for the authentication status of the user i.e logged in or logged out.
  User init(VoidCallback onUserChange) {
    currentUser.addListener(onUserChange);
    _onAuthChange = _firebaseAuth.authStateChanges().listen((User user) async {
      if (firebaseRegistered == true) {
        if (user != null) {
          tokenResult = await user.getIdTokenResult(true);
          final hasuraClaims =
              tokenResult.claims['https://hasura.io/jwt/claims'];

          if (hasuraClaims == null) {
            //runs only once for new user
            var uid = user.uid;
            final response = await http.get(
                "https://firebaseproject.cloudfunctions.net/refreshToken?uid=$uid");

            if (response.statusCode == 200) {
              tokenResult = await user.getIdTokenResult(true);
              print(tokenResult);
            } else {
              throw new Error();
            }
          }
          setAuthTokenForGraphQLServer(tokenResult.token);
          currentUser.value = AppUser(user);
        } else {
          currentUser.value = AppUser.empty();
        }
      } else {
        firebaseRegistered = true;
      }
    });
    return currentUser.value.when((u) => u, empty: () => null);
  }

  Future<void> signUpWithEmailAndPassword(
      {@required String email, @required String password}) async {
    try {
      await _firebaseAuth.createUserWithEmailAndPassword(
          email: email, password: password);
    } catch (e, st) {
      throw _throwAuthException(e, st);
    }
  }

  Future<void> loginWithEmail(
      {@required String email, @required String password}) async {
    try {
      await _firebaseAuth.signInWithEmailAndPassword(
          email: email, password: password);
    } catch (e, st) {
      throw _throwAuthException(e, st);
    }
  }

  Future<void> loginWithGoogle() async {
    try {
      final account = await _googleSignIn.signIn();
      if (account == null) {
        throw AuthException.cancelled;
      }
      final auth = await account.authentication;
      await _firebaseAuth.signInWithCredential(
        GoogleAuthProvider.credential(
            idToken: auth.idToken, accessToken: auth.accessToken),
      );
    } catch (e, st) {
      _throwAuthException(e, st);
    }
  }

  Future<UserCredential> loginWithFacebook() async {
    // Trigger the sign-in flow
    final LoginResult result = await FacebookAuth.instance.login();

    // Create a credential from the access token
    final FacebookAuthCredential facebookAuthCredential =
        FacebookAuthProvider.credential(result.accessToken.token);

    // Once signed in, return the UserCredential
    return await FirebaseAuth.instance
        .signInWithCredential(facebookAuthCredential);
  }

  Future<void> logout() async {
    try {
      await _firebaseAuth.signOut();
      await _googleSignIn.signOut();
      setAuthTokenForGraphQLServer(null);
    } catch (e, st) {
      _throwAuthException(e, st);
    }
  }

  void dispose(VoidCallback onUserChange) {
    _onAuthChange?.cancel();
    currentUser?.removeListener(onUserChange);
  }

  AuthException _throwAuthException(dynamic e, StackTrace st) {
    print(e.runtimeType);
    print(e.toString());
    if (e is AuthException) {
      throw e;
    }
    FlutterError.reportError(FlutterErrorDetails(exception: e, stack: st));
    if (e is PlatformException) {
      switch (e.code) {
        case 'ERROR_EMAIL_ALREADY_IN_USE':
          throw const AuthException(
              'The email address is already in use. check your email address.');
        case 'ERROR_INVALID_EMAIL':
          throw const AuthException('Please check your email address.');
        case 'ERROR_WRONG_PASSWORD':
          throw const AuthException('Please check your password.');
        case 'ERROR_USER_NOT_FOUND':
          throw const AuthException(
              'User not found. Is that the correct email address?');
        case 'ERROR_USER_DISABLED':
          throw const AuthException(
              'Your account has been disabled. Please contact support');
        case 'ERROR_TOO_MANY_REQUESTS':
          throw const AuthException(
              'You have tried to login too many times. Please try again later.');
      }
    }
    throw const AuthException('Sorry, an error occurred. Please try again.');
  }

  void setAuthTokenForGraphQLServer(String token) {
    Application().setGraphQLClient(token: token);
  }
}

class AuthException implements Exception {
  static const cancelled = AuthException('No account');

  const AuthException(this.message);

  final String message;

  @override
  String toString() => message;
}
main() async {
  final uri = "https://graphqlapi.com/v1/graphql";
  Application(uri: uri).setGraphQLClient();
  runApp(DevicePreview(
    enabled: false,
    builder: (context) => FirebaseApp(),
  ));
}

class FirebaseApp extends StatefulWidget {
  _FirebaseAppState createState() => _FirebaseAppState();
}

class _FirebaseAppState extends State<FirebaseApp> {
  // Set default `_initialized` and `_error` state to false
  bool _initialized = false;
  bool _error = false;

  // Define an async function to initialize FlutterFire
  void initializeFlutterFire() async {
    try {
      // Wait for Firebase to initialize and set `_initialized` state to true
      await Firebase.initializeApp();
      setState(() {
        _initialized = true;
      });
    } catch (e) {
      // Set `_error` state to true if Firebase initialization fails
      setState(() {
        _error = true;
      });
    }
  }

  @override
  void initState() {
    initializeFlutterFire();
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    // Show error message if initialization failed
    if (_error) {
      return Center(
        child: Text("Something Went Wrong..."),
      );
    }

    // Show a loader until FlutterFire is initialized
    if (!_initialized) {
      return Center(child: CircularProgressIndicator());
    }

    return App(auth: Auth.create());
  }
}

class App extends StatefulWidget {
  final Auth auth;

  App({Key key, @required this.auth}) : super(key: key);

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

class _AppState extends State<App> {
  final _navigatorKey = GlobalKey<NavigatorState>();
  fbAuth.User currentUser;

  @override
  void initState() {
    super.initState();
    currentUser = widget.auth.init(_onUserChanged);
  }

  @override
  void dispose() {
    widget.auth.dispose(_onUserChanged);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        Provider<Auth>.value(value: widget.auth),     
      ],
      child: MaterialApp(
        home: new Scaffold(
            resizeToAvoidBottomInset: false,
            body: currentUser != null
                ? SplashPage()
                : LoginPage()),
        routes: {
          '/splash': (context) => new Scaffold(
              resizeToAvoidBottomInset: false,
              body: SplashPage()),
          '/home': (context) =>
              new Scaffold(resizeToAvoidBottomInset: false, body: HomePage()),
          '/login': (context) =>
              new Scaffold(resizeToAvoidBottomInset: false, body: LoginPage()),
          '/landing': (context) => new Scaffold(
              resizeToAvoidBottomInset: false, body: LandingPage()),
          '/onboarding': (context) =>
              new Scaffold(resizeToAvoidBottomInset: false, body: Onboarding()),
        },
        navigatorKey: _navigatorKey,
      ),
    );
  }

  void _onUserChanged() {
    final user =
        widget.auth.currentUser.value.maybeWhen((u) => u, orElse: () => null);

    //returning logged in user or user logging in
    if ((currentUser != null && user != null) || (currentUser == null && user != null)) {
      _navigatorKey.currentState
          .pushNamedAndRemoveUntil('/home', (route) => false);
    }

    // User logged out
    else if (currentUser != null && user == null) {
      _navigatorKey.currentState
          .pushNamedAndRemoveUntil('/login', (route) => false);
    }
    currentUser = user;
  }
}

0 个答案:

没有答案