Pre flutter 2.0 solution.
After a few days of struggle on this subject and as there are few unanswered questions in comments I decide to post a complete, dough long , answer to help people just starting out in flutter as I am.
This is how I implement the two different packages.
As I use flutter_bloc
for state management I basically had to make the repository platform dependent as I did for user location.
To achieve it I use a stub/abstract class/ web implementation/ device implementation pattern. So in my bloc's repository I just call the abstract class methods that will map to the proper platform implementation class using the appropriate package. It seems a bit messy at first but it's quite easy once grasped the concept but Thera are a couple of trap one could fall into when starting out with the pattern.
For the device implementation flutter_auth
package is used while for web implementation flutter
package is used instead and to make it easy I made a singleton. Now the singleton returns the initialized firebase App
that give you access to all services.. auth()
, database()
, firestore()`, remoteconfig()...
Wherever you need to access any firebase service just instantiate Firebase and use the services.
App firebase = FirebaseWeb.instance.app;
...
await firebase.auth().signInWithCredential(credential);
return firebase.auth().currentUser;
So here is all the code I use for authorization, but is easy to adapt for different firebase services:
Stub:
this is just to hold a (getter) method that gets returned in the abstract class factory method (I call it switcher), and to allow for conditional import in abstract class to the proper implementation class.
import 'package:firebaseblocwebstub/platform_user_repository/platform_user_repository_switcher.dart';
UserRepositorySwitcher getUserRepository() {
print('user_repository_stub called');
}
Abstract class ( the switcher ) :
Here you import the stub to be able to conditionally import the proper implementation class. The stub (getter) method in returned in the class factory method.
In this class you need to declare all the methods you need to use. Here returns are dynamic as the package specific returns will be in the platform implementation classes.
Watch out for typos and proper file routes in the conditional import as there is no automatic check..costed me many ours to find it out hahah..
import 'package:firebaseblocwebstub/platform_user_repository/platform_user_repository_stub.dart'
if (dart.library.io) 'package:firebaseblocwebstub/platform_user_repository/platform_user_repository_device.dart'
if (dart.library.js) 'package:firebaseblocwebstub/platform_user_repository/platform_user_repository_web.dart';
abstract class UserRepositorySwitcher {
Future<dynamic> signInWithGoogle() async {
print('UserREpository switcher signInWithGoogle() called');
}
Future<void> signInWithCredential({String email, String password}) {}
Future<void> signUp({String email, String password}) {}
Future<void> signOut() async {}
Future<bool> isSignedIn() async {}
Future<dynamic> getUser() async {}
factory UserRepositorySwitcher() => getUserRepository();
}
Device implementation class:
Has to implement abstract class to get hold of and implement it's methods with specific (flutter_auth
in this case) methods and types. Here you also have to declare, outside the class scope, the same method in the stub, that returns device implementation class (see bottom code).
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebaseblocwebstub/authentication_bloc/app_user.dart';
import 'package:firebaseblocwebstub/platform_user_repository/platform_user_repository_switcher.dart';
import 'package:google_sign_in/google_sign_in.dart';
class UserRepositoryDevice implements UserRepositorySwitcher {
final FirebaseAuth _firebaseAuth;
final GoogleSignIn _googleSignIn;
UserRepositoryDevice({FirebaseAuth firebaseAuth, GoogleSignIn googleSignIn})
: _firebaseAuth = firebaseAuth ?? FirebaseAuth.instance,
_googleSignIn = googleSignIn ?? GoogleSignIn();
Future<FirebaseUser> signInWithGoogle() async {
print('signInWithGoogle() from device started');
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
print('GoogleUser is : $googleUser');
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final AuthCredential credential = await GoogleAuthProvider.getCredential(
idToken: googleAuth.idToken, accessToken: googleAuth.accessToken);
await _firebaseAuth.signInWithCredential(credential);
return _firebaseAuth.currentUser();
}
Future<void> signInWithCredential({String email, String password}) {
return _firebaseAuth.signInWithEmailAndPassword(
email: email, password: password);
}
Future<void> signUp({String email, String password}) {
return _firebaseAuth.createUserWithEmailAndPassword(
email: email, password: password);
}
Future<void> signOut() async {
return Future.wait([
_firebaseAuth.signOut(),
_googleSignIn.signOut(),
]);
}
Future<bool> isSignedIn() async {
final currentUser = _firebaseAuth.currentUser();
return currentUser != null;
}
Future<FixitUser> getUser() async {
String displayName = (await _firebaseAuth.currentUser()).displayName;
String email = (await _firebaseAuth.currentUser()).email;
String uid = (await _firebaseAuth.currentUser()).uid;
String photoUrl = (await _firebaseAuth.currentUser()).photoUrl;
String phoneNumber = (await _firebaseAuth.currentUser()).phoneNumber;
FixitUser user = FixitUser(
// fixitUser
name: displayName ?? '',
email: email,
phoneNumber: phoneNumber ?? '',
uid: uid,
photoUrl: photoUrl ?? '');
return (user);
}
}
UserRepositorySwitcher getUserRepository() => UserRepositoryDevice();
Now finally for web..
firebase singleton:
To use firebase
package in an easy way I decided to make it a singleton.
Here you can either return a Future<App>
instance but then you have to .then
everything..or return the App
directly..I chosen this way..cleaner and quicker implementation.
This way you don't have to initialize firebase in your index.html
file or you'll get an error as it's already initialized. Initialize firebase here also makes your keys not exposed..
import 'dart:async';
import 'package:firebase/firebase.dart';
class FirebaseWeb {
// Singleton instance
static final FirebaseWeb _singleton = FirebaseWeb._();
// Singleton accessor
static FirebaseWeb get instance => _singleton;
// A private constructor. Allows us to create instances of AppDatabase
// only from within the AppDatabase class itself.
FirebaseWeb._();
static App _app;
// Database object accessor
App get app {
print('firebase get app called ');
print('_app is $_app');
if (_app != null) {
return _app;
} else {
print('initialize app');
_app = initializeApp(
apiKey: "your key",
authDomain: "your key",
databaseURL: "your key",
projectId: "your key",
storageBucket: "your key",
messagingSenderId: "your key",
appId: "your key");
print('initialized app is $_app'); // await _initializeApp();
return _app;
}
}
}
Web implementation:
Here you just instantiate Firebase using the singleton, and implement abstract class methods, use its services and methods..I use auth()
here.
You can see (commented out parts) how much more verbose is implementation if return a Future<App>
in the singleton..
Here the stub getter method will return this class ..(check bottom)
import 'dart:async';
import 'package:firebase/firebase.dart';
import 'package:firebaseblocwebstub/authentication_bloc/app_user.dart';
import 'package:firebaseblocwebstub/firebase_singleton.dart';
import 'package:firebaseblocwebstub/platform_user_repository/platform_user_repository_switcher.dart';
import 'package:google_sign_in/google_sign_in.dart';
class UserRepositoryWeb implements UserRepositorySwitcher {
App firebase = FirebaseWeb.instance.app;
final GoogleSignIn _googleSignIn = GoogleSignIn();
Future<User> signInWithGoogle() async {
print('signInWithGoogle() started');
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
print('GoogleUser is : $googleUser');
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final OAuthCredential credential = await GoogleAuthProvider.credential(
googleAuth.idToken, googleAuth.accessToken);
// singleton retunrning Future<App>
// await firebase.then((firebase) {
// firebase.auth().signInWithCredential(credential);
// return;
// });
// return firebase.then((firebase) {
// return firebase.auth().currentUser;
// });
await firebase.auth().signInWithCredential(credential);
return firebase.auth().currentUser;
}
Future<void> signInWithCredential({String email, String password}) {
return firebase.auth().signInWithEmailAndPassword(email, password);
// singleton retunrning Future<App>
// return firebase.then((firebase) {
// return firebase.auth().signInWithEmailAndPassword(email, password);
// });
}
Future<void> signUp({String email, String password}) {
return firebase.auth().createUserWithEmailAndPassword(email, password);
// singleton retunrning Future<App>
// return firebase.then((firebase) {
// return firebase.auth().createUserWithEmailAndPassword(email, password);
// });
}
Future<void> signOut() async {
return Future.wait([
firebase.auth().signOut(),
// singleton retunrning Future<App>
// firebase.then((firebase) {
// firebase.auth().signOut();
// }),
_googleSignIn.signOut(),
]);
}
Future<bool> isSignedIn() async {
final currentUser = firebase.auth().currentUser;
return currentUser != null;
// singleton retunrning Future<App>
// User firebaseUser = firebase.then((firebase) {
// return firebase.auth().currentUser;
// }) as User;
// return firebaseUser != null;
}
Future<FixitUser> getUser() async {
// singleton retunrning Future<App>
// User firebaseUser = firebase.then((firebase) {
// return firebase.auth().currentUser;
// }) as User;
//
// FixitUser user = FixitUser(
// name: firebaseUser.displayName ?? '',
// email: firebaseUser.email,
// phoneNumber: firebaseUser.phoneNumber ?? '',
// uid: firebaseUser.uid,
// photoUrl: firebaseUser.photoURL ?? '');
// return (user);
// }
String displayName = (firebase.auth().currentUser).displayName;
String email = (firebase.auth().currentUser).email;
String uid = (firebase.auth().currentUser).uid;
String photoUrl = (firebase.auth().currentUser).photoURL;
String phoneNumber = (firebase.auth().currentUser).phoneNumber;
FixitUser user = FixitUser(
name: displayName ?? '',
email: email,
phoneNumber: phoneNumber ?? '',
uid: uid,
photoUrl: photoUrl ?? '');
return (user);
}
}
UserRepositorySwitcher getUserRepository() => UserRepositoryWeb();