Flutter - How to change state from inside another widget - flutter

I'm trying to implement both a Drawer and the Bottom Navigation Bar. The below class NavigationHomeScreen is my home screen and when the user clicks a menu item in the Drawer the Changeindex function updates the screenView I would like to know how I can similarly update the screenView but from the BottomNavigationBarApp using the onTabTapped method.
class NavigationHomeScreen extends StatefulWidget {
#override
_NavigationHomeScreenState createState() => _NavigationHomeScreenState();
}
class _NavigationHomeScreenState extends State<NavigationHomeScreen> {
Widget screenView;
DrawerIndex drawerIndex;
#override
void initState() {
drawerIndex = DrawerIndex.HOME;
screenView = const MyHomePage();
super.initState();
}
#override
Widget build(BuildContext context) {
return Container(
color: AppTheme.nearlyWhite,
child: SafeArea(
top: false,
bottom: false,
child: Scaffold(
appBar: emptyAppbar(),
backgroundColor: AppTheme.nearlyWhite,
body: DrawerUserController(
screenIndex: drawerIndex,
drawerWidth: MediaQuery.of(context).size.width * 0.75,
onDrawerCall: (DrawerIndex drawerIndexdata) {
changeIndex(drawerIndexdata);
//callback from drawer for replace screen as user need with passing DrawerIndex(Enum index)
},
screenView: screenView,
//we replace screen view as we need on navigate starting screens like MyHomePage, HelpScreen, FeedbackScreen, etc...
),
bottomNavigationBar:BottomNavigationBarApp(context, 1),
),
),
);
}
void changeIndex(DrawerIndex drawerIndexdata) {
if (drawerIndex != drawerIndexdata) {
drawerIndex = drawerIndexdata;
if (drawerIndex == DrawerIndex.HOME) {
setState(() {
screenView = const MyHomePage();
});
} else if (drawerIndex == DrawerIndex.Help) {
setState(() {
screenView = HelpScreen();
});
} else if (drawerIndex == DrawerIndex.FeedBack) {
setState(() {
screenView = FeedbackScreen();
});
} else if (drawerIndex == DrawerIndex.Invite) {
setState(() {
screenView = InviteFriend();
});
} else {
//do in your way......
}
}
}
}
class BottomNavigationBarApp extends StatelessWidget {
final int bottomNavigationBarIndex;
final BuildContext context;
const BottomNavigationBarApp(this. context, this.bottomNavigationBarIndex);
void onTabTapped(int index) {
}
#override
Widget build(BuildContext context) {
return BottomNavigationBar(
currentIndex: bottomNavigationBarIndex,
type: BottomNavigationBarType.fixed,
selectedFontSize: 10,
selectedLabelStyle: TextStyle(color: CustomColors.BlueDark),
selectedItemColor: CustomColors.BlueDark,
unselectedFontSize: 10,
items: [
BottomNavigationBarItem(
icon: Container(
margin: EdgeInsets.only(bottom: 5),
child: Image.asset(
'assets/images/home.png',
color: (bottomNavigationBarIndex == 1)
? CustomColors.BlueDark
: CustomColors.TextGrey,
),
),
title: Text('Home'),
),
BottomNavigationBarItem(
icon: Container(
margin: EdgeInsets.only(bottom: 5),
child: Image.asset(
'assets/images/task.png',
color: (bottomNavigationBarIndex == 0)
? CustomColors.BlueDark
: CustomColors.TextGrey,
),
),
title: Text('Appointments'),
),
],
onTap: onTabTapped,
);
}
}

You can pass a reference to a function down to a widget. Something like this:
In your HomeScreen:
void updateScreenView() {
//Do changes you want here. Dont forget to setState!
}
In your BottomNavigationBarApp (edit: Function must start with a capital F):
final int bottomNavigationBarIndex;
final BuildContext context;
final Function tapHandler;
const BottomNavigationBarApp(this. context, this.bottomNavigationBarIndex, this.tapHandler);
and then just pass the reference on:
bottomNavigationBar:BottomNavigationBarApp(context, 1, updateScreenView),
And assign the function to your handler.
onTap: () => tapHandler(),

The best way to do it and also recommended by the Flutter development team is to use the provider package from flutter to manage the state between those widgets.
The top answer does solves your issue if its small app, but as your application grows, you most likely want to change things far up a widget tree which makes functions impractical.

Related

Main app widget: setState call -> build() builds all widgets but screen not updated

App is a simple memory/guessing game with a grid of squares. Floating action button triggers a "New game" dialog, and a Yes response triggers setState() on the main widget. The print() calls show it is building all the Tile widgets in the grid, but as it returns, the old grid values are still showing. Probably done something stupid but not seeing it. Basic code is below. TIA if anyone can see what is missing/invalid/broken/etc.
Main.dart is the usual main() that creates a stateless HomePage which creates a stateful widget which uses this State:
class MemHomePageState extends State<MemHomePage> {
GameBoard gameBoard = GameBoard();
GameController? gameController;
int gameCount = 0, winCount = 0;
#override
void initState() {
super.initState();
gameController = GameController(gameBoard, this);
}
#override
Widget build(BuildContext context) {
if (kDebugMode) {
print("MemHomepageState::build");
}
gameBoard.newGame(); // Resets secrets and grids
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: GridView.count(
crossAxisCount: Globals.num_columns,
children: List.generate(Globals.num_columns * Globals.num_rows, (index) {
int x = index~/Globals.NR, y = index%Globals.NR;
int secret = gameBoard.secretsGrid![x][y];
var t = Tile(x, y, Text('$secret'), gameController!);
gameBoard.tilesGrid![x].add(t);
if (kDebugMode) {
print("Row $x is ${gameBoard.secretsGrid![x]} ${gameBoard.tilesGrid![x][y].secret}");
}
return t;
}),
),
// Text("You have played $gameCount games and won $winCount."),
),
floatingActionButton: FloatingActionButton(
onPressed: () => newGameDialog("Start a new game?"),
tooltip: 'New game?',
child: const Icon(Icons.refresh_outlined),
),
);
}
/** Called from the FAB and also from GameController "won" logic */
void newGameDialog(String message) {
showDialog<void>(
context: context,
barrierDismissible: false, // means the user must tap a button to exit the Alert Dialog
builder: (BuildContext context) {
return AlertDialog(
title: Text("New game?"),
content: Text(message),
//),
actions: <Widget>[
TextButton(
child: const Text('Yes'),
onPressed: () {
setState(() {
gameCount++;
});
Navigator.of(context).pop();
},
),
TextButton(
child: const Text('No'),
onPressed: () {
Navigator.of(context).pop();
},
),
],
);
}
);
}
The Tile class is a StatefulWidget whose state determines what that particular tile should show:
import 'package:flutter/material.dart';
import 'gamecontroller.dart';
enum TileMode {
SHOWN,
HIDDEN,
CLEARED,
}
/// Represents one Tile in the game
class Tile extends StatefulWidget {
final int x, y;
final Widget secret;
final GameController gameController;
TileState? tileState;
Tile(this.x, this.y, this.secret, this.gameController, {super.key});
#override
State<Tile> createState() => TileState(x, y, secret);
setCleared() {
tileState!.setCleared();
}
}
class TileState extends State<Tile> {
final int x, y;
final Widget secret;
TileMode tileMode = TileMode.HIDDEN;
TileState(this.x, this.y, this.secret);
_unHide() {
setState(() => tileMode = TileMode.SHOWN);
widget.gameController.clicked(widget);
}
reHide() {
print("rehiding");
setState(() => tileMode = TileMode.HIDDEN);
}
setCleared() {
print("Clearing");
setState(() => tileMode = TileMode.CLEARED);
}
_doNothing() {
//
}
#override
Widget build(BuildContext context) {
switch(tileMode) {
case TileMode.HIDDEN:
return ElevatedButton(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.teal,
),
onPressed: _unHide,
child: Text(''));
case TileMode.SHOWN:
return ElevatedButton(
onPressed: _doNothing,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
),
child: secret);
case TileMode.CLEARED:
return ElevatedButton(
onPressed: _doNothing,
style: ElevatedButton.styleFrom(
backgroundColor: Colors.black12,
),
child: const Icon(Icons.check));
}
}
}
it looks like you are calling the following in your build function. That would cause everything to reset everytime it builds. Perhaps it belongs in init instead?
gameBoard.newGame(); // Resets secrets and grids
The original problem is that the Tile objects, although correctly created and connected to the returned main widget, did not have distinct 'key' values so they were not replacing the originals. Adding 'key' to the Tile constructor and 'key: UniqueKey()' to each Tile() in the loop, solved this problem. It exposed a related problem but is out of scope for this question. See the github link in the OP for the latest version.

How to switch BottomNavigationBar tabs Programmatically in flutter (and Navigate between pages)

I am using pretty new to flutter.So any good advice will be helpful to me.
I am creating an app which have three pages - one home page, one details page, and one Settings page. So in my code for home page, after submitting some data, the user will be taken to page two (ie. the details page)
I am using curved_navigation_bar [ https://pub.dev/packages/curved_navigation_bar ] package as my BottomNavigationBar.
HomeView.dart
import 'package:curved_navigation_bar/curved_navigation_bar.dart';
//necessary imports
class HomeView extends StatefulWidget {
final GlobalKey globalKey;
HomeView(this.globalKey);
#override
_HomeViewState createState() => _HomeViewState();
}
class _HomeViewState extends State<HomeView> {
#override
Widget build(BuildContext context) {
return Container(
child: //SomeButton(
onPressed: () => {
final CurvedNavigationBar navigationBar = widget.globalKey.currentWidget;
navigationBar.onTap(1); //<-This is the line where user will be taken to page 2
}
),
);
}
}
Now I have made a NavBar.dart file to manage navigation in my app.
NavBar.dart
import 'package:curved_navigation_bar/curved_navigation_bar.dart';
//necessary imports
class NavBar extends StatefulWidget {
NavBar({Key key}) : super(key: key);
#override
_NavBarState createState() => _NavBarState();
}
class _NavBarState extends State<NavBar> {
GlobalKey globalKey = new GlobalKey(debugLabel: 'btm_app_bar');
ChatSessionDetails _chatSessionDetails = ChatSessionDetails();
int _currentIndex;
String _appBarTitle;
List<Widget> pages = [];
final PageStorageBucket bucket = PageStorageBucket();
void initState() {
super.initState();
pages = [
HomeView(globalKey),
ChatView(key: PageStorageKey('ChatPage')),
SettingsView()
];
_currentIndex = 0;
_appBarTitle = 'HomePage';
}
void changeTab(int index) { //<-PageChange logic
switch (index) {
case 0:
setState(
() => {
_appBarTitle = 'HomePage',
_currentIndex = 0,
},
);
break;
case 1:
if (_sessionDetails.file != null) {
setState(
() => {
_appBarTitle = //some title,
_currentIndex = 1
},
);
} else {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
content: Text('You haven\'t opened a session yet'),
behavior: SnackBarBehavior.floating,
));
}
break;
case 2:
setState(
() => {
_appBarTitle = 'Settings',
_currentIndex = 2,
},
);
break;
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_appBarTitle),
),
body: PageStorage(
child: pages[_currentIndex],
bucket: bucket,
),
extendBody: true,
bottomNavigationBar: CurvedNavigationBar(
key: globalKey, items: <Widget>[
Icon(Icons.home_rounded, size: 30, color: Colors.grey[600]),
Icon(CustomIcons.whatsapp, size: 30, color: Colors.grey[600]),
Icon(Icons.settings_rounded, size: 30, color: Colors.grey[600]),
],
onTap: (index) {
changeTab(index); //Handle button tap
},
),
);
}
}
The whole navigation thing goes pretty fine, but when I call that changeTab() function programmatically, (not by clicking on the bottom navigation tabs) things get weird.
Below is link to the gif MyApp
The tab should change as I didn't changed the state as _sessionDetails.file != null
is false. How can I avoid this ?
in changeTab you need to use the key to change page:
globalKey.currentState.setPage(1);
[UPDATED]
Official way from author:
Change page programmatically
//State class
int _page = 0;
GlobalKey _bottomNavigationKey = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
bottomNavigationBar: CurvedNavigationBar(
key: _bottomNavigationKey,
items: <Widget>[
Icon(Icons.add, size: 30),
Icon(Icons.list, size: 30),
Icon(Icons.compare_arrows, size: 30),
],
onTap: (index) {
setState(() {
_page = index;
});
},
),
body: Container(
color: Colors.blueAccent,
child: Center(
child: Column(
children: <Widget>[
Text(_page.toString(), textScaleFactor: 10.0),
RaisedButton(
child: Text('Go To Page of index 1'),
onPressed: () {
//Page change using state does the same as clicking index 1 navigation button
final CurvedNavigationBarState navBarState =
_bottomNavigationKey.currentState;
navBarState.setPage(1);
},
)
],
),
),
));
}

Flutter - select only single item in list view

In my app I am generating a ListView and items can be highlighted by tapping on them. That works fine and I also have a callback function that gives me the key for the just selected item. I can currently manually deselect the item by tapping on it again, but will ultimately take that functionality out.
My problem is that I want one and only one item to be selected at a time. In order to create the list I currently take some initial content in the form of a list, generate the tiles and add them to another list. I then use that list to create the ListView. My plan was on the callback from a new selection, run through the list of tiles and deselect them before highlighting the new chosen tile and carrying out the other functions. I have tried various methods to tell each tile to deselect itself but have not found any way to address each of the tiles. Currently I get the error:
Class 'OutlineTile' has no instance method 'deselect'.
Receiver: Instance of 'OutlineTile'
Tried calling: deselect()
I have tried to access a method within the tile class and to use a setter but neither worked so far. I am quite new to flutter so it could be something simple I am missing. My previous experience was with Actionscript where this system would have worked fine and I could access a method of an object (in this case the tile) easily as long s it is a public method.
I'd be happy to have another way to unselect the old item or to find a way to access a method within the tile. The challenge is to make the tiles show not highlighted without them being tapped themselves but when a different tile is tapped.
The code in my parent class is as follows:
class WorkingDraft extends StatefulWidget {
final String startType;
final String name;
final String currentContent;
final String currentID;
final List startContent;
WorkingDraft(
{this.startType,
this.name,
this.currentContent,
this.currentID,
this.startContent});
#override
_WorkingDraftState createState() => _WorkingDraftState();
}
class _WorkingDraftState extends State<WorkingDraft> {
final _formKey = GlobalKey<FormState>();
final myController = TextEditingController();
//String _startType;
String _currentContent = "";
String _name = "Draft";
List _startContent = [];
List _outLineTiles = [];
int _counter = 0;
#override
void dispose() {
// Clean up the controller when the widget is disposed.
myController.dispose();
super.dispose();
}
void initState() {
super.initState();
_currentContent = widget.currentContent;
_name = widget.name;
_startContent = widget.startContent;
_counter = 0;
_startContent.forEach((element) {
_outLineTiles.add(OutlineTile(
key: Key("myKey$_counter"),
outlineName: element[0],
myContent: element[1],
onTileSelected: clearHilights,
));
_counter++;
});
}
dynamic clearHilights(Key myKey) {
_outLineTiles.forEach((element) {
element.deselect(); // this throws an error Class 'OutlineTile' has no instance method 'deselect'.
Key _foundKey = element.key;
print("Element Key $_foundKey");
});
}
.......
and further down within the widget build scaffold:
child: ListView.builder(
itemCount: _startContent.length,
itemBuilder: (context, index) {
return _outLineTiles[index];
},
),
Then the tile class is as follows:
class OutlineTile extends StatefulWidget {
final Key key;
final String outlineName;
final Icon myIcon;
final String myContent;
final Function(Key) onTileSelected;
OutlineTile(
{this.key,
this.outlineName,
this.myIcon,
this.myContent,
this.onTileSelected});
#override
_OutlineTileState createState() => _OutlineTileState();
}
class _OutlineTileState extends State<OutlineTile> {
Color color;
Key _myKey;
#override
void initState() {
super.initState();
color = Colors.transparent;
}
bool _isSelected = false;
set isSelected(bool value) {
_isSelected = value;
print("set is selected to $_isSelected");
}
void changeSelection() {
setState(() {
_myKey = widget.key;
_isSelected = !_isSelected;
if (_isSelected) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
});
}
void deselect() {
setState(() {
isSelected = false;
color = Colors.transparent;
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Row(
children: [
Card(
elevation: 10,
margin: EdgeInsets.fromLTRB(10.0, 6.0, 5.0, 0.0),
child: SizedBox(
width: 180,
child: Container(
color: color,
child: ListTile(
title: Text(widget.outlineName),
onTap: () {
if (widget.outlineName == "Heading") {
Text("Called Heading");
} else (widget.outlineName == "Paragraph") {
Text("Called Paragraph");
widget.onTileSelected(_myKey);
changeSelection();
},
),
........
Thanks for any help.
Amended Code sample and explanation, that builds to a complete project, from here:
Following the advice from phimath I have created a full buildable sample of the relevant part of my project.
The problem is that the tiles in my listview are more complex with several elements, many of which are buttons in their own right so whilst phimath's solution works for simple text tiles I have not been able to get it working inside my own project. My approach is trying to fundamentally do the same thing as phimath's but when I include these more complex tiles it fails to work.
This sample project is made up of three files. main.dart which simply calls the project and passes in some dummy data in the way my main project does. working_draft.dart which is the core of this issue. And outline_tile.dart which is the object that forms the tiles.
Within working draft I have a function that returns an updated list of the tiles which should show which tile is selected (and later any other changes from the other buttons). This gets called when first going to the screen. When the tile is tapped it uses a callback function to redraw the working_draft class but this seems to not redraw the list as I would expect it to. Any further guidance would be much appreciated.
The classes are:
first class is main.dart:
import 'package:flutter/material.dart';
import 'package:listexp/working_draft.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: WorkingDraft(
startType: "Basic",
name: "Draft",
currentID: "anID",
startContent: [
["Heading", "New Heading"],
["Paragraph", "New Text"],
["Image", "placeholder"],
["Signature", "placeholder"]
],
));
}
}
Next file is working_draft.dart:
import 'package:flutter/material.dart';
import 'package:listexp/outline_tile.dart';
class WorkingDraft extends StatefulWidget {
final String startType;
final String name;
final String currentContent;
final String currentID;
final List startContent;
final int selectedIndex;
WorkingDraft(
{this.startType,
this.name,
this.currentContent,
this.currentID,
this.startContent,
this.selectedIndex});
#override
_WorkingDraftState createState() => _WorkingDraftState();
}
class _WorkingDraftState extends State<WorkingDraft> {
int selectedIndex;
String _currentContent = "";
String _name = "Draft";
List _startContent = [];
var _outLineTiles = [];
int _counter = 0;
int _selectedIndex;
bool _isSelected;
dynamic clearHilights(int currentIndex) {
setState(() {
_selectedIndex = currentIndex;
});
}
updatedTiles() {
if (_selectedIndex == null) {
_selectedIndex = 0;
}
_currentContent = widget.currentContent;
_name = widget.name;
_startContent = widget.startContent;
_counter = 0;
_outLineTiles = [];
_startContent.forEach((element) {
_isSelected = _selectedIndex == _counter ? true : false;
_outLineTiles.add(OutlineTile(
key: Key("myKey$_counter"),
outlineName: element[0],
myContent: element[1],
myIndex: _counter,
onTileSelected: clearHilights,
isSelected: _isSelected,
));
_counter++;
});
}
#override
Widget build(BuildContext context) {
updatedTiles();
return Scaffold(
body: Center(
child: Column(children: [
SizedBox(height: 100),
Text("Outline", style: new TextStyle(fontSize: 15)),
Container(
height: 215,
width: 300,
decoration: BoxDecoration(
border: Border.all(
color: Colors.lightGreenAccent,
width: 2,
),
borderRadius: BorderRadius.circular(2),
),
child: ListView.builder(
itemCount: _startContent.length,
itemBuilder: (context, index) {
return _outLineTiles[index];
},
),
),
]),
));
}
}
and finally is outline_tile.dart
import 'package:flutter/material.dart';
class OutlineTile extends StatefulWidget {
final Key key;
final String outlineName;
final Icon myIcon;
final String myContent;
final int myIndex;
final Function(int) onTileSelected;
final bool isSelected;
OutlineTile(
{this.key,
this.outlineName,
this.myIcon,
this.myContent,
this.myIndex,
this.onTileSelected,
this.isSelected});
#override
_OutlineTileState createState() => _OutlineTileState();
}
class _OutlineTileState extends State<OutlineTile> {
Color color;
// Key _myKey;
bool _isSelected;
#override
void initState() {
super.initState();
_isSelected = widget.isSelected;
if (_isSelected == true) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
}
void deselect() {
setState(() {
_isSelected = widget.isSelected;
if (_isSelected == true) {
color = Colors.lightBlueAccent;
} else {
color = Colors.transparent;
}
});
}
#override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.only(top: 4.0),
child: Row(
children: [
Card(
elevation: 10,
margin: EdgeInsets.fromLTRB(10.0, 6.0, 5.0, 0.0),
child: SizedBox(
width: 180,
child: Container(
color: color,
child: ListTile(
title: Text(widget.outlineName),
onTap: () {
if (widget.outlineName == "Heading") {
Text("Called Heading");
} else if (widget.outlineName == "Paragraph") {
Text("Called Paragraph");
} else if (widget.outlineName == "Signature") {
Text("Called Signature");
} else {
Text("Called Image");
}
var _myIndex = widget.myIndex;
widget.onTileSelected(_myIndex);
deselect();
},
),
),
),
),
SizedBox(
height: 60,
child: Column(
children: [
SizedBox(
height: 20,
child: IconButton(
iconSize: 30,
icon: Icon(Icons.arrow_drop_up),
onPressed: () {
print("Move Up");
}),
),
SizedBox(height: 5),
SizedBox(
height: 20,
child: IconButton(
iconSize: 30,
icon: Icon(Icons.arrow_drop_down),
onPressed: () {
print("Move Down");
}),
),
],
),
),
SizedBox(
height: 60,
child: Column(
children: [
SizedBox(
height: 20,
child: IconButton(
iconSize: 20,
icon: Icon(Icons.add_box),
onPressed: () {
print("Add another");
}),
),
SizedBox(
height: 10,
),
SizedBox(
height: 20,
child: IconButton(
iconSize: 20,
icon: Icon(Icons.delete),
onPressed: () {
print("Delete");
}),
),
],
),
),
],
),
);
}
}
Thanks again
Instead of manually deselecting tiles, just keep track of which tile is currently selected.
I've made a simple example for you. When we click a tile, we just set the selected index to the index we clicked, and each tile looks at that to see if its the currently selected tile.
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(body: Home()),
);
}
}
class Home extends StatefulWidget {
#override
_HomeState createState() => _HomeState();
}
class _HomeState extends State<Home> {
int selectedIndex;
#override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: 10,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item: $index'),
tileColor: selectedIndex == index ? Colors.blue : null,
onTap: () {
setState(() {
selectedIndex = index;
});
},
);
},
);
}
}

I want to check if any Widget exist in a List?

I want to load pages from a List and when the user taps on an item from the drawer he can go to that page (if it's already opened) otherwise the Widget will load in the selected page.
But I can't find if that widget is already exists in the List if(myList.contains(Widget1())) => print('it exist'); One guy told me to override hashCode and operator==
class Widget6 extends StatelessWidget {
final String title = 'Widget6';
final Icon icon = Icon(Icons.assessment);
#override
Widget build(BuildContext context) {
return Center(
child: icon,
);
}
#override
bool operator ==(dynamic other) {
final Widget6 typedOther = other;
return title == typedOther.title && icon == typedOther.icon;
}
#override
int get hashCode => hashValues(title, icon);
}
if I do that I can't use any child widget to those widgets. Getting exception like: type 'Center' is not a subtype of type 'Widget6'. I copied this from flutter gallery I didn't find good documentation/guide. Sorry, I am a beginner.
Complete code below
class _MyHomePageState extends State<MyHomePage> {
List pageList = [
Widget1(),
Widget2(),
Widget3(),
Widget4(),
];
PageController _pageController;
int _selectedIndex = 0;
#override
void initState() {
_pageController = PageController(
initialPage: _selectedIndex,
);
super.initState();
}
void navigatePage(Widget widget) {
// problem is here
if (pageList.contains(widget)) {
_pageController.animateToPage(pageList.indexOf(widget, 0),
duration: Duration(milliseconds: 300), curve: Curves.ease);
}
else {
setState(() {
pageList.removeAt(_pageController.page.toInt());
pageList.insert(_pageController.page.toInt(), widget);
});
_pageController.animateToPage(_pageController.page.toInt(),
duration: Duration(milliseconds: 300), curve: Curves.ease);
}
Navigator.pop(context);
}
#override
Widget build(BuildContext context) {
return Scaffold(
drawer: Drawer(
child: ListView(
children: <Widget>[
ListTile(
title: Text('Widget1'),
onTap: () => navigatePage(
Widget1(),
),
),
ListTile(
title: Text('Widget2'),
onTap: () => navigatePage(
Widget2(),
),
),
ListTile(
title: Text('Widget3'),
onTap: () => navigatePage(
Widget3(),
),
),
ListTile(
title: Text('Widget4'),
onTap: () => navigatePage(
Widget4(),
),
),
ListTile(
title: Text('Widget5'),
onTap: () => navigatePage(
Widget5(),
),
),
ListTile(
title: Text('Widget6'),
onTap: () => navigatePage(
Widget6(),
),
),
],
),
),
appBar: AppBar(
title: Text(widget.title),
),
body: PageView.builder(
onPageChanged: (newPage) {
setState(() {
this._selectedIndex = newPage;
});
},
controller: _pageController,
itemBuilder: (context, index) {
return Container(
child: pageList[index],
);
},
itemCount: pageList.length,
),
bottomNavigationBar: BottomNavigationBar(
currentIndex: _selectedIndex,
onTap: (index) => setState(() {
_selectedIndex = index;
_pageController.animateToPage(index,
duration: Duration(milliseconds: 300), curve: Curves.ease);
}),
items: pageList.map((page) {
return BottomNavigationBarItem(
backgroundColor: Colors.deepOrangeAccent,
icon: page.icon,
title: Text(page.title));
}).toList(),
),
);
}
}
Here List of dummy Widgets
class Widget1 extends StatelessWidget {
final String title = 'Widget1';
final Icon icon = Icon(Icons.school);
#override
Widget build(BuildContext context) {
return Center(
child: icon,
);
}
}
class Widget2 extends StatelessWidget {
// only title and icon are changed
}
class Widget3 extends StatelessWidget {
// only title and icon are changed
}
class Widget4 extends StatelessWidget {
// only title and icon are changed
}
class Widget5 extends StatelessWidget {
// only title and icon are changed
}
class Widget6 extends StatelessWidget {
// only title and icon are changed
}
Okay, I found the solution. And it has to do with operator== overriding
I missed this line if (runtimeType != other.runtimeType) return false;
The whole code stays the same.
#override
// ignore: hash_and_equals
bool operator ==(dynamic other) {
if (runtimeType != other.runtimeType) return false;
final Widget6 typedOther = other;
return title == typedOther.title;
}
#Ahmed Sorry for the late reply, I decided to put it in an answer rather than a comment.
One solution is yours, overriding == but I was thinking of using Key and then instead of using contains method, using something like:
if(myList.indexWhere((Widget widget)=> widget.key==_key) != -1)...
Suggestion
You can store icon and title as a map or a module instead of making 6 different Widget.
You can create another file, saying module.dart like this:
class Module {
final String title;
final Icon icon;
Module(this.title, this.icon);
#override
int get hashCode => hashValues(title.hashCode, icon.hashCode);
#override
bool operator ==(other) {
if (!identical(this, other)) {
return false;
}
return other is Module &&
this.title.compareTo(other.title) == 0 &&
this.icon == other.icon;
}
}
Then create another file that builds the page, saying mywidget.dart, like this:
class MyWidget extends StatelessWidget {
final Module module;
MyWidget({Key key,#required this.module}) : super(key: key);
#override
Widget build(BuildContext context) {
return Center(
child: module.icon,
);
}
}
Then on each ListTile's onTap, Navigate like this:
...
ListTile(
title: Text('Widget1'),
onTap: () => navigatePage(
MyWidget(module: Module('Widget1', Icon(Icons.school)),)
),
),
...
So instead of storing Widgets, you store a Type(Here Module) that you declared.
You can also use the list's map to build each ListTile of the ListView for each Module, instead of doing it one by one. (if each item on the drawer are similar), Something like this:
List<Module> myTabs = [
Module('Widget1', Icon(Icons.school)),
Module('Widget2', Icon(Icons.home)),
];
...
Drawer(
child: ListView(
children:myTabs.map((Module module)=> ListTile(
title:Text( module.title),
onTap: navigatePage(MyWidget(module: module,)),
)).toList(),
) ,
);
...

How to implement a Slider within an AlertDialog in Flutter?

I am learning app development on Flutter and can't get my Slider to work within the AlertDialog. It won't change it's value.
I did search the problem and came across this post on StackOverFlow:
Flutter - Why slider doesn't update in AlertDialog?
I read it and have kind of understood it. The accepted answer says that:
The problem is, dialogs are not built inside build method. They are on a different widget tree. So when the dialog creator updates, the dialog won't.
However I am not able to understand how exactly does it have to be implemented as not enough background code is provided.
This is what my current implementation looks like:
double _fontSize = 1.0;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(qt.title),
actions: <Widget>[
IconButton(
icon: Icon(Icons.format_size),
onPressed: () {
getFontSize(context);
},
),
],
),
body: ListView.builder(
padding: EdgeInsets.symmetric(vertical: 15.0),
itemCount: 3,
itemBuilder: (context, index) {
if (index == 0) {
return _getListTile(qt.scripture, qt.reading);
} else if (index == 1) {
return _getListTile('Reflection:', qt.reflection);
} else {
return _getListTile('Prayer:', qt.prayer);
}
})
);
}
void getFontSize(BuildContext context) {
showDialog(context: context,builder: (context){
return AlertDialog(
title: Text("Font Size"),
content: Slider(
value: _fontSize,
min: 0,
max: 100,
divisions: 5,
onChanged: (value){
setState(() {
_fontSize = value;
});
},
),
actions: <Widget>[
RaisedButton(
child: Text("Done"),
onPressed: (){},
)
],
);
});
}
Widget parseLargeText(String text) {...}
Widget _getListTile(String title, String subtitle) {...}
I understand that I will need to make use of async and await and Future. But I am not able to understand how exactly. I've spent more than an hour on this problem and can't any more. Please forgive me if this question is stupid and noobish. But trust me, I tried my best.
Here is a minimal runnable example. Key points:
The dialog is a stateful widget that stores the current value in its State. This is important because dialogs are technically separate "pages" on your app, inserted higher up in the hierarchy
Navigator.pop(...) to close the dialog and return the result
Usage of async/await
import 'package:flutter/material.dart';
void main() => runApp(App());
class App extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
double _fontSize = 20.0;
void _showFontSizePickerDialog() async {
// <-- note the async keyword here
// this will contain the result from Navigator.pop(context, result)
final selectedFontSize = await showDialog<double>(
context: context,
builder: (context) => FontSizePickerDialog(initialFontSize: _fontSize),
);
// execution of this code continues when the dialog was closed (popped)
// note that the result can also be null, so check it
// (back button or pressed outside of the dialog)
if (selectedFontSize != null) {
setState(() {
_fontSize = selectedFontSize;
});
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
Text('Font Size: ${_fontSize}'),
RaisedButton(
onPressed: _showFontSizePickerDialog,
child: Text('Select Font Size'),
)
],
),
),
);
}
}
// move the dialog into it's own stateful widget.
// It's completely independent from your page
// this is good practice
class FontSizePickerDialog extends StatefulWidget {
/// initial selection for the slider
final double initialFontSize;
const FontSizePickerDialog({Key key, this.initialFontSize}) : super(key: key);
#override
_FontSizePickerDialogState createState() => _FontSizePickerDialogState();
}
class _FontSizePickerDialogState extends State<FontSizePickerDialog> {
/// current selection of the slider
double _fontSize;
#override
void initState() {
super.initState();
_fontSize = widget.initialFontSize;
}
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Font Size'),
content: Container(
child: Slider(
value: _fontSize,
min: 10,
max: 100,
divisions: 9,
onChanged: (value) {
setState(() {
_fontSize = value;
});
},
),
),
actions: <Widget>[
FlatButton(
onPressed: () {
// Use the second argument of Navigator.pop(...) to pass
// back a result to the page that opened the dialog
Navigator.pop(context, _fontSize);
},
child: Text('DONE'),
)
],
);
}
}
You just need to warp the AlertDialog() with a StatefulBuilder()