How to show error when user can't login to system because he gives in forms bad login data(email or password)?
I would like this information to appear above email forms.
when i throw bad data in forms my response body return me in console Wrong password or User does not exists, but i want to display this information on screen
my login api:
makeLoginRequest(String email, password) async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
Map data = {
'email':emailController.text,
'password':passwordController.text
};
var jsonResponse;
var url = 'http://10.0.2.2:80/user/login';
var response = await http.post(url, body:data);
if(response.statusCode == 200){
_isLoading = false;
jsonResponse = json.decode(response.body);
sharedPreferences.setInt("id", jsonResponse['id']);
sharedPreferences.setString("firstName", jsonResponse['firstName']);
sharedPreferences.setString("lastName", jsonResponse['lastName']);
setState(() {
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (BuildContext context) => Home()), (Route<dynamic> route) => false);
});
}
else{
print(response.body);
}
}
my UI:
#override
Widget build(BuildContext context) {
double height = MediaQuery.of(context).size.height;
double width = MediaQuery.of(context).size.width;
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.light
.copyWith(statusBarColor: Colors.transparent));
return Scaffold(
body: SingleChildScrollView(
child: Form(
key: _formKey,
child: Column(
children: <Widget>[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Padding(
padding: EdgeInsets.only(top: 20.0),
child: _headerSection(),
)
],
),
Padding(
padding: EdgeInsets.all(40.0),
child: Column(
children: <Widget>[
SizedBox(height: 180.0),
_buildEmail(),
SizedBox(height: 30.0),
_buildPassword(),
SizedBox(height: 80.0),
_buttonSection(),
SizedBox(height: 40.0),
_helpText(),
],
)
)
]
)
)
),
);
}
thanks for any help :)
You can use AlertDialog yo show an error message, use the TextEditingController to pass the password if you wish. I've attached a minimalist example that you can run. The correct password would be "111" otherwise there would be an error message displayed through the ```AlertDialog````
Edit: Updated code to include snackbar instead
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final TextEditingController _controller = TextEditingController();
#override
Widget build(BuildContext context) {
return Scaffold(
body: Builder(
builder: (BuildContext context){
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
TextField(
controller: _controller,
),
RaisedButton(
onPressed: () => checkPassword(context, _controller.text),
child: Text("Login",),
),
],
);
},
)
);
}
void checkPassword(BuildContext context, String password) {
if (password != "111") {
final snackBar = SnackBar(content: Text("$password is not correct!"));
Scaffold.of(context).showSnackBar(snackBar);
return;
}
print("Password corect!!");
}
}
Related
How am I supposed to pass a value in this big mess called Flutter?
30 years old php global $var wasn't good?
All these years were to come up with setState, passed in a controller which get redeclared as a key inside a stateful widget that receive the value from a Navigator?
By the way, I tried using Navigator.push but it seems to open a completely new window, the value is there but I'd need it to show in the tab body not in a new window, below is my code:
main.dart
import 'dart:core';
import 'dart:developer';
import 'dart:io';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:qr_code_scanner/qr_code_scanner.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: HomeView(),
);
}
}
class HomeView extends StatefulWidget {
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
final tabs = [QRViewExample(), SecondView(res: '')];
int _currentIndex = 0;
#override
void initState() {
setState(() {});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
toolbarHeight: 40.0,
elevation: 0,
centerTitle: true,
title: Text('Flutter App'),
),
body: tabs[_currentIndex],
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.red,
currentIndex: _currentIndex,
type: BottomNavigationBarType.fixed,
selectedItemColor: Colors.white,
unselectedItemColor: Colors.white.withOpacity(0.5),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.qr_code),
label: 'Scan',
),
BottomNavigationBarItem(
icon: Icon(Icons.list),
label: 'List',
),
],
onTap: (index) {
setState(() {
_currentIndex = index;
});
},
),
);
}
}
// SECOND TAB WIDGET (custom)
class SecondView extends StatelessWidget {
const SecondView({Key? key, required this.res}) : super(key: key);
final String? res;
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Text(res!),
),
);
}
}
// FIRST TAB WIDGET (qrcode)
class QRViewExample extends StatefulWidget {
const QRViewExample({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => _QRViewExampleState();
}
class _QRViewExampleState extends State<QRViewExample> {
Barcode? result;
QRViewController? controller;
final GlobalKey qrKey = GlobalKey(debugLabel: 'QR');
#override
void reassemble() {
super.reassemble();
if (Platform.isAndroid) {
controller!.pauseCamera();
}
controller!.resumeCamera();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
height: 500,
child: Padding(
padding: EdgeInsets.all(8.0),
child: Column(
children: <Widget>[
Expanded(flex: 4, child: _buildQrView(context)),
Expanded(
flex: 1,
child: FittedBox(
fit: BoxFit.contain,
child: Column(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
if (result != null)
Text(
'Barcode Type: ${describeEnum(result!.format)} Data: ${result!.code}')
else
const Text('Scan a code'),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.toggleFlash();
setState(() {});
},
child: FutureBuilder(
future: controller?.getFlashStatus(),
builder: (context, snapshot) {
return Text('Flash: ${snapshot.data}');
},
)),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.flipCamera();
setState(() {});
},
child: FutureBuilder(
future: controller?.getCameraInfo(),
builder: (context, snapshot) {
if (snapshot.data != null) {
return Text(
'Camera facing ${describeEnum(snapshot.data!)}');
} else {
return const Text('loading');
}
},
)),
)
],
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.pauseCamera();
},
child: const Text('pause',
style: TextStyle(fontSize: 20)),
),
),
Container(
margin: const EdgeInsets.all(8),
child: ElevatedButton(
onPressed: () async {
await controller?.resumeCamera();
},
child: const Text('resume',
style: TextStyle(fontSize: 20)),
),
)
],
),
],
),
),
)
],
),
),
),
);
}
Widget _buildQrView(BuildContext context) {
var scanArea = (MediaQuery.of(context).size.width < 400 ||
MediaQuery.of(context).size.height < 400)
? 150.0
: 300.0;
return QRView(
key: qrKey,
onQRViewCreated: _onQRViewCreated,
overlay: QrScannerOverlayShape(
borderColor: Colors.cyanAccent,
borderRadius: 10,
borderLength: 30,
borderWidth: 10,
cutOutSize: scanArea),
onPermissionSet: (ctrl, p) => _onPermissionSet(context, ctrl, p),
);
}
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
});
controller.scannedDataStream.listen((scanData) {
controller.pauseCamera();
setState(() {
result = scanData;
});
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => SecondView(res: result!.code)))
.then((value) => controller.resumeCamera());
});
}
void _onPermissionSet(BuildContext context, QRViewController ctrl, bool p) {
log('${DateTime.now().toIso8601String()}_onPermissionSet $p');
if (!p) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('no Permission')),
);
}
}
#override
void dispose() {
controller?.dispose();
super.dispose();
}
}
How am I supposed to pass a value in this big mess called Flutter?
With state management tools like InheritedWidget, InheritedModel, Provider, BloC and many more.
30 years old php global $var wasn't good? All these years were to come up with setState, passed in a controller which get redeclared as a key inside a stateful widget that receive the value from a Navigator?
Well, you shouldn't do that and it's not meant to be done like that. We can use several methods to propagate data down the widget tree. Let me explain this with InheritedWidget. But sometimes you want to go for Provider which is a wrapper class for InheritedWidget.
First we create a class named QRListModel which extends InheritedModel:
class QRListModel extends InheritedWidget {
final List<Barcode> qrList = []; // <- This holds our data
QRListModel({required super.child});
#override
bool updateShouldNotify(QRListModel oldWidget) {
return !listEquals(oldWidget.qrList, qrList);
}
static QRListModel of(BuildContext context) {
final QRListModel? result = context.dependOnInheritedWidgetOfExactType<QRListModel>();
assert(result != null, 'No QRListModel found in context');
return result!;
}
}
updateShouldNotify is a method we have to override to tell Flutter, when we want the widgets to rebuild. We want this to happen when the list changes. The of method is just a handy way to access the QRListModel.
Now wrap a parent widget of both the scan tab view and the list tab view inside QRListModel. We go for HomeView:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter App',
theme: ThemeData(
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: QRListModel(child: HomeView()), // <- here!
);
}
}
We can take any parent widget but it should be a class where we don't call setState. Otherwise our QRListModel also gets rebuilt and our list is gone.
Now we can access QRListModel from anywhere inside the subtree. We need it here:
void _onQRViewCreated(QRViewController controller) {
setState(() {
this.controller = controller;
this.controller!.resumeCamera();
});
controller.scannedDataStream.listen((scanData) async {
controller.pauseCamera();
QRListModel.of(context).qrList.add(scanData); // <- Here we access the list
await showDialog(
context: context,
builder: (context) => SimpleDialog(
title: Text("Barcode was added!"),
children: [
Text(scanData.code!)
],
)
);
});
}
And here we read the list:
class SecondView extends StatelessWidget {
const SecondView({Key? key, required this.res}) : super(key: key);
final String? res;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: QRListModel.of(context).qrList.length,
itemBuilder: (context, index) {
return Card(
child: ListTile(
title: Text(QRListModel.of(context).qrList[index].code ?? "NO"),
),
);
}
);
}
}
Now both pages have access to the qr list. Please do mind that a InheritedWidget can only have final fields. So if you need mutable fields, you need an additional wrapper class. We don't need it as we don't change the list but only its elements.
By the way: You shouldn't call setState inside initState. You did this here:
class _HomeViewState extends State<HomeView> {
final tabs = [QRViewExample(), SecondView(res: '')];
int _currentIndex = 0;
#override
void initState() {
setState(() {}); // <- Don't call setState inside initState!
super.initState();
}
When a user takes a photo, I want to send it to the photo_preview screen, which gives the user the chance to take another photo.
This page is as follows:
import 'package:flutter/material.dart';
import 'dart:io';
class photo_previewScreen extends StatelessWidget {
final String imagePath;
const photo_previewScreen({Key key, this.imagePath}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Display the Picture')),
body: Image.file(File(imagePath)),
);
}
}
On my current camera page, what's the best way for me to send the photo to the above page?
When the take picture button is pressed, this is what is currently happening:
onPressed: () {
_openGallery();
Navigator.pop(context);
},
EDIT: Full page with edits from the answer
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:camera/camera.dart';
import 'dart:io';
import 'package:stumble/pages/PhotoPreviewScreen.dart';
class Camera extends StatefulWidget {
Function setData;
Camera({Key key, this.setData}) : super(key: key);
#override
_CameraScreenState createState() => _CameraScreenState();
}
class _CameraScreenState extends State<Camera> {
CameraController controller;
List cameras;
int selectedCameraIndex;
String imgPath;
var image;
takePicture;
Future _openGallery() async {
image = await controller.takePicture();
if (widget.setData != null) {
widget.setData(File(image.path));
}
}
#override
void initState() {
super.initState();
availableCameras().then((availableCameras) {
cameras = availableCameras;
if (cameras.length > 0) {
setState(() {
selectedCameraIndex = 0;
});
_initCameraController(cameras[selectedCameraIndex]).then((void v) {});
} else {
print('No camera available');
}
}).catchError((err) {
print('Error :${err.code}Error message : ${err.message}');
});
}
Future _initCameraController(CameraDescription cameraDescription) async {
if (controller != null) {
await controller.dispose();
}
controller = CameraController(cameraDescription, ResolutionPreset.high);
controller.addListener(() {
if (mounted) {
setState(() {});
}
if (controller.value.hasError) {
print('Camera error ${controller.value.errorDescription}');
}
});
try {
await controller.initialize();
} on CameraException catch (e) {}
if (mounted) {
setState(() {});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: SafeArea(
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
Expanded(
flex: 1,
child: _cameraPreviewWidget(),
),
Align(
alignment: Alignment.bottomCenter,
child: Container(
height: 120,
width: double.infinity,
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[_cameraControlWidget(context), Spacer()],
),
),
)
],
),
),
),
);
}
Widget _cameraPreviewWidget() {
if (controller == null || !controller.value.isInitialized) {
return const Text(
'Loading',
style: TextStyle(
color: Colors.white,
fontSize: 20.0,
fontWeight: FontWeight.w900,
),
);
}
final size = MediaQuery.of(context).size;
final deviceRatio = size.width / size.height;
return Stack(children: <Widget>[
Positioned.fill(
child: new AspectRatio(
aspectRatio: controller.value.aspectRatio,
child: new CameraPreview(controller),
),
),
]);
}
Widget _cameraControlWidget(context) {
return Expanded(
child: Align(
alignment: Alignment.center,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.max,
children: <Widget>[
FloatingActionButton(
child: Icon(
Icons.center_focus_strong,
size: 39,
color: Color(0xffffffff),
),
backgroundColor: Color(0xff33333D),
onPressed: () async {
var result = await takePicture();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PhotoPreviewScreen(
imagePath: result,
),
),
);
})
],
),
),
);
}
}
I am getting the error that
'The method 'takePicture isn't defined for the type '_CameraScreenState'
This despite takePicture(); being defined.
You can navigate to the preview screen on clicking the take picture button, passing the image path:
void onTakePictureButtonPressed() async {
var result = await takePicture();
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => PhotoPreviewScreen(
imagePath: result,
),
),
);
}
Future<String> takePicture() async {
if (!controller.value.isInitialized) {
showErrorFlushbar(context, 'Error: select a camera first.');
return null;
}
final Directory extDir = await getApplicationDocumentsDirectory();
final String dirPath = '${extDir.path}/Pictures/ompariwar';
await Directory(dirPath).create(recursive: true);
final String filePath = '$dirPath/${timestamp()}.jpg';
if (controller.value.isTakingPicture) {
// A capture is already pending, do nothing.
return null;
}
try {
await controller.takePicture(filePath);
} on CameraException catch (e) {
print(e);
return null;
}
return filePath;
}
In the preview screen, you can do this:
class PhotoPreviewScreen extends StatelessWidget {
final String imagePath;
const PhotoPreviewScreen({Key key, this.imagePath}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
onPressed: () => Navigator.pop(context), // Go back to the camera to take the picture again
child: Icon(Icons.camera_alt),
),
appBar: AppBar(title: Text('Display the Picture')),
body: Column(
children: [
Expanded(child: Image.file(File(imagePath))),
SomeButton(), // Add a button to send the image to server or go back to home screen here
],
),
);
}
}
Btw it's a convention to name your class/ Widget name in UpperCamelCase. Read more on this Dart style guide for clean code.
Navigator.push is not working with setState.
Once I removed the setState in the retry() function then it works but I want to use the setState inside the retry function.
import 'package:covid_19/screens/home.dart';
import 'package:covid_19/viewmodel/home_view_model.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyAppError());
class MyAppError extends StatefulWidget {
#override
_MyAppErrorState createState() => _MyAppErrorState();
}
class _MyAppErrorState extends State<MyAppError> {
bool _loading = false;
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return _loading ? CircularProgressIndicator() : MaterialApp(
home: Scaffold(
key: _scaffoldKey,
body: Builder(
builder:(context)=>
SafeArea(
child: Container(
margin: EdgeInsets.all(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"No internet found. Check your connection",
textAlign: TextAlign.center,
),
FlatButton(
child: Text('Retry'),
onPressed: retry,
)
],
),
),
),
),
)
);
}
Future<void> retry() async{
setState(() {
_loading = true;
});
print('retrying...');
HomeViewModel homeViewModel = HomeViewModel();
homeViewModel.onAppStart().then((_){
print('pushing');
Navigator.pushReplacement(_scaffoldKey.currentContext, MaterialPageRoute(builder: (context)=> Home(data : homeViewModel.data)));
}).catchError((e){
setState(() {
_loading = false;
});
});
}
}
logs when the retry button clicked without setState:
retrying...
pushing...
and navigate to Home()
logs when the retry button clicked with setState:
retrying...
pushing...
and nothing happens
It has a logical error. when the _loading is true, there is no scaffold and you are using Scaffold key for navigating.
try this.
import 'package:covid_19/screens/home.dart';
import 'package:covid_19/viewmodel/home_view_model.dart';
import 'package:flutter/material.dart';
void main() => runApp(MyAppError());
class MyAppError extends StatefulWidget {
#override
_MyAppErrorState createState() => _MyAppErrorState();
}
class _MyAppErrorState extends State<MyAppError> {
bool _loading = false;
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
key: _scaffoldKey,
body:_loading ? CircularProgressIndicator() : Builder(
builder:(context)=>
SafeArea(
child: Container(
margin: EdgeInsets.all(10),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
"No internet found. Check your connection",
textAlign: TextAlign.center,
),
FlatButton(
child: Text('Retry'),
onPressed: retry,
)
],
),
),
),
),
)
);
}
Future<void> retry() async{
setState(() {
_loading = true;
});
print('retrying...');
HomeViewModel homeViewModel = HomeViewModel();
homeViewModel.onAppStart().then((_){
print('pushing');
Navigator.pushReplacement(_scaffoldKey.currentContext, MaterialPageRoute(builder: (context)=> Home(data : homeViewModel.data)));
}).catchError((e){
setState(() {
_loading = false;
});
});
}
}
I am making an app with flutter and I want a loading screen while fetching data from firestore I used to do this in android by setvisibilty .I am new to flutter and I don't know how to do it I saw some questions on stack but they didn't seem to help full
I want to show the loading screen if firebaseUser is not null,
this is my initState method
void initState() {
super.initState();
isRegistered();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(32),
child: Form(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Login"),
SizedBox(
height: 16,
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Container(
width: 50,
child: TextFormField(
maxLength: 4,
keyboardType: TextInputType.number,
controller: countryCodeController,
decoration: InputDecoration(
hintText: '+251',
),
),
),
Container(
width: 200,
child: TextFormField(
maxLength: 9,
keyboardType: TextInputType.number,
controller: phonenumberController,
decoration: InputDecoration(
hintText: '912345678',
),
),
),
],
),
SizedBox(
height: 16,
),
Container(
width: double.infinity,
child: FlatButton(
child: Text('Login'),
color: Colors.white,
padding: EdgeInsets.all(16),
onPressed: () {
final phoneNumber = countryCodeController.text.trim() + phonenumberController.text.trim();
if(phonenumberController.text.trim().length == 9 || countryCodeController.text.trim().length == 4){
loginUser(phoneNumber, context);
}else{
Fluttertoast.showToast(msg: "wronge input");
}
}),
)
],
),
),
),
);
}
void isRegistered() async{
FirebaseAuth firebaseAuth = FirebaseAuth.instance;
final FirebaseUser firebaseUser = await firebaseAuth.currentUser();
final snapShot = await Firestore.instance.collection("users").document(
firebaseUser.uid).get();
if (firebaseUser != null) {
if (snapShot.exists) {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => HomePage(
firebaseUser: firebaseUser,
)));
}else{
}
}
}
}
Just check out this example I have created for you:
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
bool _isLoading = false; // This is initially false where no loading state
List<Timings> timingsList = List();
#override
void initState() {
super.initState();
dataLoadFunction(); // this function gets called
}
dataLoadFunction() async {
setState(() {
_isLoading = true; // your loader has started to load
});
// fetch you data over here
setState(() {
_isLoading = false; // your loder will stop to finish after the data fetch
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: _isLoading
? CircularProgressIndicator() // this will show when loading is true
: Text('You widget tree after loading ...') // this will show when loading is false
),
);
}
}
Let me know if it works
I use flutter_spinkit for the animation.
The package flutter_spinkit is a collection of loading indicators animated with flutter.
Here the Loading widget:
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
class Loading extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
color: Colors.white,
child: Center(
child: SpinKitFadingCube(
color: Colors.lightGreen[100],
size: 50.0
)
)
);
}
}
Then, from within your widgets, you need to:
import '[yourpath]/loading.dart';
bool loading = false;
#override
Widget build(BuildContext context) {
return loading ? Loading() : Scaffold(
body: Container(...
Wherever is your click event, you should set the state of loading to TRUE:
setState(() => loading = true)
and where the callback is, you should set the state back to FALSE:
setState(() => loading = false)
You can try creating a widget component such as this and save it with the name progress.dart
import 'package:flutter/material.dart';
Container circularProgress() {
return Container(
alignment: Alignment.center,
padding: EdgeInsets.only(top: 10.0),
child: CircularProgressIndicator(
strokeWidth: 2.0,
valueColor: AlwaysStoppedAnimation(primaryColor), //any color you want
),
);
}
Then import the progress.dart and create a separate container
Container loadingScreen() {
return circularProgress();
}
Then change your code to:
class RootScreenSM extends StatefulWidget {
#override
_RootScreenSMState createState() => _RootScreenSMState();
}
class _RootScreenSMState extends State<RootScreenSM> {
#override
Widget build(BuildContext context) {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return loadingScreen(); // Container that you just created
} else {
if (snapshot.hasData) {
return HomePage(
firebaseUser: snapshot.data,
);
} else {
return
notloggedin();
}
}
},
);
}
You can try this method and let us know if it worked
You can do something like this
class RootScreenSM extends StatefulWidget {
#override
_RootScreenSMState createState() => _RootScreenSMState();
}
class _RootScreenSMState extends State<RootScreenSM> {
#override
Widget build(BuildContext context) {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return new Container(
color: Colors.white,
//customize container look and feel here
);
} else {
if (snapshot.hasData) {
return HomePage(
firebaseUser: snapshot.data,
);
} else {
return
notloggedin();
}
}
},
);
}
In Flutter, I want to make screens like with Fragment in android, in this my code i try to replace each screens into current screen like with Fragment.replecae in android, i used Hook and Provider and my code work fine when in click on buttons to switch between them but i can't implementing back stack, which means when i click on Back button on phone, my code should show latest screen which i stored into _backStack variable, each swtich between this screens i stored current screen index into the this variable.
how can i solve back from this stack in my sample code?
// Switch Between screens:
DashboardPage(), UserProfilePage(), SearchPage()
-------------> -------------> ------------->
// When back from stack:
DashboardPage(), UserProfilePage(), SearchPage()
Exit from application <-------------- <---------------- <-----------
i used Hook and i want to implementing this action with this library features
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
import 'package:provider/provider.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(MultiProvider(providers: [
Provider.value(value: StreamBackStackSupport()),
StreamProvider<homePages>(
create: (context) =>
Provider.of<StreamBackStackSupport>(context, listen: false)
.selectedPage,
)
], child: StartupApplication()));
}
class StartupApplication extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'BackStack Support App',
home: MainBodyApp(),
);
}
}
class MainBodyApp extends HookWidget {
final List<Widget> _fragments = [
DashboardPage(),
UserProfilePage(),
SearchPage()
];
List<int> _backStack = [0];
int _currentIndex = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('BackStack Screen'),
),
body: WillPopScope(
// ignore: missing_return
onWillPop: () {
customPop(context);
},
child: Container(
child: Column(
children: <Widget>[
Consumer<homePages>(
builder: (context, selectedPage, child) {
_currentIndex = selectedPage != null ? selectedPage.index : 0;
_backStack.add(_currentIndex);
return Expanded(child: _fragments[_currentIndex]);
},
),
Container(
width: double.infinity,
height: 50.0,
padding: const EdgeInsets.symmetric(horizontal: 15.0),
color: Colors.indigo[400],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () => Provider.of<StreamBackStackSupport>(
context,
listen: false)
.switchBetweenPages(homePages.screenDashboard),
child: Text('Dashboard'),
),
RaisedButton(
onPressed: () => Provider.of<StreamBackStackSupport>(
context,
listen: false)
.switchBetweenPages(homePages.screenProfile),
child: Text('Profile'),
),
RaisedButton(
onPressed: () => Provider.of<StreamBackStackSupport>(
context,
listen: false)
.switchBetweenPages(homePages.screenSearch),
child: Text('Search'),
),
],
),
),
],
),
),
),
);
}
void navigateBack(int index) {
useState(() => _currentIndex = index);
}
void customPop(BuildContext context) {
if (_backStack.length - 1 > 0) {
navigateBack(_backStack[_backStack.length - 1]);
} else {
_backStack.removeAt(_backStack.length - 1);
Provider.of<StreamBackStackSupport>(context, listen: false)
.switchBetweenPages(homePages.values[_backStack.length - 1]);
Navigator.pop(context);
}
}
}
class UserProfilePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(' screenProfile ...'),
);
}
}
class DashboardPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(' screenDashboard ...'),
);
}
}
class SearchPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(' screenSearch ...'),
);
}
}
enum homePages { screenDashboard, screenProfile, screenSearch }
class StreamBackStackSupport {
final StreamController<homePages> _homePages = StreamController<homePages>();
Stream<homePages> get selectedPage => _homePages.stream;
void switchBetweenPages(homePages selectedPage) {
_homePages.add(homePages.values[selectedPage.index]);
}
void close() {
_homePages.close();
}
}
TL;DR
The full code is at the end.
Use Navigator instead
You should approach this problem differently. I could present you with a solution that would work with your approach, however, I think that you should instead solve this by implementing a custom Navigator as this is a built-in solution in Flutter.
When you are using a Navigator, you do not need any of your stream-based management, i.e. you can remove StreamBackStackSupport entirely.
Now, you insert a Navigator widget where you had your Consumer before:
children: <Widget>[
Expanded(
child: Navigator(
...
),
),
Container(...), // Your bottom bar..
]
The navigator manages its routes using strings, which means that we will need to have a way to convert your enum (which I renamed to Page) to Strings. We can use describeEnum for that and put that into an extension:
enum Page { screenDashboard, screenProfile, screenSearch }
extension on Page {
String get route => describeEnum(this);
}
Now, you can get the string representation of a page using e.g. Page.screenDashboard.route.
Furthermore, you want to map your actual pages to your fragment widgets, which you can do like this:
class MainBodyApp extends HookWidget {
final Map<Page, Widget> _fragments = {
Page.screenDashboard: DashboardPage(),
Page.screenProfile: UserProfilePage(),
Page.screenSearch: SearchPage(),
};
...
To access the Navigator, we need to have a GlobalKey. Usually we would have a StatefulWidget and manage the GlobalKey like that. Since you want to use flutter_hooks, I opted to use a GlobalObjectKey instead:
#override
Widget build(BuildContext context) {
final navigatorKey = GlobalObjectKey<NavigatorState>(context);
...
Now, you can use navigatorKey.currentState anywhere in your widget to access this custom navigator. The full Navigator setup looks like this:
Navigator(
key: navigatorKey,
initialRoute: Page.screenDashboard.route,
onGenerateRoute: (settings) {
final pageName = settings.name;
final page = _fragments.keys.firstWhere((element) => describeEnum(element) == pageName);
return MaterialPageRoute(settings: settings, builder: (context) => _fragments[page]);
},
)
As you can see, we pass the navigatorKey created before and define an initialRoute, making use of the route extension we created. In onGenerateRoute, we find the Page enum entry corresponding to the route name (a String) and then return a MaterialPageRoute with the appropriate _fragments entry.
To push a new route, you simply use the navigatorKey and pushNamed:
onPressed: () => navigatorKey.currentState.pushNamed(Page.screenDashboard.route),
Back button
We also need to customly call pop on our custom navigator. For this purpose, a WillPopScope is needed:
WillPopScope(
onWillPop: () async {
if (navigatorKey.currentState.canPop()) {
navigatorKey.currentState.pop();
return false;
}
return true;
},
child: ..,
)
Access the custom navigator inside of the nested pages
In any page that is passed to onGenerateRoute, i.e. in any of your "fragments", you can just call Navigator.of(context) instead of using the global key. This is possible because these routes are children of the custom navigator and thus, the BuildContext contains that custom navigator.
For example:
// In SearchPage
Navigator.of(context).pushNamed(Page.screenProfile.route);
Default navigator
You might be wondering how you can get access to the MaterialApp root navigator now, e.g. to push a new full screen route. You can use findRootAncestorStateOfType for that:
context.findRootAncestorStateOfType<NavigatorState>().push(..);
or simply
Navigator.of(context, rootNavigator: true).push(..);
Here is the full code:
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_hooks/flutter_hooks.dart';
void main() {
runApp(StartupApplication());
}
enum Page { screenDashboard, screenProfile, screenSearch }
extension on Page {
String get route => describeEnum(this);
}
class StartupApplication extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'BackStack Support App',
home: MainBodyApp(),
);
}
}
class MainBodyApp extends HookWidget {
final Map<Page, Widget> _fragments = {
Page.screenDashboard: DashboardPage(),
Page.screenProfile: UserProfilePage(),
Page.screenSearch: SearchPage(),
};
#override
Widget build(BuildContext context) {
final navigatorKey = GlobalObjectKey<NavigatorState>(context);
return WillPopScope(
onWillPop: () async {
if (navigatorKey.currentState.canPop()) {
navigatorKey.currentState.pop();
return false;
}
return true;
},
child: Scaffold(
appBar: AppBar(
title: Text('BackStack Screen'),
),
body: Container(
child: Column(
children: <Widget>[
Expanded(
child: Navigator(
key: navigatorKey,
initialRoute: Page.screenDashboard.route,
onGenerateRoute: (settings) {
final pageName = settings.name;
final page = _fragments.keys.firstWhere(
(element) => describeEnum(element) == pageName);
return MaterialPageRoute(settings: settings,
builder: (context) => _fragments[page]);
},
),
),
Container(
width: double.infinity,
height: 50.0,
padding: const EdgeInsets.symmetric(horizontal: 15.0),
color: Colors.indigo[400],
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () => navigatorKey.currentState
.pushNamed(Page.screenDashboard.route),
child: Text('Dashboard'),
),
RaisedButton(
onPressed: () => navigatorKey.currentState
.pushNamed(Page.screenProfile.route),
child: Text('Profile'),
),
RaisedButton(
onPressed: () => navigatorKey.currentState
.pushNamed(Page.screenSearch.route),
child: Text('Search'),
),
],
),
),
],
),
),
),
);
}
}
class UserProfilePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(' screenProfile ...'),
);
}
}
class DashboardPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(' screenDashboard ...'),
);
}
}
class SearchPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
child: Text(' screenSearch ...'),
);
}
}