我有一个使用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;
}
}