I would like to put a switch statement inside of my Column to decide what mix of children widgets to buildin my FLUTTER APP.
In my specific case I have created an enum with 4 different possible states (AnimationEnum.None, AnimationEnum.etc), each triggering a different build mix of children.
I can get this working fine by writing an if statement above EVERY possible widget, but that is clearly an inefficient way of doing things, and want to streamline my code.
I feel like I am close but cant quite get there. Here is a simplified version of the code with placeholder widgets:
thanks!
child: Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
switch(widget._cardAnimType)
case AnimationEnum.None: {
Widget1('args'),
Widget2('args'),
Widget3('args'),
//etc
break;
}
case AnimationEnum._card1Correct: {
Widget1('args'),
Widget2('args'),
Widget3('args'),
//etc
break;
}
///more switch statements here...
]
),
),
),
It's not possible as dart supports collections if's(inside the collection itself) but not switch.
Your best chance at it is extracting the logic and using a spread operator like follows.
#override
Widget build(BuildContext context) {
return Column(children: [...extractedMethod(widget._cardAnimType)],);
}
List<Widget> (AnimationEnum animation){
switch(animation)
case AnimationEnum.None: {
Widget1('args'),
Widget2('args'),
Widget3('args'),
//etc
break;
}
case AnimationEnum._card1Correct: {
Widget1('args'),
Widget2('args'),
Widget3('args'),
//etc
break;
}
}
Also for more info on collection operation check out this article
Related
Goodmorning,
I'm developing an app with flutter but I'm facing some problems with Provider (I think something miss in my knowledge).
My app fetch data from my API and displays them in listview.
In whole app I have different screen which displays different data type in listview and now I want to create filtering logic.
To avoid rewrite same code multiple times I thought to create one screen to reuse for filtering purposes but I'm facing problems with state management.
What I did:
create base model for filter information
`
enum FilterWidget { TEXT_FIELD, DROPDOWN } //to resolve necessary Widget with getWidget() (to implement)
class FilterBaseModel with ChangeNotifier {
String? value= 'Hello';
FilterWidget? widgetType;
FilterBaseModel(this.value, this.widgetType);
onChange() {
value= value== 'Hello' ? 'HelloChange' : 'Hello';
notifyListeners();
}
}
`
One screen for display filters depending on request
List<FilterBaseModel> filters = [];
FilterScreen() {
//Provided from caller. Now here for test purposes
filters.add(FilterBaseModel('Filter1', FilterWidget.TEXT_FIELD));
filters.add(FilterBaseModel('Filter2', FilterWidget.TEXT_FIELD));
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: SafeArea(
minimum: EdgeInsets.symmetric(vertical: 15, horizontal: 15),
child: SingleChildScrollView(
child: Container(
height: 400,
child: Column(
children: filters
.map(
(e) => Consumer<FilterBaseModel>(
builder: (_, filter, child) =>
ChangeNotifierProvider.value(
value: filter,
child: CustomTextField(
`your text` initialText: e.value,
onTap: () {
e.onChange();
filter.onChange();
},
),
),
),
)
.toList(),
))),
),
);
}
`
The problem is in Consumer and ChangeNotifier.value.
Screen works quite well: widget are displayed and callback are called, what is wrong? I need to use onChange method of both instance to update UI otherwhise method was called but widget is not rebuilt.
I know that probably putting consumer there is not right way but I tried also to put outside but doesn't work.
I expect to have one filter screen which receives in input filters list information, display them, handle their state managment and return their value.
P.S: this code now works, but I know is not the right way
Thank you for help!
EDIT
Have same behaviour without ChangeNotifierProvider.value. Therefore I'm more confused than before because still persist the double call to onChange for correct rebuilding.
More bit confused about ChangeNotifierProvider.value using...
How would it be possible to dynamically create rows and columns in flutter using a map element as the schematics?
If I had the following map:
"rows":{
0:{
"cols":{
0:{"text":"ABC"},
1:{"text":"DEF"},
2:{"text":"GHI"},
},
},
1:{
"cols":{
0:{
"rows":{
0:{"text":"JKL"},
1:{"text":"MNO"}
},
},
1:{"text":"PQR"},
2:{"text":"STU"},
},
},
},
how would I make it into a series of flutter widgets:
Row(
children:[
Column(children: <Widget>[Text("ABC"),]),
Column(children: <Widget>[Text("DEF"),]),
Column(children: <Widget>[Text("GHI"),]),
),
Row(
children:[
Column(children:
Row(children: [
<Widget>[Text("JKL"),
<Widget>[Text("MNO"),
],
),
Column(children: <Widget>[Text("PQR"),]),
Column(children: <Widget>[Text("STU"),]),
]
)
I'd like it so that the number of levels deep is dynamic/nested widgets is infinite, that is, it doesn't iterate two levels down and then just stop.
To date, I've tried using for loops inside functions which refer to themselves, but I can't quite get it.
You can try a Map.forEach() Recursion and validate for "cols" and "rows"something like this:
recursiveColsAndRows(Map blob) {
blob.forEach((key, value) => {
if(key == "cols") {
// Do a recursion
recursiveColsAndRows(value)
},
if(key == "rows") {
// Do a recursion
recursiveColsAndRows(value)
},
print(value)
});
}
But with infinite nesting the way you think about, will bring you other big problems very soon, because you cannot display it infinitely without any scrolling features. That means after your third or fourth iteration, you will get an Overflowed Error because your Rows fall out of the Screen.
Infinite nesting the way you try to accomplish is not a good approach for displaying different data. Try to create proper widgets which fit your different types of needs.
PS: Are you maybe searching for a Table feature?
I've this code:
ListView(
children: episodes.map((Episode episode) {
return EpisodeRow(episide: episode)
);
I understand that the intentions for built-in lazy loading is good on flutter, however I really need each widget to be loaded on the screen because each widget must download a file and store it for use when the device is without internet connection.
Why don't you simply download all necessary files when the screen is loaded inside of the initState method, so that you do not need to care about lazy loading destroying the user experience in case the internet connection breaks down.
A solution would be to use a SingleChildScrollView with a Column widget:
SingleChildScrollView(
child: Column(
children: episodes.map((Episode episode) {
return EpisodeRow(episide: episode);
}).toList(),
),
);
I have been trying to figure out the least impactful way of creating an overflow when there isn't enough room to show otherwise, 3 button icons. Ideally, I could find out a way to know the space between the two widgets on the row and once it is X distance away, it turns into the minimised version.
I have tried 'off methods' like counting how many characters each item in the array has and making a number, but as it expands, it'll turn into the tablet and then desktop layout, therefore will not be dependable to rely on it or MediaQuery.of(context).size.
Hopefully what I am saying makes sense.
Picture to show what I mean:
minimise: true
minimise: false
Thanks for any advice you can give.
class ListCardChipsWithActionBar extends StatelessWidget {
#override
Widget build(BuildContext context) {
//print('categoryList is ${categoriesList.length}');
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: ListCardCategoryChips(
categories: categoriesList,
),
),
ListCardActionBar(
minimised: true,
),
],
);
}
}
Column(
children: <Widget>[
...myObject
.map((data) => Text("Text 1"), Text("Text 2")),
]
);
This block of code will fail because I'm returning 2 widgets instead of one. How could I fix it and return as many widget as I want without creating another column inside the map?
First you cant use an arrow function to return multiple values, you need to change it to a normal function that returns a list of widgets. Second, you need to use the .toList() method since .map is lazy and you need to iterate in order to map execute.
With this 2 steps you are going to end with a List<List<Widget>> and you should flat it before return it to a column that needs a List<Widget>. This can be achieved with the Iterable.expand() with an identity function.
You can try something like this:
Column(
children: <Widget>[
..._generateChildrens(myObjects),
],
),
And the actual implementation to obtain the widgets is:
List<Widget> _generateChildrens(List myObjects) {
var list = myObjects.map<List<Widget>>(
(data) {
var widgetList = <Widget>[];
widgetList.add(Text("Text 1"));
widgetList.add(Text("Text 2"));
return widgetList;
},
).toList();
var flat = list.expand((i) => i).toList();
return flat;
}
Hope it helps!