LateInitialization Error In Flutter main.dart - firebase

I am developing my app and I don't know why this late Initialization error has come I use this same code in my other apps as well there I don't face such error but in this main file the error is persisting and I have been trying for so long It doesn't works. bool? userLoggedIn isn't also working flutter doesn't letting it used. Here is my main.dart file code. Also If anyone can tell me how can I handle 2 logins of app shared preferences that would be a lot helpful
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late bool userLoggedIn;
#override
void initState() {
getLoggedInState();
super.initState();
}
getLoggedInState() async {
await HelperFunctions.getUserLoggedInSharedPreference().then((value) {
setState(() {
userLoggedIn = value!;
});
});
}
#override
Widget build(BuildContext context) {
SystemChrome.setEnabledSystemUIMode(SystemUiMode.manual, overlays: []);
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Dashee',
theme: ThemeData(
primarySwatch: Colors.deepPurple,
),
home: userLoggedIn ? const Dashboard() : Splash());
}
}

LateInitializationError means a variable declared using the late keyword was not initialized on the constructor
You declare this boolean variable:
late bool userLoggedIn
but did not declare a constructor, so it won't be initialized, the obvious thing to do is giving it a value on the constructor, like so:
_MyAppState() {
userLoggedIn = false; // just some value so dart doesn't complain.
}
However may I suggest you don't do that and instead simply remove the late keyword?
Doing that, of course, will give you an error because userLoggedIn is never initialized, but you can fix that by giving it a default value straight on it's declaration or on the constructor initialization:
bool userLoggedIn = false;
or
_MyAppState(): userLoggedIn = false;
note how on the second option I didn't use the constructor's body, you should only declare a variable late if you plan on initializing it on the constructor's body.
This should solve the LateInitializationError that you are getting.
Regarding multiple log-ins
if you want to have three (or more!) log in states, I recommend you declare an enum of those states:
enum LogInState {
LoggedOff,
LoggedInAsRider,
LoggedInAsUser,
}
In order to store said state in sharedPreferences, you could store them as integers:
Future<void> savedLoggedInState(LogInState state) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setInt('some key', state.index);
}
then to read said value from shared preferences:
Future<LogInState> getLoginState() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int index = prefs.getInt('some key') ?? 0;
return LogInState.values[index];
}
finally to display each different log in state, you'd do something like this:
home: _getLogInScreen(),
[...]
Widget _getLogInScreen() {
switch (userLogIn) {
case LogInState.LoggedOff:
return Splash();
case LogInState.LoggedInAsRider:
return RiderDashboard();
case LogInState.LoggedInAsUser:
return UserDashBoard();
}
// if you make a new log in state, you need to add it to the switch
// statement or it will throw an unimplemented error
throw UnimplementedError();
}

Related

How to update boolean value in Firestore from a switch tile?

My goal is to have a userNotifications collection in Firestore that is used to track user notification preferences. The user can toggle between true and false based on tapping a switch tile. With the code below, Firestore is immediately and correctly updating the boolean value based on user interaction and the user interface is updating and reflecting the changes. If the user logs out and logs back in, the boolean value is accurately reflected in the user interface.
Before I apply this approach more broadly in the code I was hoping that someone can comment and let me know if my approach to updating the boolean value in Firestore is valid or point me in a better direction so I can improve my code. Links to SO posts or documentation are fine as I am more than willing to read and learn. Thanks in advance for any help.
class NotificationsMessagesTile extends StatefulWidget {
const NotificationsMessagesTile({
Key? key,
}) : super(key: key);
#override
State<NotificationsMessagesTile> createState() =>
_NotificationsMessagesTileState();
}
class _NotificationsMessagesTileState extends State<NotificationsMessagesTile> {
bool notificationsActive = false;
final String? currentSignedInUserID = Auth().currentUser?.uid;
Future<void> updateNotifications() async {
if (!notificationsActive) {
notificationsActive = true;
FirebaseFirestore.instance
.collection('userNotifications')
.doc(currentSignedInUserID)
.update({
'messages': false,
});
} else {
notificationsActive = false;
FirebaseFirestore.instance
.collection('userNotifications')
.doc(currentSignedInUserID)
.update({
'messages': true,
});
}
setState(() {});
}
#override
Widget build(BuildContext context) {
return SwitchListTileSliver(
icon: Provider.of<NotificationsPageProvider>(context).areMessagesTurnedOn
? Icons.notifications_active
: Icons.notifications_off,
onChanged: (bool value) {
final provider = Provider.of<NotificationsPageProvider>(
context,
listen: false,
);
provider.updateMessagesSettings(isOn: value);
updateNotifications();
},
subTitle:
Provider.of<NotificationsPageProvider>(context).areMessagesTurnedOn
? const Text(
SettingsPageString.messagesOn,
)
: const Text(
SettingsPageString.messagesOff,
),
title: SettingsPageString.messages,
value:
Provider.of<NotificationsPageProvider>(context).areMessagesTurnedOn,
);
}
}
You could improve your updateNotifications() function to not have duplicate code:
Future<void> updateNotifications() async {
await FirebaseFirestore.instance
.collection('userNotifications')
.doc(currentSignedInUserID)
.update({
'messages': notificationsActive,
});
setState(() {
notificationsActive = !notificationsActive;
});
}
And also I would suggest to you to listen to your Firestore collection and update UI on change. You can look up on how to do that here.

InitState in flutter, deriving document value from firestore

I am working on an app where I need to filter out every user that signs in. Once they signed in, they will be redirected to wrapper that checks if the user ID exists on Firestore document collection. Here is my code.
class adminCheck extends StatefulWidget {
const adminCheck({Key? key}) : super(key: key);
#override
State<adminCheck> createState() => _adminCheckState();
}
class _adminCheckState extends State<adminCheck> {
User? user= FirebaseAuth.instance.currentUser;
bool isAdmin=false;
void initState() {
checkIfAdmin();
super.initState();
}
#override
Widget build(BuildContext context) {
if (isAdmin==true){
countDocuments();
return HomeScreen();
}else{
return notAdmin();
}
}
void checkIfAdmin() async{
DocumentSnapshot currentUser= await FirebaseFirestore.instance.collection('Administrator')
.doc(user!.email)
.get();
if(currentUser.exists){
print("This user is admin");
setState((){
isAdmin=true;
});
}
if(!currentUser.exists){
print("This user is not an admin");
}
}
}
The problem is it returns the notAdmin() class even the void method returns true which supposed to return HomeScreen(), and after few seconds, it will return HomeScreen(). I think there is a delay happening from initializing the data coming from the Firestore. Please help me with my problem. Or is there a way so that it will wait to be initialized first before returning any of those two classes?
The purpose of initState is to determine the state of the widget when it first renders. This means that you can't use asynchronous information (such as data that you still need to load from Firestore) in initState.
If the widget should only be rendered once the data is available, you should not render it until that data has been loaded. You can do this by adding another value to the state to indicate while the document is loading:
class _adminCheckState extends State<adminCheck> {
User? user= FirebaseAuth.instance.currentUser;
bool isAdmin=false;
bool isLoading=true; // 👈
And then use that in rendering:
#override
Widget build(BuildContext context) {
if (isLoading) { // 👈
return CircularProgressIndicator(); // 👈
} else if (isAdmin==true){
countDocuments();
return HomeScreen();
}else{
return notAdmin();
}
}
You can render whatever you want while the data is loading of course (or nothing at all), but the CircularProgressIndicator is typically a reasonable default.
And finally of course, you'll want to clear the loading indicator when the isAdmin is set:
void checkIfAdmin() async{
DocumentSnapshot currentUser= await FirebaseFirestore.instance.collection('Administrator')
.doc(user!.email)
.get();
if(currentUser.exists){
setState((){
isAdmin=true;
isLoading=false; // 👈
});
}
if(!currentUser.exists){
print("This user is not an admin");
}
}
This pattern is so common that it is also encapsulated in a pre-built widget. If that sounds appealing, you'll want to look at using a FutureBuilder.

How to initialize firebase crashlytics in Flutter?

I have already implemented firebase crashlytics to my Flutter project through dependency in pubspec.yaml and also in Gradle files and able to see the crashlytics dashboard in the firebase console.
Now my question is how can I initialize crashlytics in main.dart file and how to write log and catch error or crash for a particular page(say Home page).
I have tried from this link: https://pub.dev/packages/firebase_crashlytics/example
main.dart
final _kShouldTestAsyncErrorOnInit = false;
// Toggle this for testing Crashlytics in your app locally.
final _kTestingCrashlytics = true;
main() {
WidgetsFlutterBinding.ensureInitialized();
runZonedGuarded(() {
runApp(MyApp());
}, (error, stackTrace) {
print('runZonedGuarded: Caught error in my root zone.');
FirebaseCrashlytics.instance.recordError(error, stackTrace);
});
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: "My App",
debugShowCheckedModeBanner: false,
home: MainPage(),
theme: ThemeData(
accentColor: Colors.blue
),
);
}
}
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Future<void> _initializeFlutterFireFuture;
Future<void> _testAsyncErrorOnInit() async {
Future<void>.delayed(const Duration(seconds: 2), () {
final List<int> list = <int>[];
print(list[100]);
});
}
// Define an async function to initialize FlutterFire
Future<void> _initializeFlutterFire() async {
// Wait for Firebase to initialize
await Firebase.initializeApp();
if (_kTestingCrashlytics) {
// Force enable crashlytics collection enabled if we're testing it.
await FirebaseCrashlytics.instance.setCrashlyticsCollectionEnabled(true);
} else {
// Else only enable it in non-debug builds.
// You could additionally extend this to allow users to opt-in.
await FirebaseCrashlytics.instance
.setCrashlyticsCollectionEnabled(!kDebugMode);
}
// Pass all uncaught errors to Crashlytics.
Function originalOnError = FlutterError.onError;
FlutterError.onError = (FlutterErrorDetails errorDetails) async {
await FirebaseCrashlytics.instance.recordFlutterError(errorDetails);
// Forward to original handler.
originalOnError(errorDetails);
};
if (_kShouldTestAsyncErrorOnInit) {
await _testAsyncErrorOnInit();
}
}
#override
void initState() {
super.initState();
_initializeFlutterFireFuture = _initializeFlutterFire();
Firebase.initializeApp().whenComplete(() {
print("completed");
setState(() {});
});
checkLoginStatus();
}
}
Is it correct or any otherway to initialize crashlytics in flutter?
If i have to check whether there is any crash in HomePage, then how can i get that crash from home page and will show it in firbase crashlytics?
Yes, your configuration of the crashlytics is ok.
If you are using Firebase Auth, you can add the following code in order to have the ability to track crashes specific to a user:
FirebaseAuth.instance.onAuthStateChanged.listen((firebaseUser) {
if (firebaseUser != null && firebaseUser?.email != null) {
Crashlytics.instance.setUserEmail(firebaseUser.email);
}
if (firebaseUser != null && firebaseUser?.uid != null) {
Crashlytics.instance.setUserIdentifier(firebaseUser.uid);
}
if (firebaseUser != null && firebaseUser?.displayName != null) {
Crashlytics.instance.setUserName(firebaseUser.displayName);
}
});
Also, don't forget to track specific exceptions in catch of the try-catch block like this:
try {
//some code here...
} catch (e, s) {
Crashlytics.instance.recordError(e, s, context: "an error occured: uid:$uid");
}

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.

Flutter Redux Navigator GlobalKey.currentState returns null

I am developing Flutter with Redux.
When a user starts an application, I want Redux to automatically dispatch an action. This action will make the Navigator push different routes dependently.
This snippet provided by a Flutter dev member uses the GlobalKey to use the Navigator inside the middleware.
Following this, I organize my code as follows:
main.dart
void main() {
final store = new Store(appStateReducer,
middleware:createRouteMiddleware()
);
runApp(new MyApp(store));
}
class MyApp extends StatelessWidget {
final Store<AppState> store;
MyApp(this.store);
#override
Widget build(BuildContext context) {
return new StoreProvider<AppState>(
store: store,
child: MaterialApp(
routes: {
Routes.REGISTER: (context) {
return RegisterScreenContainer();
},
Routes.SET_PROFILE: (context) {
return SetProfileScreenContainer();
},
//Routes.HOME = "/" so this route will be run first
Routes.HOME: (context) {
return StoreBuilder<AppState>(
//onInit is called to dispatch an action automatically when the application starts. The middleware will catch this and navigate to the appropriate route.
onInit: (store) => store.dispatch(
ChangeRoute(routeStateType: RouteStateType.Register)),
builder: (BuildContext context, Store vm) {
return RegisterScreenContainer();
},
);
},
}));
}
}
middleware.dart
Middleware<AppState> createRouteMiddleware(
{#required GlobalKey<NavigatorState> navigatorKey}) {
final changeRoute = _createChangeRouteMiddleware(navigatorKey: navigatorKey);
return TypedMiddleware<AppState, ChangeRoute>(changeRoute);
}
Middleware<AppState> _createChangeRouteMiddleware(
{#required GlobalKey<NavigatorState> navigatorKey}) {
print(navigatorKey.currentState);
return (Store store, action, NextDispatcher next) async {
switch ((action.routeStateType as RouteStateType)) {
case RouteStateType.Home:
navigatorKey.currentState.pushNamed(Routes.HOME);
break;
case RouteStateType.Register:
//The middleware intercepts and push the appropriate route depending on the action
navigatorKey.currentState.pushNamed(Routes.REGISTER);
break;
default:
break;
}
next(action);
};
}
And this is the error I got
[ERROR:topaz/lib/tonic/logging/dart_error.cc(16)] Unhandled exception:
E/flutter ( 2544): NoSuchMethodError: The method 'pushNamed' was called on null.
E/flutter ( 2544): Receiver: null
E/flutter ( 2544): Tried calling: pushNamed("/register")
This means that the action was successfully dispatched, however, the currentState of the navigatorKey was null.
What am I missing here?
Note that I am aware of this seemingly similar question which does not really apply to my question. Even when I merge the main.dart and middleware.dart into one file, it still doesn't work.
I solved this issue by having the global navigator key in a separate file. Then I used that in my materialApp and in the middleware.
I put the navigator key in my keys.dart file:
import 'package:flutter/widgets.dart';
class NoomiKeys {
static final navKey = new GlobalKey<NavigatorState>();
}
Added the key to MaterialApp widget "navigatorKey: NoomiKeys.navKey," in my main.dart file (alot of code is removed to make it faster to read):
import 'package:noomi_nursing_home_app/keys.dart';
#override
Widget build(BuildContext context) {
return StoreProvider<AppState>(
store: store,
child: MaterialApp(
//Add the key to your materialApp widget
navigatorKey: NoomiKeys.navKey,
localizationsDelegates: [NoomiLocalizationsDelegate()],
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
And use it in navigate_middleware.dart;
import 'package:noomi_nursing_home_app/actions/actions.dart';
import 'package:noomi_nursing_home_app/keys.dart';
import 'package:redux/redux.dart';
import 'package:noomi_nursing_home_app/models/models.dart';
List<Middleware<AppState>> navigateMiddleware() {
final navigatorKey = NoomiKeys.navKey;
final navigatePushNamed = _navigatePushNamed(navigatorKey);
return ([
TypedMiddleware<AppState, NavigatePushNamedAction>(navigatePushNamed),
]);
}
Middleware<AppState> _navigatePushNamed(navigatorKey) {
return (Store<AppState> store, action, NextDispatcher next) {
next(action);
navigatorKey.currentState.pushNamed(action.to);
};
}
looks like you forgot to declare & pass navigatorKey to middleware
final navigatorKey = GlobalKey<NavigatorState>();
void main() {
final store = Store(appStateReducer,
middleware:createRouteMiddleware(navigatorKey: navigatorKey)
);
runApp(MyApp(store));
}
and your MaterialApp is missing navigatorKey too
MaterialApp(
navigatorKey: navigatorKey
routes: /* your routes */
)

Resources