I want to iterate through a list of very similar buttons but I'm not sure what the best way to do it is since each button will need an onPressed function that passes a single argument.
Column(
children: buttons
.map(
(item) => Row(
children: <Widget>[
myButton(item[0], item[2]),
myButton(item[1], item[2]),
],
),
)
.toList(),
),
class MyButton extends StatelessWidget {
MyButton(this.abool, this.onPressed);
final bool abool;
final Function onPressed;
#override
Widget build(BuildContext context) {
return RawMaterialButton(
child:
Image.asset('images/button.png'),
onPressed: onPressed,
);
So of course I have a problem with storing the different onPressed functions in a List array. What's the best way to iterate through a list of similar buttons like this in a column?
If the Buttons don't share similar behavior , you should not have to make the Mybutton class.
you can't make one class for Sign up button and Login button ,each one has different responsibility and behavior .
This class should be used when you have data model (shopItem) for example (and from your code i think this is the case) , and you need (add to cart) functionality for example , this is a case where you have to make addToCartButton and be something like this :
class AddToCartButton extends StatefulWidget {
final ShoptItem item;
AddToCartButton({Key key, this.item}) : super(key: key);
#override
_AddToCartButtonState createState() => _AddToCartButtonState();
}
class _AddToCartButtonState extends State<AddToCartButton> {
#override
Widget build(BuildContext context) {
return RaisedButton(
child: Text('Add to Cart'),
onPressed: () {
setState(() {
cartList.add(item);
});
},
);
}
}
notice that i've used stateful widget (not necessary ) if there is any kind of state in the button itself like
Text(inCart? 'already in cart':'add to cart'),'
unless you are using state management like provider ,bloc, etc.
the second notice here (most Important) that i've used cartList which basically a list of ShopItem .
Now all the buttons have similar functionality (adding item to the cart) ,but every button should add different item , that can be done in column you're using or the ListView builder which i prefer
the Column approach:
Column(
children: <Widget>[
for( item in itemsList)
AddToCartButton(item : item);
],
ListViewBuilder approach :
ListView.builder(
itemCount: itemsList.length,
itemBuilder: (context,index){
return AddToCartButton(item : itemsList[index]);
},
)
I hope that can help
You can use list comprehension (also called UI-as-code) since Dart 2.3 to generate buttons on the fly right inside the list.
List<String> labels = ['apple', 'banana', 'pineapple', 'kiwi'];
List<VoidCallback> actions = [_buyApple, _doSomething, _downloadData, () => print('Hi')];
...
Column(
children: [
for (int i = 0; i < labels.length; i++)
RaisedButton(
label: Text(labels[i]),
onPressed: actions[i],
),
],
)
Related
Summing up the situation, I'm making a simple App in Flutter, which displays a List of Items you've added (I won't detail the app, as it would be unnecessary).
The file I created (log.dart) has a Property Class
class LogItem { /* code and stuff inside */ }
And it has a List with items
List<LogItem> itemsList = [test01, test02];
I created a simple Widget to display data for each item in this List
class SpecificItem extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: Row(
children: [
Text(
" R\$ ${itensList[i].price}",
),
Spacer(),
Text(
"${itensList[i].title}",
),
],
),
);
}
}
Just below in another widget, I created a for loop to make a variable "i" change, to display different items from this list.
class LogGraphical extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(
child: ListView(
children: [
for (int item = 0; item < itensList.length; item++) SpecificItem(item),
],
),
);
}
}
Can someone explain to me exactly how I do the Widget ACCEPT PARAMETERS and change it? In this way creating multiple items?
I tried in many ways, but I could never get it to work!
(In the code I inserted here, I didn't put the Widget accepting anything.)
I have quite the problem wrapping my head around the whole state management of GetX so naturally I'm facing some issues.
I'm getting a collection from firebase which I put into a listview populated with Card widgets (CustomCard()), each document into a Card widget. In this Card widget I have a boolean that controls whether the card should be expanded (simply by Adding a Row()) or not if Card is tapped. The issue I'm facing is that if use GetX for this boolean, all the cards will trigger and not each individual card. In a way this seems logical because I only have one controller that manages this boolean.
So to clarify, bool isCardExpanded seem to be global for all Card widgets meaning that if I tap one card, ALL cards will expand, which is not what I want. I need them to act individually.
Do I need one separate controller for every Card in the list view or is this solvable in another way?
Controller
class Controller extends GetxController {
RxBool isCardExpanded = false.obs;
void changeExpanded() {
isCardExpanded.value = !isCardExpanded.value;
update();
}
}
ListView:
class CustomScreen extends State<CustomScreen>
implements ItemScreenInterface {
Controller ctrl = Get.find();
#override
Widget build(BuildContext context) {
return StreamBuilder(
stream: FirebaseFirestore.instance
.collection(someCol)
.doc(SomeDoc)
.collection(anotherCol).snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return Column(
children: [
Expanded(
child: ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.docs.length,
itemBuilder: (_, i) {
return CustomCard( // <------------ Card widget
document: snapshot.data.docs[i]);
}),
)
],
);
},
);
}
Card class
class CustomCard extends StatefulWidget {
CustomCard({required this.doc});
var doc;
#override
_CustomCardState createState() => _CustomCardState();
}
class _CustomCardState extends State<CustomCard> {
Controller ctrl = Get.find();
RxBool _isCardExpanded = false.obs;
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Card(
child: GestureDetector(
onTap: () {
ctrl.changeExpanded(); // <-- change bool
},
child: Padding(
padding: EdgeInsets.all(2),
child: Column(
children: <Widget>[
Row(
children: <Widget>[
Text('Top Part')
]),
if (_isCardExpanded.value) Divider(thickness: 2),
if (_isCardExpanded.value) // Controlled by _isCardExpanded. Add Row if true
Row(
children: <Widget>[
Text('EXPANDED'),
],
),
],
),
),
),
);
}
}
Desired outcome:
Actual outcome:
Because your controller instance initializes only once.
To solve your problem, you need to make a List that has the status of the cards in the Controller.
Or don't use the State management tool, but separately place the _isExpanded value in the _CustomCardState class. And use the setState() function.
EDIT
There is another way by using the tag argument when creating the controller like Get.create(someController, tag: TAG_NAME);
I try to create custom dropdown for my app. And the dropdown can have three duplicate on the same screen. When each dropdown tapped, there is a variable called isDropdownOpened set to true. The case, when one of the dropdown opened then I wan't the others have to set it's isDropdownOpened variable to false again. So, how to change the isDropdownOpened value automatically when other instances of dropdown tapped?
should i use state management like provider, or bloc and cubit? Or even i can do it with setState.
here is the code.
class SearchDropdownButton extends StatefulWidget {
const SearchDropdownButton({
Key? key,
required this.text,
}) : super(key: key);
final String text;
#override
State<SearchDropdownButton> createState() => _SearchDropdownButtonState();
}
class _SearchDropdownButtonState extends State<SearchDropdownButton> {
late OverlayEntry _categoryBottomBar;
bool isDropdownOpened = false;
#override
Widget build(BuildContext context) {
return Expanded(
child: ElevatedButton(
onPressed: () {
setState(() {
if (isDropdownOpened) {
_categoryBottomBar.remove();
} else {
Overlay.of(context)!.insert(_categoryBottomBar);
}
isDropdownOpened = !isDropdownOpened;
});
},
and the instances on a row.
Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: const [
SizedBox(width: 20),
SearchDropdownButton(text: "Consume"),
SizedBox(width: 20),
SearchDropdownButton(text: "Gadget"),
SizedBox(width: 20),
SearchDropdownButton(text: "Fashion"),
SizedBox(width: 20),
],
),
the complete code : https://pastebin.com/QtfDfXzU
Your case is not specific to flutter (React is same). Basic way to do this is moving isDropdownOpened state to parent stateful widget. Corresponding react tutorial is here.
If you want to do this in implicit way then yes, you should use state management library for inter-component state sharing.
I have some Widget groups that go into various list based widgets throughout my app, with this kind of structure:
bool showB = false
Column(
children: <Widget>[
WidgetA(),
FlatButton(onPressed: () => setState(() => showB = !showB )),
if(showB) WidgetB(),
],
);
I would like to decouple these child widgets from their List/Column view and be able to insert them so that I can decorate the List view with additional things, like:
Column(
children: <Widget>[
...GroupedWidgets(),
Text('Something unrelated'),
],
);
I initially thought to use a function that returns an array like List<Widget> groupedWidgets() but the problem is that I want to encapsulate the state of showB for the sake of convenience, otherwise I have to pass showB and the function to setState from the outside. So what I really want is a class that acts like a normal widget except that it builds an array of widgets.
You could make your own StatefullWidget for the Group like this:
class WidgetGroup extends StatefulWidget {
WidgetGroupState createState() => WidgetGroupState();
}
class WidgetGroupState extends State<WidgetGroup> {
bool _showB = false;
#override
Widget build(BuildContext context) {
return Column(
children: <Widget>[
WidgetA(),
FlatButton(onPressed: () => setState(() => _showB = !_showB )),
if (_showB) WidgetB(),
],
);
}
}
Then you could use this Widget anywhere you would need to: in a ListView, Column, Row, basically in any Widget as a child.
In your case you would have something like this:
Column(
children: <Widget>[
WidgetGroup(),
Text('Something unrelated'),
],
);
I hope this solves you problem.
I started Flutter recently and my app required bottom navigation. I have created bottom navigation and manage to access the child widget based on the tab selected.
Under the child widget there is drop down selection where I can change the bottom navigation text in one of the tabs for different selections.
I have tried a few days but still could not figure out how the child widget can change the text.
I have tried callback but cannot get it work. I have tried navigation.push - material page route but it rebuild the whole widget and my selection gone. I have also tried to use GlobalKey or Sharedpreference to capture my selection so that when it rebuild, it will use back the stored selection but I couldn't get it work.
I only wish to change the bottom navigation text in one of the text from child widget drop down selection.
Which is the best method to achieve this?
I would recommend you try to use the bloc pattern with a StreamBuilder. I have an example below. Regardless, in the example there is a stateful widget, a bloc, and a data class. Try to understand this code and modify it to your needs.
import 'package:flutter/material.dart';
import 'dart:async';
class StreamScaffold extends StatefulWidget {
#override
_StreamScaffoldState createState() => _StreamScaffoldState();
}
class _StreamScaffoldState extends State<StreamScaffold> {
ScaffoldDataBloc bloc;
#override
void initState() {
super.initState();
bloc = ScaffoldDataBloc();
}
#override
Widget build(BuildContext context) {
return StreamBuilder<ScaffoldDataState>(
stream: bloc.stream, // The stream we want to listen to.
initialData: bloc.initial(), // The initial data the stream provides.
builder: (context, snapshot) {
ScaffoldDataState state = snapshot.data;
Widget page;
if (state.index == 0) {
// TODO separate this into its own widget, this is messy.
page = Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () => bloc.updateText(state,"Sales"),
child: Text("Set text to Sales")
),
RaisedButton(
onPressed: () => bloc.updateText(state, "Purchases"),
child: Text("Set text to Purchases"),
)
]),
);
}
if (state.index == 1) {
// TODO separate this into its own widget, this is messy.
page = Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
RaisedButton(
onPressed: () => bloc.updateText(state, "Stock"),
child: Text("Set text to Stock"),
),
RaisedButton(
onPressed: () => bloc.updateText(state, "Budget"),
child: Text("Set text to Budget"),
)
]));
}
return Scaffold(
body: page,
bottomNavigationBar: BottomNavigationBar(
currentIndex: state.index,
onTap: (int) => bloc.updateIndex(state, int),
items: [
BottomNavigationBarItem(
icon: Icon(Icons.play_arrow),
// Obtain the text from the state
title: Text(state.variableText)),
BottomNavigationBarItem(
icon: Icon(Icons.play_arrow), title: Text("Test")),
]),
);
});
}
#override
void dispose() {
super.dispose();
bloc.dispose();
}
}
// A data class to hold the required data.
class ScaffoldDataState {
int index;
String variableText;
ScaffoldDataState({this.index = 0, this.variableText = "Hello"});
}
// A bloc to handle updates of the state.
class ScaffoldDataBloc {
StreamController<ScaffoldDataState> scaffoldDataStateController = StreamController<ScaffoldDataState>();
Sink get updateScaffoldDataState => scaffoldDataStateController.sink;
Stream<ScaffoldDataState> get stream => scaffoldDataStateController.stream;
ScaffoldDataBloc();
ScaffoldDataState initial() {
return ScaffoldDataState();
}
void dispose() {
scaffoldDataStateController.close();
}
// Needs to be called every time a change should happen in the UI
// Add updated states into the Sink to get the Stream to update.
void _update(ScaffoldDataState state) {
updateScaffoldDataState.add(state);
}
// Specific methods for updating the different fields in the state object
void updateText(ScaffoldDataState state, String text) {
state.variableText = text;
_update(state);
}
void updateIndex(ScaffoldDataState state, int index) {
state.index = index;
_update(state);
}
}
Hope it helps!
Additional Questions from comment:
The easiest solution would be to simply pass the bloc as a parameter to the widget. Create a new dart file in your project, create a StatelessWidget there, create the code for the page in the build method. Note: it would make sense for you to separate the bloc into its own file along with the data class.
import 'package:flutter/material.dart';
// Import the file where the bloc and data class is located
// You have to have a similar import in the parent widget.
// Your dart files should be located in the lib folder, hit ctrl+space for
// suggestions while writing an import, or alt+enter on a unimported class.
import 'package:playground/scaffold_in_stream_builder.dart';
class ChildPage extends StatelessWidget {
final ScaffoldDataBloc bloc;
final ScaffoldDataState state;
const ChildPage({Key key, this.bloc, this.state}) : super(key: key);
#override
Widget build(BuildContext context) {
return Container(); // TODO replace with your page
}
}
However, if the these child widgets get their own children in separate files it would be better to use a InheritedWidget instead, with the bloc and state. This avoids "passing state down". See this article on inherited widgets