2

I am trying to create a single interface for Google and Facebook login, my SignInProvider looks like:

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 { ... }

My Landing page:

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'),
                ),
              ],
            );
          }
        }),
      ),
    );
  }
}

In this UI, clicking on Login with Google and Login with Facebook both are referring to FacebookSignInProvider, since that is provided last in the list of providers. How can I make it act on respective provider?

When I change the onPressed definition to Provider.of<GoogleSignInProvider> or Provider.of<FacebookSignInProvider> it crashes.

General Grievance
  • 4,555
  • 31
  • 31
  • 45
itsji10dra
  • 4,603
  • 3
  • 39
  • 59

3 Answers3

1

Try this solution

Create a single generic SignInProvider with ChangeNotifier along with SignInType enum

enum SignInType {
  none,
  facebook,
  google
}

class SignInProvider with ChangeNotifier {
  bool _isSigningIn = false;
  SignInType _signInType = SignInType.none;

  set signInType(SignInType signType) {
    _signInType = signType;
  }

  SignInType get signInType {
    return _signInType;
  }


  set signingIn(bool signingIn) {
    _isSigningIn = signingIn;
  }

  bool get signingIn {
    return _isSigningIn;
  }

  void login() {
    switch (signInType) {
      case SignInType.none:
      case SignInType.facebook:
      case SignInType.google:
    }
  }

  void logout() {}
}

Create _HomeState class as follows

class _HomeState extends State<Home> {

  @override
  Widget build(BuildContext context) {
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<SignInProvider>(create: (context) => SignInProvider()),
      ],
      child: Scaffold(
        body: StreamBuilder(
            stream: FirebaseAuth.instance.authStateChanges(),
            builder: (context, snapshot) {
              final provider =
              Provider.of<SignInProvider>(context, listen: true);
              if (provider._isSigningIn) {
                return const Text("loader");
              } else if (snapshot.hasData) {
                return const Text("loader");
              } else {
                // Give option to login via Google and Facebook
                return Row(children: [
                  TextButton(
                    onPressed: () {
                      final provider = Provider.of<SignInProvider>(
                        context,
                        listen: false,
                      );
                      provider.signInType = SignInType.google;
                      provider.login();
                    },
                    child: const Text('Login with Google'),
                  ),
                  TextButton(
                    onPressed: () {
                      final provider = Provider.of<SignInProvider>(
                        context,
                        listen: false,
                      );
                      provider.signInType = SignInType.facebook;
                      provider.login();
                    },
                    child: const Text('Login with Facebook'),
                  ),
                ],
                );
              }
            }),
      ),
    );
  }
}

This way you will have a more generic provider that can handle multiple type of login providers.
You can also create separate abstract class following the above pattern.
Hope it helps. Thanks :)

Mearaj
  • 1,828
  • 11
  • 14
0

Replace ChangeNotifierProvider<SignInProvider> with ChangeNotifierProvider< FacebookSignInProvider> and ChangeNotifierProvider<GoogleSignInProvider>

Rahul
  • 3,529
  • 1
  • 5
  • 19
  • In my use-case how will I get to know `provider.isSigningIn` is true or false? – itsji10dra Jun 08 '23 at 15:07
  • you have two isSigningIn. So you will have to listen to both and make decision. – Rahul Jun 08 '23 at 17:13
  • That is precisely my point. How can I make it generic is the question? Because the user will be choosing one of these. I want to know which one is being used. – itsji10dra Jun 08 '23 at 22:34
0

Link to the output

  • In your main() function
void main() {
  runApp(
    MultiProvider(
      providers: [
        ChangeNotifierProvider<FacebookSignInProvider>.value(
          value: FacebookSignInProvider(),
        ),
        ChangeNotifierProvider<GoogleSignInProvider>.value(
          value: GoogleSignInProvider(),
        ),
      ],
     child: const MyApp()
    )
  );
}
  • Your buttons
return Row(
    children: [
        TextButton(
            onPressed: () {
                final provider = Provider.of<GoogleSignInProvider>(context, listen: false);
                provider.login();
            },
            child: const Text('Login with Google'),
         ),
         TextButton(
             onPressed: () {
                 final provider = Provider.of<FacebookSignInProvider>(context, listen: false);
                 provider.login();
             },
             child: const Text('Login with Facebook'),
         ),
     ],
);
Sowat Kheang
  • 668
  • 2
  • 4
  • 12
  • What about other 2 methods of abstract class? `provider.isSigningIn` and `provider.logOut`? How to access them later? Or as mentioned in my example, when to show loader? – itsji10dra Jun 08 '23 at 15:06
  • I am sure that FirebaseAuth.instance.authStateChanges() will return User?. So, inside StreamBuilder method, you may check if (snapshot.hasData) { User user = snapshot.data! as User; print(user.providerData[0].providerId); // it may provide something like google.com or facebook.com and from there you may have your logic to check which provider you gonna use }. After you know which provider you gonna user, you can set isSigningIn or logout. What do you think? – Sowat Kheang Jun 09 '23 at 03:24