authenticator 0.1.2

  • Readme
  • Changelog
  • Example
  • Installing
  • 65

Code coverage Pipeline stateus

License

Flutter package: authenticator #

A firebase authentication package that includes mocked platform channels so login flows can be tested with flutter test.

Short and sweet (TLDR;) #

var auth = Authenticator();
var userId = await auth.signInWithGoogle();
print(userId);
auth.signOut();

Full documentation #

Documentation generated by dartdoc can be found here.

Getting started #

First, the if the app will be using Authenticator, it also needs to have plugins for:

Follow the instructions for each of those plugins so the platform the app is targeting can authenticate.

Setting those up will likely be the most time-consuming part of getting Authentication working, as the app will need to be registered with Firebase and Facebook.

Add authenticator to your pubspec.yaml file as a dependency. For example, to use authenticator from the master branch in this repo, add authenticator to the dependencies section, like so:

environment:
  sdk: ">=2.1.0 <3.0.0"

dependencies:
  flutter:
    sdk: flutter

  firebase_auth:
  google_sign_in:
  flutter_facebook_login: ^3.0.0

  authenticator: ^0.1.2

Using Authenticator #

The example folder contains a bare-bones app that includes Google and Facebook sign in code. The example will not run on a device platform, since it has no credentials from Google or Facebook, which are required to build (signing keys, google-services.json, Facebook API key, etc...). The example DOES run under the flutter test framework, so to see it in action, attach a debugger and run the test.

As a Provider #

Add a single parameter to your application widget, taking an Authenticator as an argument. This is to allow both device platforms and unit tests to supply an appropriate Authenticator.

class MyApp extends StatelessWidget {
  // This widget is the root of your application
  final Authenticator auth;
  MyApp(Authenticator authenticator) : auth = authenticator;

  @override
  Widget build(BuildContext context) {
    return ListenableProvider<Authenticator>.value(
        value: auth,
        child: MaterialApp(
          title: 'Authenticator Demo',
          theme: ThemeData(
            primarySwatch: Colors.blue,
          ),
          home: Root(),
        ));
  }
}

Notice the ListenableProvider<Authenticator> in the build override. When an authentication event changes the authentication state of the user and application, the widget tree will be updated.

In the application's main(), pass a default Authenticator to the application widget.

void main() {
  var auth = Authenticator();
  runApp(MyApp(auth));
}

In the application widget tests, a different constructor can used to construct an Authenticator that mocks platform channels and allows tests to be run.

void main() {
  testWidgets('Can sign in and sign out', (WidgetTester tester) async {
    var auth = Authenticator.createMocked();
    // Build our app and trigger a frame.
    await tester.pumpWidget(MyApp(auth));

    /// ...

Most applications will decide whether to direct a user to a signin page or to the application home page based on their authentication status.

class Root extends StatelessWidget {
  Root() : super(key: ValueKey('Root Page'));

  @override
  Widget build(BuildContext context) {
    final auth = Provider.of<Authenticator>(context);
    return ListenableProvider<Authenticator>.value(
        value: auth,
        child: Consumer(builder: (context, Authenticator auth, _) {
          switch (auth.authState) {
            case AuthState.AUTHENTICATED:
              return Home();
              break;
            default:
              return Login();
              break;
          }
        }));
  }
}

And of course, the login page will have buttons asking the user to choose sign in.

class Login extends StatelessWidget {
  Login() : super(key: ValueKey('Login Page'));

  @override
  Widget build(BuildContext context) {
    final auth = Provider.of<Authenticator>(context);
    return ListenableProvider<Authenticator>.value(
        value: auth,
        child: Consumer(builder: (context, Authenticator auth, _) {
          return Column(
              mainAxisAlignment: MainAxisAlignment.center,
              mainAxisSize: MainAxisSize.max,
              children: <Widget>[
                Text("Sign in with ..."),
                FlatButton(
                    key: ValueKey('signInWithGoogle button'),
                    child: Text('Google'),
                    onPressed: () {
                      auth.signInWithGoogle();
                    }),
                FlatButton(
                    key: ValueKey('signInWithFacebook button'),
                    child: Text("Facebook"),
                    onPressed: () {
                      auth.signInWithFacebook();
                    })
              ]);
        }));
  }
}

They may also have a sign in with email option that presents a page with text fields for an email address and password. The onPressed value for that page would simply call:

onPressed: () { auth.signInWithEmailAndPassword(email, password); }

The application could also provide new user registration with Firebase using an email address and password, or to request an password reset email to be sent.

See:

  • Future<String> createUserWithEmailAndPassword(String email, String password) async
  • Future<void> sendPasswordResetEmail(String emailAddress) async

Lastly, there should be an option to sign out as well from the home page.

class Home extends StatelessWidget {
  Home() : super(key: ValueKey('Home Page'));

  @override
  Widget build(BuildContext context) {
    final auth = Provider.of<Authenticator>(context);
    return ListenableProvider<Authenticator>.value(
        value: auth,
        child: Consumer(builder: (context, Authenticator auth, _) {
          return FlatButton(
              key: ValueKey('signOut button'),
              child: Text("Sign out"),
              onPressed: () {
                auth.signOut();
              });
        }));
  }
}

Other goodies #

Authenticator exposes a Firebase user, which in turn provides a lot of useful data, including things like a photoUrl to display, email address, and a UID that is useful in conjunction with Firebase storage to uniquily identify users in documents or paths.

If your IDE supports showing documentation for exported member data and methods (as Visual Studio Code does,for example), explore a bit, hover over methods or members that seem interesting. They include additional information and examples.

Advanced testing #

The behavior of each MockXxxChannel can be modified by setting static member values for the duration of whichever tests need to execute.

Overriding responses #

The responses sent from the Mocked channels included with Authenticator can be customized to trigger specific failures to increase and improve code coverage.

The test suite included with Authenticator has examples showing how to accomplish this.

  test('Facebook authentication fail no resut', () async {
    MockFacebookLoginChannel.loginResponse['status'] = 'error';
    var uid = await auth.signInWithFacebook();
    expect(uid, isNull,
        reason:
            'signInWithFacebook should not return a result when there is an error');
    expect(auth.authState, AuthState.UNAUTHENTICATED,
        reason: 'User is not in an UNAUTHENTICATED state after signing out!');
    await auth.signOut();
    MockFacebookLoginChannel.resetLoginResponse();
  });

Note: Be sure to call resetLoginResponse() unless it should remain set for the next call to the mocked platform channel.

Forcing exceptions #

The mocked channels can be forced to throw on any operation by setting throwOnEveryChannelMessage to the desired exception on the channel. Just be sure to set it to null when the tests should no longer trigger exceptions.

For example:

  test('Always Throw Up for Google', () async {
    MockGoogleSignInChannel.throwOnEveryChannelMessage = PlatformException(code: 'ALWAYS_THROW_UP');
    var exceptionCaught = false;
    try {
      await auth.signInWithGoogle();
    } on PlatformException catch (e) {
      MockGoogleSignInChannel.throwOnEveryChannelMessage = null;
      expect(e.code, 'ALWAYS_THROW_UP',
          reason:
              'Expected the Platform exception code to send ALWAYS_THROW_UP');
      exceptionCaught = true;
    } finally {
      MockGoogleSignInChannel.throwOnEveryChannelMessage = null;
    }
    expect(exceptionCaught, true,
        reason: 'An exception should have been thrown, but was not');
    MockGoogleSignInChannel.throwOnEveryChannelMessage = null;
  });

This could easily be adapted to a widget test suite that will take a user back to the application splash or login screen if an unexpected exception is thrown from a plugin. Just set the MockXxxChannel.throwOnEveryChannelMessage to null once the app should no longer throw for testing purposes.

Known issues #

The mocked platform channels do not handle every possible method call that may be sent by some providers. This is either because the providers have no publicly exposed interface to trigger the calls, or more likely, are not functionality that anyone is interested in using yet.

If your application does need to support these, please do open an issue with as much information as possible describing the scenario so the support can be added. Better yet, submit a pull request!

To track down unhandled messages within a test framework, simply set throwOnUnhandledChannelMessages on whichever mock channel is of interest. For example:

    MockFirebaseAuthChannel.throwOnUnhandledChannelMessages = true;

If an authentication provider invokes a method on a platform channel that is unhandled, it will throw in the test. If you have such a test handy and would like to have support for that message in Authenticator, send the test along when opening a new issue.

Changelog #

[0.1.2] - 2019-10-21

  • Some pedantic documentation corrections to avoid confision when using the package.

[0.1.1] - 2019-10-21

  • Can provide custom exceptions to force platform channel mocks to throw.

  • Tests can trigger platform channel mocks to always throw on any call to the platform. Useful for forcing failure cases in application code during unit tests.

  • Example tests and application added to the package and repository.

  • Generate dartdoc documentation for the package.

[0.0.1] - 2019-10-15

  • Initial public release of Playscale's Authenticator package.

example/README.md

Example #

This is sample code that will not build for a device platform as it requires specific application integration with Facebook and Firebase.

It needs API keys and google-services.json with a registered application.

The sample code does build with the provided widget_test.dart and will execute with flutter test.

To see it working on a device, the project needs to be modified with valid credentials from Google and Facebook.

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  authenticator: ^0.1.2

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:authenticator/authenticator.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
38
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
80
Overall:
Weighted score of the above. [more]
65
Learn more about scoring.

We analyzed this package on Dec 4, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.6.1
  • pana: 0.12.21
  • Flutter: 1.9.1+hotfix.6

Platforms

Detected platforms: Flutter

References Flutter, and has no conflicting libraries.

Maintenance issues and suggestions

Support latest dependencies. (-20 points)

The version constraint in pubspec.yaml does not support the latest published versions for 2 dependencies (firebase_auth, provider).

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
firebase_auth ^0.14.0+5 0.14.0+9 0.15.1
flutter 0.0.0
flutter_facebook_login ^3.0.0 3.0.0
google_sign_in ^4.0.7 4.0.14
provider ^3.1.0 3.2.0 4.0.0-dev
Transitive dependencies
collection 1.14.11 1.14.12
firebase_core 0.4.2+1
firebase_core_platform_interface 1.0.0
google_sign_in_platform_interface 1.0.0
meta 1.1.7 1.1.8
quiver_hashcode 2.0.0
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test