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

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.

Related

How to handle lists initial building in Getx

I'm making a reactive model with Getx on a product list, but when I start the list it comes with no value and causes an index error even though there are actually values ​​in the list it appears empty at first, which somehow gets fixed automatically. (this is inside a build of a statelesswidget)
return GetX<CartController>(
init: CartController(),
builder: (controller) {
try {
return Text(
"${StringConvert.toMoney(controller.totalByProduct[productId])}",
style: kSmallTextBold,
);
} catch (e) {
return const Text("Error...");
}
},
);
}
I did try catch to manage this, but the catch part doesn't show up;
this is relevant part of the controller
var totalByProduct = [].obs;
fetchTotal() {
List products = storage.read(StorageKeys.cartProducts);
double currentValue = 0.0;
List currentTotals = [];
for (var item in products) {
currentTotals.add(item['total'] * item['amount']);
currentValue += item['total'] * item['amount'];
}
total.value = currentValue;
totalByProduct.value = currentTotals;
}
I believe it's not the right way to do this, so what do I need to know to fix this correctly?
If helps this is the error:
With a method to read the storage (sharedPreferences) in async mode, with a FutureBuilder it was possible to correct the error, because in the initial state the list takes the value assigned explicitly. Even if it then receives the correct value, accessing the index in its initial state causes the error, this explains why even with the error it works.

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 - Replace a subsstring with a Widget

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
}

How to exclude ElementArrayFinder items that exists in another ElementArrayFinder in Protractor?

I would like to obtain menu-ui items that user is able to click.
Unfortunately, isEnabled method returns always true for all of my items.
That's why I try another approach. I noticed that disabled ones, always have 'ui-state-disabled' class. As a consequence, I'm able to get all disabled items, using following function:
function getDisabledMenuItems() {
return getCustomGrid().all(by.className('menu-ui')).all(by.className('ui-state-disabled')).all(by.className('menu-item-text'));
}
and then all menu items using following one:
function getAllMenuItems() {
return getCustomGrid().all(by.className('menu-ui')).all(by.className('menu-item-text'));
}
Now I would like to exclude items returned by getDisabledMenuItems from items returned by getAllMenuItems.
Question
What is the easiest way to exclude ElementArrayFinder items that exists in another ElementArrayFinder?
I'm trying to do that by means of filter method as follows:
const disabledText = getDisabledMenuItems().getText();
const allItems = getAllMenuItems();
allItems.filter(function(elem, index) {
return elem.getText().then(function(text) {
return disabledText.indexOf(text) < 0 ;
});
});
but my code does not work (indexOf does not exists on type Promise<string>).
I also wonder what is the easiest way to do that.
Because getDisabledMenuItems().getText() return a promise, you have to consume its eventual value in then():
const disabledText = getDisabledMenuItems().getText();
const allItems = getAllMenuItems();
const enableItems = allItems.filter(function(elem, index) {
return elem.getText().then(function(text) {
return disabledText.then(function(txts){
return txts.includes(text) === false;
// or use return txts.indexOf(text) < 0;
});
});
});

RxJs Observable with infinite scroll OR how to combine Observables

I have a table which uses infinite scroll to load more results and append them, when the user reaches the bottom of the page.
At the moment I have the following code:
var currentPage = 0;
var tableContent = Rx.Observable.empty();
function getHTTPDataPageObservable(pageNumber) {
return Rx.Observable.fromPromise($http(...));
}
function init() {
reset();
}
function reset() {
currentPage = 0;
tableContent = Rx.Observable.empty();
appendNextPage();
}
function appendNextPage() {
if(currentPage == 0) {
tableContent = getHTTPDataPageObservable(++currentPage)
.map(function(page) { return page.content; });
} else {
tableContent = tableContent.combineLatest(
getHTTPDataPageObservable(++currentPage)
.map(function(page) { return page.content; }),
function(o1, o2) {
return o1.concat(o2);
}
)
}
}
There's one major problem:
Everytime appendNextPage is called, I get a completely new Observable which then triggers all prior HTTP calls again and again.
A minor problem is, that this code is ugly and it looks like it's too much for such a simple use case.
Questions:
How to solve this problem in a nice way?
Is is possible to combine those Observables in a different way, without triggering the whole stack again and again?
You didn't include it but I'll assume that you have some way of detecting when the user reaches the bottom of the page. An event that you can use to trigger new loads. For the sake of this answer I'll say that you have defined it somewhere as:
const nextPage = fromEvent(page, 'nextpage');
What you really want to be doing is trying to map this to a stream of one directional flow rather than sort of using the stream as a mutable object. Thus:
const pageStream = nextPage.pipe(
//Always trigger the first page to load
startWith(0),
//Load these pages asynchronously, but keep them in order
concatMap(
(_, pageNum) => from($http(...)).pipe(pluck('content'))
),
//One option of how to join the pages together
scan((pages, p) => ([...pages, p]), [])
)
;
If you need reset functionality I would suggest that you also consider wrapping that whole stream to trigger the reset.
resetPages.pipe(
// Used for the "first" reset when the page first loads
startWith(0),
//Anytime there is a reset, restart the internal stream.
switchMapTo(
nextPage.pipe(
startWith(0),
concatMap(
(_, pageNum) => from($http(...)).pipe(pluck('content'))
),
scan((pages, p) => ([...pages, p]), [])
)
).subscribe(x => /*Render page content*/);
As you can see, by refactoring to nest the logic into streams we can remove the global state that was floating around before
You can use Subject and separate the problem you are solving into 2 observables. One is for scrolling events , and the other is for retrieving data. For example:
let scrollingSubject = new Rx.Subject();
let dataSubject = new Rx.Subject();
//store the data that has been received back from server to check if a page has been
// received previously
let dataList = [];
scrollingSubject.subscribe(function(page) {
dataSubject.onNext({
pageNumber: page,
pageData: [page + 10] // the data from the server
});
});
dataSubject.subscribe(function(data) {
console.log('Received data for page ' + data.pageNumber);
dataList.push(data);
});
//scroll to page 1
scrollingSubject.onNext(1);
//scroll to page 2
scrollingSubject.onNext(2);
//scroll to page 3
scrollingSubject.onNext(3);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.js"></script>