my FutureBuilder methods return my app everytimes I swap pages.
It cause very bad performances when i navigate between pages.
I have checked solutions on post already in this forum, tried to use provider (didn't fixed my problem), I also tried to move my FutureBuilder into my initState so it's called only one time but didn't manage to make it.
For more details, I printed a line when firebase initialization from FutureBuilder is done, and I witness that everytime i swap pages it print my lane again
My main.dart file:
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].
bool? _isLoggedIn;
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
#override
void initState() {
// initIsLoggedIn();
super.initState();
_asyncMethod();
}
void _asyncMethod() 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;
});
print("returning to login page");
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => LoginPage()),
);
}
} 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;
});
print("returning to login page");
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()));
}
}
//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;
// 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) {
print("Initialize FireBase done");
return appContentWidget();
}
// Otherwise, show something whilst waiting for initialization to complete
print("loadingpage showing to let initialization load");
return LoadingPage();
},
);
}
Widget somethingWentWrongWidget() {
return Center(
child: Text('Someting went wrong', textDirection: TextDirection.ltr),
);
}
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
PageController _pageController = PageController(
initialPage: 1,
);
final _bottomNavigationItems = [
BottomNavigationBarItem(
activeIcon: ImageIcon(
AssetImage("assets/settingsActive.png"),
size: 32,
),
icon: ImageIcon(
AssetImage("assets/settings.png"),
size: 32,
color: Colors.black,
),
label: ''),
BottomNavigationBarItem(
activeIcon: ImageIcon(
AssetImage("assets/dashboardActive.png"),
size: 32,
),
icon: ImageIcon(
AssetImage("assets/dashboard.png"),
size: 32,
color: Colors.black,
),
label: ''),
BottomNavigationBarItem(
activeIcon: ImageIcon(
AssetImage("assets/userActive.png"),
size: 32,
color: Colors.black,
),
icon: ImageIcon(
AssetImage("assets/user.png"),
size: 32,
color: Colors.black,
),
label: ''),
];
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: PageView(
physics: AlwaysScrollableScrollPhysics(),
onPageChanged: (int index) {
setState(() {
_selectedIndex = index;
});
},
controller: _pageController,
pageSnapping: true,
children: [
SettingsPage(key: PageStorageKey('settings')),
HomePage(key: PageStorageKey('home')),
AccountPage(
key: PageStorageKey('account'),
),
],
),
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(
type: BottomNavigationBarType.fixed,
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: _bottomNavigationItems,
selectedItemColor: Colors.black,
currentIndex: _selectedIndex,
onTap: (int index) {
_pageController.animateToPage(index,
duration: Duration(milliseconds: 500),
curve: Curves.easeInCubic);
},
),
),
),
),
);
}
}
}
What i would like to achieve is that FutureBuilder initialize only on initState.
you're using await Firebase.initializeApp() already in your main function, why would you need to initialize it one more time in the FutureBuilder ?
On a more general note, your FutureBuilder function is called many times because of the setState on your nav bar, which recalls the build function where your FutureBuilder is.
Whenever you face that, I suggest you split your widget, by having:
-a StatelessWidget containing your FutureBuilder
-a StatefulWidget containing all the display and logic needed.
Related
I have 3 tabs in my application and there is a date picker which is same for all the tabs whenever i choose the date (from which ever tab it may be)all the data in 3 tabs will change corresponding to the choosen date and so many apis have been provided to this.
But the problem is every time whenever i switch the tab all the apis are hiting again.so how can i manage the tabs so that it will not hit the apis on switching until i choose the date again
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
SelectedDates _selectedDates = SelectedDates();
List<DateTime> selectedDates = List();
int _currentIndex = 0;
List<Widget> _children = [
FirstPage(),
ChartPage(),
ActionPage(),
];
onTabSelected(int index) {
setState(() {
_currentIndex = index;
});
}
#override
Widget build(BuildContext context) {
final _homeProvider = Provider.of<HomeProvider>(context);
final _chartProvider = Provider.of<ChartListingProvider>(context);
final _actionProvider = Provider.of<ActionProvider>(context);
showDatePickerDialog(BuildContext context) async {
final List<DateTime> picked = await DateRagePicker.showDatePicker(
context: context,
initialFirstDate: DateTime.now(),
firstDate: DateTime(2015),
initialLastDate: (DateTime.now()).add(
Duration(days: 7),
),
lastDate: DateTime(2025),
);
if (picked != null && picked.length == 2 && picked != selectedDates) {
setState(() {
selectedDates = picked;
var formatter = DateFormat('dd/MM/yyyy');
_selectedDates?.fromDate = formatter.format(picked[0]);
_selectedDates?.endDate = formatter.format(picked[1]);
_actionProvider.setDate(_selectedDates);
_chartProvider.setDate(_selectedDates);
_homeProvider.setDate(_selectedDates);
});
}
}
return ValueListenableBuilder(
valueListenable: Hive.box(userDetailsBox).listenable(),
builder: (_, Box box, __) {
String token = box.get(authTokenBoxKey);
String id = box.get(companyIdBoxKey);
_actionProvider.setTokenAndCompanyId(token, id);
//in the above function i have provided the apis related to third tab
_chartProvider.setTokenAndCompanyId(token, id);
//in the above function i have provided the apis related to second tab
__homeProvider.setTokenAndCompanyId(token, id);
//in the above function i have provided the apis related to first tab
return DefaultTabController(
length: 3,
initialIndex: 1,
child: Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () {
showDatePickerDialog(context);
},
child: Icon(Icons.date_range),
backgroundColor: Theme.of(context).accentColor,
),
appBar: AppBar(title: Text("Tab Controller"), actions: <Widget>[]),
bottomNavigationBar: BottomNavigationBar(
onTap: onTabSelected,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Home"),
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
title: Text("Chart"),
),
BottomNavigationBarItem(
icon: Icon(Icons.redo),
title: Text("Action"),
),
],
currentIndex: _currentIndex,
),
body: _children[_currentIndex],
),
);
},
);
}
}
It's because of the setstate which force your whole widget to rebuild.
onTabSelected(int index) {
setState(() {
_currentIndex = index;
});
}
For this case you can use providers or other State Management librarys out there like RXdart, Riverpod, Providers or anything else.
These state management librarys give you access to the states and notifies about changes without rebuilding the whole tree. There are a lot of concepts out there you can explore by googling.
Implementation
This is an example implementation using Providers package:
NavigationNotifier:
import 'package:flutter/material.dart';
class NavigationNotifier with ChangeNotifier {
int _currentIndex = 0;
get currentIndex => _currentIndex;
set currentIndex(int index) {
_currentIndex = index;
notifyListeners();
}
}
Your main file/ home, whatever:
class Home extends StatelessWidget {
final List<Widget> _children = [Screen1(), Screen2()];
#override
Widget build(BuildContext context) {
var provider = Provider.of<NavigationNotifier>(context);
...
bottomNavigationBar: BottomNavigationBar(
onTap: (index) {
provider.currentIndex = index;
},
currentIndex: provider.currentIndex,
showSelectedLabels: true,
showUnselectedLabels: true,
items: [
BottomNavigationBarItem(
icon: new Icon(Icons.home), label: 'home'),
BottomNavigationBarItem(
icon: new Icon(Icons.supervised_user_circle_outlined),
label: 'Profile')
],
),
body: _children[provider.currentIndex]),
I wanted to show appbar logo for all the pages from a json file. Now the issue is if I use Futurebuilder
then in every page load, appbar logo awaits before showing. I tried to use shared preference but having issues. Maybe I am doing it wrong. I am a newbie in flutter. If possible please give the answer in simple way so I can understand. Or anyone can help me by creating that part for appbar. That will be helpful.
Here is my json file for logo
import 'dart:convert';
import 'package:models/app_logo.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
import './home_screen.dart';
import './login_screen.dart';
import '../constants.dart';
import '../screens/courses_screen.dart';
import '../screens/my_courses_screen.dart';
import '../screens/my_wishlist_screen.dart';
import '../screens/account_screen.dart';
import '../widgets/filter_widget.dart';
import '../providers/auth.dart';
import 'package:http/http.dart' as http;
class TabsScreen extends StatefulWidget {
#override
_TabsScreenState createState() => _TabsScreenState();
}
class _TabsScreenState extends State<TabsScreen> {
List<Widget> _pages = [
HomeScreen(),
LoginScreen(),
LoginScreen(),
LoginScreen(),
];
var _isInit = true;
var _isLoading = false;
int _selectedPageIndex = 0;
bool _isSearching = false;
final searchController = TextEditingController();
Future<AppLogo> futureLogo;
Future<AppLogo> fetchMyLogo() async {
var url = BASE_URL + '/app_logo';
try {
final response = await http.get(url);
print(response.body);
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
print(response.body);
return AppLogo.fromJson(jsonDecode(response.body));
}
// print(extractedData);
} catch (error) {
throw (error);
}
}
#override
void initState() {
super.initState();
this.fetchMyLogo();
// Provider.of<Auth>(context).tryAutoLogin().then((_) {});
}
#override
void didChangeDependencies() {
if (_isInit) {
setState(() {
_isLoading = true;
});
final _isAuth = Provider.of<Auth>(context, listen: false).isAuth;
if (_isAuth) {
_pages = [
HomeScreen(),
MyCoursesScreen(),
MyWishlistScreen(),
AccountScreen(),
];
}
}
_isInit = false;
super.didChangeDependencies();
}
void _handleSubmitted(String value) {
final searchText = searchController.text;
if (searchText.isEmpty) {
return;
}
searchController.clear();
Navigator.of(context).pushNamed(
CoursesScreen.routeName,
arguments: {
'category_id': null,
'seacrh_query': searchText,
'type': CoursesPageData.Search,
},
);
// print(searchText);
}
void _selectPage(int index) {
setState(() {
_selectedPageIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
iconTheme: IconThemeData(
color: kSecondaryColor, //change your color here
),
title: !_isSearching
? FutureBuilder<AppLogo>(
future: fetchMyLogo(),
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.waiting) {
return Center(
child: Container(),
);
} else {
if (snapshot.error != null) {
return Center(
child: Text("Error Occured"),
);
} else {
return Image.network(
snapshot.data.darkLogo,
fit: BoxFit.contain,
height: 27,
);
}
}
},
)
: TextFormField(
decoration: InputDecoration(
labelText: 'Search Here',
prefixIcon: Icon(
Icons.search,
color: Colors.grey,
),
),
controller: searchController,
onFieldSubmitted: _handleSubmitted,
),
backgroundColor: kBackgroundColor,
actions: <Widget>[
IconButton(
icon: Icon(
Icons.search,
color: kSecondaryColor,
),
onPressed: () {
setState(() {
_isSearching = !_isSearching;
});
}),
],
),
body: _pages[_selectedPageIndex],
floatingActionButton: FloatingActionButton(
onPressed: () => _showFilterModal(context),
child: Icon(Icons.filter_list),
backgroundColor: kDarkButtonBg,
),
bottomNavigationBar: BottomNavigationBar(
onTap: _selectPage,
items: [
BottomNavigationBarItem(
backgroundColor: kBackgroundColor,
icon: Icon(Icons.school),
title: Text('Course'),
),
BottomNavigationBarItem(
backgroundColor: kBackgroundColor,
icon: Icon(Icons.shopping_basket),
title: Text('My Course'),
),
BottomNavigationBarItem(
backgroundColor: kBackgroundColor,
icon: Icon(Icons.favorite_border),
title: Text('Wishlist'),
),
BottomNavigationBarItem(
backgroundColor: kBackgroundColor,
icon: Icon(Icons.account_circle),
title: Text('Account'),
),
],
backgroundColor: kBackgroundColor,
unselectedItemColor: kSecondaryColor,
selectedItemColor: kSelectItemColor,
currentIndex: _selectedPageIndex,
type: BottomNavigationBarType.fixed,
),
);
}
}
There are some ways you can do to reduce the load time of the logo in your AppBar. Since this AppBar is common between the tabs, you should only load it once and avoid loading again every time the tab is changed.
First is to use StreamBuilder instead of FutureBuilder to reduce the number of loads.
// Create a StreamController
final _controller = StreamController<AppLogo>();
// Run fetchMyLogo() in your initState() like in your code
// In your fetchMyLogo(), add the result to the stream
fetchMyLogo() async {
// ... other lines
if (response.statusCode == 200) {
// If the server did return a 200 OK response,
// then parse the JSON.
var logo = AppLogo.fromJson(jsonDecode(response.body));
_controller.add(logo);
}
// Then, listen to this logo in your StreamBuilder
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
// ... other lines
title: !_isSearching
? StreamBuilder<AppLogo>(
stream: _controller.stream,
builder: (context, snapshot) {
// ... other lines
Second is to use the cached_network_image instead of Image.network, so that your logo image is cached, which reduce load time for network images.
return CachedNetworkImage(
imageUrl: snapshot.data.darkLogo,
fit: BoxFit.contain,
height: 27,
);
Small note: For each of the page in your _pages list, if you want the page to persist (not reload after every tab change), you can use the AutomaticKeepAliveClientMixin to persist the state of each page, or use IndexedStack like:
// The IndexedStack will load all pages initially
body: IndexedStack(
children: _pages,
index: _selectedPageIndex,
),
// ... other lines
I am managing state using provider but ChangeNotifierProvider does not change the value of variable.
I want to show progress indicator when user is registering. But ChangeNotifierProvider does not provide me update value rather it always return me _isLoading = false;
My Code:
AuthServices.dart
class AuthServices with ChangeNotifier {
bool _isLoading = false;
bool get loading => _isLoading;
///Register User
Future registerUser(
{#required String email, #required String password}) async {
try {
_isLoading = true;
notifyListeners();
await http.post(
BackEndConfigs.baseUrl +
BackEndConfigs.version +
BackEndConfigs.auth +
EndPoints.register,
body: {
"email": email,
"password": password
}).then((http.Response response) {
_isLoading = false;
notifyListeners();
return RegisterUser.fromJson(json.decode(response.body));
});
} catch (e) {
print(e);
}
}
}
RegisterScreen.dart
class RegisterScreen extends StatefulWidget {
#override
_RegisterScreenState createState() => _RegisterScreenState();
}
class _RegisterScreenState extends State<RegisterScreen> {
AuthServices _authServices = AuthServices();
ProgressDialog pr;
#override
Widget build(BuildContext context) {
pr = ProgressDialog(context, isDismissible: true);
// print(status.loading);
return Scaffold(
appBar:
customAppBar(context, title: 'Register Yourself', onPressed: () {}),
body: _getUI(context),
);
}
Widget _getUI(BuildContext context) {
return LoadingOverlay(
isLoading: false,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0),
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
VerticalSpace(10.0),
GreyBoldText('Account Information'),
VerticalSpace(20.0),
IconsButtonRow([
Icon(
FontAwesomeIcons.linkedinIn,
color: Color(0xff0e76a8),
),
Icon(
FontAwesomeIcons.facebook,
color: Color(0xff3b5998),
),
Icon(
FontAwesomeIcons.google,
color: Color(0xff4285F4),
),
]),
VerticalSpace(10.0),
GreyNormalText('or sign up with Email'),
VerticalSpace(10.0),
BlackBoldText("Email"),
AppTextField(
label: 'Email',
),
BlackBoldText("Password"),
AppTextField(
label: 'Password',
),
BlackBoldText("Confirm Password"),
AppTextField(
label: 'Confirm Password',
),
VerticalSpace(20.0),
ChangeNotifierProvider(
create: (_) => AuthServices(),
child: Consumer(
builder: (context, AuthServices user, _) {
return Text(user.loading.toString());
},
),
),
AppButton(
buttonText: 'Next',
onPressed: () async {
// await pr.show();
_registerNewUser();
}),
VerticalSpace(30.0),
ToggleView(ToggleViewStatus.SignUpScreen),
VerticalSpace(20.0),
],
),
),
),
);
}
_registerNewUser() async {
_authServices.registerUser(
email: 'sdjfkldsdf#ssdfdsddfdgsffd.com', password: 'sldjsdfkls');
}
}
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
scaffoldBackgroundColor: Colors.white,
hintColor: FrontEndConfigs.hintColor,
cursorColor: FrontEndConfigs.hintColor,
fontFamily: 'Gilory'),
home: RegisterScreen(),
);
}
}
You created two different instances of AuthServices.
One at the start of your State class and one using the ChangeNotifierProvider.
When calling _registerNewUser you use the AuthServices created in your state class not the provided one.
When you call registerUser on the first AuthServices, the value does not change for the second AuthServices provided by the ChangeNotifierProvider down in the Widget tree.
Try deleting the AuthServices instance created by the state class and move your ChangeNotifierProvider up the widget tree so all your functions share the same instance of AuthServices.
After a user successfully logins in and saving the state, I want to hide the login screen and just load the home screen, but I end up with the error
The following assertion was thrown building
Navigator-[GlobalObjectKey
_WidgetsAppState#6686e](dirty, dependencies: [UnmanagedRestorationScope, HeroControllerScope], state:
NavigatorState#c7e9f(tickers: tracking 1 ticker)):
'package:flutter/src/widgets/navigator.dart': Failed assertion: line
5070 pos 12: '': is not true.
What is the right way of hiding the login screen when the token is still valid, and just load the home screen?
my code
Main.dart
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'What',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
scaffoldBackgroundColor: Palette.scaffold,
),
// home: SignIn(),
routes: {
//Homepage and being controled by PagesProvider
'/': (context) => SignIn(),
'nav': (context) => NavScreen(),
// add all routes with names here
},
);
}
}
my signin.dart
class SignIn extends StatefulWidget {
const SignIn({Key key}) : super(key: key);
#override
_SignInState createState() => _SignInState();
}
class _SignInState extends State<SignIn> {
ProgressDialog progressDialog;
MsalMobile msal;
bool isSignedIn = false;
bool isLoading = true;
#override
void initState() {
super.initState();
MsalMobile.create('assets/auth_config.json', authority).then((client) {
setState(() {
msal = client;
});
refreshSignedInStatus();
});
}
/// Updates the signed in state
refreshSignedInStatus() async {
bool loggedIn = await msal.getSignedIn();
if (loggedIn) {
isSignedIn = loggedIn;
if (isSignedIn) {
dynamic data = await handleGetAccount();
dynamic token = await handleGetTokenSilently();
dynamic result = token;
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
sharedPreferences.get("username");
sharedPreferences.get("token");
print('access token (truncated): ${result.accessToken}');
Navigator.of(context).pop();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => NavScreen(),
),
);
}
// Remaining code for navigation
}
}
/// Gets a token silently.
Future<dynamic> handleGetTokenSilently() async {
String authority = "https://login.microsoftonline.com/$TENANT_ID";
final result = await msal.acquireTokenSilent([SCOPE], authority);
if (result != null) {
// print('access token (truncated): ${result.accessToken}');
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
sharedPreferences.setString("token", result.accessToken);
return result;
} else {
print('no access token');
return null;
}
}
/// Signs a user in
handleSignIn() async {
await msal.signIn(null, [SCOPE]).then((result) {
// ignore: unnecessary_statements
refreshSignedInStatus();
}).catchError((exception) {
if (exception is MsalMobileException) {
logMsalMobileError(exception);
} else {
final ex = exception as Exception;
print('exception occurred');
print(ex.toString());
}
});
}
logMsalMobileError(MsalMobileException exception) {
print('${exception.errorCode}: ${exception.message}');
if (exception.innerException != null) {
print(
'inner exception = ${exception.innerException.errorCode}: ${exception.innerException.message}');
}
}
/// Signs a user out.
handleSignOut() async {
try {
print('signing out');
await msal.signOut();
print('signout done');
refreshSignedInStatus();
} on MsalMobileException catch (exception) {
logMsalMobileError(exception);
}
}
/// Gets the current and prior accounts.
Future<dynamic> handleGetAccount() async {
// <-- Replace dynamic with type of currentAccount
final result = await msal.getAccount();
if (result.currentAccount != null) {
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
sharedPreferences.setString("username", result.currentAccount.username);
//print(result.currentAccount.username);
return result.currentAccount;
} else {
print('no account found');
return null;
}
}
#override
Widget build(BuildContext context) {
progressDialog = ProgressDialog(context, type:ProgressDialogType.Normal, isDismissible: false, );
return MaterialApp(
home: new Scaffold(
body: Builder(
builder: (context) => Stack(
fit: StackFit.expand,
children: <Widget>[
Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Image.asset('assets/landing.webp',
fit: BoxFit.fill,
color: Color.fromRGBO(255, 255, 255, 0.6),
colorBlendMode: BlendMode.modulate),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
SizedBox(height: 10.0),
Container(
width: 130.0,
child: Align(
alignment: Alignment.center,
child: RaisedButton(
shape: RoundedRectangleBorder(
borderRadius: new BorderRadius.circular(30.0)),
color: Color(0xffffffff),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Icon(
FontAwesomeIcons.microsoft,
color: Color(0xFF01A6F0),
),
// Visibility(
// visible: !isSignedIn,
SizedBox(width: 10.0),
Text(
'Sign in',
style: TextStyle(
color: Colors.black, fontSize: 18.0),
),
// child: RaisedButton(
// child: Text("Sign In"),
// onPressed: handleSignIn,
// ),
// ),
],
),
onPressed: () => {
progressDialog.show(),
handleSignIn(),
progressDialog.hide()
})),
)
],
),
],
),
),
));
}
}
you should Navigate to the homePage only if the login is successful
because Naviagation.Pop is Equal to the Back button and user can do it manually
here is a better approach :
in main.dart add this :
routes: {
Homepage and being controled by PagesProvider
'nav': (context) => NavScreen(),
'home': (context) => HomePage(),
// add all routes with names here
},
in your refreshSignedInStatus() :
remove this :
Navigator.of(context).pop();
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => NavScreen(),
),
);
add this :
Navigator.pushNamed(context, 'nav');
I have a few textfields with hint text and bottom navigation. Text entered into the textfield (in Page 1) will be saved to shared preferences "on changed".
When I click on the bottom navigation next page (Page 2) and back to Page 1 again, it seems like the widget rebuild and will show the hintText before shared preference stored data display on the widget.
I have tried to get the sharedpreference data during initState but it does not work. I have also tried to use future builder however when I typed the value in the TextField is not that smooth, sometimes the text would flicker between the characters before and after. I am not sure which method should I use or whether is my my coding wrong.
Could someone advise which method should I use?
Thanks in advance!
login.dart
#override
Widget build(BuildContext context){
return new Scaffold(
appBar: new AppBar(
title: Text('Login Page'),
),
body: bottomNav[currentBottomNavIndex],
bottomNavigationBar: BottomNavigationBar(
onTap: onTapped,
currentIndex: currentBottomNavIndex,
type: BottomNavigationBarType.fixed,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text("Page1"),
),
BottomNavigationBarItem(
icon: Icon(Icons.mail),
title: Text('Page2'),
),
],
),
);
}
page1.dart
class Page1 extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _Page1State();
}
}
class _Page1State extends State<Page1> {
TextEditingController name = TextEditingController();
String name_str;
Future<String> getName(String key) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
name_str = prefs.getString(key);
setState(() {
name = new TextEditingController(text: name_str);
});
return name_str;
}
Future<bool> setName(String key, String value) async {
final SharedPreferences prefs = await SharedPreferences.getInstance();
return prefs.setString(key, value);
}
#override
void initState() {
super.initState();
getName('name');
}
#override
Widget build(BuildContext context) {
//return FutureBuilder(
//future: getName('name'),
//builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
// if (snapshot.hasData) {
return Container(
margin: EdgeInsets.all(100.0),
width: 185,
child: Center(
child: TextField(
controller: name,
textAlign: TextAlign.center,
decoration: new InputDecoration(
hintText: "Name (Original)",
),
onTap:() {
name.clear();
setName('name', '');
},
onChanged: (String str) {
setState(() {
name_str = str;
setName('name', str);
});
},
)
)
);
// }else{
// return Container();
// }
// }
//);
}
}
Page2.dart
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text('Page2'),
),
);
}
}
I think the problem resides here
setState(() {
name = new TextEditingController(text: name_str);
});
doing this
new TextEditingController(text: name_str);
will create a new instance instead just update the value of name controller using shared preferences.
setState(() {
name.text=name_str;
});
This should work for you