I got this weird bug causing a lot of bugs on my app like :
- not able to keep the user connected when you close and reopen the app
- cause sometimes to have bug while retriving user info (blank user page)
- When the bug disconnect you, it says on reconnect that user info is incorrect
I have been searching everywhere since 2 weeks and couldn't get to see where the problem is. I'm gonna share you some code like main.dart
Gif of all the manipulations leading to the bug
Main.dart code :
NotificationService notificationService = NotificationService();
Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp(); notificationService.init(); SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); runApp(AppWrapper()); }
class AppWrapper extends StatefulWidget { @override State<StatefulWidget> createState() {
return _AppWrapperState(); } }
class _AppWrapperState extends State<AppWrapper> { @override Widget build(BuildContext context) {
return OverlaySupport.global(
child: MaterialApp(
theme: ThemeData(
textTheme: GoogleFonts.openSansTextTheme(
Theme.of(context).textTheme,
)),
debugShowCheckedModeBanner: false,
home: App()),
); } }
/// We are using a StatefulWidget such that we only create the [Future] once, /// no matter how many times our widget rebuild. /// If we used a [StatelessWidget], in the event where [App] is rebuilt, that /// would re-initialize FlutterFire and make our application re-enter loading state, /// which is undesired. class App extends StatefulWidget { // Create the initialization Future outside of `build`: @override _AppState createState() => _AppState(); }
class _AppState extends State<App> { /// The future is part of the state of our widget. We should not call `initializeApp` /// directly inside [build]. final Future<FirebaseApp> _initialization = Firebase.initializeApp(); bool? _isLoggedIn;
@override void initState() {
initIsLoggedIn();
super.initState(); }
//The following variables are used to handle the navigation between the 3 main pages List<Widget> pages = [
SettingsPage(key: PageStorageKey('settings')),
HomePage(key: PageStorageKey('home')),
AccountPage(
key: PageStorageKey('account'),
), ]; int _selectedIndex = 1;
initIsLoggedIn() async {
try {
// Get values from storage
final storage = new FlutterSecureStorage();
String isLoggedIn = await storage.read(key: "isLoggedIn") ?? "";
if (isLoggedIn == "true") {
//If the user seems logged in, we check it by trying to authenticate
String mail = await storage.read(key: "mail") ?? "";
String password = await storage.read(key: "password") ?? "";
UserResponse userResponse = await APIUser().login(mail, password);
User? user = userResponse.user;
if (user != null) {
setState(() {
_isLoggedIn = true;
});
} else {
//If we cannot connect the user with the stored mail and password, then we redirect the user to the login page
setState(() {
_isLoggedIn = false;
});
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (context) => LoginPage()),
(_) => false,
);
}
} else {
//If we cannot connect the user with the stored mail and password, then we redirect the user to the login page
setState(() {
_isLoggedIn = false;
});
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => LoginPage()));
}
} catch (e) {
//print the error and redirect the user to the login page
print(e);
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => LoginPage()));
} }
void _changePage(int index) {
setState(() {
_selectedIndex = index;
}); }
@override Widget build(BuildContext context) {
return FutureBuilder(
// Initialize FlutterFire:
future: _initialization,
builder: (context, snapshot) {
// Check for errors
if (snapshot.hasError) {
return somethingWentWrongWidget();
}
// Once complete, show your application
if (snapshot.connectionState == ConnectionState.done &&
_isLoggedIn != null) {
return appContentWidget();
}
// Otherwise, show something whilst waiting for initialization to complete
return LoadingPage();
},
); }
Widget somethingWentWrongWidget() {
return Center(
child: Text('Someting went wrong', textDirection: TextDirection.ltr),
); }
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
Widget appContentWidget() {
double bottomNavbarHeight = 70;
if (Platform.isIOS) {
bottomNavbarHeight = 90;
}
// check if the user is logged in, if not go to login page
bool loggedIn = _isLoggedIn ?? false;
if (!loggedIn) {
return LoginPage();
} else {
return WillPopScope(
onWillPop: () {
return Future.value(false);
},
child: Scaffold(
key: _scaffoldKey,
body: IndexedStack(
index: _selectedIndex,
children: pages,
),
bottomNavigationBar: Container(
height: bottomNavbarHeight,
decoration: BoxDecoration(
boxShadow: [BoxShadow(color: Colors.grey.shade200)]),
child: Theme(
data: ThemeData(
brightness: Brightness.light,
splashColor: Colors.transparent,
highlightColor: Colors.transparent,
),
child: BottomNavigationBar(
elevation: 20,
backgroundColor: Colors.white,
selectedFontSize: 0,
//As the label is obligatory, we give it a size of zero
unselectedFontSize: 0,
//As the label is obligatory, we give it a size of zero
items: const <BottomNavigationBarItem>[
BottomNavigationBarItem(
icon: ImageIcon(
AssetImage("assets/settings.png"),
size: 32,
),
label: ''),
BottomNavigationBarItem(
icon: ImageIcon(
AssetImage("assets/dashboard.png"),
size: 32,
),
label: ''),
BottomNavigationBarItem(
icon: ImageIcon(
AssetImage("assets/user.png"),
size: 32,
),
label: ''),
],
currentIndex: _selectedIndex,
selectedItemColor: Colors.black,
onTap: _changePage,
),
),
),
),
);
} } }
My login_page.dart :
class LoginPage extends StatefulWidget {
@override
LoginPageState createState() => LoginPageState();
}
class LoginPageState extends State<LoginPage> {
final _loginFormKey = GlobalKey<FormState>();
TextEditingController mailController = TextEditingController();
TextEditingController passwordController = TextEditingController();
bool displayIncorrectLoginErrorMessage = false;
bool displayNotVerifiedEmailErrorMessage = false;
TextEditingController mailVerificationCodeController =
TextEditingController();
TextEditingController mailToResetPasswordController = TextEditingController();
bool obscurePassword = true;
bool passwordForgottenWWrongEmail = false;
@override
Widget build(BuildContext context) {
double screenHeight = MediaQuery.of(context).size.height;
double spaceBeforeEndPage = 20;
if (screenHeight > 680) {
spaceBeforeEndPage = screenHeight - 680;
}
return Scaffold(
backgroundColor: Colors.white,
body: SingleChildScrollView(
child: Column(
children: [
Container(
//This one is for setting the background image
decoration: BoxDecoration(
image: DecorationImage(
alignment: Alignment.topCenter,
fit: BoxFit.fitWidth,
image: AssetImage('assets/home_background.png'))),
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 25.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(
height: 120,
),
Image(
//Logo Ellipse
image: AssetImage('assets/.png'),
width: 150,
),
SizedBox(
height: 80,
),
Align(
alignment: Alignment.centerLeft,
child: Text(
'Connectez-vous.',
style: TextStyle(
fontSize: 25, fontWeight: FontWeight.bold),
),
),
SizedBox(
height: 50,
),
Form(
key: _loginFormKey,
child: Column(
children: [
VariableDecorationHeightTextFormField(
controller: mailController,
labelText: "Adresse mail"),
VariableDecorationHeightAndObscureIconTextFormField(
controller: passwordController,
labelText: "Mot de passe",
),
],
)),
Align(
alignment: Alignment.topRight,
child: TextButton(
onPressed: () async {
//Open an alert dialog that ask the email address then send the request to the api
showDialog(
context: context,
barrierDismissible: false,
builder: (BuildContext context) {
return resetPasswordAlertDialog(context);
});
},
child: Text(
" Mot de passe oublié ?",
style: TextStyle(color: Colors.grey, fontSize: 12),
),
),
),
incorrectLoginErrorMessageWidget(),
notVerifiedEmailErrorMessageWidget(),
SizedBox(
height: 20,
),
ElevatedButton(
onPressed: () async {
String mail = mailController.text;
String? password = passwordController.text;
mail = await Utility().removeSpacesAtTheEnd(mail);
UserResponse userResponse =
await APIUser().login(mail, password);
if (!userResponse.userConfirmed) {
//The user is not confirmed
setState(() {
displayNotVerifiedEmailErrorMessage = true;
});
} else if (userResponse.user != null) {
User? user = userResponse.user;
if (user != null) {
print("User " +
user.username +
" connected without problem");
await login(mail, password);
//Update user's notification token
await NotificationService()
.tryConnectUserAndUpdateNotificationTokenIfNeeded();
Navigator.of(context).push(MaterialPageRoute(
builder: (context) => App()));
} else {
print('User not connected');
setState(() {
displayIncorrectLoginErrorMessage = true;
});
}
} else {
setState(() {
displayIncorrectLoginErrorMessage = true;
});
}
},
child: Text(
"Connexion",
style: TextStyle(color: Colors.white),
),
style: ElevatedButton.styleFrom(primary: Colors.black),
),
],
),
),
),
),
SizedBox(
height: spaceBeforeEndPage,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"Pas encore de compte ?",
style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16),
),
CenteredShadowButton(
function: () {
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (context) => RegisterPage()));
},
child: Text(
"S'inscrire",
style: TextStyle(color: Colors.black),
),
),
],
),
),
],
),
),
);
}
login(String mail, String password) async {
// Create storage
final storage = new FlutterSecureStorage();
// Write values
await storage.write(key: "mail", value: mail);
await storage.write(key: "password", value: password);
await storage.write(key: "isLoggedIn", value: "true");
//Refresh main
Navigator.of(context).push(MaterialPageRoute(builder: (context) => App()));
}
...
Sorry for the flood of code line, if you need anything just ask me, I will try my best to provide it to you.