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?
Related
I am working on a UI where I have 3 checks in the same container.
I am able to achieve only one condition but not able to the second one or third one.
till now I have created a container where I have made a column and included all things and used a bool variable which changes when I click the text and it reverts back when I click the close button.
But now the problem is How can I use 3 conditions in the same column?
my code till now
bool makePayment = false;
makePayment ? Column( crossAxisAlignment:
children: [
const Text('Quick actions'),
const SizedBox(),
Row(),
Row()
]) : Column()
you could use a lot of condition in the same way as you did just the question will be more complicated , example :
A.isNumber ? do1 : A.isAlpha? do2 : A.isSymbol ? do3 : do4
You can use else-if into your column children.
The Syntax is
Column(
children: <Widget>[
if(your condition) ...[
//YOUR CODE
] else if(your condition) ...[
//YOUR CODE
] else ...[
//YOUR CODE
]
]
)
I currently have a ListView that displays a large collection of objects. Quite a lot of these objects share the same first one/two words, for example:
Peter Johnson
Peter Jackson
Peter Smith
I would like to split this ListView into groups based on the start of their names. So for example all the Peters would be put in the same group. And then the next name that multiple people share would also be split into their own group.
The problem is that I can't explicitly say what these names are, how would I go about splitting these up?
This is a hard one, I'm going to try to simplify this as much as possible, but the general answer is first write an algorithm that splits your string how you want (in this case, if the first word is the same) and then nest these into a column or whatever.
Here I split the values into a map with the first name as a key and a list of all the people with that first name as a value
// Theres probably a better way to do this
Map<String, List<String>> valuesByFirstName(List<String> values) {
Map<String, List<String>> result = {};
for (final val in values) {
final fn = val.split().first;
if (result[fn] != null) result[fn]!.add(val);
else result[fn] = [val];
}
return result;
}
Then I'm not sure how you wanna group each of these so I just made some basic example:
var values = valuesByFirstName(originalValues);
return Row(
children: [
for (var key in values.keys)
SizedBox(
height: 200,
width: 100,
child: ListView(
children: [
Text(key, style: TextStyle(fontSize: 20)),
for (var value in values[key])
Text(value),
]
),
),
),
],
);
If what you want is to contract the different sections, take a look at the ExpansionPanel widget
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
Dart supports using "if" and "for" during creation of a list :
var nav = [
'Home',
'Furniture',
'Plants',
if (promoActive) 'Outlet'
];
and:
var listOfInts = [1, 2, 3];
var listOfStrings = [
'#0',
for (var i in listOfInts) '#$i'
];
assert(listOfStrings[1] == '#1');
What is the point of this ? How is it better than creating a list, and appending to it afterwards like in other programming languages:
var nav = [
'Home',
'Furniture',
'Plants',
];
if (promoActive) nav.add('Outlet');
There are multiple factors playing a role:
performance
readability
Readability
The main reason why this feature was implemented is for ListView/Stack and all other widgets with a children parameter.
These widgets do not support null as parameter, which caused a significant amount of frustration (which led to this github issue: Allow null values in child or children[] collections to signal that an element should not be drawn)
In short, the problem is that the declarative nature of widgets means that using add & co isn't really an option.
Without if/for inside collections, we would have to write:
List<Widget> children = [
Foo(),
]
if (condition)
children.add(Bar());
return Scaffold(
body: ListView(
children: children,
),
);
which makes it difficult to understand what is rendered on screen since the build method is now fragmented
Alternatively, we would have to write:
return Scaffold(
body: ListView(
children: [
Foo(),
condition ? Bar() : null,
].where((e) => e != null).toList(),
),
);
This is more readable but significantly lack in flexibility as anything more complex than this example will be difficult to implement
As a solution, we can now write:
return Scaffold(
body: ListView(
children: [
Foo(),
if (condition)
Bar(),
]
),
);
This is both readable, easy to write, and not error-prone.
Performance
An interesting aspect of this feature is, it increases the performance of your Flutter apps.
On thing to consider when writing:
final list = <Widget>[
Foo(),
];
if (condition)
list.add(Bar());
is that by using add, the list size changes over time. This means that any add can potentially cause the List to be re-allocated to support more items, which is expensive.
The same issue applies to:
ListView(
children: [
Foo(),
condition ? Bar() : null,
].where((e) => e != null).toList(),
)
where we are effectively instantiating the List twice and iterating over all of its items twice too (once for the where, another time by ListView)
These performance issues do not happen when using if/for inside collections.
When writing:
ListView(
children: [
Foo(),
if (condition)
Bar(),
],
);
this immediately allocates the List with the correct size, and the allocation if performed once and only once.
The reason is, in reality this syntax is equivalent to:
List<Widget> children;
if (condition)
children = [
Foo(),
Bar(),
];
else
children = [
Foo(),
]
which involves neither add nor where/toList
I read the following proposal to understand this better:
https://github.com/dart-lang/language/blob/master/accepted/2.3/spread-collections/feature-specification.md
A key goal of Flutter's API design is that, as much as possible, the
textual layout of the code reflects the nesting structure of the
resulting user interface.
It seems that the reason is simply for code readability purposes in flutter. For example, the following code:
var command = [
engineDartPath,
frontendServer,
];
for (var root in fileSystemRoots) {
command.add('--filesystem-root=$root');
}
for (var entryPointsJson in entryPointsJsonFiles) {
if (fileExists("$entryPointsJson.json")) {
command.add(entryPointsJson);
}
}
command.add(mainPath);
can instead be written in a more concise form:
var command = [
engineDartPath,
frontendServer,
for (var root in fileSystemRoots) '--filesystem-root=$root',
for (var entryPointsJson in entryPointsJsonFiles)
if (fileExists("$entryPointsJson.json")) entryPointsJson,
mainPath
];
There are many more examples in the document, but the short answer seems to be simply for readability and conciseness of code.
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!