Flutter中MultiProvider结合抽象类实现多登录提供商区分调用问题
我尝试为Google和Facebook登录创建统一接口,SignInProvider定义如下:
abstract class SignInProvider with ChangeNotifier { bool get isSigningIn; set isSigningIn(bool isSigningIn); void login(); void logout(); } class FacebookSignInProvider with ChangeNotifier implements SignInProvider { ... } class GoogleSignInProvider with ChangeNotifier implements SignInProvider { ... }
首页代码如下:
class _HomeState extends State<Home> { @override Widget build(BuildContext context) { return MultiProvider( providers: [ ChangeNotifierProvider<SignInProvider>(create: (context) => GoogleSignInProvider()), ChangeNotifierProvider<SignInProvider>(create: (context) => FacebookSignInProvider()) ], child: Scaffold( body: StreamBuilder( stream: FirebaseAuth.instance.authStateChanges(), builder: (context, snapshot) { final provider = Provider.of<SignInProvider>(context, listen: true); if (provider.isSigningIn) { return // Show loader } else if (snapshot.hasData) { return // Show logged-in UI } else { // Give option to login via Google and Facebook return Row(children: [ TextButton( onPressed: () { final provider = Provider.of<SignInProvider>( context, listen: false, ); provider.login(); }, child: const Text('Login with Google'), ), TextButton( onPressed: () { final provider = Provider.of<SignInProvider>( context, listen: false, ); provider.login(); }, child: const Text('Login with Facebook'), ), ], ); } }), ), ); } }
当前UI中,点击‘Login with Google’和‘Login with Facebook’按钮时,均指向最后注册的FacebookSignInProvider;若将onPressed中的获取方式改为Provider.of<GoogleSignInProvider>或Provider.of<FacebookSignInProvider>则会崩溃。请问如何让按钮调用对应的登录提供商?
核心原因
你遇到的问题是:同类型的Provider会被后注册的实例覆盖。因为你用ChangeNotifierProvider<SignInProvider>注册了两个实例,Flutter无法通过父类类型区分它们,只会保留最后一个注册的FacebookSignInProvider。而直接获取子类类型崩溃,是因为你注册的是父类类型的Provider,而非子类类型。
解决方法1:注册具体子类Provider(推荐)
直接将两个登录提供商以具体子类类型注册,这样就能精准获取对应实例:
修改MultiProvider的注册代码:
MultiProvider( providers: [ // 注册Google登录提供商的具体类型 ChangeNotifierProvider<GoogleSignInProvider>(create: (context) => GoogleSignInProvider()), // 注册Facebook登录提供商的具体类型 ChangeNotifierProvider<FacebookSignInProvider>(create: (context) => FacebookSignInProvider()) ], child: Scaffold(/* ... 原有代码 ... */) )
然后在按钮的onPressed中获取对应子类的Provider:
Row(children: [ TextButton( onPressed: () { final googleProvider = Provider.of<GoogleSignInProvider>(context, listen: false); googleProvider.login(); }, child: const Text('Login with Google'), ), TextButton( onPressed: () { final fbProvider = Provider.of<FacebookSignInProvider>(context, listen: false); fbProvider.login(); }, child: const Text('Login with Facebook'), ), ])
解决方法2:用family参数区分同类型Provider(适合坚持用父类的场景)
如果一定要用SignInProvider作为泛型,可以通过family参数给每个实例加标识,实现区分:
修改注册代码:
MultiProvider( providers: [ ChangeNotifierProvider<SignInProvider>.family( create: (context, type) => GoogleSignInProvider(), param: 'google', ), ChangeNotifierProvider<SignInProvider>.family( create: (context, type) => FacebookSignInProvider(), param: 'facebook', ), ], child: Scaffold(/* ... 原有代码 ... */) )
获取时传入对应标识:
// Google登录按钮 final provider = Provider.of<SignInProvider>(context, listen: false, param: 'google'); provider.login(); // Facebook登录按钮 final provider = Provider.of<SignInProvider>(context, listen: false, param: 'facebook'); provider.login();
额外优化:统一监听登录状态
如果需要统一监听两个提供商的isSigningIn状态,可以自定义一个组合Provider:
class CombinedAuthProvider with ChangeNotifier { final GoogleSignInProvider googleProvider; final FacebookSignInProvider fbProvider; CombinedAuthProvider(this.googleProvider, this.fbProvider) { // 监听两个子Provider的状态变化 googleProvider.addListener(notifyListeners); fbProvider.addListener(notifyListeners); } // 只要有一个在登录,就显示加载状态 bool get isSigningIn => googleProvider.isSigningIn || fbProvider.isSigningIn; @override void dispose() { googleProvider.removeListener(notifyListeners); fbProvider.removeListener(notifyListeners); super.dispose(); } }
然后在MultiProvider中注册这个组合Provider:
MultiProvider( providers: [ ChangeNotifierProvider<GoogleSignInProvider>(create: (context) => GoogleSignInProvider()), ChangeNotifierProvider<FacebookSignInProvider>(create: (context) => FacebookSignInProvider()), // 注册组合Provider,依赖两个子Provider ChangeNotifierProxyProvider2<GoogleSignInProvider, FacebookSignInProvider, CombinedAuthProvider>( create: (context) => CombinedAuthProvider( Provider.of(context, listen: false), Provider.of(context, listen: false), ), update: (context, google, fb, previous) => CombinedAuthProvider(google, fb), ), ], child: Scaffold( body: StreamBuilder( stream: FirebaseAuth.instance.authStateChanges(), builder: (context, snapshot) { final combinedProvider = Provider.of<CombinedAuthProvider>(context); if (combinedProvider.isSigningIn) { return const Center(child: CircularProgressIndicator()); } // ... 原有UI逻辑 }, ), ), )
内容的提问来源于stack exchange,提问作者itsji10dra

