I have an app that starts in the login page as initial route. I want to check if the users' token is still valid, and if it is, I want to go directly to another route instead of the login page.
I have all the server logic and am making a get request that returns a boolean to say if the user session(token) is valid.
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
var initRoute;
if (_isTokenValid() as bool){
initRoute = '/login';
}else{
initRoute = '/main';
}
return GestureDetector(
onTap: () {
FocusScopeNode currentFocus = FocusScope.of(context);
if (!currentFocus.hasPrimaryFocus &&
currentFocus.focusedChild != null) {
FocusManager.instance.primaryFocus.unfocus();
}
},
child: MaterialApp(
title: 'Mappin',
theme: Themes.lightAppTheme,
darkTheme: Themes.darkAppTheme,
initialRoute: initRoute,
routes: {
'/main': (context) => NavBar(),
'/profile': (context) => ProfilePage(),
'/settings': (context) => SettingsPage(),
'/messageDetail': (context) => MessageDetail(),
'/login': (context) => LoginPage(),
},
),
);
}
Future<bool> _isTokenValid() async {
// in this function we can pass the controlers directly bcs they are declared in the class. In login they are declared inside each widget (login/signup)
final isValid = await APIService().isAccessValid();
if (!isValid){
return await APIService().isRefreshValid();
}
return isValid;
}
}
The Problem with this code is that casting Future<bool> to bool is invalid. In other pages I would set the state of a variable, but since app is a stateless widget, this is not possible.
What is a good way of receiving a boolean from the GET request (async function) and changing the initial route according to its value?
What is being received from the APIService? That might be why you are getting that error.
Try adding a toggle breakpoint on
Future<bool> _isTokenValid() async {
To see where the problems from.
FutureBuilder can help you to get a callback
FutureBuilder(
// get the Provider, and call the _isTokenValid method
future: _isTokenValid(),
// wait for the future to resolve and render the appropriate
// widget for HomePage or LoginPage
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return snapshot.hasData ? NavBar() : LoginPage();
} else {
return CircularProgressIndicator();
}
},
),
Related
I want to load a static list data when entering indexScreen,but the list sometimes show twice the same content,sometimes not.
This is my list setting:List<ListClass> listItems=List<ListClass>();,ListClass is a simple class with on different attributes and a constructor.
I use home:IndexScreen() in main.dart to show Index page.
return MaterialApp(
home: IndexScreen(),
debugShowCheckedModeBanner: false,
onGenerateRoute: router.generator,
builder: EasyLoading.init(),
);
And before this page build,it will update listItems using:
Future<bool> initUserAndIndex() async{
if (curUserEmail==null) sharedGetData(USER_EMAIL).then((value) => curUserEmail=value.toString());
print(curUserEmail);
await UserTable().getUserInfo(curUserEmail).then((value){print("user ok");});
await CollectionTable().getIndexList().then((value){print("Collection ok");return true;});
return null;
}
buildPage:
#override
Widget build(BuildContext context) {
return FutureBuilder<Object>(
future: initUserAndIndex(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState==ConnectionState.waiting)
{
EasyLoading.show(status: 'loading...');
// avoid no return,this cause a whiteborad transition,I don't know how to solve it,too.
return Container();
}
else
{
EasyLoading.dismiss();
return SafeArea(
child: Scaffold(
// the listItems is used in Body()
body: Body(),
),
);
}
},
);
}
}
I run this app,and it prints twice user ok and Collection ok.But when I use ROUTER.NAVIGETE,it only prints once.
User Information is OK,but the list is such a great problem--the page shows twice content
I put my code at an order of relevance of this prblom,I think.Next I put my the two awaited funtion here:
User:
Future<bool> getUserInfo(String userEmail) async{
await userCollection.where({'userEmail':userEmail}).get().then((res) async {
//assign to the static variables
return true;
});
return null;
}
Collection:
Future<bool> getIndexList() async {
listItems.clear();
await listCollection.get().then((value){
var v = value.data;
for (var data in v) {
//get data and package them,add after the listItems list.
listItems.add(ListClass(header, content, userId, favorCount, wordCount));
}
return true;
});
}
You probably want to assign your future in your widget class, but not in the build method as the documentation show, otherwise, everytime your build method is triggered, it will call again your FutureBuilder.
final Future<String> _calculation = Future<String>.delayed(
const Duration(seconds: 2),
() => 'Data Loaded',
);
#override
Widget build(BuildContext context) {
return DefaultTextStyle(
style: Theme.of(context).textTheme.headline2!,
textAlign: TextAlign.center,
child: FutureBuilder<String>(
future: _calculation, // a previously-obtained Future<String> or null
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
// ...
}
),
),
}
When my app starts up, it detects whether the user is logged in or not (with firebase) and based on this check, it shows the homepage or the login page. Up to now everything is fine, but I would like to add one more layer.
The user can login as normal user or as admin.
So the check should be not only on the authentication state, but also on the "level" of the user, and show different pages, based on the user level.
I get the user level with a query on the firestore database, so it's a Future.
This is the code i'm using:
final usersCollection = FirebaseFirestore.instance.collection('users');
User loggedUser = FirebaseAuth.instance.currentUser;
Future<InfoUtente> userInfo;
String livelloLogin;
// here I get the user from the firestore database, based on the authenticated user id
Future<InfoUtente> fetchInfoUtente() async {
final response = await usersCollection
.where(
'uid',
isEqualTo: loggedUser.uid,
)
.get();
return InfoUtente.fromFireStore(response.docs.first);
}
// here I return the page based on the user authentication "level"
Future<Widget> widgetChoice() async {
if (!isLogged)
return LoginNewPage();
else {
userInfo.then(
(value) {
livelloLogin = value.loginLevel;
if (livelloLogin == 'struttura')
return StrutturaPage();
else
return MainPage();
},
);
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
// the homepage of the material app is a future builder
home: FutureBuilder(
future: widgetChoice(),
builder: (BuildContext context, AsyncSnapshot<Widget> widget) {
if (!widget.hasData) {
return Center(
child: CircularProgressIndicator(),
);
}
return widget.data;
},
),
);
}
something is not right because it always shows the circular progress indicator.
What am I doing wrong?
Is this the correct way of doing this or am I completely wrong?
If there is no data fetched or found, your screen will stuck on loading infinitely. Update your builder's implementation as
builder: (BuildContext context, AsyncSnapshot<Widget> widget) {
if(widget.connectionState == ConnectionState.done){
if (!widget.hasData) {
return Center(
child: Text('No Data exists')
);
}
return widget.data;
}
return Center(
child: CircularProgressIndicator(),
);
},
And update your widgetChoice as
Future<Widget> widgetChoice() async {
if (!isLogged)
return LoginNewPage();
else {
var userInfo = await fetchInfoUtente();
livelloLogin = userInfo.loginLevel;
if (livelloLogin == 'struttura')
return StrutturaPage();
else
return MainPage();
}
}
If i'm right you have to call the future function like that:
FutureBuilder(
future: widgetChoice,
Without ()
I implemented Authentication by provider
The problem is when is the first time myHomeCalss is notified that the user is Authenticated by dont return the correctPage (MainGui)
SplashPages is page with a button continue, and push the login page ,
The Login page is pushed outside of costumer
but when I dont pass in the SplashPages is worked perfectyl
any adea please
//splash page
ContinueButton(
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(
builder: (_) =>
ListenableProvider.value(
value: yourModel,
child: LoginPage(),
),
),
);
}
)
//main
void main() async {
setupLocator();
WidgetsFlutterBinding.ensureInitialized();
await firebase_core.Firebase.initializeApp();
runApp(
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => AuthenticationService()),
],
child: MyApp(),
),
);
}
//My app
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: MyHome(),
builder: (context, child) => Navigator(
key: locator<DialogService>().dialogNavigationKey,
onGenerateRoute: (settings) => MaterialPageRoute(
builder: (context) => DialogManager(child: child)),
));
}
}
MyHome
Class MyHome extends StatelessWidget {
#override
Widget build(BuildContext context) {
return SafeArea(
child: FutureBuilder<bool>(
future: startTime(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot2) {
if (snapshot2.hasData) {
if (snapshot2.data) {
return SplashPages();
} else {
return Consumer<AuthenticationService>(builder: (_, auth, __) {
if (auth.currentUserr == null) {
return LoginPage();
} else {
return FutureBuilder(
future: auth.populateCurrentUser(auth.currentUserr),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (auth.currentUserr.emailVerified) {
return MainGui();
} else {
return ValidationMailPage(
email: auth.currentUserr.email,
);
}
} else
return Container(
// child: Center(
// child: SpinKitRotatingCircle(
// color: Colors.white,
// size: 50.0,
// ))
);
});
}
});
}
}
You may consider using SharedPreferences, in which you will store the user (or maybe just the token), and then check in main if there is a token/user stored there before rendering the app; if there is a token you log in and then push to the homepage, if not you navigate directly to the login page.
SharedPrefenreces is persisted data storage that persists even if you restart the app, but Provider is a state management solution that doesn't persist between app restarts.
Here is the SharedPreferences plugin you may use.
I have been struggling with this problem for like two days. My social media app should save its state, when signed in so that when you leave the app and come back again it should start from the home page, not the sign in page. I have found that it is possible to do this with StreamBuilder and FutureBuilder. I have tried some things with FutureBuilder and I have some errors.
Below is how my main page looks like:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => UserData(),
child: MaterialApp(
title: 'Curtain App',
debugShowCheckedModeBanner: false,
home: FutureBuilder(
future: SharedPreferencesHelper.getPrefs(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasData) {
Provider.of<UserData>(context).currentUserId =
snapshot.data.token;
return HomeScreen();
} else {
return LoginScreen();
}
},
),
),
);
}
}
class SharedPreferencesHelper {
static final String _tokenCode = "token";
static Future<String> getPrefs() async {
final SharedPreferences preferences = await SharedPreferences.getInstance();
return preferences.getString(_tokenCode) ?? "empty";
}
}
And this is my LoginPage submit btn code:
_submit() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
// logging in the user w/ Firebase
//AuthService.login(_email, _password);
var user = await DatabaseService.loginUser(_username, _password);
final data = json.decode(user);
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
print("Hi ${data['username']}");
print("Status ${data['status']}");
print("Token ${data['token']}");
if (data['username'] != null) {
setState(() {
_message = "Hi ${data['username']}";
sharedPreferences.setString('token', data['token']);
});
Navigator.of(context).pushAndRemoveUntil(
CupertinoPageRoute(
builder: (context) => HomeScreen(),
),
(Route<dynamic> route) => false);
}
}
}
Any ideas on how to solve this ?
Just remove the .token from the line where the error occurs. snapshot.data already is the token.
I want to switch between the login screen and Home screen based on bool value(user.status) from the model class below
class User extends ChangeNotifier {
int phoneNumber;
bool status = false;
notifyListeners();
}
The bool User.status value is flipped from below function
User _user = Provider.of<User>(context);
...
...
if (form.validate()) {
_user.status = true;
}
The below function has to listen to the changes in the status value from the User model and change the screen to Home().
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
User authStatus = Provider.of<User>(context);
return authStatus.status ? Home() : Auth();
}
}
I don't have any errors, all the values are updating accordingly but the Wrapper() is not being rebuilt after listening to the changes from ChangeNotifier
Here's how I do it with Provider :
routes: {
"/": (context) => MainPage(),
"/detail": (context) => UserDetailPage(),
},
builder: (context, child) {
return Consumer<UsersProvider>(
child: child,
builder: (context, provider, child) {
final value = provider.user;
if (!value.status) {
return Navigator(
onGenerateRoute: (settings) => MaterialPageRoute(
settings: settings, builder: (context) => LoginPage()),
);
}
return MultiProvider(
providers: [
ChangeNotifierProvider(create: (context) => UsersProvider()),
ChangeNotifierProvider(
create: (context) => InvoicesProvider()),
ChangeNotifierProvider(create: (context) => EventsProvider()),
],
child: child,
);
},
);
},
Basically use builder in main.dart and defines routes, then inside builder use Consumer were child is the initial route MainPage() so if the user already login they will go there, and if not, base on status they will redirect to LoginPage(). I hope you can understand feel free to comment