I have an app with three different pages. To navigate between the pages I use a bottom navigation. One of the pages contains a form. Before leaving the form page, I want to display a confirmation dialog. I know that I can intercept the back button of the device with WillPopScope. This works perfectly. But WillPopScope is not triggered when I leave the page via the bottom navigation.
I have also tried using the WidgetsBindingObserver, but both didPopRoute and didPushRoute are never triggered.
Any ideas what I can do?
My form page:
class TodayTimeRecordFormState extends State<TodayTimeRecordForm> with WidgetsBindingObserver{
final formKey = GlobalKey<FormState>();
TodayTimeRecordFormState();
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
#override
Future<bool> didPopRoute() {
//never called
_onWillPop();
}
#override
Future<bool> didPushRoute(String route){
//never called
_onWillPop();
}
#override
Widget build(BuildContext context) {
return WillPopScope(
// called when the device back button is pressed.
onWillPop: _onWillPop,
child: Scaffold(...));
}
Future<bool> _onWillPop() {
return showDialog(
context: context,
child: new AlertDialog(
title: new Text('Are you sure?'),
content: new Text('Unsaved data will be lost.'),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.of(context).pop(false),
child: new Text('No'),
),
new FlatButton(
onPressed: () => Navigator.of(context).pop(true),
child: new Text('Yes'),
),
],
),
);
}
}
Page containing the bottom navigation:
const List<Navigation> allNavigationItems = <Navigation>[
Navigation('Form', Icons.home, Colors.orange),
Navigation('Page2', Icons.work_off, Colors.orange),
Navigation('Page3', Icons.poll, Colors.orange)
];
class HomePage extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return HomePageState();
}
}
class HomePageState extends State<HomePage> {
int selectedTab = 0;
final pageOptions = [
TodayTimeRecordForm(),
Page2(),
Page3(),
),
];
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(AppConfig.instance.values.title),
),
body: pageOptions[selectedTab],
bottomNavigationBar: BottomNavigationBar(
currentIndex: selectedTab,
unselectedItemColor: Colors.grey,
showUnselectedLabels: true,
selectedItemColor: Colors.amber[800],
onTap: (int index) {
setState(() {
selectedTab = index;
});
},
items: allNavigationItems.map((Navigation navigationItem) {
return BottomNavigationBarItem(
icon: Icon(navigationItem.icon),
backgroundColor: Colors.white,
label: navigationItem.title);
}).toList(),
),
);
}
}
You can do it by modifying your BottomNavigationBar's onTap.
onTap: (int index) {
if (selectedTab == 0 && index != 0){
// If the current tab is the first tab (the form tab)
showDialog(
context: context,
child: new AlertDialog(
title: new Text('Are you sure?'),
content: new Text('Unsaved data will be lost.'),
actions: <Widget>[
new FlatButton(
onPressed: () => Navigator.pop(context), // Closes the dialog
child: new Text('No'),
),
new FlatButton(
onPressed: () {
setState(()=>selectedTab = index); // Changes the tab
Navigator.pop(context); // Closes the dialog
},
child: new Text('Yes'),
),
],
),
);
} else {
// If the current tab isn't the form tab
setState(()=>selectedTab = index);
}
},
Related
I am using a bottom navigation bar with 3 pages. One of them (HomePage) has a search icon. Every time I pressed on it, the keyboard appears for barely one second, then disappears and the app rebuilds itself. I tried other solutions online such as putting the future values in initState() but it did not work. Note that I have also implemented a sign up/sign in system. When the user has signed up/ signed in, they will be then redirected to the page which has a bottom navigation bar. (Also, I did some research, and apparently, only android phones are "suffering" from this problem when the flutter app becomes complex. I don't know how much it is true though)
Here is the code for the Bottom Navigation Bar
class BottomNavigationPage extends StatefulWidget{
final AfreecaInvestAccount _afreecaInvestAccount;
_BottomNavigationPageState createState() => _BottomNavigationPageState();
BottomNavigationPage(this._afreecaInvestAccount);
}
class _BottomNavigationPageState extends State<BottomNavigationPage> {
static List<Widget> widgetOptions;
int selectedIndex = 0;
#override
void initState() {
widgetOptions =
[
HomePage(widget._afreecaInvestAccount),
StockSearchPage(),
AccountPage(widget._afreecaInvestAccount)
];
super.initState();
}
void onTabTapped(int index) {
setState(() {
selectedIndex = index;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: widgetOptions.elementAt(selectedIndex),
bottomNavigationBar: _bottomNavigationBar(),
);
}
BottomNavigationBar _bottomNavigationBar() {
return BottomNavigationBar(
items: const<BottomNavigationBarItem>
[
BottomNavigationBarItem(icon: Icon(Icons.home), label: "Home"),
BottomNavigationBarItem(icon: Icon(Icons.search), label: "Search Stocks"),
BottomNavigationBarItem(icon: Icon(Icons.account_box), label: "Account"),
],
currentIndex: selectedIndex,
onTap: onTabTapped,
);
}
}
Here is the code for the Home Page:
class HomePage extends StatefulWidget {
final AfreecaInvestAccount _afreecaInvestAccount;
#override
_HomePage createState() => _HomePage(_afreecaInvestAccount);
HomePage(this._afreecaInvestAccount);
}
class _HomePage extends State<HomePage> {
Future<List<int>> _futureBPandOSValue;
final AfreecaInvestAccount _afreecaInvestAccount;
FutureBuilder<List<int>> _futureBuilder;
String _accessToken;
List<String> list =
[
"TSLA",
"AMZN",
"GME",
"AAPL"
];
_HomePage(this._afreecaInvestAccount);
#override
void initState() {
// TODO: implement initState
// timer = Timer.periodic(Duration(seconds: 30), (timer) {build(context);});
_accessToken = _afreecaInvestAccount.accessToken;
_futureBPandOSValue = _futureBPAndOSValues(_accessToken);
_futureBuilder = FutureBuilder<List<int>> (
future: _futureBPandOSValue,
builder: _BPandOSValueBuilder,
);
super.initState();
}
void buildWidget() {
setState(() {
});
}
#override
Widget build(BuildContext context) {
// String _selectedItem;
return Scaffold(
appBar: AppBar(
title: Text('Home'),
automaticallyImplyLeading: false,
actions: [
IconButton(
icon: Icon(Icons.search),
onPressed: () {
showSearch(context: context, delegate: StockSearch());
},
)
],
),
body: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
// crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_futureBuilder,
SizedBox(height: 10.0,),
Card(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: _returnStocksTable(context, _accessToken),
/* _returnStocksTable method is not included in this code block because it is not the cause of the problem */
)
),
],
),
)
);
}
}
For this code, showSearch is already provided by flutter itself :
onPressed: () {
showSearch(context: context, delegate: StockSearch());
},
Here is the StockSearch() file:
class StockSearch extends SearchDelegate<String> {
String accessToken = "djdjicjscnjdsncjs";
List<String> stockList =
[
"TSLA",
"AMZN",
"GME",
"AAPL"
];
final recentStocks = ['TSLA'];
#override
List<Widget> buildActions(BuildContext context) {
// actions for app bar
return [
IconButton(icon: Icon(Icons.clear), onPressed: () {
query = "";
})
];
}
#override
Widget buildLeading(BuildContext context) {
// leading icon on the left of the app bar
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow,
progress: transitionAnimation,
),
onPressed: () {
close(context, null);
}
);
}
#override
Widget buildResults(BuildContext context) {
return Container();
}
#override
Widget buildSuggestions(BuildContext context) {
// show when someone searches for stocks
final suggestionList = query.isEmpty? recentStocks
: stockList.where((stock) => stock.startsWith(query)).toList();
return ListView.builder(
itemBuilder: (context, index) => ListTile(
onTap: () {
WidgetsBinding.instance.addPostFrameCallback((timeStamp) {
Navigator.push(context, MaterialPageRoute(builder: (context) => TransactionPage(suggestionList.elementAt(index), accessToken)));
});
},
title: RichText(text: TextSpan(
text: suggestionList.elementAt(index).substring(0, query.length),
style: TextStyle( color: Colors.black, fontWeight: FontWeight.bold),
children: [TextSpan(
text: suggestionList.elementAt(index).substring(query.length),
style: TextStyle(color: Colors.grey)
)
]
)
),
),
itemCount: suggestionList.length,
);
}
}
Thank you in advance for your help!
I am trying to get text to show on the front landing page that has the FloatingActionButton, rather than redirect the text input onto a new page. Can someone point out where I am going wrong? Any help is appreciated
Below is the code, I have removed the code the Class code that the FloatingActionButton is on the Class is Home Extends StatelessWidget:
floatingActionButton: FloatingActionButton(
onPressed: () {
createAlertDialog(context);
},
child: Icon(Icons.add),
backgroundColor: Colors.red,
),
);
}
createAlertDialog(BuildContext context) {
showDialog(context: context, builder: (context) {
String value;
return AlertDialog(
title: Text("Add RSS URL"),
content: TextField(
keyboardType: TextInputType.emailAddress,
onChanged: (text) {
value = text;
},
),
actions: <Widget>[
MaterialButton(
elevation: 5.0,
child: Text('Add'),
onPressed: () {
Navigator.of(context)
.push(MaterialPageRoute(builder: (context) => Dude(value)));
},
),
],
);
},
);
}
}
class URL extends StatefulWidget {
final String text;
URL(this.text);
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<URL> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
child: Center(
child: Text(widget.text),
),
),
);
}
}
I'm unsure what you're asking but your button opens a Modal Dialog which is a pop-up screen.
If you want to remain on the same screen, then get rid of the modal.
You want to use a combination of Visibility() which takes in a visibility: boolean parameter.
Let's say you declare bool _visibility = false;
When the user presses the FloatingActionButton, you want
onPressed: () => {setState(() { _visibility = !_visibility }); }
https://api.flutter.dev/flutter/widgets/Visibility-class.html
This is my scenario:
The App has a Tabbar with 5 Tabs
Some Views have a detail view
Implementation of the detail view:
onTap: () => Navigator.push(context,
MaterialPageRoute(builder: (context) => RPlanDetail(lesson))),
(RPlanDetail is the detail view and lesson is the passed data)
The base view
Now to the problem:
When I return to the base view the index on the tabbar is wrong. So the tabbar indicates that you are on the first view while you are on another view.
How can I fix this?
Please let me know if you need additional information.
Thank you for your help!
I have a code demo as below:
import 'package:flutter/material.dart';
void main() {
runApp(
MaterialApp(
home: TabBarDemo(),
),
);
}
class TabBarDemo extends StatefulWidget {
#override
State<StatefulWidget> createState() {
// TODO: implement createState
return TabBarDemoState();
}
}
class TabBarDemoState extends State<TabBarDemo> with TickerProviderStateMixin {
static int _index = 0;
TabController _controller;
#override
void initState() {
super.initState();
_controller = TabController(
initialIndex: TabBarDemoState._index, length: 3, vsync: this);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
bottom: TabBar(
controller: _controller,
tabs: [
Tab(icon: Icon(Icons.directions_car)),
Tab(icon: Icon(Icons.directions_transit)),
Tab(icon: Icon(Icons.directions_bike)),
],
onTap: (int index) {
TabBarDemoState._index = index;
},
),
title: Text('Tabs Demo'),
),
body: TabBarView(
controller: _controller,
children: [
Center(
child: FlatButton(
color: Colors.red,
child: Text("Go to Detail page"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (ct) => DetailPage(TabBarDemoState._index)));
},
),
),
Center(
child: FlatButton(
color: Colors.red,
child: Text("Go to Detail page"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (ct) => DetailPage(TabBarDemoState._index)));
},
),
),
Center(
child: FlatButton(
color: Colors.red,
child: Text("Go to Detail page"),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (ct) => DetailPage(TabBarDemoState._index)));
},
),
),
],
),
);
}
}
class DetailPage extends StatelessWidget {
DetailPage(this.index);
final int index;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text("Detail page $index"),
),
);
}
}
I hope it can help you. But I have an advise for you that you should replace Navigator.push to Navigator.pushName and use Navigate with named routes, Because it don't create a new screen. You can read more: https://flutter.dev/docs/cookbook/navigation/named-routes
Set your Tab Controller
inside 'initState();'
Never put Tab Controller in 'build(BuildContext context)'
#override
void initState() {
super.initState();
_tabController = new TabController(length: 3, vsync: this);
}
I added a SimpleDialog with 2 options: lost and found. Whenever I make my selection and get redirected to where I want, the SimpleDialog doesn't close and stays on my screen.
The switch:
switch (
await showDialog(
context: context,
child: new SimpleDialog(
title: new Text("Which category?"),
children: <Widget>[
new SimpleDialogOption(child: new Text("Found"),
onPressed: () {
goToCreate();
},
),
new SimpleDialogOption(child: new Text("Lost"),
onPressed: () {
//Whatever
},
),
],
)
)
)
And the cases:
{
case "Found":
goToCreate();
break;
case "Lost":
//Whatever
break;
}
You can do this from the dialog when you press Accept (or whatever):
Navigator.pop(context, true); // You could return any Dart type, like an enum
From the caller:
bool dialogReturnValue = await showDialog(...);
if (dialogReturnValue == true){
// do something
}
From official docs: https://docs.flutter.io/flutter/material/SimpleDialog-class.html
You need to execute inside options' onPressed method this:
Navigator.pop(context, ===arguments===);
Full example:
SimpleDialog(
title: const Text('Select assignment'),
children: <Widget>[
SimpleDialogOption(
onPressed: () { Navigator.pop(context, Department.treasury); },
child: const Text('Treasury department'),
),
SimpleDialogOption(
onPressed: () { Navigator.pop(context, Department.state); },
child: const Text('State department'),
),
],
);
EDIT:
switch (
await showDialog(
context: context,
child: new SimpleDialog(
title: new Text("Which category?"),
children: <Widget>[
new SimpleDialogOption(child: new Text("Found"),
onPressed: () {
Navigator.pop(context, 'Found'); //Close the SimpleDialog then=>
goToCreate();
},
),
new SimpleDialogOption(child: new Text("Lost"),
onPressed: () {
Navigator.pop(context, 'Lost'); //For closing the SimpleDialog
//After that do whatever you want
},
),
],
)
)
)
)
EDIT 2 (Demo Application):
import 'package:flutter/material.dart';
void main() {
runApp(App());
}
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Test(),
);
}
}
class Test extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: RaisedButton(onPressed: () {
_askedToLead(context);
}),
),
);
}
Future<void> _askedToLead(BuildContext context) async {
switch (await showDialog<String>(
context: context,
builder: (BuildContext context) {
return SimpleDialog(
title: const Text('Select assignment'),
children: <Widget>[
SimpleDialogOption(
onPressed: () {
Navigator.pop(context, 'Found');
},
child: const Text('FOUND'),
),
SimpleDialogOption(
onPressed: () {
Navigator.pop(context, 'Lost');
},
child: const Text('LOST'),
),
],
);
})) {
case 'Found':
print('FOUND!');
break;
case 'Lost':
print('LOST!');
break;
}
}
}
Can anyone post a sample flutter code to enable a button when the scrollbar is at the bottom of the list?
I used NotificationListener and NotificationListener but event is not firing when scrollbar is at the bottom of the list.
I want to enable the button only when the scrollbar is at the bottom of the list else it should be disabled.
Below is the code that I am using.
class _TermsAndCondnsPage extends State<TermsAndCondnsPage> {
bool _isButtonEnabled = false;
bool _scrollingStarted() {
setState(() => _isButtonEnabled = false);
return false;
}
bool _scrollingEnded() {
setState(() => _isButtonEnabled = true);
return true;
}
ScrollController scrollcontroller;
#override
Widget build(BuildContext context) {
var acceptAndContinueButtonPressed;
if (_isButtonEnabled) {
acceptAndContinueButtonPressed = () {};
} else {
acceptAndContinueButtonPressed == null;
}
return new Scaffold(
appBar: new AppBar(
automaticallyImplyLeading: false,
title: new Text('Terms And Conditions', textAlign: TextAlign.center),
),
body:
NotificationListener<ScrollStartNotification>(
onNotification: (_) => _scrollingStarted(),
child: NotificationListener<ScrollEndNotification>(
onNotification: (_) => _scrollingEnded(),
child: new MaterialApp(
home: new Scaffold(
body: new SingleChildScrollView(
controller: scrollcontroller,
child: new Column(
children: <Widget>[
new Center(
child: new HtmlView(
data: html,
),
),
],
),
),
persistentFooterButtons: <Widget>[
new RaisedButton(
color: Colors.blue,
onPressed: _isButtonEnabled ? () {} : null,
child: new Text(
'Accept and Continue',
style: new TextStyle(
color: Colors.white,
),
)),
],
),
),
)),
);
}
String html = '''
<p>Sample HTML</p>
''';
}
Here you have a basic example, enable the button when reach the bottom, and disable when reach the top.
class ExampleState extends State<Example> {
final list = List.generate((40), (val) => "val $val");
final ScrollController _controller = new ScrollController();
var reachEnd = false;
_listener() {
final maxScroll = _controller.position.maxScrollExtent;
final minScroll = _controller.position.minScrollExtent;
if (_controller.offset >= maxScroll) {
setState(() {
reachEnd = true;
});
}
if (_controller.offset <= minScroll) {
setState(() {
reachEnd = false;
});
}
}
#override
void initState() {
_controller.addListener(_listener);
super.initState();
}
#override
void dispose() {
_controller.removeListener(_listener);
_controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Stack(children: [
Positioned(
top: 0.0,
bottom: 50.0,
width: MediaQuery.of(context).size.width,
child: ListView.builder(
controller: _controller,
itemBuilder: (context, index) {
return ListTile(
title: Text(list[index]),
);
},
itemCount: list.length,
),
),
Align(
alignment: Alignment.bottomCenter,
child: RaisedButton(
child: Text("button"),
color: Colors.blue,
onPressed: reachEnd ? () => null : null,
))
]);
}
}