Flutter中Consumer触发类型错误:期望(() => void)?却得到_Future<dynamic>
Flutter Google登录点击按钮抛出类型错误问题
问题现象
未登录页面可正常显示,但点击登录按钮时抛出以下类型异常:
══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════ The following TypeErrorImpl was thrown building Consumer<ControllerLogin>(dirty, dependencies: [_InheritedProviderScope<ControllerLogin?>]): Expected a value of type '(() => void)?', but got one of type '_Future<dynamic>' The relevant error-causing widget was: Consumer<ControllerLogin> Consumer:file:///E:/CoderLife/Learning/flutter-learning/google_signin_authentication/lib/loginScreen.dart:17:12
相关代码
登录页面UI控制逻辑
loginUIController() { return Consumer<ControllerLogin>(builder: (context, model, child) { // 若用户已登录 if (model.userModel != null) { return Center( // 展示用户详情 child: alreadyLoggedInScreen(model), ); } else { // 若用户未登录 return notLoginScreen(model); } }); }
未登录页面实现
notLoginScreen(ControllerLogin model) { return Center( child: Column( children: [ Padding( padding: EdgeInsets.all(10), child: Image.asset('assets/googleback.jpg'), ), GestureDetector( onTap: () { Provider.of<ControllerLogin>(context, listen: false).allowUserToLogin(); }, child: Image.asset( 'assets/google.png', width: 250, ), ) ], ), ); }
页面构建方法
Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, body: loginUIController(), ); }
ControllerLogin状态管理类
class ControllerLogin with ChangeNotifier { FirebaseAuth auth = FirebaseAuth.instance; var googleSignIn = GoogleSignIn(); GoogleSignInAccount? googleSignInAccount; UserCredential? userCredential; UserModel? userModel; User? user; allowUserToLogin() async { if (kIsWeb) { GoogleAuthProvider authProvider = GoogleAuthProvider(); try { userCredential = await auth.signInWithPopup(authProvider); user = userCredential!.user; } catch (e) { print(e); } } else { googleSignInAccount = await googleSignIn.signIn(); if (googleSignInAccount != null) { final GoogleSignInAuthentication googleSignInAuthentication = await googleSignInAccount!.authentication; final AuthCredential credential = GoogleAuthProvider.credential( accessToken: googleSignInAuthentication.accessToken, idToken: googleSignInAuthentication.idToken, ); try { userCredential = await auth.signInWithCredential(credential); user = userCredential!.user; } on FirebaseAuthException catch (e) { if (e.code == 'account-exists-with-different-credential') { print(e); } else if (e.code == 'invalid-credential') { print(e); } } catch (e) { print(e); } } } if (user != null) { userModel = new UserModel( displayName: user!.displayName, email: user!.email, photoUrl: user!.photoURL); print(userModel!.toJson()); notifyListeners(); } } allowUserToLogOut() async { this.googleSignInAccount = await googleSignIn.signOut(); userModel = null; notifyListeners(); } }
Provider初始化代码
Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider(create: (context) => ControllerLogin()), ], child: MaterialApp( title: 'Google Login', theme: ThemeData( primarySwatch: Colors.blue, ), home: FutureBuilder( future: _initApp, builder: (BuildContext context, AsyncSnapshot snapshot) { if (snapshot.hasError) { print('error'); } if (snapshot.connectionState == ConnectionState.done) { return LoginScreen(); } return CircularProgressIndicator(); }, ), ), ); }
错误原因
核心问题在于notLoginScreen方法中:
- 该方法未接收
BuildContext参数,直接使用的context是无效的,导致Provider.of调用时上下文不匹配 - 虽然
allowUserToLogin是异步方法,但onTap只要求返回void的回调,之前修改异步函数的方向正确,但没解决上下文的根本问题
解决方案
推荐方案:直接使用Consumer提供的model调用方法
既然已经通过Consumer获取了ControllerLogin实例,无需再调用Provider.of,直接使用传入的model调用登录方法即可,避免上下文问题:
notLoginScreen(ControllerLogin model) { return Center( child: Column( children: [ Padding( padding: EdgeInsets.all(10), child: Image.asset('assets/googleback.jpg'), ), GestureDetector( onTap: () async { // 直接使用传入的model调用异步登录方法 await model.allowUserToLogin(); }, child: Image.asset( 'assets/google.png', width: 250, ), ) ], ), ); }
备选方案:传递有效BuildContext参数
修改notLoginScreen方法,让它接收BuildContext参数,确保Provider.of使用有效上下文:
// 修改方法签名,添加context参数 notLoginScreen(BuildContext context, ControllerLogin model) { return Center( child: Column( children: [ Padding( padding: EdgeInsets.all(10), child: Image.asset('assets/googleback.jpg'), ), GestureDetector( onTap: () async { Provider.of<ControllerLogin>(context, listen: false).allowUserToLogin(); }, child: Image.asset( 'assets/google.png', width: 250, ), ) ], ), ); }
同时在Consumer中调用时传入context:
loginUIController() { return Consumer<ControllerLogin>(builder: (context, model, child) { if (model.userModel != null) { return Center( child: alreadyLoggedInScreen(model), ); } else { // 传入有效context return notLoginScreen(context, model); } }); }
内容的提问来源于stack exchange,提问作者Leon W
相关产品推荐
相关产品推荐

