Opening a screen out the result of a statement - flutter

enter code hereI want to open a screen to add extra information if it is not set yet. So after the user is logged in I check if the extra info is set. If not I want it to go to a screen to fill in the info. If the user is done it should go to a "Homescreen". If the user info is already set it should immediately go to the home screen.
I already tried to just go to the extra info form and then Navigator.push to the home screen but then it has difficulties with logging out. I searched for a long time but can not find anything.
class CampPage extends StatelessWidget {
final String email;
final String uid;
const CampPage({super.key, required this.email, required this.uid});
#override
Widget build(BuildContext context) {
return FutureBuilder(
// ignore: unrelated_type_equality_checks
future: context.read<UserProvider>().exists(uid) == true
? null
: Future.delayed(Duration.zero, () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NewUserPage(email: email, userId: uid),
),
);
}),
builder: (context, snapshot) => Scaffold(
drawer: const DrawerHomePage(),
appBar: AppBar(
title: const Text("Camp Page"),
),
body: Column(
children: const [
Text("nieuwe features"),
],
),
),
);
}
}
this is one of the things I try but the NewUserPage always pops up and I only want it to pop up if context.read<UserProvider>().exists(uid) == false
also the solution mentioned does not work for me. I think because there is a screen in between the login and logout (The form screen) the logout function does not work properly.
`
class UserPage extends StatelessWidget {
const UserPage({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
child: const Text("Submit"),
onPressed: () {
//Log out of Firestore Authentication
},
),
);
}
}
class NewForm extends StatelessWidget {
const NewForm({super.key});
#override
Widget build(BuildContext context) {
return Scaffold(
body: ElevatedButton(
child: const Text("Submit"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => const UserPage()),
);
},
),
);
}
}
Widget build(BuildContext context) {
return FutureBuilder(
future: context.read<UserProvider>().exists(uid)
builder: (context, snapshot) {
if (snapshot.hasdata){
if (snapshot.data == true) {
return const UserPage();
} else {
return const NewForm();
}
}
else // show a proggress bar
}
);
}
`
Does someone still have another solution?

I think you should do this:
Widget build(BuildContext context) {
return FutureBuilder(
future: context.read<UserProvider>().exists(uid)
builder: (context, snapshot) {
if (snapshot.hasdata){
if (snapshot.data == true) // then the user exist
else // the user doesn't exist
}
else // show a proggress bar
}
);
}

Related

Check content function (Flutter)

I use the following function to check and display the content either in Dialog or Bottom Sheet, but when executing it does not work properly, as it displays both together, what is the reason and how can the problem be solved?
Is it possible to suggest a better name for the function?
Content function:
content(BuildContext context, dynamic dialog, dynamic bottomSheet) {
(MediaQuery.of(context).orientation == Orientation.landscape) ? dialog : bottomSheet;
}
Implementation:
ElevatedButton(
child: Text('Button'),
onPressed: () {
content(context, dialog(context), bottomSheet(context));
},
),
How can this be solved?
In order to determine the Orientation of the screen, we can use the OrientationBuilder Widget. The OrientationBuilder will determine the current Orientation and rebuild when the Orientation changes.
void main() async {
runApp(const Home(
));
}
class Home extends StatefulWidget {
const Home({Key key}) : super(key: key);
#override
State<Home> createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return MaterialApp(home: Scaffold(
body: Center(
child: OrientationBuilder(
builder: (context, orientation) {
return ElevatedButton(
child: Text('Button'),
onPressed: () {
revealContent(orientation,context);
},
);
},
)
),
));
}
revealContent(Orientation orientation, BuildContext context) {
orientation == Orientation.landscape ? dialog(context) : bottomSheet(context);
}
dialog(BuildContext context){
showDialog(
context: context,
builder: (context) => const Dialog(
child: Padding(
padding: EdgeInsets.all(20.0),
child: Text('test'),
),
)
);
}
bottomSheet(final BuildContext context) {
return showModalBottomSheet(
context: context,
isScrollControlled: true,
builder: (builder) => const Padding(
padding: EdgeInsets.all(20.0),
child: Text('test'),
),
);
}
}
here are screenshots:
happy coding...
The reason the function is not working properly is because you're not actually showing the dialog or bottom sheet. To show the dialog or bottom sheet, you need to call showDialog or showModalBottomSheet, respectively, and pass them the result of calling dialog or bottomSheet.
try this
void revealContent(BuildContext context, Widget dialog, Widget bottomSheet) {
(MediaQuery.of(context).orientation == Orientation.landscape)
? showDialog(context: context, builder: (context) => dialog)
: showModalBottomSheet(context: context, builder: (context) => bottomSheet);
}
You have a fundamental misunderstanding as to what your code is doing.
Take your "Implementation" and revealContent code, for example:
ElevatedButton(
child: Text('Button'),
onPressed: () {
revealContent(context, dialog(context), bottomSheet(context));
},
),
revealContent(BuildContext context, dynamic dialog, dynamic bottomSheet) {
(MediaQuery.of(context).orientation == Orientation.landscape) ? dialog : bottomSheet;
}
You think that revealContent will invoke either dialog or bottomSheet based on the orientation of the screen. What you are actually doing, however, is you are invoking both of them and then passing the result of the invocations to revealContent, which isn't actually doing anything with them.
What you need to be doing is passing the functions as callbacks to revealContent and then invoking the callbacks within the function:
ElevatedButton(
child: Text('Button'),
onPressed: () {
revealContent(context, () => dialog(context), () => bottomSheet(context));
},
),
revealContent(BuildContext context, void Function() dialog, void Function() bottomSheet) {
if (MediaQuery.of(context).orientation == Orientation.landscape) {
dialog()
} else {
bottomSheet();
}
}
You should be calling showDialog and showModalBottomSheet inside revealContent.
Dialog
dialog(BuildContext context){
return Dialog( //.. );
}
BottomSheet
bottomSheet(final BuildContext context) {
return Widget( /.. );
}
Reveal Content
void revealContent(BuildContext context, Widget dialog, Widget bottomSheet) {
if (MediaQuery.of(context).orientation == Orientation.landscape) {
return showDialog(context: context, builder: (context) => dialog);
} else {
return showModalBottomSheet(context: context, builder: (context) => bottomSheet);
}
}

How to return a widget if I need to go to a new screen?

Why do this code in not properly set up? I get the error: This function has a return type of 'Widget', but doesn't end with a return statement.
Obviously, it doesn like the use of Navigator stuff in future builder. How to make it properly?
MaterialApp(
home: const Splash1(),
);
class Splash1 extends StatelessWidget {
const Splash1({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<bool>(
future: checkIsSeen(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData) {
if (snapshot.data == true) {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const HomeView()),
);
} else {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const IntroScreen()),
);
}
} else if (snapshot.hasError) {
return const Icon(
Icons.error_outline,
size: 60,
);
} else {
return CircularProgressIndicator();
}
}),
);
}
There is a statement about your issue (Obviously, it does not like the use of Navigator stuff in the future builder.). Future.builder shouldn't include logic beyond building widgets (e.g. don't call Navigator.push).
Instead of FutureBuilder, you can just put the async call in build().
Widget build(BuildContext context) {
check().then((success) {
if (success) {
Navigator.pushReplacementNamed(context, '/home');
} else {
Navigator.pushReplacementNamed(context, '/login');
}
});
You can learn more about this issue at this link: https://github.com/flutter/flutter/issues/16218
Since HomeView and IntroScreen both contain a Scaffold, you can reorganise your code like this, without using Navigator in the build method:
class Splash1 extends StatelessWidget {
const Splash1({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: checkIsSeen(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData) {
return snapshot.data ? const HomeView() : const IntroScreen();
} else {
return Scaffold(
body: snapshot.hasError
? const Icon(
Icons.error_outline,
size: 60,
)
: const CircularProgressIndicator());
}
});
}
}

Flutter rebuild parent screen with provider on navigator push

I want to create a license validation system for my application in order to activate it if a license is entered or already present.
If when starting the application the license does not exist then I display a page which allows me to scan it by QR code.
If the scanned license is valid then I push a success page with a button on it that allows me to unlock the application. When I click on this button I want to close my success page and rebuild my application with the homepage of the application unlocked.
This works up to the success page. When I click on my button to unblock the app, I can't rebuild the parent page with the unblock app.
MyApp: This change choose the screen if I have a license or not.
class AppScreen extends StatelessWidget {
const AppScreen({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Impact',
debugShowCheckedModeBanner: false,
theme: AppTheme().data,
home: ChangeNotifierProvider<LicenseNotifier>(
create: (BuildContext context) => LicenseNotifier(),
child: Consumer(
builder: (context, LicenseNotifier license, _) {
return license.showScreenWithLicenseState();
},
),
),
);
}
}
My license notifier:
class LicenseNotifier with ChangeNotifier {
LicenseState state = LicenseState.Uninitialized;
String getLicenseInApp;
String licenseScanned;
String personnalKey;
final String _scanBarColor = "#f57873";
LicenseNotifier(){
personnalKey = "1010";
_checkIfAppIsUnlocked();
}
Future<void> _checkIfAppIsUnlocked() async {
if (getLicenseInApp != null) {
state = LicenseState.Authenticated;
}
else{
state = LicenseState.Unauthenticated;
}
notifyListeners();
}
Future scanQRCode(BuildContext context) async{
await FlutterBarcodeScanner.scanBarcode(_scanBarColor, "Cancel", true, ScanMode.QR).then((value) {
licenseScanned = value.toString();
});
licenseValidator(context);
}
void licenseValidator(BuildContext context){
if(licenseScanned == personnalKey){
showSuccessLicenseScreen(context);
}
notifyListeners();
}
void showSuccessLicenseScreen(BuildContext context){
Navigator.push(context, MaterialPageRoute(
builder: (context) => ChangeNotifierProvider<LicenseNotifier>(
create: (BuildContext context) => LicenseNotifier(),
child: LicenseSuccessScreen(title: "Valid license")
),
));
}
activateApp(BuildContext context) {
Navigator.pop(context);
state = LicenseState.Authenticated;
notifyListeners();
}
showScreenWithLicenseState(){
switch (state) {
case LicenseState.Uninitialized:
return SplashScreen();
break;
case LicenseState.Unauthenticated:
case LicenseState.Authenticating:
return LicenseActivationScreen(title: "Activate license");
break;
case LicenseState.Authenticated:
return ChangeNotifierProvider<BottomNavigationBarNotifier>(
create: (BuildContext context) => BottomNavigationBarNotifier(),
child: NavigationBarScreen(),
);
break;
}
return null;
}
}
My success screen: When I scan a valid license
class LicenseSuccessScreen extends StatelessWidget{
final String title;
const LicenseSuccessScreen({Key key, this.title}) : super(key: key);
#override
Widget build(BuildContext context)
{
return GestureDetector(
onTap: (() => FocusScope.of(context).requestFocus(FocusNode())),
child: Scaffold(
appBar: AppBar(
backgroundColor: AppColors.colorContrastGreen,
elevation: 0,
centerTitle: true,
title: Text(title),
),
body : _buildBody(context),
),
);
}
Widget _buildBody(BuildContext context)
{
var _licenseProvider = Provider.of<LicenseNotifier>(context);
return Container(
color: AppColors.colorContrastGreen,
padding: EdgeInsets.symmetric(horizontal: AppUi.RATIO * 5),
height: double.infinity,
child: Column(
children: [
ContainerComponent(
background: AppColors.colorBgLight,
alignement: CrossAxisAlignment.center,
children: [
ButtonComponent.primary(
context: context,
text: "Débloquer",
onPressed: () async{
_licenseProvider.activateApp(context);
},
),
],
),
],
),
);
}
}
So when I click on my button who call "activateApp" in notifier, the success screen closes but I haven't my application content. It just show the LicenseActivationScreen. How to resolve this problem ?

Auto navigation on login page in flutter app

My app needs to automatically initiate biometric login and navigate to a page based on outcome.
This is a common need and I followed advice similar to this solution
Code below
class _LoginPageState extends State<LoginPage> {
#override
Widget build(BuildContext context) {
final UserModel userModel = Provider.of<UserModel>(context);
return (userModel.biometricLoginEnabled && !userModel.isAuthenticated)
? _attemptBiometricAuthentication(context, userModel)
: _buildLoginForm(context, userModel);
}
Widget _attemptBiometricAuthentication(
BuildContext context, UserModel userModel) {
return FutureBuilder(
future: _initiateBiometricAuthentication(),
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.data == true) {
// make sure user is marked as authenticated
userModel.setAuthenticationWithoutNotification(true);
return HomePage(); // <-- WHOA!!
} else if (snapshot.hasData && snapshot.data == false) {
// we should have an updated error from _initiateBiometricAuthentication
return _buildLoginForm(context, userModel);
} else if (snapshot.hasError) {
return _buildLoginForm(context, userModel);
} else {
// we're waiting
return Container(
constraints: BoxConstraints(
maxHeight: MediaQuery.of(context).size.height,
minHeight: MediaQuery.of(context).size.height,
),
alignment: Alignment.center,
child: SafeArea(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Image(
image: AssetImage('images/Logo.png'),
fit: BoxFit.fitWidth,
),
CircularProgressIndicator(),
],
),
),
);
}
},
);
}
}
The problem is with the line to return HomePage() if authentication succeeds.
If there is a call to setState() and a rebuild occurs I have the HomePage being rebuilt inside LoginPage. Routing is also a little messed up because the app thinks it's on route /login but its actually on /home.
I feel like I'm missing something entirely in triggering routing automatically.
You need to listen result from the Future method and navigate to other Page. (never do it inside build Widget).
Demo:
Code example:
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
class FutureNavigation extends StatefulWidget {
#override
_FutureNavigationState createState() => _FutureNavigationState();
}
class _FutureNavigationState extends State<FutureNavigation> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo Future Navigator"),
),
body: buildBody(context),
);
}
Widget buildBody(BuildContext context) {
return FutureBuilder(
future: _login(),
builder: (context, snapshot) {
return Center(child: CircularProgressIndicator());
},
);
}
Future<String> _login() async {
await Future.delayed(Duration(seconds: 3)).then((value) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
});
return "Logined";
}
}

Flutter screen navigating back when updating firestore document

I'm have successfully displayed list of users in ListView using StreamBuilder. But when I'm updating user document in firestore, screen in my mobile app is automatically navigating back.
This is my screens flow. Login -> Home -> ManageUsers -> UserDetails.
By using below code, I created a list in Manage Users screen. Now I'm trying to update user first name in firebase console. After updating the data ManageUsers screen is closing.
Screen Rec
Widget build(BuildContext context) {
return new StreamBuilder(
stream: Firestore.instance.collection('users').snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData)
return Center(
child: CircularProgressIndicator(),
);
if (snapshot.hasError)
return Center(child: new Text('Error: ${snapshot.error}'));
final int itemsCount = snapshot.data.documents.length;
switch (snapshot.connectionState) {
case ConnectionState.none:
// TODO: Handle this case.
return new CircularProgressIndicator();
break;
case ConnectionState.waiting:
// TODO: Handle this case.
return new CircularProgressIndicator();
break;
default:
return new ListView.builder(
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: itemsCount,
addAutomaticKeepAlives: true,
itemBuilder: (BuildContext context, int index) {
final DocumentSnapshot document =
snapshot.data.documents[index];
return new ListTile(
title: new Text(document['first_name']),
subtitle: new Text(document['last_name']),
onTap: () => {openUserDetailsScreen(document, context)},
);
},
);
}
},
);
}
Actually it should refresh the data in the same screen instead of navigating back. Am I doing anything wrong in building the list.
Home Screen Code
class HomeScreen extends StatefulWidget {
HomeScreen({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomeScreenState createState() => _MyHomeScreenState();
}
class _MyHomeScreenState extends State<HomeScreen> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
automaticallyImplyLeading: false,
leading: Builder(
builder: (context) =>
IconButton(
icon: new Icon(Icons.menu),
onPressed: () => Scaffold.of(context).openDrawer(),
),
),
),
drawer: MyDrawer(widget.title),
body: Center(
child: Text("Home Screen"),
),
);
}
#override
void initState() {
print('in home page ${globals.loggedInUser.firstName}');
}
}
From home page I'm navigating to ManageUsers screen via drawer. Here is the code for drawer.
class MyDrawer extends StatelessWidget {
MyDrawer(this.currentPage);
final String currentPage;
bool isAdmin = true;
#override
Widget build(BuildContext context) {
var currentDrawer = Provider.of<DrawerStateInfo>(context).getCurrentDrawer;
return Drawer(
child: ListView(
children: <Widget>[
_CustomListTile(
currentPage, globals.HOME_MENU_TITLE, currentDrawer),
_CustomListTile(
currentPage, globals.LOGIN_MENU_TITLE, currentDrawer),
ConditionalBuilder(
condition: isAdmin,
builder: (context) => _CustomListTile(currentPage,
globals.MANAGE_USERS_MENU_TITLE, currentDrawer),
)
],
),
);
}
}
class _CustomListTile extends StatelessWidget {
final String currentPage;
final String tileTitle;
final currentDrawer;
_CustomListTile(this.currentPage, this.tileTitle, this.currentDrawer);
#override
Widget build(BuildContext context) {
return ListTile(
title: Text(
tileTitle,
style: currentDrawer == 1
? TextStyle(fontWeight: FontWeight.bold)
: TextStyle(fontWeight: FontWeight.normal),
),
onTap: () {
Navigator.of(context).pop();
if (this.currentPage == tileTitle) return;
Provider.of<DrawerStateInfo>(context).setCurrentDrawer(1);
switch (tileTitle) {
case globals.HOME_MENU_TITLE:
{
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => HomeScreen(
title: globals.HOME_MENU_TITLE,
)));
break;
}
case globals.LOGIN_MENU_TITLE:
{
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => LoginScreen(
title: globals.LOGIN_MENU_TITLE,
)));
break;
}
case globals.MANAGE_USERS_MENU_TITLE:
{
Navigator.of(context).pushNamed("/ManageUsers");
break;
}
default:
{
break;
}
}
});
}
}
ListView StreamBuilder means it will listening to your collection.
Please remove the listener when you move to next screen.
This piece of code looks wrong to me:
Provider.of<DrawerStateInfo>(context).setCurrentDrawer(1);
You should have something like an enum or even the tileTitle to use as the saved state for the currently selected option on the drawer, otherwise, you only know there is a selected option, but not exactly which one.
This leads you to this crazy behavior of calling incorrect routes.
Try something like this
class MyDrawer extends StatelessWidget {
MyDrawer(this.currentPage);
final String currentPage;
bool isAdmin = true;
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
children: <Widget>[
_CustomListTile(currentPage, globals.HOME_MENU_TITLE),
_CustomListTile(currentPage, globals.LOGIN_MENU_TITLE),
isAdmin
? _CustomListTile(currentPage, globals.MANAGE_USERS_MENU_TITLE)
: Container(),
],
),
);
}
}
class _CustomListTile extends StatelessWidget {
final String currentPage;
final String tileTitle;
_CustomListTile(
this.currentPage,
this.tileTitle,
);
#override
Widget build(BuildContext context) {
return Consumer<DrawerStateInfo>(
builder: (context, draweStateInfo, _) {
final currentSelectedItem = draweStateInfo.getCurrentDrawer();
return ListTile(
title: Text(
tileTitle,
style: currentSelectedItem == tileTitle
? TextStyle(fontWeight: FontWeight.bold)
: TextStyle(fontWeight: FontWeight.normal),
),
onTap: () {
Navigator.of(context).pop();
if (currentSelectedItem == tileTitle) return;
draweStateInfo.setCurrentDrawer(tileTitle);
switch (currentSelectedItem) {
case globals.HOME_MENU_TITLE:
{
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => HomeScreen(
title: globals.HOME_MENU_TITLE,
)));
break;
}
case globals.LOGIN_MENU_TITLE:
{
Navigator.of(context).pushReplacement(MaterialPageRoute(
builder: (BuildContext context) => LoginScreen(
title: globals.LOGIN_MENU_TITLE,
)));
break;
}
case globals.MANAGE_USERS_MENU_TITLE:
{
Navigator.of(context).pushNamed("/ManageUsers");
break;
}
default:
{
break;
}
}
});
},
);
}
}