This is my HomePage.dart Codes. Here I Want to access the _pushSaved() function into the 'onpressed' property from a different file named "randomwords.dart" file (code attached for review). It seems I need to declare the _pushSaved() somewhere but do not have any idea as I am new to dart and flutter. I Will be thankful if somebody could help. Thanks in advance.
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("StartUp Name Generator"),
actions: [
IconButton(icon: Icon(Icons.list), **onPressed: _pushSaved**)
],
),
body: RandomWords(),
);
}
}
The _pushSaved() function code in the randomwords.dart file needs to be accessed in the HomePage.dart as object...
class RandomWords extends StatefulWidget {
#override
_RandomWordsState createState() => _RandomWordsState();
}
class _RandomWordsState extends State<RandomWords> {
final _suggestions = <WordPair>[];
final _saved = Set<WordPair>();
final _biggerFont = TextStyle(fontSize: 20.0, color: Colors.pink);
#override
Widget build(BuildContext context) {
return ListView.builder(
itemBuilder: (context, i) {
if (i.isOdd) return Divider();
final index = i ~/ 2;
if (index >= _suggestions.length) {
_suggestions.addAll(generateWordPairs().take(10));
}
return _buildRow(_suggestions[index]);
},
);
}
Widget _buildRow(WordPair pair) {
final alreadySaved = _saved.contains(pair);
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
trailing: Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color: alreadySaved ? Colors.red : null,
),
onTap: () {
setState(() {
if (alreadySaved) {
_saved.remove(pair);
} else {
_saved.add(pair);
}
});
},
);
}
void **_pushSaved()** {
Navigator.of(context).push(
MaterialPageRoute<void>(
builder: (BuildContext context) {
final tiles = _saved.map(
(WordPair pair) {
return ListTile(
title: Text(
pair.asPascalCase,
style: _biggerFont,
),
);
},
);
final divided =
ListTile.divideTiles(tiles: tiles, context: context).toList();
return Scaffold(
appBar: AppBar(
title: Text("Saved Suggestions"),
),
body: ListView(
children: divided,
),
);
},
),
);
}
}
Although the answer above solves your problem , but thats not a good practice , use absctract classes for defining functions that are supposed to be used at various places.
Your function is defined as a private property. You need to change that first.
pushSaved()
Secondly, You need to define this function as a static function to access it in other classes.
static void pushSaved()
Now call your function in your HomePage class in onPressed function like this:
onPressed: () => RandomWords.pushSaved
Import the HomePage.dart in the Homepage file like this;
import 'package:main.dart' as main; //it doesn't have to be as 'main'
and then acces the function like this:
IconButton(icon: Icon(Icons.list),*onPressed: main._pushSaved)
Note: now when I've done somthing like this and my function startes with a _ it unfortunantly doesn't work, I know that everyone makes functions with _ first, but apperantly it didn't work for me when importing it from another file. So beware of that.
Also the function has to be out of the _RandomWordsState to be accesed by the other file. Just put it down underneath.
Related
Currently, I have two sample files Parent.dart and Child.dart.
In Parent.dart file this is what the code is like:
Parent.dart file:
children:
[
isDisabled
? Icon(Icons.public, color: Colors.grey)
: Icon(Icons.public, color:Colors.white),
InkWell(
onTap:()=> Navigator.push(context,MaterialPageRoute(builder: (context)=> Child(
isDisabled: isDisabled, function: ()=> function())),
]
function()
{
setState(()=> isDisabled = !isDisabled);
}
and in Child.dart the code is something like this:
children:
[
widget.isDisabled
? Icon(Icons.public, color: Colors.grey)
: Icon(Icons.public, color:Colors.white),
InkWell(
onTap:()=> widget.function(),
]
I have some data being fetched from a server that is used to populate a list of cards inside listview.builder.
What I'm trying to do is inherent variables from the parent and use their value to update the child. Currently, if I run this parent does change, but the child doesn't until you navigate back from parent to child.
For a better context: Imagine a list of cards. Each has an add-to-list button. Now if you click on the card it goes to another screen "child.dart" where it gives you more details about the item on the card you clicked. Now if you click the add-to-list button on the child screen it should also update the parent.
I tried different ways of achieving this "UI synchrony" for a better user experience. But I didn't find a proper way to implement it.
Things I tried: Provider (but it updates all the items on the list instead of each instance.),
a "hacky" method of editing the data in the list on the client side and updating the widget based on that. (This technique does work, but ewwwww)
I'm not really sure to understand your question.
If your question is how trigger a function in parent from child screen, here is your answer.
I made a working example. I think you were really close.
Another option for state management is riverpod 2.0
Or you can pass value in Navigator.pop and trigger the function in parent.
Parent model
class Parent {
String title;
bool isDisabled = false;
Parent({required this.title});
}
Main.dart
import 'package:flutter/material.dart';
import 'parent.dart';
import 'ParentCard.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
MyApp({super.key});
List<Parent> parentList = [Parent(title: 'Item 1'), Parent(title: 'Item 2')];
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: ListView.builder(
itemCount: parentList.length,
itemBuilder: (BuildContext context, int position) {
return ParentCard(title: parentList[position].title);
},
),
),
);
}
}
ParentCard.dart
import 'package:flutter/material.dart';
import 'child.dart';
class ParentCard extends StatefulWidget {
String title;
ParentCard({super.key, required this.title});
#override
State<ParentCard> createState() => _ParentCardState();
}
class _ParentCardState extends State<ParentCard> {
bool isDisabled = false;
#override
Widget build(BuildContext context) {
return Row(
children: [
Text(widget.title),
isDisabled
? Icon(Icons.public, color: Colors.green)
: Icon(Icons.public, color: Colors.black),
IconButton(
icon: Icon(Icons.plus_one),
onPressed: () => Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ChildCard(isDisabled: isDisabled, handler: handler)),
),
)
],
);
}
handler() {
setState(() => isDisabled = !isDisabled);
}
}
** child.dart**
import 'package:flutter/material.dart';
class ChildCard extends StatefulWidget {
VoidCallback handler;
bool isDisabled;
ChildCard({super.key, required this.isDisabled, required this.handler});
#override
State<ChildCard> createState() => _ChildCardState();
}
class _ChildCardState extends State<ChildCard> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(children: [
const Text('child !'),
widget.isDisabled
? const Icon(Icons.public, color: Colors.green)
: const Icon(Icons.public, color: Colors.black),
ElevatedButton(
onPressed: () {
setState(() {
widget.isDisabled = !widget.isDisabled;
});
widget.handler();
},
child: const Text('click to trigger'),
),
ElevatedButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('pop it'),
)
]),
);
}
}
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.
I have a drawer that have a listview that have a nested expansion tiles as its children.
1- I want to close all open expanded tiles exepet the one that just opened.(No more the 1 expanded tile that is open)
What is the best way to do this?
2- i also want to keep the open one stay open when i close and reopen the drawer (I acheved this by using key:PageStorageKey but if there is a better way i would like to hear it).
I solved the problem by using this custom ExpansionTile widget from https://stackoverflow.com/a/58221096/19329222
It have errors (I think because the answer is 3 years old) but you can get rid of them by replacing the incorrect syntax with the correct one from the original ExpansionTile widget file.
This just work, but go through solution and adopt solution with your project design pattern and adopt it with your state management(Provider)
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(new ExpansionTileSample());
}
class ExpansionTileSample extends StatefulWidget {
#override
ExpansionTileSampleState createState() => new ExpansionTileSampleState();
}
class ExpansionTileSampleState extends State {
String foos = 'One';
int _key;
_collapse() {
int newKey;
do {
_key = new Random().nextInt(10000);
} while(newKey == _key);
}
#override
void initState() {
super.initState();
_collapse();
}
#override
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: new AppBar(
title: const Text('ExpansionTile'),
),
body: new ExpansionTile(
key: new Key(_key.toString()),
initiallyExpanded: false,
title: new Text(this.foos),
backgroundColor: Colors.gray,
children: [
new ListTile(
title: const Text('One'),
onTap: () {
setState(() {
this.foos = 'One';
_collapse();
});
},
),
new ListTile(
title: const Text('Two'),
onTap: () {
setState(() {
this.foos = 'Two';
_collapse();
});
},
),
new ListTile(
title: const Text('Three'),
onTap: () {
setState(() {
this.foos = 'Three';
_collapse();
});
},
),
]
),
),
);
}
}
I have this value of type list that I'll be using on a ListViewBuilder.
I have a condition that checks if the list has a length of 0 or more so that I can show an Empty State widget if there are no available items in the list and to show the ListViewBuilder if there are items in the list.
Running the logic actually show's that it works but my problem is that since the list object's length starts at 0 it always draws my "Empty State" widget first but then quickly draws the actual ListViewBuilder when my Future function finishes incrementing on my list. Though it works, the experience for the user to quickly see the "Empty State" even if there actually items on list is quite jarring.
Hoping you guys can help me on a way I can only show either state my list is on without having to pass through from the initial state of zero.
Below is a representation of what I am talking about, you can see that isolated my three states: NULL (White), ==0 (Blue), >=1 (Green).
What I'm trying to achieve is to just go to Green without showing Blue.
EDIT: ADDED SAMPLE CODE BELOW
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class MainProvider extends ChangeNotifier {
List<String> list = <String>[];
MainProvider() {
initList();
}
initList() async {
// CODE BELOW IS TO SIMULATE MAKING DATABASE CALLS
for (int i = 1; i <= 5; i++) {
await Future.delayed(Duration(milliseconds: 800), () {
addToList('String');
});
}
}
addToList(String string) {
list.add(string);
notifyListeners();
}
}
void main() => runApp(AppIndex());
class AppIndex extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: AppProvider(),
);
}
}
class AppProvider extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<MainProvider>(
create: (context) => MainProvider(),
child: AppContent(),
);
}
}
class AppContent extends StatelessWidget {
#override
Widget build(BuildContext context) {
var mainProvider = Provider.of<MainProvider>(context);
return Scaffold(
appBar: AppBar(),
body: mainProvider.list == null
? Container(
color: Colors.red,
child: Center(
child: Center(
child: Text(
'NULL',
),
),
),
)
: mainProvider.list.length == 0
? Container(
color: Colors.blue,
child: Center(
child: Text(
mainProvider.list.length.toString(),
style: TextStyle(fontSize: 32.0),
),
),
)
: mainProvider.list.length >= 0
? Container(
color: Colors.green,
child: Center(
child: Text(
mainProvider.list.length.toString(),
style: TextStyle(fontSize: 32.0),
),
),
)
: Container(
color: Colors.red,
child: Center(
child: Text(
mainProvider.list.length.toString(),
style: TextStyle(fontSize: 32.0),
),
),
),
);
}
}
In my code, I am using my Future function to call out database items
to add to my list. In the sample code above, I intentionally added a
delay to simulate database call times.
Given your comment, I think something like this will do:
class _HomePageState extends State<HomePage> {
Future<List<Item>> _items;
#override
void initState() {
super.initState();
// here maybe you won't use the server side, but some local storage
_items = Web.fetchItems();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<List<Item>>(
future: _items,
builder: (context, snapshot) {
if (snapshot.hasData) {
if (_items.isEmpty) {
return _MyCallToActionButtonLayout();
}
return Scaffold(
appBar: AppBar(
title: Text('My title'),
),
body: buildListView(snapshot.data)),
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
} else {
return Scaffold(body: Center(child: CircularProgressIndicator()));
}
},
);
}
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(),
) ,
);
...