Auto navigation on login page in flutter app - flutter

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";
}
}

Related

Flutter: How to await currentUrl() from WebViewController in FutureBuilder?

What I try to do
I implemented a webview and want to show the current url on another page using provider.
So onWebViewCreated I try to set the controller value via setController and consume it in the Consumer widget together with a FutureBuilder and an if statement. If hasData is truthy, I want to access the controller e.g. to get the currentUrl().
Where I'm stuck
The Text with controller.data?.currentUrl() returns Instance of 'Future<String?>'. I know I need to await it, but I don't know how.
Code
profile.page.dart
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:quirion_flutter/providers/router.providers.dart';
import 'package:quirion_flutter/widgets/webview.widgets.dart';
import 'package:webview_flutter/webview_flutter.dart';
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
#override
Widget build(BuildContext context) {
return Consumer<WebviewRouter>(builder: ((context, value, child) {
return SafeArea(
child: Stack(children: [
const BankingWebView(
initialUrl: 'https://banking-dev.quirion.de/setup/personal-data',
),
FutureBuilder(
future: value.controller.future,
builder: ((BuildContext context,
AsyncSnapshot<WebViewController> controller) {
if (controller.hasData) {
return Column(
children: [
Text('${controller.data?.currentUrl()}'),
Text(value.route),
],
);
}
return const SafeArea(child: Text('Nothing here'));
})),
]),
);
}));
}
}
References
https://medium.com/flutter/the-power-of-webviews-in-flutter-a56234b57df2
https://codelabs.developers.google.com/codelabs/flutter-webview#11
https://discord.com/channels/420324994703163402/421445316617961502/1039197342231973898
I went with a probably fairly simply solution. I just used another FutureBuilder which future then is controller.data?.currentUrl() (we remember, it returned Instance of Future<String?>) and then the snapshot to access the resolved data. Worked for me. Though, if there are better solution, I'm still happy for additional answers.
class ProfilePage extends StatelessWidget {
const ProfilePage({super.key});
#override
Widget build(BuildContext context) {
return Consumer<WebviewRouter>(builder: ((context, value, child) {
return SafeArea(
child: Stack(children: [
const BankingWebView(
initialUrl: 'https://my-url.com',
),
FutureBuilder(
future: value.controller.future,
builder: ((BuildContext context,
AsyncSnapshot<WebViewController> controller) {
if (controller.hasData) {
// SOLUTION START
return FutureBuilder(
future: controller.data?.currentUrl(),
builder: (context, AsyncSnapshot<String?> snapshot) {
if (snapshot.hasData) {
return Text('${snapshot.data}');
}
return const SafeArea(child: Text('Loading...'));
});
// SOLUTION END
}
return Container();
})),
]),
);
}));
}
}

Opening a screen out the result of a statement

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
}
);
}

How to pass 'context' to another widget outside buildContext widget

I have this code
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('All users'),
),
body: StreamBuilder<List<User>>(
stream: readUsers(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text('error fetching data');
} else if (snapshot.hasData) {
if (snapshot.data!.isEmpty) {
// return const Text('no data to fect');
return Container(
padding: const EdgeInsets.all(10.0),
child: const Text('no data'),
);
} else {
final users = snapshot.data!;
return ListView(
children: users.map(buildUser).toList(),
);
}
} else {
return const Center(child: CircularProgressIndicator());
}
},
),
}
Then at this point
return ListView(
children: users.map(buildUser).toList(),
);
I want to return data from another widget outside buildContext widget but the issue here is that I don't know how to pass the 'context' in the users.map(buildUser).toList() unorder to eliminate the error in the image below.
Create a class like bellow
import 'package:flutter/material.dart';
class GlobalContextService {
static GlobalKey<NavigatorState> navigatorKey =
GlobalKey<NavigatorState>();
}
now assign this key to the MaterialApp in main.dart just like bellow
return MaterialApp(
navigatorKey: GlobalContextService.navigatorKey, // set property
);
Now you can access the context any where you want by using the following line of code
GlobalContextService.navigatorKey.currentContext
try this:
Widget buildUser(User user, BuildContext context) =>
Recommended approach ->User helper widget instead of helper method.
or
you can pass context as parameter to method

Why do I get snapshot error and setState() or markNeedsBuild() called during build?

When no internet available I get
No internet :( Reason: bla bla
════════ Exception caught by widgets library ═══════════════════════════════════════════════════════
The following assertion was thrown building FutureBuilder<bool>(dirty, state: _FutureBuilderState<bool>#bf2f3):
setState() or markNeedsBuild() called during build.
and it renders snapshot.hasError part on screen
the code
Future<void> main() async {
setupServiceLocator();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Internet(),
);
}
}
class Internet extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: checkConnection(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData) {
var result = snapshot.data;
if (result = true) {
Navigator.pushReplacement(
context,
MaterialPageRoute(builder: (context) => AllMessages(),
);
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('No internet :('),
SizedBox(height: 30),
RaisedButton(
child: Text('Try again'),
onPressed: () {
MaterialPageRoute(builder: (context) => Internet());
},
)
],
),
);
} else if (snapshot.hasError) {
print(snapshot.error);
print('snapshot has error');
return Scaffold(
body: Center(
child: Text('Error. \nSomething went wrong :('),
),
);
} else {
return Scaffold(
body: Center(
child: SizedBox(
child: CircularProgressIndicator(),
width: 60,
height: 60,
),
),
);
}
},
);
}
}
Future<bool> checkConnection() async {
bool result = await DataConnectionChecker().hasConnection;
if (result == true) {
print('YAY! Free cute dog pics!');
} else {
print('No internet :( Reason:');
print(DataConnectionChecker().lastTryResults);
}
return result;
}
You are getting the error because you are navigating to a different MaterialPage and then trying to return a widget. What do you wish to do when hasData is true?

Flutter How to Populate ListView on app launch with sqflite?

I'm trying to display data in a ListView with a FutureBuilder. In debug mode, when I launch the app, no data is displayed, but, if I reload the app (hot Reload or hot Restart), the ListView displays all the data. I already tried several approaches to solve this - even without a FutureBuilder, I still haven't succeeded. If I create a button to populate the ListView, with the same method "_getregistos()", the ListView returns the data correctly.
This is the code I'm using:
import 'package:flutter/material.dart';
import 'package:xxxxx/models/task_model.dart';
import 'package:xxxxx/shared/loading.dart';
class AddTask extends StatefulWidget {
static const id = 'add_task';
#override
_AddTaskState createState() => _AddTaskState();
}
class _AddTaskState extends State<AddTask> {
dynamic tasks;
final textController = TextEditingController();
_getRegistos() async {
List<TaskModel> taskList = await _todoHelper.getAllTask();
// print('DADOS DA tasklist: ${taskList.length}');
return taskList;
}
TaskModel currentTask;
final TodoHelper _todoHelper = TodoHelper();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(32),
child: Column(
children: <Widget>[
TextField(
controller: textController,
),
FlatButton(
child: Text('Insert'),
onPressed: () {
currentTask = TaskModel(name: textController.text);
_todoHelper.insertTask(currentTask);
},
color: Colors.blue,
textColor: Colors.white,
),
//
FutureBuilder(
future: _getRegistos(),
builder: (context, snapshot) {
if (snapshot.hasData) {
tasks = snapshot.data;
return ListView.builder(
shrinkWrap: true,
itemCount: tasks == null ? 0 : tasks.length,
itemBuilder: (BuildContext context, int index) {
TaskModel t = tasks[index];
return Card(
child: Row(
children: <Widget>[
Text('id: ${t.id}'),
Text('name: ${t.name}'),
IconButton(
icon: Icon(Icons.delete), onPressed: () {})
],
),
);
},
);
}
return Loading();
}),
],
),
),
);
}
}
Thank you.
You need to use ConnectionState inside your builder. Look at this code template: (Currently your builder returns ListView widget without waiting for the future to complete)
return FutureBuilder(
future: yourFuture(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// future complete
// if error or data is false return error widget
if (snapshot.hasError || !snapshot.hasData) {
return _buildErrorWidget();
}
// return data widget
return _buildDataWidget();
// return loading widget while connection state is active
} else
return _buildLoadingWidget();
},
);
Thanks for your help.
I already implemented ConnectionState in the FutureBuilder and the issue persists.
When I launch the app, I get error "ERROR or No-Data" (is the message I defined in case of error of no-data.
If I click on the FlatButton to call the method "_getTasks()", the same method used in FutureBuilder, everything is ok. The method return data correctly.
This is the code refactored:
import 'package:flutter/material.dart';
import 'package:xxxx/models/task_model.dart';
import 'package:xxxx/shared/loading.dart';
class AddTask extends StatefulWidget {
static const id = 'add_task';
#override
_AddTaskState createState() => _AddTaskState();
}
class _AddTaskState extends State<AddTask> {
final textController = TextEditingController();
Future<List<TaskModel>> _getTasks() async {
List<TaskModel> tasks = await _todoHelper.getAllTask();
print('Tasks data: ${tasks.length}');
return tasks;
}
TaskModel currentTask;
//list to test with the FlatButton List all tasks
List<TaskModel> tasksList = [];
final TodoHelper _todoHelper = TodoHelper();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(32),
child: Column(
children: <Widget>[
TextField(
controller: textController,
),
FlatButton(
child: Text('Insert'),
onPressed: () {
currentTask = TaskModel(name: textController.text);
_todoHelper.insertTask(currentTask);
},
color: Colors.blue,
textColor: Colors.white,
),
//when clicking on this flatButton, I can populate the taskList
FlatButton(
child: Text('Show all Tasks'),
onPressed: () async {
List<TaskModel> list = await _getTasks();
setState(() {
tasksList = list;
print(
'TaskList loaded by "flatButton" has ${tasksList.length} rows');
});
},
color: Colors.red,
textColor: Colors.white,
),
//
FutureBuilder(
future: _getTasks(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
// future complete
// if error or data is false return error widget
if (snapshot.hasError || !snapshot.hasData) {
return Text('ERROR or NO-DATA');
}
// return data widget
return ListItems(context, snapshot.data);
// return loading widget while connection state is active
} else
return Loading();
},
),
],
),
),
);
}
}
//*****************************************
class ListItems extends StatelessWidget {
final List<TaskModel> snapshot;
final BuildContext context;
ListItems(this.context, this.snapshot);
#override
Widget build(BuildContext context) {
return Expanded(
child: ListView.builder(
itemCount: snapshot == null ? 0 : snapshot.length,
itemBuilder: (context, index) {
TaskModel t = snapshot[index];
return Text(' ${t.id} - ${t.name}');
}),
);
}
}