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
Related
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 want to make an editable TextWidget in flutter but I don't really know how to go around it, I did some research, but still can't find a good solution.
Here's my sample code below.
I have a variable called
int qty = 1;
and so I called the variable in TextWidget
Column(
children: [
Text(
"${qty}",
style: TextStyle(),
)
],
),
I want to have these features that make user tab on the value to change it if they want, upon tap, a pop-up dialog will show to give the user the ability to change the existing value to whatever the user wants.
Please if anyone knows how, please help.
You will need a statfull widget to call setState and make the UI update with the new value stored in your qty variable. (I'am assuming that you are not using any state managment).
I wrote a possible solution for what you need.
Let look into some considerations:
Text will show whatever is in the qty as long we call setState after (or do it inside) we change the value of qty.
You need some widget to detect your tap. If you want to the text be 'clicable' then it should be wraped inside that widget.
The onTap/onPress call back of that widget should show a new widget. For this you can use the already made showDialog() and pass it a Dialog Widget. in here you will put your ui for that.
In some point of that UI you need to introduce the new value. So you can use a simple TextField that will save the introduced value, where you can assign it to qty, without forgetting to call setState! Note that it deal with strings, so you neet to do an int.parse() ou double.parse accordingly to you qty var type.
And I think that's it.
The could be other ways of doing it. This is a good and simple approach for your need.
I wrote a piece of code to help or somelse how is trying to do it:
InkWell(
// can be gesture detector, button, etc
onTap: () => showDialog(
context: context,
builder: (context) => Dialog(
child: Container(
color:
Colors.white60, // change it accordingly to you
height: 80, // change it accordingly to you
width: 200, // change it accordingly to you
child: Column(
children: [
const Text('Change your value here'),
TextField(
decoration:
InputDecoration(hintText: qty.toString()),
onChanged: (insertValue) => setState(() {
qty = int.parse(insertValue);
}),
// you can use other callBack function (like onComplete,
// onSaved), wich is more eficient than calling setState eveytime,
// but you have to do the needed adtaptions. Like onSave
// needs a key to call the save function. is easy just google it.
),
],
)),
)),
child: Text(
"${qty}",
),
),
What you are probably looking is a DropdownButton.
You would have something like this:
int qty = 1;
List<int> listOfValues = [1,2,3,4];
and then in your column you would have
DropdownButton<int>(
// This are the list of items that will appear in your dropdown menu.
// items is all the options you want your users to be able to select from,
// and it take a list of `DropdownMenuItem`. So instead of creating a `DropdownMenuItem`
// for each of the items in `listOfValues`, we iterate through it and return
// a `DropdownMenuItem`
items: listOfValues
.map((item) => DropdownMenuItem<int>(
value: item,
child: Text('$item'),
))
.toList(),
value: qty,
onChanged: (value) {
if (value != null) {
setState(() {
qty = value;
});
}
},
),
For more information on DropDownButton, check the following links:
https://api.flutter.dev/flutter/material/DropdownButton-class.html
https://www.youtube.com/watch?v=K8Y7sWZ7Q3s
Note: In a scenario where you want to increase the quantity of an item, like in a shopping cart, maybe having a button increment qty by 1 would be better.
I have a single string which contains steps of how to do certain thing. Now I am able to get each step but I am using nested splits so it is getting very long so how can I use loop on it.
code
Column(
children: [
Text(
'${asset.lockoutApplicationProcess.split('2.')[0]}',
),
Text(
'2.${asset.lockoutApplicationProcess.split('2.')[1].split('3.')[0]}',
),
Text(
'3.${asset.lockoutApplicationProcess.split('2.')[1].split('3.')[1].split('4.')[0]}',
),
Text(
'4.${asset.lockoutApplicationProcess.split('2.')[1].split('3.')[1].split('4.')[1].split('5.')[0]}',
),
Text(
'5.${asset.lockoutApplicationProcess.split('2.')[1].split('3.')[1].split('4.')[1].split('5.')[1]}',
),
],
),
the string,
"1. Notify affected personnel. 2. Properly shut down machine. 3. Isolate all energy sources. 4. Apply lockout devices, locks, & tags. 5. Verify total de-energization of all sources."
so How can I use loop to reduce the code?
How about this?
// Collect all the data in an indexed list, skipping the first (empty) element
final splitData = asset.lockoutApplicationProcess.split(RegExp(r"\d\.")).asMap().entries.skip(1);
// Create a Text Widget for each entry, prefixed with the index number
final textWidgets = splitData.map((entry) => Text("${entry.key}. ${entry.value}"));
// Render the Text widgets in a column
return Column(children: textWidgets.toList());
Note that this will only work for single digits (1-9). If the list could potentially grow beyond 9, you should edit the RegExp to take this into account. For example, the regex RegExp(r"\d+\.") will also catch numbers with more than one digit.
The most amazing part of Flutter is that every widget is an object. But, when I try to make an object from a Row and add a List of widgets to its children, I get this error on runtime:
Cannot add to an unmodifiable list
I'm currently creating my Row like this:
Row rowStar = Row();
rowStar.children.addAll(rowChildren);
Is this wrong? If so, what's the correct way?
1. Why it won't work
If all you want to do is add a list to a Row, you should create the list and then initialize the Row with it. You cannot add the list afterwards because Flutter's widgets typically have final fields/properties, which are not modifiable at runtime.
2. The Flutter Way
However, what you could do to dynamically modify the list is pass a function that builds your list internally at runtime. And that's exactly what the ListView.builder() constructor of ListView does.
For example, this the docs example for dynamically creating a List of Containers based on the the Lists entries and colorCodes.
final List<String> entries = <String>['A', 'B', 'C'];
final List<int> colorCodes = <int>[600, 500, 100];
ListView.builder(
padding: const EdgeInsets.all(8),
itemCount: entries.length,
itemBuilder: (BuildContext context, int index) {
return Container(
height: 50,
color: Colors.amber[colorCodes[index]],
child: Center(child: Text('Entry ${entries[index]}')),
);
}
);
You could add a button to .add, say, the letter D and the colorCode 400, to the respective Lists, and Flutter would render the respective Containers dynamically on the screen.
I'm not sure if I get it right, but what you're trying to do doesn't make sense. If you want to reuse a Row with widgets and information you can just build it once and save the whole thing as widget.
Try it like this:
Build a Row.
Create widgets for your children.
Display them as widgets of the row.
Row(
children: <Widget>[
customWidget1(),
customWidget2(),
customWidget3(),
],
)
Your custom widgets are the content of the Row then.
Hope it helps.
In my case, I can make a Row from a previously created List<Widget>:
Row calculateListOfStars(ranking) {
final List<Widget> rowList = [];
rowList.addAll([
// make my custom widget
]);
final Row rowStars = Row(
children: rowList,
);
return rowStars;
}
Use Listview:
ListView(
children: rowStar((data) {
return ListTile();
}).toList();
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!