I have created a Navigation Drawer in Flutter.
Source available here - https://github.com/deadcoder0904/flutter_navigation_drawer
It looks like this -
First Screen
When I click the button, it goes to
Second Screen
When I click the button, it goes to
Tabs Screen
When I click the hamburger icon on First Screen, it goes to
Drawer Screen
Now when I click on the 2nd List Item on Drawer Screen, I get the Second Screen like this
Relevant code is in navigation_drawer.dart which looks like -
class NavigationDrawer extends StatefulWidget {
_NavigationDrawerState createState() => _NavigationDrawerState();
}
class _NavigationDrawerState extends State<NavigationDrawer> {
int _selectionIndex = 0;
final drawerItems = [
DrawerItem("First Screen", Icons.looks_one),
DrawerItem("Second Screen", Icons.looks_two),
DrawerItem("Tabs", Icons.tab),
];
_getDrawerItemScreen(int pos) {
switch (pos) {
case 1:
return SecondScreen();
case 2:
return Tabs();
default:
return FirstScreen();
}
}
_onSelectItem(int index) {
setState(() {
_selectionIndex = index;
_getDrawerItemScreen(_selectionIndex);
});
Navigator.of(context).pop();
}
#override
Widget build(BuildContext context) {
List<Widget> drawerOptions = [];
for (var i = 0; i < drawerItems.length; i++) {
var d = drawerItems[i];
drawerOptions.add(ListTile(
leading: Icon(d.icon),
title: Text(
d.title,
style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.w400),
),
selected: i == _selectionIndex,
onTap: () => _onSelectItem(i),
));
}
return Scaffold(
appBar: AppBar(
title: Text('First Screen'),
),
drawer: Drawer(
child: Column(
children: <Widget>[
UserAccountsDrawerHeader(
accountName: Text('Akshay Kadam (A2K)'),
accountEmail: Text('a2k#gmail.com'),
),
Column(
children: drawerOptions,
),
],
),
),
body: _getDrawerItemScreen(_selectionIndex),
);
}
}
How do I get the Second Screen without the Hamburger Icon & First Screen title?
First, change your code to set HomePage
body: _getDrawerItemScreen(_selectionIndex),
to
body: FirstScreen(),
Secondly,
_onSelectItem(int index) {
setState(() {
_selectionIndex = index;
_getDrawerItemScreen(_selectionIndex);
});
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => _getDrawerItemScreen(_selectionIndex),
),
);
}
Related
So in my app, I have a screen with a tabBar. When the screen loads, it sorts out the items and moves them into different tabs. But when I run this, The Items keep duplicating, and I am shown an error in the debug console that says I called setState() after dispose()
Here's the code for the screen:
import 'package:flutter/material.dart';
import './uiComponents/customWidgets.dart';
import './ticketsComponents/ticketsList.dart';
import './tabs.dart';
class Tickets extends StatefulWidget {
Tickets({ this.tickets, this.user });
final List tickets;
final Map user;
#override
_TicketsState createState() => new _TicketsState();
}
class _TicketsState extends State<Tickets> with SingleTickerProviderStateMixin {
TabController controller; // Tab controller for the screen
List _tickets;
// Variables to Store the sorted Tickets
List _availableTickets = [];
List _usedTickets = [];
List _expiredTickets = [];
#override
void initState(){
controller = new TabController(
vsync: this,
length: 4,
initialIndex: 1
);
WidgetsBinding.instance.addPersistentFrameCallback((_) async {
// Get the tickets and sort them
_tickets = widget.tickets;
if(_tickets != null){
_sortTickets();
}
});
super.initState();
}
#override
void dispose(){
controller.dispose();
super.dispose();
}
// DELETE A TICKET (FROM ID)
void deleteTicket(int id){
setState(() {
_tickets.removeWhere((item)=> item["id"] == id);
_availableTickets = [];
_usedTickets = [];
_expiredTickets = [];
_sortTickets();
});
}
// SORT THE TICKETS INTO AVAILABLE / UNUSED, USED AND EXPIRED
void _sortTickets(){
for (int i = 0; i < _tickets.length; i++){
Map ticket = _tickets[i];
if(ticket["isUsed"]){
setState(() {
_usedTickets.add(ticket);
});
}
else if(ticket["expired"]){
setState(() {
_expiredTickets.add(ticket);
});
}
else{
setState(() {
_availableTickets.add(ticket);
});
}
}
}
// NAVIGATE TO MAIN TAB AND CLEAR PREVIOUS ROUTES
void _navProfile(){
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (context) => Tabs(
user: widget.user
)
),
(route)=> false
);
}
// TabBar for Filtering Tickets
TabBar _buildTabBar(){
return new TabBar(
controller: controller,
indicatorWeight:2.2,
labelStyle: TextStyle(
fontSize:14.0,
fontWeight: FontWeight.bold
),
unselectedLabelStyle: TextStyle(
fontWeight:FontWeight.normal
),
labelColor: blue,
indicatorColor: blue,
unselectedLabelColor: Colors.black,
tabs: [
Tab(text: "All"),
Tab(text: "Available"),
Tab(text: "Used"),
Tab(text: "Expired")
],
);
}
// THE AppBar with the sub menu under it
AppBar _buildAppBar(){
Function onBackButtonPressed = _navProfile;
return new AppBar(
title: customTextBold("My Tickets"),
iconTheme: new IconThemeData(
color: blue
),
leading: GestureDetector(
child: Icon(Icons.arrow_back, color: blue),
onTap: onBackButtonPressed,
),
elevation: 1.0,
backgroundColor: Colors.white,
bottom: _buildTabBar()
);
}
// BUILD MAIN SCREEN
Container _buildTicketsPage(){
return new Container(
padding: EdgeInsets.all(10.0),
decoration: BoxDecoration(
color: backgroundBlue
),
child: new TabBarView(
controller: controller,
children: [
TicketsList(
tickets: _tickets,
deleteTicket: deleteTicket,
),
TicketsList(
tickets: _availableTickets,
deleteTicket: deleteTicket,
),
TicketsList(
tickets: _usedTickets,
deleteTicket: deleteTicket,
),
TicketsList(
tickets: _expiredTickets,
deleteTicket: deleteTicket,
)
],
)
);
}
// UI
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: _buildAppBar(),
body: (_tickets == null)
? buildLoadingScreen("Fetching Tickets")
: _buildTicketsPage()
);
}
}
Running this will render the correct screen, but the ticket items will start duplicating, and this error is displayed on the debug console:
.
E/flutter (31673): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: setState() called after dispose(): _TicketsState#2cafe(lifecycle state: defunct, not mounted, ticker inactive)
E/flutter (31673): This error happens if you call setState() on a State object for a widget that no longer appears in the widget tree (e.g., whose parent widget no longer includes the widget in its build). This error can occur when code calls setState() from a timer or an animation callback. The preferred solution is to cancel the timer or stop listening to the animation in the dispose() callback. Another solution is to check the "mounted" property of this object before calling setState() to ensure the object is still in the t
Please how do I fix this ?
add before each setState a condition
if(!mounted) return;
for example:
if(!mounted) return;
setState(() {
_expiredTickets.add(ticket);
});
I'm new to flutter, so I'm trying to create a widget that shows an alert dialog. In the content of alert dialog I got SingleChildScrollView and in, so called, button bar I have a text, checkbox and a button, which I want to align(put checkbox with text on the left side, the button on the right side), but I don't know how. Tried expanded and flexible, also tried to insert row with mainAxisAlignment set to spaceEvenly, not working, could someone please help me?
Here is the code:
class TermsAndConditionsAlertDialog extends StatefulWidget {
TermsAndConditionsAlertDialogState createState() {
return new TermsAndConditionsAlertDialogState();
}
}
class TermsAndConditionsAlertDialogState
extends State<TermsAndConditionsAlertDialog> {
static bool _isChecked = false;
//TODO get the terms and conditions message
static final String _TERMS_AND_CONDITIONS_MESSAGE =
'blablabla this is a terms and conditions message and a blablababl and a bla bla and a aaaaaaaaaaaa bla';
static final String _DIALOG_TITLE = 'Terms and Conditions';
#override
Widget build(BuildContext context) {
// TODO: implement build
return new AlertDialog(
title: new Text(_DIALOG_TITLE),
content: new SingleChildScrollView(
child: new Text(
_TERMS_AND_CONDITIONS_MESSAGE,
style: new TextStyle(fontSize: 50.0),
),
),
actions: <Widget>[
new Text('Accept'),
new Checkbox(
// title: Text('Accept'),
value: _isChecked,
onChanged: (bool newValue) {
setState(() {
_isChecked = newValue;
});
},
),
new RaisedButton(
onPressed: () {
_printDialogResult();
_closeDialog();
//TODO add a method to move on with an app
},
child: new Text(
'Start',
style: TextStyle(color: Colors.white),
)),
],
);
}
void _printDialogResult() {
//simply prints the result in console
print('You selected 1');
}
void _closeDialog() {
if (_isChecked) {
Navigator.pop(context);
}
}
}[FL][1]
You want to use the content property to place your widgets because the actions will actually be wrapped in a ButtonBar and placed on the bottom right.
So a solution may be split the content of the dialog with a Column letting the SingleChildScrollView to expand to fill the viewport and placing a Row with your desired widgets on the bottom with the mainAxisAlignment: MainAxisAlignment.spaceBetween,. Since you also want to group the Text and CheckBox, another Row will do the job to gather both next to each other.
I've edited your example so you can copy/paste and try/tweak it yourself. This will produce the result below.
class TermsAndConditionsAlertDialog extends StatefulWidget {
TermsAndConditionsAlertDialogState createState() {
return new TermsAndConditionsAlertDialogState();
}
}
class TermsAndConditionsAlertDialogState extends State<TermsAndConditionsAlertDialog> {
static bool _isChecked = false;
static final String _TERMS_AND_CONDITIONS_MESSAGE =
'blablabla this is a terms and conditions message and a blablababl and a bla bla and a aaaaaaaaaaaa bla';
static final String _DIALOG_TITLE = 'Terms and Conditions';
#override
Widget build(BuildContext context) {
return new AlertDialog(
title: new Text(_DIALOG_TITLE),
content: new Column(
children: <Widget>[
new Expanded(
child: new SingleChildScrollView(
child: new Text(
_TERMS_AND_CONDITIONS_MESSAGE,
style: new TextStyle(fontSize: 50.0),
),
),
),
new Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: <Widget>[
new Row(
children: <Widget>[
new Text('Accept'),
new Checkbox(
value: _isChecked,
onChanged: (bool newValue) {
setState(() {
_isChecked = newValue;
});
},
),
],
),
new RaisedButton(
onPressed: () {
_printDialogResult();
_closeDialog();
},
child: new Text(
'Start',
style: TextStyle(color: Colors.white),
)),
],
),
],
),
);
}
void _printDialogResult() {
print('You selected 1');
}
void _closeDialog() {
if (_isChecked) {
Navigator.pop(context);
}
}
}
I'm using Flutter SearchDelegate in my app and here's the code:
class NameSearch extends SearchDelegate<String> {
#override
List<Widget> buildActions(BuildContext context) {
return [
IconButton(
icon: Icon(Icons.clear),
onPressed: () {
query = "";
},
)
];
}
#override
Widget buildLeading(BuildContext context) {
return IconButton(
icon: AnimatedIcon(
icon: AnimatedIcons.menu_arrow, progress: transitionAnimation),
onPressed: () {
close(context, null);
},
);
}
#override
Widget buildResults(BuildContext context) {
return null;
}
#override
Widget buildSuggestions(BuildContext context) {
suggestionList = query.isEmpty ? [] : List.generate(nameList.length,
(i) => nameList[i]).where((p) => p.name.startsWith(query)).toList();
return ListView.builder(
itemBuilder: (context, index) => ListTile(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailScreen(
detail: suggestionList[index],
)));
},
leading: Icon(Icons.book),
title: RichText(
text: TextSpan(
text: suggestionList[index].name.substring(0, query.length),
style: TextStyle(
color: Colors.black, fontWeight: FontWeight.bold),
children: [
TextSpan(
text: suggestionList[index].name.substring(query.length),
style: TextStyle(color: Colors.grey))
]),
),
),
itemCount: suggestionList.length,
);
}
}
When I clicked on an item in a suggestion list, It gets me to the new detail screen and works properly. But when I want to back to the search screen, the text input become like that:
And I can't insert any text anymore, till restart the app or go to another page and after that go back to search page again!
And here is my DetailScreen code:
class DetailScreen extends StatelessWidget {
final BookDetail detail;
DetailScreen({Key key, #required this.detail}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(detail.name),
),
body: Padding(
padding: EdgeInsets.all(16.0),
child: Text(detail.description),
),
);
}
}
In the line 83 -85 of the flutter search source code :
Once the user has selected a search result, [SearchDelegate.close] should be
called to remove the search page from the top of the navigation stack and
to notify the caller of [showSearch] about the selected search result.
So the the showSearch is structed for single-use only. And you have to call it again when you navigate back from your DetailScreen if you intend to use it for another search query.
I was facing same issue so what i did is i simply copy the search.dart from material library and replace
bool get maintainState => false;
to
bool get maintainState => true;
on line 294 it worked for me.
I am using a WebviewScaffold. It works well, but now I want to hide the BottomNavigationBar and the AppBar on Scroll up. OnScroll down it should show the AppBar and BottomNavigationBar. Like it works in Chrome on iOS.
As I know I cant use a Sliver because
"Warning: The webview is not integrated in the widget tree, it is a native view on top of the flutter view. you won't be able to use snackbars, dialogs ..."
It would be Nice if somebody could help me.
Thanks in advance!
I found how to get ScrollParams.... But now I have to hide/show the AppBar and the BottomNavigationBar.
#override
void initState() {
// TODO: implement initState
super.initState();
_onScrollYChanged =
flutterWebViewPlugin.onScrollYChanged.listen((double x) {
if (mounted) {
print(_onScrollYChanged.toString());
setState(() {
if (_posY < x) {
_showBar = false;
print("DOWN");
} else {
_showBar = true;
print("UP");
}
_posY = x;
print("Scroll in Y Direction: $x");
});
}
});
}
My Build in my APP looks like this:
#override
Widget build(BuildContext context) {
return WebviewScaffold(
url: "https://www.domain.de",
appBar: AppBar(
toolbarOpacity: _showBar ? 1.0 : 0.0,
title: const Text('Widget WebView'),
leading: IconButton(
icon: Icon(Icons.apps),
onPressed: () {
flutterWebViewPlugin.goBack();
},
),
),
bottomNavigationBar: BottomAppBar(
color: Colors.blue,
child: Row(
children: <Widget>[
IconButton(
icon: const Icon(Icons.arrow_back_ios),
onPressed: () {
flutterWebViewPlugin.goBack();
},
),
],
),
),
withZoom: true,
withLocalStorage: true,
hidden: true,
headers: _HTMLheaders,
);
}
}
I want to nest PageViews in flutter, with a PageView inside a Scaffold inside a PageView. In the outer PageView I will have the logo and contact informations, as well as secundary infos. As a child, I will have a scaffold with the inner PageView and a BottomNavigationBar as the main user interaction screen. Here is the code I have so far:
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget{
#override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp>{
int index = 0;
final PageController pageController = PageController();
final Curve _curve = Curves.ease;
final Duration _duration = Duration(milliseconds: 300);
_navigateToPage(value){
pageController.animateToPage(
value,
duration: _duration,
curve: _curve
);
setState((){
index = value;
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PageViewCeption',
home: PageView(
children: <Widget>[
Container(
color: Colors.blue,
),
Scaffold(
body: PageView(
controller: pageController,
onPageChanged: (page){
setState(() {
index = page;
});
},
children: <Widget>[
Container(
child: Center(
child: Text('1', style: TextStyle(color: Colors.white))
)
),
Container(
child: Center(
child: Text('2', style: TextStyle(color: Colors.white))
)
),
Container(
child: Center(
child: Text('3', style: TextStyle(color: Colors.white))
)
),
],
),
backgroundColor: Colors.green,
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: (value) =>_navigateToPage(value),
currentIndex: index,
items: [
BottomNavigationBarItem(
icon: Icon(Icons.cake),
title: Text('1')
),
BottomNavigationBarItem(
icon: Icon(Icons.cake),
title: Text('2')
),
BottomNavigationBarItem(
icon: Icon(Icons.cake),
title: Text('3')
)
],
),
),
Container(
color: Colors.blue
)
],
),
);
}
}
Here is the result:
Problem is: When I am in the inner PageView, I can't get away from it to the outer one scrolling left on the first page, or scrolling right on the last page of the inner PageView. The only way to go back to the outer PageView in scrolling (swiping) on the BottomNavigationBar.
In the docs of the Scroll Physics Class we find this in the description:
For example, determines how the Scrollable will behave when the user reaches the maximum scroll extent or when the user stops scrolling.
But I haven't been able to come up with a solution yet. Any thoughts?
Update 1
I had progress working with a CustomScrollPhysics class:
class CustomScrollPhysics extends ScrollPhysics{
final PageController _controller;
const CustomScrollPhysics(this._controller, {ScrollPhysics parent }) : super(parent: parent);
#override
CustomScrollPhysics applyTo(ScrollPhysics ancestor) {
return CustomScrollPhysics(_controller, parent: buildParent(ancestor));
}
#override
double applyBoundaryConditions(ScrollMetrics position, double value) {
assert(() {
if (value == position.pixels) {
throw new FlutterError(
'$runtimeType.applyBoundaryConditions() was called redundantly.\n'
'The proposed new position, $value, is exactly equal to the current position of the '
'given ${position.runtimeType}, ${position.pixels}.\n'
'The applyBoundaryConditions method should only be called when the value is '
'going to actually change the pixels, otherwise it is redundant.\n'
'The physics object in question was:\n'
' $this\n'
'The position object in question was:\n'
' $position\n'
);
}
return true;
}());
if (value < position.pixels && position.pixels <= position.minScrollExtent){ // underscroll
_controller.jumpTo(position.viewportDimension + value);
return 0.0;
}
if (position.maxScrollExtent <= position.pixels && position.pixels < value) {// overscroll
_controller.jumpTo(position.viewportDimension + (value - position.viewportDimension*2));
return 0.0;
}
if (value < position.minScrollExtent && position.minScrollExtent < position.pixels) // hit top edge
return value - position.minScrollExtent;
if (position.pixels < position.maxScrollExtent && position.maxScrollExtent < value) // hit bottom edge
return value - position.maxScrollExtent;
return 0.0;
}
}
Which is a modification of the ClampingScrollPhysics applyBoundaryConditions. It kinda works but because of the pageSnapping it is really buggy. It happens, because according to the docs:
Any active animation is canceled. If the user is currently scrolling, that
action is canceled.
When the action is canceled, the PageView starts to snap back to the Scafold page, if the user stop draggin the screen, and this messes things up. Any ideas on how to avoid the page snapping in this case, or for better implementation for that matter?
I was able to replicate the issue on the nested PageView. It seems that the inner PageView overrides the detected gestures. This explains why we're unable to navigate to other pages of the outer PageView, but the BottomNavigationBar can. More details of this behavior is explained in this thread.
As a workaround, you can use a single PageView and just hide the BottomNavigationBar on the outer pages. I've modified your code a bit.
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return _MyAppState();
}
}
class _MyAppState extends State<MyApp> {
var index = 0;
final PageController pageController = PageController();
final Curve _curve = Curves.ease;
final Duration _duration = Duration(milliseconds: 300);
var isBottomBarVisible = false;
_navigateToPage(value) {
// When BottomNavigationBar button is clicked, navigate to assigned page
switch (value) {
case 0:
value = 1;
break;
case 1:
value = 2;
break;
case 2:
value = 3;
break;
}
pageController.animateToPage(value, duration: _duration, curve: _curve);
setState(() {
index = value;
});
}
// Set BottomNavigationBar indicator only on pages allowed
_getNavBarIndex(index) {
if (index <= 1)
return 0;
else if (index == 2)
return 1;
else if (index >= 3)
return 2;
else
return 0;
}
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'PageViewCeption',
home: Scaffold(
body: Container(
child: PageView(
controller: pageController,
onPageChanged: (page) {
setState(() {
// BottomNavigationBar only appears on page 1 to 3
isBottomBarVisible = page > 0 && page < 4;
print('page: $page bottom bar: $isBottomBarVisible');
index = page;
});
},
children: <Widget>[
Container(
color: Colors.red,
),
Container(
color: Colors.orange,
),
Container(
color: Colors.yellow,
),
Container(
color: Colors.green,
),
Container(color: Colors.lightBlue)
],
),
),
bottomNavigationBar: isBottomBarVisible // if true, generate BottomNavigationBar
? new BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: (value) => _navigateToPage(value),
currentIndex: _getNavBarIndex(index),
items: [
BottomNavigationBarItem(icon: Icon(Icons.cake), label: '1'),
BottomNavigationBarItem(icon: Icon(Icons.cake), label: '2'),
BottomNavigationBarItem(icon: Icon(Icons.cake), label: '3')
],
)
//else, create an empty container to hide the BottomNavigationBar
: Container(
height: 0,
),
),
);
}
}