Are there negative consequences for calling Firebase initializeApp() twice? - firebase

While Firebase.initializeApp() only needs to be called once, are there negative consequences for calling it twice?
Background: I'm troubleshooting a [core/no-app] No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp() error and temporarily fixed it by adding await Firebase.initializeApp(); in void main() async in addition to my pre-existing final Future<FirebaseApp> _fbApp = Firebase.initializeApp();
Everything seems to work okay now. I intend to fix it, but if calling Firebase.initializeApp() twice isn't hurting anything, I can stick with my immediate priorities and move forward.
Here's the relevant block of code:
void main() async {
WidgetsFlutterBinding
.ensureInitialized(); // added per https://stackoverflow.com/questions/57689492/flutter-unhandled-exception-servicesbinding-defaultbinarymessenger-was-accesse
await Firebase
.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
final Future<FirebaseApp> _fbApp = Firebase
.initializeApp(); // changed from "async { await Firebase.initializeApp();" per official "Getting started with Firebase on Flutter - Firecasts" video at https://youtu.be/EXp0gq9kGxI?t=920
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return StreamProvider<Userrr>.value(
value: AuthService().user,
// above specifies what stream we want to listen to and what data we expect to get back
child: MaterialApp(
Thanks!
UPDATE: I tried all the good advice below and nothing seemed to work. I think my code, an exact duplication from two tutorials (one for getting started with Firebase and another for Firebase auth 101) had one or more gaps because of package updates or other incompatibilities.
I went back to basics and wrote-out by hand and re-implemented every step for installing and setting-up Firebase Core from the official "FlutterFire Overview."
I re-read all the documentation, as suggested below.
I updated all packages, including firebase_auth: “^0.20.0" to firebase_auth: “^0.20.0+1" (the +1 change is to “FIX: package compatibility,” per the changelog).
And then finally, I created a backup of main.dart as old_main.dart, then copy-pasted the exact "Initializing FlutterFire" FurtureBuilder code, then carefully replaced each part of that generic code with my own. Here is each item I replaced:
// replaced "_initialization" with "_fbApp"
// replaced if (snapshot.hasError) ... "return SomethingWentWrong();" with the response from below
// replaced "return Loading();" with CircularProgressIndicator form below
// changed return MyAwesomeApp(); to return MyApp();
// changed "class App extends StatelessWidget" to "class MyApp extends StatelessWidget
// replaced "MyAwesomeApp();" from "if (snapshot.connectionState == ConnectionState.done) { return MyAwesomeApp();" with all the "StreamProvider<Userrr>.value(..." code EXCEPT changed home to "home: Wrapper(),"
It may seem elementary, but for a novice like myself, it was the only way forward. Thankfully it worked!
Here's the full working code excerpt:
void main() {
WidgetsFlutterBinding
.ensureInitialized(); // added by mgav, per https://stackoverflow.com/questions/57689492/flutter-unhandled-exception-servicesbinding-defaultbinarymessenger-was-accesse
// await Firebase.initializeApp(); // added by mgav to test, KNOWING the Firebase is already initialized as a future below in line 25. Was trying to test (temp?) fix for error: “[core/no-app] No Firebase App '[DEFAULT]' has been created - call Firebase.initializeApp() The relevant error-causing widget was: MyApp file:///Users/mgav/AndroidStudioProjects/brand_new_flutter_app/lib/main.dart:21:10”
runApp(MyApp());
}
// BEGIN Firebase FutureBuilder code pasted from docs at https://firebase.flutter.dev/docs/overview/#initializing-flutterfire (replaces next section of commented-out code)
class MyApp extends StatelessWidget {
// Create the initialization Future outside of `build`:
final Future<FirebaseApp> _fbApp = Firebase.initializeApp();
#override
Widget build(BuildContext context) {
return FutureBuilder(
// Initialize FlutterFire:
future: _fbApp,
builder: (context, snapshot) {
// Check for errors
if (snapshot.hasError) {
print('You have an error! ${snapshot.error.toString()}');
return Text('Something went wrong main.dart around line 48');
}
// Once complete, show your application
if (snapshot.connectionState == ConnectionState.done) {
return StreamProvider<Userrr>.value(
value: AuthService().user,
// above specifies what stream we want to listen to and what data we expect to get back
child: MaterialApp(
title: 'Real Revs and Q&A',
theme: ThemeData(
primarySwatch: Colors.blueGrey,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
routes: {
// '/welcome': (context) => WelcomeScreen(),
'/cleanwritereview': (context) => CleanWriteReviewScreen(),
'/registrationscreen': (context) => RegistrationScreen(),
'/loginscreen': (context) => LoginScreen(),
'/viewreviewsscreen': (context) => ViewReviewsScreen(),
'/homescreen': (context) => Home(),
},
home: Wrapper(),
),
);
}
// Otherwise, show something whilst waiting for initialization to complete
return Center(
child: CircularProgressIndicator(),
);
},
);
}
}

You'll get an error message if you call initializeApp() twice for the same FirebaseApp.
In your case, you can get the app that you've already created with:
final FirebaseApp _fbApp = Firebase.app();
Also see the documentation on FlutterFire, specifically initializing and using FirebaseApp.

To initialise firebase you either do:
main(){
await Firebase.initializeApp(); // don't run the app until firebase is initialized
runApp(MyApp());
}
Or use a FutureBuilder which ensure the future is resolved before running the code inside the builder function.
#override
Widget build(BuildContext context) {
final _fbApp = Firebase.initializeApp();
return FutureBuilder(
future: _fbApp,
builder: (context, snapshot) { // waits until _fbApp is resolved to execute.
....
});
}
You get an error because you don't await _fbApp future.
In your code there is no guarantee AuthService().user is executed after initializeApp has finished. To garantee this you have to wait until initializeApp() is resolved by using await, then or a FutureBuilder.

Add a try catch to understand why the first call in initializeApp is not working.
Firebase initialises its core services only once. there is exactly one FirebaseApp instance by name. When you don't specify the name '[DEFAULT]' is used.
Try doing this:
final app = await Firebase.initializeApp();
final app2 = await Firebase.initializeApp();
print(app == app2); // is true
Please provide more details on your setup:
firebase_auth, firebase_core versions,
Execution plateform (Android, ios or web).
In the last version of fire_auth we use:
FirebaseAuth.instance.authStateChanges // stream to subscribe to the user's current authentication state.

Related

Flutter app error message: No Firebase App

I recently went back to a Flutter project I've not touched for a couple of months and updated a few bits and pieces. Now, when I run the app in my emulator, I get the message:
[core/no-app] No Firebase App '[DEFAULT]' has been created
I added this line of code to initialize it in the app: await Firebase.initializeApp();... but it now doesn't appear to connect to my Firebase, just an empty one that I presume it has created on the fly.
This was my original code, which according to this site is the correct way to initialize the app: https://firebase.flutter.dev/docs/overview/
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark(),
home: FutureBuilder(
future: _initialization,
builder: (context, snapshot) {...
Any idea what I'm doing wrong? The original app on my phone works fine with Firebase, so I presume it's something to do with updating Flutter, Firebase to the latest versions.
Thanks
You should initalize your app in the main function before you run your app, not in the MyApp widget.
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(); // add this line
runApp(MyApp());
}
Ah, it wasn't anything to do with the incorrect Firebase app. I did initialize it in the main function, but in debugging I found that it was hitting an error in reading an object from the database. It was just incorrectly defined. I fixed that and it works now.
Thanks

Flutter Firebase Dynamic Link Navigator.push not navigating

I am trying to implement Firebase Dynamic links in a flutter. But when I click on the link it calls the functions but does not take me to the specified page.
Code Implementation
main.dart
Main Entry Final for Application
void main() {
Crashlytics.instance.enableInDevMode = true;
FlutterError.onError = Crashlytics.instance.recordFlutterError;
runZoned(() {
runApp(MyApp());
}, onError: Crashlytics.instance.recordError);
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
DynamicLinks dynamicLinks = new DynamicLinks();
dynamicLinks.initDynamicLinks(context);
SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]);
return LayoutBuilder(builder: (context, constraints) {
return OrientationBuilder(builder: (context, orientation) {
SizeConfig().init(constraints, orientation);
return MaterialApp(
title: 'APP NAME',
theme: ThemeData(
primarySwatch: Colors.orange,
brightness: Brightness.light,
),
debugShowCheckedModeBanner: false,
home:SplashScreenMain(),
);
});
});
}
}
dynamicLinkManager.dart
Another class to handle Dynamic Links.
class DynamicLinks {
void initDynamicLinks(BuildContext context) async{
var data = await FirebaseDynamicLinks.instance.getInitialLink();
FirebaseDynamicLinks.instance.onLink(onSuccess: (dynamicLink) async {
print("Main = ${dynamicLink}");
var deepLink = dynamicLink?.link;
final queryParams = deepLink.queryParameters;
debugPrint('DynamicLinks onLink $deepLink');
print("queryParams $queryParams");
if(DynamicLinksConst.inviteUser == deepLink.path){
print("Step 1.......Code Works");
/* THIS PART CODE IS NOT WORKING */
Login.setActiveContext(context);
Navigator.push(context,
EaseInOutSinePageRoute(
widget: SignupPage()), //MaterialPageRoute
);
}else{
Navigator.push(context,
EaseInOutSinePageRoute(
widget: LoginPage()), //MaterialPageRoute
);
}
}, onError: (e) async {
debugPrint('DynamicLinks onError $e');
});
}
}
Console Output
Here is the output you can see that its returning data captured by dynamic link.
I Don't Think it a problem with firebase dynamic link it feels like more of a Navigator problem but I am unable to identify the problem here as this Navigator is working properly throughout the project expect here.
EaseInOutSinePageRoute just adds animation to navigations.
I/flutter ( 395): Main = Instance of 'PendingDynamicLinkData'
I/flutter ( 395): DynamicLinks onLink https://example.com/abc?para1=dataOne
I/flutter ( 395): queryParams {para1: dataOne}
I/flutter ( 395): Step 1.......Code Works
As mentioned in my comment, the issue here is that the expected BuildContext isn't used in Navigator.push().
Without a minimal repro, it's difficult to provide a concrete solution. Since you've mentioned that you're using an Authenticator class that pushes a new page/screen, it might be safer to manage the screen of the app in a single class. With this, it's easier to manage the BuildContext being used in Navigator.push(). You may check this sample in this blog post and see if it fits your use case.

Flutter FutureBuilder throws error, but on simulation it works as expected

I'm a beginner to Flutter and currently I'm having a problem with correctly implementing FutureBuilder in flutter.
I'm trying to build a user page, where I have my user information stored in Firebase, and every time I access the user page it retrieves the current user data and shows the data on the page. Here's the code I've written for the implementation:
class UserPage extends StatefulWidget{
#override
UserPageState createState() => UserPageState();
}
class UserPageState extends State<UserPage>{
String userName;
String userEmail;
String collegeName;
Future _infoInit() async {
userName = await HelperFunctions.getUserNamePreference();
userEmail = await HelperFunctions.getUserEmailPreference();
collegeName = await HelperFunctions.getUserCollegePreference();
}
Widget userScaffold(BuildContext context, AsyncSnapshot snapshot){
return Scaffold(
appBar: AppBar(
elevation: 0,
title: Text(
userName
),
backgroundColor: Colors.lightBlue,
),
body:Center(
child: Text("This is User Page")
)
);
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: _infoInit(),
builder: (context,AsyncSnapshot snapshot) => userScaffold(context, snapshot)
);
}
}
At the moment the only part I've written is showing the currently logged in user on the app bar, and when I run the code, it seems that it's successfully running. However, when I look at the android studio console I could see that it's actually facing and error which I think is related to async function executed in the FutureBuilder widget.
Error Message:
Performing hot reload...
Syncing files to device iPhone 11...
Reloaded 7 of 650 libraries in 397ms.
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building FutureBuilder<dynamic>(dirty, state: _FutureBuilderState<dynamic>#772f1):
A non-null String must be provided to a Text widget.
'package:flutter/src/widgets/text.dart':
Failed assertion: line 298 pos 10: 'data != null'
The relevant error-causing widget was:
FutureBuilder<dynamic> file:///Users/nossu3751/Downloads/flutter_project/moim_app/lib/user/profile.dart:39:12
When the exception was thrown, this was the stack:
#2 new Text (package:flutter/src/widgets/text.dart:298:10)
#3 UserPageState.userScaffold (package:moimapp/user/profile.dart:26:18)
#4 UserPageState.build.<anonymous closure> (package:moimapp/user/profile.dart:41:54)
#5 _FutureBuilderState.build (package:flutter/src/widgets/async.dart:732:55)
#6 StatefulElement.build (package:flutter/src/widgets/framework.dart:4619:28)
More exactly, it says that the userName that I'm trying to use in the FutureBuilder is null, even though I believe I assigned the value already through running _infoInit() method and it does actually show correctly on the simulator.
I would really appreciate it if someone can let me know what I'm doing wrong here, and what I can do to stop this message from appearing again. Thank you very much in advance!
The problem is that the FutureBuilder's builder method is invoked each time the AsyncSnapshot is changed (and, initially, the snapshot has no data). Therefore, in the first couple of times that builder is called, userName will be null, thus giving you that error; but after some point, the username will have been fetched, and when the builder function is called you'll see the username on the screen correctly.
The idiomatic way to use FutureBuilder is the following:
FutureBuilder(
future: myFuture,
builder: (context, AsyncSnapshot snapshot) {
// Try adding this print to your code to see when this method is being executed!
print('Building with snapshot = $snapshot');
if (snapshot.hasData) {
// return widget with data - in your case, userScaffold
}
else if (snapshot.hasError) {
// return widget informing of error
}
else {
// still loading, return loading widget (for example, a CircularProgressIndicator)
}
},
);
So, initially, the builder function will be called will be with a snapshot without data (the "else" branch). You'll probably want to show a loading widget in that case. Then, after some time, the Future completes, and the builder function is called with a snapshot having either the data or some error.
Another important thing in your code is that your function _infoInit doesn't actually return anything. So, in fact, your FutureBuilder isn't using the data from the AsyncSnapshot (which means that the above snippet will not actually work, since snapshot.hasData will never be true). With FutureBuilder, you normally want to build the widget using the data returned by the AsyncSnapshot. Instead, what happens in your code is:
FutureBuilder is created. This calls _infoInit(), which triggers fetching the data from Firebase;
FutureBuilder's builder method is called. It tries using userName, but it is null so Flutter shows the failed assertion;
_infoInit() fetches all the data, and returns a Future (this future is returned automagically because of the async clause in the method signature; however, without a return clause, it doesn't actually return any data). But despite the Future not having any data, the 3 variables in the state (including userName) have been updated, and now contain some data.
Since the future passed to the FutureBuilder has completed, the builder method is called again. This time, userName has data, so it builds correctly.
It's fine to write the code as you did, but in that case, you don't need to use a FutureBuilder. You could just invoke _infoInit() from the widget's initState() method (initState is a method called once when the State is first built) and, after the data is fetched, call setState(). Here's how that would look:
class UserPage extends StatefulWidget {
#override
UserPageState createState() => UserPageState();
}
class UserPageState extends State<UserPage> {
String userName;
String userEmail;
String collegeName;
bool loadingData = true;
#override
void initState() {
_infoInit();
}
// This can be void now, since we're changing the state, rather than returning a Future
void _infoInit() async {
String userName = await HelperFunctions.getUserNamePreference();
String userEmail = await HelperFunctions.getUserEmailPreference();
String collegeName = await HelperFunctions.getUserCollegePreference();
setState(() {
// In theory, we could have just updated the state variables above, but the
// recommended practice is to update state variables inside setState.
this.userName = userName;
this.userEmail = userEmail;
this.collegeName = collegeName;
loadingData = false;
});
}
Widget userScaffold(BuildContext context) {
return Scaffold(
appBar: AppBar(
elevation: 0,
title: Text(userName),
backgroundColor: Colors.lightBlue,
),
body: Center(child: Text("This is User Page")));
}
#override
Widget build(BuildContext context) {
if (loadingData) {
// We don't have the data yet, so return a widget to indicate some loading state
return Scaffold(
body: Center(child: CircularProgressIndicator()),
);
}
return userScaffold(context);
}
}
The above snippet doesn't treat for errors in fetching the data, which you'll probably want to do. And to do that, you might use a flag called 'hasError' or something - which ultimately, will give very similar code to how the "idiomatic" FutureBuilder builder method is written.
Both are valid approaches; FutureBuilder maybe uses less code (and might be simpler to use if the rest of your code already uses Futures), but ultimately, it's up to your preference.
You need to use ConnectionState inside your builder. Look at this code template: (Currently your builder return userScaffold without waiting for the future to complete)
return FutureBuilder(
future: yourFuture(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// future complete
// if error or data is false return error widget
if (snapshot.hasError) {
return _buildErrorWidget('SOMETHING WENT WRONG, TAP TO RELOAD');
}
// return data widget
return _buildDataWidget();
// return loading widget while connection state is active
} else
return _buildLoadingWidget();
},
);

Flutter - how to get current context?

I am using Firebase cloud messaging for notifications, and i want to show a dialog or snackbar once i receive a notification when i am inside the application, my problem is that i am initializing the firebase configuration at the top of my widget tree (Splash screen once the app is starting)
_fireBaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
dynamic data = message['data'];
................ // Adding a snackbar/alertdialog here doesn't work
},
);
obviously if i set a dialog or snackbar it won't show since i need the context of my current page, is there any way to get the current context?
I also tried putting it inside the build widget of my splash screen but still the dialog isn't showing once i am on another page.
#override
Widget build(BuildContext context) {
_fireBaseMessaging.configure(
onMessage: (Map<String, dynamic> message) async {
print("onMessage: $message");
dynamic data = message['data'];
if (data['id'] == '1') {
newPro = true;
} else if (data['id'] == '2') {
print("THIS WORKS!!!");
showDialog(
context: context,
builder: (context) => AlertDialog(
content: ListTile(
title: Text("TEST"),
subtitle: Text("TEST"),
),
actions: <Widget>[
FlatButton(
child: Text("OK"),
onPressed: () => Navigator.pop(context),
)
],
));
}
},
);
I had the exact same issue, but I found a brilliant thread on GitHub. Basically, you can create a navigatorKey and pass that in to MaterialApp, and then use that navigatorKey to change route.
See how in this thread: https://github.com/brianegan/flutter_redux/issues/5#issuecomment-361215074
I ended up using Overlay support:
https://pub.dev/packages/overlay_support
It is basically called at the very beginning of my tree just like wrapping providers at the main.dart, it worked like a charm, nothing else worked at all!
Also here is a tutorial that helped me a lot:
https://medium.com/flutter-community/in-app-notifications-in-flutter-9c1e92ea10b3
Because it makes me uncomfortable to have the answer embedded in a link, here is the answer (credit to xqwzts on Github).
Use a GlobalKey you can access from anywhere to navigate:
Create the key:
final GlobalKey<NavigatorState> navigatorKey = new GlobalKey<NavigatorState>();
Pass it to your App:
new MaterialApp(
title: 'MyApp',
onGenerateRoute: generateRoute,
navigatorKey: navigatorKey,
);
Push routes:
navigatorKey.currentState.pushNamed('/someRoute');
An elegant solution to this problem is to use GlobalKey. That'll let you find the current BuildContext and do things with it.
You make a file called eg. global.dart looking like this:
import 'package:flutter/material.dart';
class GlobalVariable {
static final GlobalKey<NavigatorState> navState = GlobalKey<NavigatorState>();
}
You use this in your main() and MaterialApp() like this:
import 'global.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'fcm.dart'; // My Firebase Cloud Messaging code
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'screens/welcome_screen.dart';
void main() {
print('Running main()');
WidgetsFlutterBinding.ensureInitialized();
Firebase.initializeApp();
initializeFcm('', GlobalVariable.navState); // Sending the global key when initializing Firebase Cloud Messaging
FirebaseMessaging.onBackgroundMessage(firebaseMessagingBackgroundHandler);
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: WelcomeScreen(),
navigatorKey: GlobalVariable.navState, // Putting the global key in the MaterialApp
);
}
}
Then, in the file that handles Firebase Cloud Messaging, which I've named fcm.dart, you'll be able to use the GlobalKey to find the current context and use it, for example like this:
import 'package:blackbox/global.dart';
import 'online_screens/game_hub_screen.dart';
import 'package:flutter/material.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
void initializeFcm(String token, GlobalKey myGlobalKey) async {
print('Initializing Firebase Cloud Messaging...');
await Firebase.initializeApp();
FirebaseMessaging.onMessageOpenedApp.listen((remoteMsg) {
// Using the currentContext found with GlobalKey:
Navigator.push(GlobalVariable.navState.currentContext, MaterialPageRoute(builder: (context) {
return GameHubScreen();
}));
});
}
do the initializing inside a build method of your first widget in the tree ! which normally it called an App widget and it is StateLess StateFull widget and inside the build method you have access to the BuildContext

Returning null user data from Firestore. How to reference it globaly instead?

I'm quite new to Flutter and I've been struggling to access a user's document on Firestore.
On the profile page,
I'm setting the current user's UID inside initState, but uid returns null for a quick second, then the page updates with correct info.
So I am able to retrieve a certain field (like displayName), but it isn't quite the best practice. I don't want to have a bunch of boilerplate code and await functions mixed with UI and such.
Code:
FirebaseUser user;
String error;
void setUser(FirebaseUser user) {
setState(() {
this.user = user;
this.error = null;
});
}
void setError(e) {
setState(() {
this.user = null;
this.error = e.toString();
});
}
#override
void initState() {
super.initState();
FirebaseAuth.instance.currentUser().then(setUser).catchError(setError);
}
Then in my body I have a Stream builder to get the document.
body: StreamBuilder(
stream: Firestore.instance
.collection('users')
.document(user.uid)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: CircularProgressIndicator(
valueColor:
AlwaysStoppedAnimation<Color>(Colors.deepOrange),
),
);
} else {
var userDocument = snapshot.data;
return showProfileHeader(userDocument);
}
},
)
I want to make 'global' references to be accessed throughout the app. Instead of getting the user's id on every page and streaming a specific field when I might need multiple ones.
The only ways I found online to do something similar, created lists with all the data in it. I feel like this might get extra fields I don't need.
How can I make data from Firestore available across the app?
I am using the "Provider" package for doing state management across my app. Nowadays its also the suggested way by the google flutter team when it comes to state management. See the package here: https://pub.dev/packages/provider
Regarding Firebase Auth and accessing the credentials application wide, i am using that said package like stated on this page:
https://fireship.io/lessons/advanced-flutter-firebase/
Short version below. Bootstrap your app like so:
import 'package:provider/provider.dart';
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
// Make user stream available
StreamProvider<FirebaseUser>.value(
stream: FirebaseAuth.instance.onAuthStateChanged),
// not needed for your problem but here you can see how
// to define other Providers (types) for your app.
// You need a counter class which holds your model of course.
ChangeNotifierProvider(builder: (_) => Counter(0)),
],
// All data will be available in this child and descendents
child: MaterialApp(...)
);
}
}
Then in your child widgets, just do:
// Some widget deeply nested in the widget tree...
class SomeWidget extends StatelessWidget {
#override
Widget build(BuildContext context) {
var user = Provider.of<FirebaseUser>(context);
return Text(user.displayName) // or user.uid or user.email....
}
}
This should do the trick.
That happens because FirebaseAuth.instance.currentUser() returns a future, and until that future is completed, you will not have the proper FirebaseUser object.
Making the user object global is not a bad idea. In addition, you can hook it up to the FirebaseAuth stream so that it gets updated everytime the user auth status changes, like so in a user.dart file:
class User {
static FirebaseUser _user;
static get user => _user;
static void init() async {
_user = await FirebaseAuth.instance.currentUser();
FirebaseAuth.instance.onAuthStateChanged.listen((firebaseUser) {
_user = firebaseUser;
});
}
}
You can call User.init() in main() and access the user object with User.user.

Resources