Reputation: 53327
In my app, I have a drawer with a UserAccountsDrawerHeader, which I feed its properties by simply getting the x property from FirebaseAuth.instance.currentUser.x
In the latest firebase_auth 0.2.0 version , where currentUser() is async.
I have been trying for several hours to store the information of the currently logged user and have not yet reached the correct way to do this.
I understand that I can access them by something like the following:
Future<String> _getCurrentUserName() async {
FirebaseUser user = await FirebaseAuth.instance.currentUser();
return user.displayName;
}
...
new UserAccountsDrawerHeader(accountName: new Text(_getCurrentUserName()))
I understand that these code snippets will give type mismatch, but I am just trying to illustrate what I am trying to do.
What am I missing exactly that is preventing me from reaching a solution?
Update
class _MyTabsState extends State<MyTabs> with TickerProviderStateMixin {
TabController controller;
Pages _page;
String _currentUserName;
String _currentUserEmail;
String _currentUserPhoto;
@override
void initState() {
super.initState();
_states();
controller = new TabController(length: 5, vsync: this);
controller.addListener(_select);
_page = pages[0];
}
My method
I just coupled the auth state with my previously implemented TabBar state
_states() async{
var user = await FirebaseAuth.instance.currentUser();
var name = user.displayName;
var email = user.email;
var photoUrl = user.photoUrl;
setState(() {
this._currentUserName=name;
this._currentUserEmail=email;
this._currentUserPhoto=photoUrl;
_page = pages[controller.index];
});
}
My Drawer
drawer: new Drawer(
child: new ListView(
children: <Widget>[
new UserAccountsDrawerHeader(accountName: new Text(_currentUserName) ,
accountEmail: new Text (_currentUserEmail),
currentAccountPicture: new CircleAvatar(
backgroundImage: new NetworkImage(_currentUserPhoto),
),
Here is the exception I get from the debug console
I/flutter (14926): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (14926): The following assertion was thrown building MyTabs(dirty, state: _MyTabsState#f49aa(tickers:
I/flutter (14926): tracking 1 ticker)):
I/flutter (14926): 'package:flutter/src/widgets/text.dart': Failed assertion: line 207 pos 15: 'data != null': is not
I/flutter (14926): true.
I/flutter (14926): Either the assertion indicates an error in the framework itself, or we should provide substantially
Update 2:
This is how I modified the google sign in function from the firebase examples:
Future <FirebaseUser> _testSignInWithGoogle() async {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
//checking if there is a current user
var check = await FirebaseAuth.instance.currentUser();
if (check!=null){
final FirebaseUser user = check;
return user;
}
else{
final FirebaseUser user = await _auth.signInWithGoogle(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
assert(user.email != null);
assert(user.displayName != null);
assert(!user.isAnonymous);
assert(await user.getToken() != null);
return user;
}
}
Update 3:
My main function
void main() {
runApp(
new MaterialApp(
home: new SignIn(),
routes: <String, WidgetBuilder>{
"/SignUp":(BuildContext context)=> new SignUp(),
"/Login": (BuildContext context)=> new SignIn(),
"/MyTabs": (BuildContext context)=> new MyTabs()},
));
}
And then my SignIn contains a google button that when pressed:
onPressed: () { _testSignInWithGoogle(). //async returns FirebaseUser
whenComplete(()=>Navigator.of(context).pushNamed("/MyTabs")
);
}
and the Drawer from update 1 is included within MyTabs build.
Upvotes: 3
Views: 1923
Reputation: 277067
"How to properly implement authentification" ? You should definitely not do it this way. You'll have tons of code duplication.
The most ideal way to organise layers such as Authentification is like this :
runApp(new Configuration.fromFile("confs.json",
child: new Authentification(
child: new MaterialApp(
home: new Column(
children: <Widget>[
new Text("Hello"),
new AuthentifiedBuilder(
inRoles: [UserRole.admin],
builder: (context, user) {
return new Text(user.name);
}
),
],
),
),
),
));
And then, when you need a configuration or the current user inside a widget, you'd do this :
@override
Widget build(BuildContext context) {
var user = Authentification.of(context).user;
var host = Configuration.of(context).host;
// do stuff with host and the user
return new Container();
}
There are so many advantages about doing this, that there's no reason not to do it. Such as "Code once, use everywhere". Or the ability to have a generic value and override it for a specific widget. You'll realise that a lot of Flutter widgets are following this idea. Such as Navigator, Scaffold, Theme, ...
But "How to do this ??"
It's all thanks to the BuildContext context
parameter. Which provides a few helpers to do it.
For exemple, the code of Authentification.of(context)
would be the following :
class Authentification extends StatefulWidget {
final Widget child;
static AuthentificationData of(BuildContext context) {
final AuthentificationData auth = context.inheritFromWidgetOfExactType(AuthentificationData);
assert(auth != null);
return auth;
}
Authentification({this.child});
@override
AuthentificationState createState() => new AuthentificationState();
}
Upvotes: 1
Reputation: 277067
There are several possibilities.
First : Use a stateful widget Override the initState method like this :
class Test extends StatefulWidget {
@override
_TestState createState() => new _TestState();
}
class _TestState extends State<Test> {
String _currentUserName;
@override
initState() {
super.initState();
doAsyncStuff();
}
doAsyncStuff() async {
var name = await _getCurrentUserName();
setState(() {
this._currentUserName = name;
});
}
@override
Widget build(BuildContext context) {
if (_currentUserName == null)
return new Container();
return new Text(_currentUserName);
}
}
Second : Use the FutureBuilder widget Basically, it's a wrapper for those who don't want to use a stateful widget. It does the same in the end. But you won't be able to reuse your future somewhere else.
class Test extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new FutureBuilder(
future: _getCurrentUserName(),
builder: (context, AsyncSnapshot<int> snapshot) {
if (snapshot.hasData)
return new Text(snapshot.data.toString());
else
return new Container();
},
);
}
}
Explanation : Your getCurrentUserName is asynchronous. You can't just directly mix it with other synchronous functions. Asynchronous functions are quite useful. But if you want to use them, just remember two things :
Inside another async function, you can var x = await myFuture
, which will wait until myFuture
finish to get it's result.
But you can't use await
inside a sync function.
Instead, you can use
myFuture.then(myFunction)
or myFuture.whenComplete(myFunction)
. myFunction
will be called when the future is finished. And they both .then
and .whenComplete
will pass the result of your future as parameter to your myFunction
.
Upvotes: 2