I'm developing an app that is using location data. So in the landing screen, I will check whether the permission to access location data is granted or not. I do this using the geolocator package. When the user grants permission, I will collect the data like current location and then I want to automatically move into another screen, which is the main screen of the app. Is there any way that I'm able to do that?
I'm making a simple app to demonstrate the permission checking and proper handling of the navigation based on the outcome. You can check the below code and commentation to understand the flow:
import 'package:flutter/material.dart';
import 'package:geolocator/geolocator.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SomeScreen(),
);
}
}
class SomeScreen extends StatelessWidget {
final _scaffoldKey = GlobalKey<ScaffoldState>();
void _determinePosition(context) async {
bool serviceEnabled;
LocationPermission permission;
serviceEnabled = await Geolocator.isLocationServiceEnabled();
// Checking if the service is enabled
if (!serviceEnabled) {
// If not, display a Snackbar
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text('Location services are disabled.'),
duration: Duration(seconds: 2),
));
}
// Checking the permission here
permission = await Geolocator.checkPermission();
if (permission == LocationPermission.denied) {
permission = await Geolocator.requestPermission();
if (permission == LocationPermission.deniedForever) {
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text(
'Location permissions are permanently denied, we cannot request permissions.'),
duration: Duration(seconds: 2),
));
}
if (permission == LocationPermission.denied) {
_scaffoldKey.currentState.showSnackBar(SnackBar(
content: Text('Location permissions are denied'),
duration: Duration(seconds: 2),
));
}
}
// Finally if the permission is granted, navigate the user to new screen
Position position = await Geolocator.getCurrentPosition();
Navigator.push(context,
MaterialPageRoute(builder: (context) => Home(position: position)));
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: Container(
alignment: Alignment.center,
color: Colors.blue,
child: FlatButton(
onPressed: () => _determinePosition(context),
child: Text("Check permission"),
color: Colors.red,
),
),
);
}
}
// A simple screen to display the position
class Home extends StatefulWidget {
final Position position;
Home({this.position});
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: Text(
'Location: ${widget.position.altitude} ${widget.position.longitude}'),
),
),
);
}
}
If you want your main screen depend on a value or condition I think you could simply use a Streambuilder at your home, whose streamcontroller receives a boolean, depending on which it'll return one screen or another.
Here for example I used this Streambuilder that listens to a boolean. I will change the value of this boolean to true by pressing a button. In my Streambuilder at my home I have the condition that, if the info received, the snapshot.data is true, then it should not return the MyHomePage but instead TheOtherRoute.
import 'dart:async';
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
final StreamController<bool> _streamController =
StreamController<bool>.broadcast();
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: StreamBuilder(
stream: _streamController.stream,
builder: (context, snapshot) {
if(snapshot.data == true){
return TheOtherRoute();
}
return MyHomePage();
},
),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('this is a testo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Container(
child: FlatButton(
color: Colors.black38,
child: Text('Change boolean'),
onPressed: () {
_streamController.add(true);
},
),
),
],
),
),
);
}
}
class FirstRoute extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('First Route'),
),
body: Center(
child: Text('Your boolean changed. Your home page is now FirstRoute()'),
),
);
}
}
Try it out and let me know.
Related
I have a function named saveData which is executed on pressing a button. I want if I click on the button I execute saveData function and the value of the button become stop then when I click on stop the function should be fininish.
this is the button code:
Align(
alignment: Alignment.bottomCenter,
child: TextButton(
onPressed: () {
saveData();
},
child: Text('Save Data'),
),
),
One way to achieve what you want is simply to create a flag to control which button (text/action) is shown at any given moment:
TextButton(
onPressed: isSaving ? Finish : saveData,
child: isSaving ? const Text("Stop") : const Text("Save Data"),
)
Try the following working complete sample to see what i mean:
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key, required this.title});
final String title;
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool isSaving = false;
Future saveData() async {
isSaving = true;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Saving data..."),duration: Duration(hours: 1),)
);
setState(() { });
}
void Finish() {
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text("Saving data stopped..."),duration: Duration(seconds: 1),)
);
isSaving = false;
setState(() { });
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: TextButton(
onPressed: isSaving ? Finish : saveData,
child: isSaving ? const Text("Stop") : const Text("Save Data"),
)
),
);
}
}
This will produce a result like:
State 1
After Save Data is tapped
You need state management.
State Management
This is a way to manage your user interface controls such as text fields, buttons, images, etc. It controls what and when something should display or perform an action. More about Flutter state management here
Codebase Sample
String name = ""; // setting an empty name variable
Align(
alignment: Alignment.bottomCenter,
child: TextButton(
onPressed: () {
setState(() {
name = "new name"; // updating the name variable with setState
});
},
child: Text('Save Data'),
),
),
Now, to implement your idea. You need a bool variable that changes the state on the button click action. To do that, look what I did below
bool isClicked = false;
Align(
alignment: Alignment.bottomCenter,
child: TextButton(
onPressed: () {
setState(() => isClicked = !isClicked); // change click state
if (isClicked) {
// do something on click
} else {
// do something off click
}
},
child: Text(isClicked ? "Stop" : "Save Data"), // if isClicked display "Stop" else display "Save Data"
),
),
Another way to do this is to create two different functions. One for saving user data, and the other of stop and calling the action based on a bool state.
onPressed: isSaving ? saveData : stop,
You can use the method above to update your user data as well if any misunderstand or need future help, comment below. Bye
Basically this is a state management problem.
you get more information about state management from here
Here a code for solve your problem
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomeView(),
);
}
}
class HomeView extends StatefulWidget {
const HomeView({Key? key}) : super(key: key);
#override
State<HomeView> createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
bool _savePressed = false;
void _save() {
// TODO do whatever you want
}
void _stop() {
// TODO do whatever you want
}
void _onButtonPressed() {
setState(() {
_savePressed = !_savePressed;
_savePressed ? _save() : _stop();
});
}
String get _getButtonText => _savePressed ? "Stop" : "Save";
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Align(
alignment: Alignment.bottomCenter,
child: TextButton(
onPressed: _onButtonPressed,
child: Text(_getButtonText),
),
),
);
}
}
I want a widget that will sit on top of the entire application. When I have tried to do this with Overlay.of(context).insert the overlay would later disappear after replacing that route. Is there a way I can have a widget on top of my app even if the screen is later popped?
Maybe a more optimal way exists, but as an option this is an example with two pages, local navigator and Overlay.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: WillPopScope(
onWillPop: () async => !await _navigatorKey.currentState.maybePop(),
child: LayoutBuilder(
builder: (context, constraints) {
WidgetsBinding.instance.addPostFrameCallback((_) => _insertOverlay(context));
return Navigator(
key: _navigatorKey,
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/page2':
return MaterialPageRoute(builder: (_) => Page2());
default:
return MaterialPageRoute(builder: (_) => Page1(_navigatorKey));
}
},
);
},
),
),
);
}
void _insertOverlay(BuildContext context) {
return Overlay.of(context).insert(
OverlayEntry(builder: (context) {
final size = MediaQuery.of(context).size;
print(size.width);
return Positioned(
width: 56,
height: 56,
top: size.height - 72,
left: size.width - 72,
child: Material(
color: Colors.transparent,
child: GestureDetector(
onTap: () => print('ON TAP OVERLAY!'),
child: Container(
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.redAccent),
),
),
),
);
}),
);
}
}
class Page1 extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
Page1(this.navigatorKey);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green[200],
appBar: AppBar(title: Text('Page1')),
body: Container(
alignment: Alignment.center,
child: RaisedButton(
child: Text('go to Page2'),
onPressed: () => navigatorKey.currentState.pushNamed('/page2'),
),
),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.yellow[200],
appBar: AppBar(title: Text('back to Page1')),
body: Container(
alignment: Alignment.center,
child: Text('Page 2'),
),
);
}
}
Screenshot (Null safe):
Full code:
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Offset _offset = Offset.zero;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: LoginPage(),
builder: (context, child) {
return Stack(
children: [
child!,
Positioned(
left: _offset.dx,
top: _offset.dy,
child: GestureDetector(
onPanUpdate: (d) => setState(() => _offset += Offset(d.delta.dx, d.delta.dy)),
child: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.black,
child: Icon(Icons.add),
),
),
),
],
);
},
);
}
}
LoginPage:
class LoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('LoginPage')),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => HomePage())),
child: Text('Page2'),
),
),
);
}
}
HomePage:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: FlutterLogo(size: 300),
);
}
}
After reading the comments, find github-repo-link
created an overlay that will sit on top of everything
that can be called from anywhere.
just 4 easy steps to follow
flutterflutter-layout
STEP-1: in main.dart:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Stack( <-- using stack
children: [
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
OverlayView(),<-- my overlay widget
],
),
);
}
}
STEP-2: OverLayView.dart
class OverlayView extends StatelessWidget {
const OverlayView({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>( <--- IMP , using ValueListenableBuilder for showing/removing overlay
valueListenable: Loader.appLoader.loaderShowingNotifier,
builder: (context, value, child) {
if (value) {
return yourOverLayWidget(); <-- your awesome overlay
} else {
return Container();
}
},
);
}
STEP-3: loder_controller.dart (to show/hide)
class Loader {
static final Loader appLoader = Loader(); <-- singleton
ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false);
ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');
void showLoader() { <-- using to show from anywhere
loaderShowingNotifier.value = true;
}
void hideLoader() { <-- using to hide from anywhere
loaderShowingNotifier.value = false;
}
void setText({String errorMessage}) { <-- using to change error message from anywhere
loaderTextNotifier.value = errorMessage;
}
void setImage() { <-- DIY
// same as that of setText //
}
}
FINAL STEP-4: show/hide loder
I'm showing it, on boilerplate code of increment method to show the loader
void _incrementCounter() async {
Loader.appLoader.showLoader(); <-- show loder
Loader.appLoader.setText(errorMessage: 'this is custom error message');<-- set custom message
await Future.delayed(Duration(seconds: 5)); <-- im hiding it after 5 sec
Loader.appLoader.hideLoader(); <-- do whatever you want
}
As a supplement to other answers: If you want to show some overlays, the flutter_portal may indeed be a better choice that is simpler to use.
Basically, it looks like:
PortalTarget(
// Declarative
portalFollower: MyAwesomeOverlayWidget(),
// Align anywhere you like. Now `portalFollower` floats at right of `child`
anchor: const Aligned(follower: Alignment.topLeft, target: Alignment.topRight),
child: MyChildWidget(),
)
Notice that it is declarative (not imperative as opposed to Overlay). Moreover, you get the bonus that the alignment is very easy, and the context is intuitive.
Disclaimer: I am the current owner of this library.
Have you tried to add a Navigator as a child/descendant of your Scaffold? As far as I remember, the default navigator is in the MaterialApp, which is above everything. When you add your own Navigator, your routing will happen under the Scaffold rather than above it in the tree.
I have a BottomNavigationBar with 3 tabs. Consider I select a product in an e-commerce app from the pages inside the first BottomNavigationBarItem. I need to see that product in the second BottomNavigationBarItem(cart page). I have written the network call code in initState() of second BottomNavigationBarItem; but it will not be called when I go to that page and I can't see the recently added product to the cart. Is it better to write them in the build method itself? Writing them in the build method calls it every time I go to other tabs also.
Use FutureBuilder or StreamBuilder to network call and flow the data to UI
Hope this will help you
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
int _selectedPage = 0;
String _selectedProduct;
Widget getCurrentPage(){
switch(_selectedPage){
case 0:
return Page1((selectedProduct){
setState((){
this._selectedProduct = selectedProduct;
_selectedPage=1;
});});
case 1:
return Page2(this._selectedProduct);
case 2:
return Page3();
default:
return Center(child:Text('Error'));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: getCurrentPage(),
bottomNavigationBar: BottomNavigationBar(
onTap: (index){
setState((){
_selectedPage = index;
});
},
currentIndex: _selectedPage,
items: ['tab 1', 'tab 2', 'tab3'].map((e)=>BottomNavigationBarItem(
icon: Container(),
title: Text(e),
)).toList(),),
);
}
}
class Page1 extends StatelessWidget {
final Function(String) onProductClick;
const Page1(this.onProductClick);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:Text('Page 1')),
body:Column(
children: <Widget>[
RaisedButton(
child: Text('Product 1'),onPressed: ()=>onProductClick('Product 1'),),
RaisedButton(
child: Text('Product 2'),onPressed: ()=>onProductClick('Product 2'),),
RaisedButton(
child: Text('Product 3'),onPressed: ()=>onProductClick('Product 3'),),
RaisedButton(
child: Text('Product 4'),onPressed: ()=>onProductClick('Product 4'),),
RaisedButton(
child: Text('Product 5'),onPressed: ()=>onProductClick('Product 5'),),
],)
);
}
}
class Page2 extends StatelessWidget {
final String selectedProduct;
const Page2(this.selectedProduct);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:Text('Page 2')),
body:Center(child:Text(selectedProduct??'Nothing selected'))
);
}
}
class Page3 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title:Text('Page 3')),
body:Center(child:Text('Page 3'))
);
}
}
I want a widget that will sit on top of the entire application. When I have tried to do this with Overlay.of(context).insert the overlay would later disappear after replacing that route. Is there a way I can have a widget on top of my app even if the screen is later popped?
Maybe a more optimal way exists, but as an option this is an example with two pages, local navigator and Overlay.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _navigatorKey = GlobalKey<NavigatorState>();
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: WillPopScope(
onWillPop: () async => !await _navigatorKey.currentState.maybePop(),
child: LayoutBuilder(
builder: (context, constraints) {
WidgetsBinding.instance.addPostFrameCallback((_) => _insertOverlay(context));
return Navigator(
key: _navigatorKey,
onGenerateRoute: (RouteSettings settings) {
switch (settings.name) {
case '/page2':
return MaterialPageRoute(builder: (_) => Page2());
default:
return MaterialPageRoute(builder: (_) => Page1(_navigatorKey));
}
},
);
},
),
),
);
}
void _insertOverlay(BuildContext context) {
return Overlay.of(context).insert(
OverlayEntry(builder: (context) {
final size = MediaQuery.of(context).size;
print(size.width);
return Positioned(
width: 56,
height: 56,
top: size.height - 72,
left: size.width - 72,
child: Material(
color: Colors.transparent,
child: GestureDetector(
onTap: () => print('ON TAP OVERLAY!'),
child: Container(
decoration: BoxDecoration(shape: BoxShape.circle, color: Colors.redAccent),
),
),
),
);
}),
);
}
}
class Page1 extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
Page1(this.navigatorKey);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.green[200],
appBar: AppBar(title: Text('Page1')),
body: Container(
alignment: Alignment.center,
child: RaisedButton(
child: Text('go to Page2'),
onPressed: () => navigatorKey.currentState.pushNamed('/page2'),
),
),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.yellow[200],
appBar: AppBar(title: Text('back to Page1')),
body: Container(
alignment: Alignment.center,
child: Text('Page 2'),
),
);
}
}
Screenshot (Null safe):
Full code:
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Offset _offset = Offset.zero;
#override
Widget build(BuildContext context) {
return MaterialApp(
home: LoginPage(),
builder: (context, child) {
return Stack(
children: [
child!,
Positioned(
left: _offset.dx,
top: _offset.dy,
child: GestureDetector(
onPanUpdate: (d) => setState(() => _offset += Offset(d.delta.dx, d.delta.dy)),
child: FloatingActionButton(
onPressed: () {},
backgroundColor: Colors.black,
child: Icon(Icons.add),
),
),
),
],
);
},
);
}
}
LoginPage:
class LoginPage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('LoginPage')),
body: Center(
child: ElevatedButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => HomePage())),
child: Text('Page2'),
),
),
);
}
}
HomePage:
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('HomePage')),
body: FlutterLogo(size: 300),
);
}
}
After reading the comments, find github-repo-link
created an overlay that will sit on top of everything
that can be called from anywhere.
just 4 easy steps to follow
flutterflutter-layout
STEP-1: in main.dart:
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Stack( <-- using stack
children: [
MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
),
OverlayView(),<-- my overlay widget
],
),
);
}
}
STEP-2: OverLayView.dart
class OverlayView extends StatelessWidget {
const OverlayView({
Key key,
}) : super(key: key);
#override
Widget build(BuildContext context) {
return ValueListenableBuilder<bool>( <--- IMP , using ValueListenableBuilder for showing/removing overlay
valueListenable: Loader.appLoader.loaderShowingNotifier,
builder: (context, value, child) {
if (value) {
return yourOverLayWidget(); <-- your awesome overlay
} else {
return Container();
}
},
);
}
STEP-3: loder_controller.dart (to show/hide)
class Loader {
static final Loader appLoader = Loader(); <-- singleton
ValueNotifier<bool> loaderShowingNotifier = ValueNotifier(false);
ValueNotifier<String> loaderTextNotifier = ValueNotifier('error message');
void showLoader() { <-- using to show from anywhere
loaderShowingNotifier.value = true;
}
void hideLoader() { <-- using to hide from anywhere
loaderShowingNotifier.value = false;
}
void setText({String errorMessage}) { <-- using to change error message from anywhere
loaderTextNotifier.value = errorMessage;
}
void setImage() { <-- DIY
// same as that of setText //
}
}
FINAL STEP-4: show/hide loder
I'm showing it, on boilerplate code of increment method to show the loader
void _incrementCounter() async {
Loader.appLoader.showLoader(); <-- show loder
Loader.appLoader.setText(errorMessage: 'this is custom error message');<-- set custom message
await Future.delayed(Duration(seconds: 5)); <-- im hiding it after 5 sec
Loader.appLoader.hideLoader(); <-- do whatever you want
}
As a supplement to other answers: If you want to show some overlays, the flutter_portal may indeed be a better choice that is simpler to use.
Basically, it looks like:
PortalTarget(
// Declarative
portalFollower: MyAwesomeOverlayWidget(),
// Align anywhere you like. Now `portalFollower` floats at right of `child`
anchor: const Aligned(follower: Alignment.topLeft, target: Alignment.topRight),
child: MyChildWidget(),
)
Notice that it is declarative (not imperative as opposed to Overlay). Moreover, you get the bonus that the alignment is very easy, and the context is intuitive.
Disclaimer: I am the current owner of this library.
Have you tried to add a Navigator as a child/descendant of your Scaffold? As far as I remember, the default navigator is in the MaterialApp, which is above everything. When you add your own Navigator, your routing will happen under the Scaffold rather than above it in the tree.
I am trying to rebuild iOS app in Flutter, but facing a problem with navigation.
Here what I am trying to do:
List of Added Exchange Pairs with Add button (A screen)
Add button opens Picker with Exchanges (B screen) with transition from bottom to top.
By tapping on exchange it pushes new Picker with Pairs (C
screen) with transition from right to left.
when user taps on pair it closes all pickers at once and deliver result of picking to A screen.
I have tried double pop and popUntil but result always same, I see 2 back transitions (left to right and top to bottom) at same time.
How it looks in iOS native app:
How it looks in Flutter app:
Solved with nested Navigator
Wrapped Screen B with Navigator and used this navigator to push screen C, on screen C used root navigator to pop. Result is below:
Here the example of how I solved it:
import 'package:flutter/material.dart';
void main() {
MaterialPageRoute.debugEnableFadingRoutes = true;
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Demo',
theme: new ThemeData(
primarySwatch: Colors.blue,
),
home: new MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String _result = "--";
void _openSubscreen() {
Navigator.of(context).push<String>(
new MaterialPageRoute(
settings: RouteSettings(name: '/subscreen'),
builder: (context) => SubScreen(),
),
).then((result) => setState((){
_result = result;
}));
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(widget.title),
),
body: new Center(
child: new Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(
'Result from navigator:',
),
new Text(
_result,
textAlign: TextAlign.center,
style: Theme.of(context).textTheme.headline,
),
SizedBox(height: 32.0,),
OutlineButton(
onPressed: _openSubscreen,
child: Text('Start flow'),
),
],
),
),
);
}
}
class SubScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Navigator(
onGenerateRoute: (routeSettings) {
final path = routeSettings.name;
if (path == '/') {
return new MaterialPageRoute(
settings: routeSettings.copyWith(isInitialRoute: true),
builder: (_) => SubScreenPage1(),
);
} else if (path == '/nexpage') {
return new MaterialPageRoute(
settings: routeSettings,
builder: (_) => SubScreenPage2(),
);
}
},
),
);
}
}
class SubScreenPage1 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Center(
child: OutlineButton(
child: Text('Next sub page!'),
onPressed: () {
Navigator.of(context).pushNamed('/nexpage');
},
),
);
}
}
class SubScreenPage2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Center(
child: OutlineButton(
child: Text('Deliver result!'),
onPressed: () {
final date = DateTime.now().toString();
Navigator
.of(context, rootNavigator: true)
.pop('Delivered at $date');
},
),
);
}
}
When you build your MaterialApp by setting home: and routes: you can achieve "pop to root" without hardcoding what route to pop until by;
Navigator.popUntil(
context,
ModalRoute.withName(Navigator.defaultRouteName),
);
Because Navigator.defaultRouteName will be set to whatever you set home: to.
Going a bit off-topic but, this is especially nice if you have "variable" home screen, as in using a FutureBuilder to decide what will be the home screen. For example, if you are showing a splash screen until you are loading the initial state from disk.
home: isUserLoggedIn
? HomePage()
: FutureBuilder(
future: () async {
print('Initializing');
print('Waiting For NoReason');
await Future.delayed(const Duration(seconds: 1));
print('Initialization Complete');
}(),
builder: (_, snap) {
if (snap.connectionState == ConnectionState.waiting) {
return SplashPage();
} else {
return LogInPage();
}
},
),