An authentication starter kit for flutter with a pluggable authentication provider design
MIT License
Note: I am no longer maintaining this project. I wrote this in the early stages of learning Flutter, and if given a second go at it would have made different architectural choices.
This auth starter project for flutter simplifies the addition of signing in and up of the user with the application. You could add full (albiet mocked) authentication flow within your flutter app within minutes.
This is an opinionated view of authentication within a flutter app. Specific use cases may not be covered or different to what is expected.
Pages
Functionality
ForgotPassword
the page will either be a full sized modal dialog for smaller screens, or a dialog with margin having a semi transpaetrnt border rendering the page underneath it.There are a number of ways to get started.
In my opinion it may be better to first fork into your own repository as a "copy", then use the pubspec reference method into your real project. This will give you the control of merging any changes back into your fork but also treating the package as a module into the application.
Simply fork the entire repository.
If you clone or fork the code first, then you could simply copy the core
folder into your project
To treat this like any other package you can reference the git repository directly
flutter_auth_starter:
git:
url: https://github.com/aqwert/flutter_auth_starter.git
Pros:
Cons:
Once your own custom project is created, at a minimum the following should be included in the pubspec.yaml
file:
<snip>
dependencies:
<snip>
scoped_model: "^0.2.0"
async_loader: "^0.1.1"
font_awesome_flutter: 6.0.0
url_launcher: "^3.0.0"
flutter_auth_base: "^0.1.5"
flutter_platform_widgets: "^0.2.0"
flutter_auth_starter:
git:
url: https://github.com/aqwert/flutter_auth_starter.git
<snip>
Note that the versions may be higher than listed. See 'Getting Started' above if the package
flutter_auth_starter
is to be included this way.
An example of the setup can be found inside main.dart
This has the following:
AppInfo is just a bunch of information relating to what the app is:
var appInfo = new AppInfo(
appName: 'Flutter Auth Starter',
appVersion: "0.0.1",
appIconPath: "assets/icons/appIcon.jpg",
avatarDefaultAppIconPath: "assets/icons/profileIcon.png",
applicationLegalese: '',
privacyPolicyUrl: "http://yourPrivacyPolicyUrl",
termsOfServiceUrl: "http://yourTermsOfServiceUrl");
appName
Used mainly for the About and Licence page to display the application name. This could be different to the app name set as the app title within the MaterialApp
.
appVersion
Version displayed on the About and Licence page.
appIconPath
The path to the default application icon that your application should set. (Be sure to set the path to the file in the assets section of the pubspec.yaml file).
avatarDefaultAppIconPath
The icon used when there is no icon/profile image for the user when viewing the Drawer or Profile page. If using the GravatarProvider you would probably never see this default image since the gravatar provider has a fallback image.
applicationLegalese
Optional legal text displayed on the About page.
privacyPolicyUrl
The external url that links to your application's privacy policy. Accessed from the Terms of Use page and the Drawer / Profile page.
termsOfServiceUrl
The external url that links to your applications terms of service. Accessed from the Terms of Use page and the Drawer / Profile page.
Note there is a Firebase implementation which can be used.
Out of the box this starter has mocked instances to get started.
var authService = auth.createMockedAuthService();
Where createMockedAuthService can be defined as
import 'package:flutter_auth_base/flutter_auth_base.dart';
import 'core/auth/mock/mock_service.dart';
import 'core/imageProviders/combined_image_provider.dart';
import 'core/imageProviders/gravatar_provider.dart';
import 'core/imageProviders/user_photo_url_provider.dart';
AuthService createMockedAuthService() {
var authService = new MockService();
authService.preAuthPhotoProvider = new GravatarProvider();
authService.postAuthPhotoProvider = new CombinedPhotoProvider()
..add(new AuthUserImageProvider(service: authService))
..add(new GravatarProvider(missingImageType: ImageType.MysteryMan));
return authService;
}
This sets the mock service with email and google signin providers (mocked of course) and uses The AuthUserImageProvider
which will attempt to download the photoUrl returned for the user. GravatarProvider
will check http://gravatar.com/ with the entered email address to pull down an image, or use the MysteryMan
if one does not exist.
This starter uses ScopedModel for holding the state of the user authentication, plus providing access to the authentication service. The use of ScopedModel
should not interfere with the use of any other state management packages (such as Flutter Redux) or Inhertited Widgets.
The ScopedModel
wraps the main application widget MaterialApp
so that it can be accessed by the routed pages.
var app = ScopedModel<AppModel>(
model: AppModel(appInfo: appInfo, authService: authService),
child: MaterialApp(
title: appInfo.appName,
navigatorKey: _navKey,
debugShowCheckedModeBanner: false,
theme: theme(),
home: Splash(),
routes: routing.buildRoutes(authService),
onGenerateRoute: routing.buildGenerator()));
AppModel
This is the main application model that holds authentication information such as the state of the currect user, notify of any changes to the user whether they login or logout and access to the main authentication service
home
This should always be Splash
(core/pages/splash_page.dart) as it handles the routing of the user to either signin/up if there is no authenticated user, or to the home page (defined in the routes) if the user is authenticated.
routes
At a bare minimum the only route that needs to defined is the home page (the first page navigated to after signin). All other routing within the starter pages are routed directly using MaterialPageRoute
. It maybe possible to swap to using fluro as the routing manager (not tested).
Example: routes.dart.
Map<String, WidgetBuilder> buildRoutes(AuthService authService) {
var routes = new Map<String, WidgetBuilder>();
routes['/home'] = (BuildContext context) => new HomePage();
return routes;
}
Remember to set the HomePage to your own specific home page.
In order to route to the Splash
page when the user logs out or to route to the HomePage
when the user is succesasfully authenticated, a listener needs to be placed on the AppModel
.
authService.authUserChanged.addListener(() {
app.model.refreshAuthUser().then((model) {
if (model.hasChanged) {
if (model.isValidUser) {
_navKey.currentState.pushNamedAndRemoveUntil('/home', (_) => false);
} else {
_navKey.currentState.pushNamedAndRemoveUntil('/', (_) => false);
}
}
});
});
Simply: runApp(app);
at the end
This starter project also has helper functions to render to a mobile or tablet layout.
For mobile, it is desireable in portrait mode to render a single column. When the device has a certain width (either when viewing in Portrait mode or on a tablet) it should show a 2 column layout.
Example
Looking at the Splash page the layout will change from a single column to a double column
@override
Widget build(BuildContext context) {
return ScopedModelDescendant<AppModel>(
builder: (_, child, model) {
Color bgColor = Colors.white;
Color fgColor = Colors.black87;
return TabletAwareScaffold(
mobileView: (_) =>
_buildMobileView(model.appInfo, _loader, fgColor),
tabletView: (_) =>
_buildTabletView(model.appInfo, _loader, bgColor, fgColor),
backgroundColor: bgColor);
},
);
}
The TabletAwareScaffold
will render the mobileView if the screen width is under 660, otherwise it will render the tabletView. Of course it is up to the implmentation of these WidgetBuilders
to return the appropriate view (i.e. 1 column vs 2 column layout).
Instead of using the showDialog
directly there is a utility wrapper that will either:
Example
Looking at the Sign in page it will display a modal dialog for the forgotten passowrd page.
Widget _forgotPasswordButton(AuthService authService) {
return authService.options.canSendForgotEmail
? Padding(
padding: const EdgeInsets.only(
bottom: 16.0, top: 16.0, left: 32.0, right: 32.0),
child: PlatformButton(
child: Text('Forgot password'),
onPressed: super.showProgress
? null
: () async => await openDialog(
context: context, builder: (_) => ForgotPassword())))
: Container();
}
Note the await openDialog(...)
call.
Please create an issue to provide feedback or an issue.