Flutter - Replace a subsstring with a Widget - flutter

I am building an application with flutter, but I am having a bit of trouble with one of my widgets. I am getting a JSON response from an API endpoint in order to build the comments on posts, but I need to be able to take part of a string and wrap it in a GestureDetector, in order to handle "# mentions".
For example: I have the string hey there #MattChris how are you? I need to be able to wrap the #MattChris in a GestureDetector.
At the moment I parse the incoming string and provide a list with each space-separated word from the actual comment. Like so:
List<Widget> comment = new List();
outer: for (String word in json['content'].toString().split(" ")) {
if (word != null && word.isNotEmpty) {
if (word.startsWith('#')) {
comment.add(GestureDetector(
onTap: goToProfile,
child: Text(word + ' ')
);
} else {
comment.add(Text(word + ' '));
}
}
}
Only issue now is that's a lot of extra elements taking up memory, and a difficulty with ensuring that the text wraps in the way I expect. I've seen the answer here, but I'm not sure how to ensure that text wraps as if it were one string in a Text widget.

I was able to come to a working solution. Reading from the implementation I liked to again, and looking in the comments, I decided to use a recursive function:
List<TextSpan> _mentionParser(String message, Iterable<dynamic> mentions) {
if (message == null || message.isEmpty) // Don't return anything if there is no message.
return [];
for (Map<String, dynamic> mention in mentions) { // Loop through the list of names to replace
if (message.contains("#${mention['username']}")) { // If the message contains the name to replace
List<TextSpan> _children = [];
String preUsernameMessage = message.substring(0, message.indexOf("#${mention['username']}")).trimLeft(); // Get everything before the mention
if (preUsernameMessage != null && preUsernameMessage.isNotEmpty)
_children.add(TextSpan(children: _mentionParser(preUsernameMessage, mentions))); // if it isn't empty, recurse and add to the list
_children.add( // Always add the display name to the list
TextSpan(
text: "${mention['display_name']}".trim(),
style: TextStyle(color: Color(0xff2e6da4)),
recognizer: TapGestureRecognizer()
..onTap = () => {gotoProfile(json['username'])}
)
);
String postUsernameMessage = message.substring(message.indexOf("#${mention['username']}") + "#${mention['username']}".length, message.length).trimRight(); // Get everything after the mention
if (postUsernameMessage != null && postUsernameMessage.isNotEmpty) // If it isn't empty, recurse and add it to the list
_children.add(TextSpan(children: _mentionParser(postUsernameMessage, mentions)));
return _children; // return the constructed list
}
}
return [TextSpan(text: message)]; // If the string didn't contain any of the strings to replace, then just return the message as passed.
}
Then I just call this as the children variable on a TextSpan inside Text.rich. It took some time, but I was able to get the implementation working!

public String method(String str) {
if (str != null && str.length() > 0 && str.charAt(str.length() - 1) == 'x') {
str = str.substring(0, str.length() - 1);
}
return str; // to remove last character
}

Related

value of my List is changing when i delete text from TextField Flutter

i have a list that i want to search in (I remove the values that don't match my search), I write the search word in a TextField and save the initial list in order to show it again when the user deletes the search word (or want to search for another thing) .
i can save the list in a variable but the issue is when I start deleting the search word the value of my initial list becomes the same that the list I show for the search !
onChanged: (val) {
//i save the list before doing any change here
if (_initList == null) _initList = myList;
// this part works fine
if (val.length > 2)
setState(() {
myList.removeWhere((element) => !element
.getName()
.toLowerCase()
.contains(val.toLowerCase()));
});
// when i’m in the else, when i try to print _initList value i find that it’s the same as myList
// the value of _initList is not changing anywhere else in the code
else {
setState(() {
myList = _initList;
});
}
},
when you do this
if (_initList == null) _initList = myList;
both variables will point to the same list. changes to one list will also change the other, because they are in fact the same list. What you want to do is safe a copy of the list in it. You can do it by calling toList() on it, like
if (_initList == null) _initList = myList.toList();

How to display only special parts in a list FLUTTER

I'm very new to flutter and I wondered how I can make something like that:
I want loop trough a List of Items and make a Widget for all of them. I do this with a for-block instead of a listView.builder, because so it isn't a list. But I want to display for example every Item which has "valid" as value for worth in a different block as them, which has "invalid" as value.
I thought I could do this like that:
Text("Valid"),
for (ValuePair item in items)
if (item.worth == "valid"){
return myOwnView(item);
},
Text("Invalid"),
for (ValuePair item in items)
if (item.worth == "invalid"){
return myOwnView(item);
};
What could I do instead of that?
First solution (preferable):
I would filter the values in the list first, than pass it to your own builder like that:
List<YourItemClass> yourUnsortedList = [...];
List<YourItemClass> yourSortedList = [...];
for (var I = 0; I < yourUnsortedList.length; i++) {
if (yourUnsortedList.worth == 'valid') {
yourSortedList.add(yourUnsortedList[i])
}
}
next - just build this list.
Second solution (bad one):
Inside your builder place if statement on top, and if you need nothing to be built, return empty container, like that:
List<YourItemClass> yourUnsortedList = [...];
Listview.builder(
itemCount: yourUnsortedList.length;
itemBuilder: (context, idx) {
if (yourUnsortedList[idx].worth == 'valid') {
return YourOwnItem(item);
} else {
return Container();
}
}
)
Try making two lists of type Widget, one for valid and the other for invalid.
So, we have:
List<Widget> validChildren = List.empty(growable=true);
List<Widget> invalidChildren = List.empty(growable=true);
then use for loop to add the children:
for(Valuepair item in items)
if(item.worth == "valid")
validChildren.add(MyOwnView(item));
else
invalidChildren.add(MyOwnView(item));
you can add these lists as children of a column or a row.
You can give continue; in place of invalidChildren.add() if you don't want that list.

How to replace text with image(is it possible in flutter)

var replaced = text.replaceAll(regExp, '*');
Instead of the asterisk mark can we replace this with actual image..
Ok. So you have bunch of emojis stored locally. and you have to replace a text with emoji.
I don't have a perfect answer. but how you can achieve a path that i can show.
Using a map. as you are suppose to change the text for an image there must be a pair. so you can define a map for it. a key will be a text and the value will be the path of the emoji. considering your folder as a images and format is png.
Map<String, String> myData = {"like" : "images/like.png", "time":
"images/time.png"};
and so on. Like this you can create a full map of it. if it's limited like 30 -40 records that you will do manually.
Now you can perform operations on that. Suppose you have String value = " I like your shirt!";
so we need to scan the string and then search every word inside tha map and replace if you found it.
the code looks like this
void main() {
//your value that you are going to scan
String str = " I like your shirt";
//Defining the map
Map<String, String> data = {"like" : "images/like.png", "shirt" :
"images/shirt.png", "happy" : "images/happy.png"};
//function that will do the changes and display
void replaceTextWithEmojis(String value, Map<String, String> data){
//splitting the value into the array or list of items so we can iterate easily
List<String> inputvalues = str.split(" ");
//looping through the text
for (int i =0; i<inputvalues.length; i++){
//checking where that text is present in the emojis map or not.
if(data.containsKey(inputvalues[i])){
//this will replace the text with the path for that key
inputvalues[i] = data[inputvalues[i]];
}
} //end of the for loop
print(inputvalues);
}
replaceTextWithEmojis(str, data);
}
This will replace with the desired values. But You want to display a image between the text. That's what something difficult. I don't think you can do it in flutter. Or it will be like you need to identify the position of the replaceable text and then break the string and store it in other variable. append the image at the breakpoint. so at the place where we are replacing with the map you can add a widget(child ImageProvider(inputvalue[i]));
I would suggest you to use unicode for emojis.
you can use the package for unicode emjois : https://pub.dev/packages/emojis
Also they will give you better performance.
/// Replace an emoticon or any string token with an image having a tooltip and Help cursor.
static List<Widget> replaceEmoji(final String text, final String fromToken, final String toImgTooltip, final String toImg) {
Widget _getImage(final String path, final String tooltip) => MouseRegion(
cursor: SystemMouseCursors.help,
child: Tooltip(message: tooltip, child: Image.asset(path)),
);
if (!text.contains(fromToken)) {
return [Text(text)];
}
final result = <Widget>[];
var buffer = text;
var n = -1;
while (true) {
n = buffer.indexOf(fromToken);
if (n != 0) {
result.add(Text(buffer.substring(0, n)));
}
result.add(_getImage(toImg, toImgTooltip));
buffer = buffer.substring(n + fromToken.length);
if (buffer.isEmpty) {
break;
}
}
return result;
}
You use it as follows:
...
Wrap(
children: replaceEmoji('my :) face - :):)', ':)', 'smile', 'assets/images/emoji/smile.png')
)
...

Flutter - stuck on multiple google sheets calls

New to flutter and need help.
I'm using google sheets as a database. Supposed to use multiple sheets each with different sheetId.
I have 2 function to get the data:
getChildSchedule - to get data from one specific sheet
getAllSchedule - to get data from multiple sheets.
In the widget I'm checking if I'm supposed to display data from a particular sheet or from all the sheets and call the appropriate func. When I'm calling getChildSchedule from the widget all works perfectly and it shows the data.
But when I need the getAllSchedule it gets stuck. It doesn't
stop running but seems as if it's in an infinite loop though there is no such loop to get stuck on.
From the prints and the tracking I did, it calls on the getChild with index 0 but never returns from it - though the child data is being printed inside getChild.
What am I doing wrong here?
Future<List<Lesson>> getChildSchedule(int childId) async {
print('in getChild: child $childId: ${ChildrenManager.children[childId].spreadsheetId}');
spreadsheetId = ChildrenManager.children[childId].spreadsheetId;
await init();
final lessons = await _scheduleSheet.values.allRows(fromRow: 2);
print('in getChild: child $childId lessons: $lessons');
return List.generate(
lessons.length,
(index) => Lesson(
weekDay: lessons[index][0],
startTime: double.tryParse(lessons[index][1] ?? ''),
endTime: double.tryParse(lessons[index][2] ?? ''),
grade: ChildrenManager.children[childId].grade,
teacher: lessons[index][3],
header: lessons[index][4],
description: lessons[index][5],
zoomLink: Uri.tryParse(lessons[index][6] ?? ''),
meetingID: lessons[index][7],
meetingCode: lessons[index][8],
supplies: lessons[index][9],
assignment: Uri.tryParse(lessons[index][10] ?? ''),
),
);
}
Future<List<Lesson>> getAllSchedule() async {
List<List<Lesson>> schedules = List<List<Lesson>>();
for (int i = 0; i < ChildrenManager.children.length; i++) {
print('in getAll schedules: $i');
schedules[i] = await getChildSchedule(i);
print('in getAll schedules: got $i child'); //this never gets printed
}
print('in getAll schedules: $schedules');
List<Lesson> schedule = List<Lesson>();
for (List<Lesson> sc in schedules) {
schedule.addAll(sc);
}
schedule.sort((a, b) {
int result = a.startTime.compareTo(b.startTime);
if (result == 0) {
return a.endTime.compareTo(b.endTime);
}
return result;
});
return schedule;
}
I think the issue here was because of repeated calls that changed the sheet id while the previous was still running.
I've moved the getAll to another class and called it with a new manager object(that contains the getChild) for each child and it solved the issue.

Flutter for loop containing .then statement completing first half then second half

I have a list called wastedProducts which I want to iterate through and create a new list, wastedProductsSet based on each item in the original list.
This is the code I have to do that:
for (var productHeld in wastedProducts) {
if (wastedProductsSetNames.contains(productHeld.masterCat)) {
print("duplicate ${productHeld.masterCat}");
} else {
_wastedCount = wastedProducts
.where((p) => p.masterCat == productHeld.masterCat)
.fold(0,(amountWasted, product) => amountWasted + product.amountWasted);
_masterCat = Firestore.instance
.collection('masterCategories')
.document(productHeld.masterCat);
// repeats the above for each item before completing the below for each item
_masterCat.get().then(
(value) {
baseUnit = value.data['baseUnit'];
if (baseUnit == null) {
baseUnit = '';
} else {
baseUnit = value.data['baseUnit'];
}
wastedProductsSet.add(
WastedProduct(
productName: productHeld.masterCat,
wastedCount: _wastedCount.toInt(),
baseUnit: baseUnit,
),
);
wastedProductsSetNames.add(productHeld.masterCat);
},
);
}
}
Based on the print statements, I can see it is completing the code up to the _masterCat.get().then( line and doing that for each item in wastedProducts, then completing the code below _masterCat.get().then( for each item.
I assume it must have something to do with the asynchronous nature of the .then but cannot work out what the problem is.
I originally was using .forEach instead of for (var productHeld in wastedProducts) but changed based on the answer in this post My async call is returning before list is populated in forEach loop.