How to handle lists initial building in Getx - flutter

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.

Related

why the dart function did not return the sub function result

I am now using this functon to fetch new music in flutter:
class QuietPlayQueueInterceptor extends PlayQueueInterceptor {
#override
Future<List<MusicMetadata>> fetchMoreMusic(BackgroundPlayQueue queue, PlayMode playMode) async {
if (queue.queueId == kFmPlayQueueId) {
final musics = await (neteaseRepository!.getPersonalFmMusicsFromQueue() as FutureOr<List<Music>>);
final musicListExt= musics.toMetadataList();
return musicListExt;
}
return super.fetchMoreMusic(queue, playMode);
}
}
and this is the function getPersonalFmMusicsFromQueue define:
Future<List<Music>?> getPersonalFmMusicsFromQueue() async {
if(fmPlayQueue.isEmpty){
return getPersonalFmMusics();
}else{
final Music music = fmPlayQueue.first;
final List<Music> musics = List.empty(growable: true);
musics.add(music);
return Future.value(musics);
}
}
what makes me confusing is that the getPersonalFmMusicsFromQueue function did not return any result. I make a breakpoint on the line final musicListExt= musics.toMetadataList(); but did not hited. The console is no error output. where am I doing wrong? what should I do to fix this problem?
getPersonalFmMusics looks asynchronous? Perhaps you're not awaiting
Future<List<Music>?> getPersonalFmMusicsFromQueue() async {
if(fmPlayQueue.isEmpty){
return await getPersonalFmMusics();
}
// ...
I would also advise against casting unless you're sure you need it. Instead, have the return type of getPersonalFmMusicsFromQueue return a FutureOr
(neteaseRepository!.getPersonalFmMusicsFromQueue() as FutureOr<List<Music>>); // Remove FutureOr<List<Music>>
// and make the function signature instead look like this:
FutureOr<List<Music>> getPersonalFmMusicsFromQueue(); // Force unwrapping with a `!` but also throwing proper exceptions when null.
The reason being is that casting usually hides errors the compiler would otherwise be warning you about.
Another idea I have if the above isn't the issue is the .first call.
final Music music = fmPlayQueue.first;
If this is a first getter on a stream then that need to be awaited and it likely isn't working because it's just indefinitely waiting for the stream to output a value.

Flutter : How to use setter to reset value

I have variable called to totalQuantity in provider:
get totalQuantity => total_quantity();
total_quantity() {
var totalQty = 0;
for (var x in myCart) {
totalQty += (x.quantity);
}
return totalQty;
}
I use it in the app bar:
child: Text('${prod.totalQuantity}',
I have a logout function I want when I pressed on it to reset totalQuantity, I guess using setter for that in provider, but I don't know how to do that.
IconButton(
onPressed: () {
prod.clear_myCart();
loginProd.log_out();
// ----------------- I want to reset it here
},
I found my mistake ,I forgot to add listen notifier
void clear_myCart() {
myCart.clear();
notifyListeners();
}
after I add it ,it works fine
I understand that you want to return totalQuantity to the original (empty) value, so lets have a look at where it gets its value from:
Your total_quantity() function depends on one variable, myCart.
So, if you clear myCart in prod.clear_myCart();, the quantity should also be updated accordingly.
Now, what your code does not show is how the value change of myCart is being handled in your code;
I am speculating here because your code snippets don't provide enough information, but your ChangeNotifier might just not call notifyListeners() when you call prod.clear_myCart(); (See https://flutter.dev/docs/development/data-and-backend/state-mgmt/simple).

StreamBuilder's snapshot doesn't have data despite sending it through Stream

TL;DR: Values being added to StreamSink are for some unknown reason being overridden by the respected StreamBuilder's initialValue
From what it appears, my issue is similar to this issue on Github, but the only difference is that instead of getting a single value, I see two statements on the console log, one with the correct value which was added to the stream which was immediately followed by the initialValue which was passed to the stream builder.
In my case, I was using generic_bloc_provider
Elaboration
I have a bloc udhariBloc.dart(line 99) which listens to a collection reference and adds the value to some sinks.
void _fetchUdhari() {
Firestore.instance
.collection("Users 3.0")
.document("data")
.collection("udhari")
.where("participants", arrayContains: userBloc.phoneNumber)
.where("firstPartyDeleted", isEqualTo: false)
.snapshots()
.listen((QuerySnapshot snapshot) {
//Initialized these to zero because their
//value must be recalculated upon every change.
//These variables were initialized to zero(their default value) when declared.
_udhariList = List<UdhariClass>();
_totalDebit = 0;
_totalCredit = 0;
for (int i = 0; i < snapshot.documents.length; i++) {
_udhariList.add(UdhariClass.fromSnapshot(
snapshot: snapshot.documents[i],
userBloc: userBloc,
));
}
// Remove the udhari records where the participant is secondParty
// and secondParty has already deleted the record
_udhariList.removeWhere((UdhariClass udhari) {
return ((udhari.partyType == PartyType.SecondParty) &&
(udhari.secondPartyDeleted == true));
});
for (int i = 0; i < _udhariList.length; i++) {
if (_udhariList[i].udhariType == Udhari.Borrowed) {
_totalDebit += _udhariList[i].amount;
} else {
_totalCredit += _udhariList[i].amount;
}
}
//The correct values are calculated correctly,
//printed and added to respective sinks as well
print("Debit: $_totalDebit");
print("Credit: $_totalCredit");
print("List: ${_udhariList[0].context}");
_totalDebitSink.add(_totalDebit);
_totalCreditSink.add(_totalCredit);
_udhariListSink.add(_udhariList);
});
}
and here are the streams and their controllers
/// Stream controller for displaying total debit on dashboard
StreamController<double> _totalDebitController =
StreamController<double>.broadcast();
Stream<double> get totalDebitStream => _totalDebitController.stream;
StreamSink<double> get _totalDebitSink => _totalDebitController.sink;
/// Stream controller for displaying list of udhari
StreamController<List<UdhariClass>> _udhariListController =
StreamController<List<UdhariClass>>.broadcast();
Stream<List<UdhariClass>> get udhariListStream =>
_udhariListController.stream;
StreamSink<List<UdhariClass>> get _udhariListSink =>
_udhariListController.sink;
/// Stream controller for displaying total credit on dashboard
StreamController<double> _totalCreditController =
StreamController<double>.broadcast();
Stream<double> get totalCreditStream => _totalDebitController.stream;
StreamSink<double> get _totalCreditSink => _totalDebitController.sink;
and this is my stream builder consuming the above streams
StreamBuilder<double>(
initialData: udhariBloc.getTotalDebit,
stream: udhariBloc.totalDebitStream,
builder: (BuildContext context,
AsyncSnapshot<double> snapshot) {
print(
"============INSIDE DEBIT STREAM BLDR============");
print("Conn State: ${snapshot.connectionState}");
print("Has Data: ${snapshot.hasData}");
print("Data: ${snapshot.data}");
print("Has Error: ${snapshot.hasError}");
print("Error: ${snapshot.error}\n\n");
return Text("₹${snapshot.data.floor()}",
style: _textStyleFooter);
},
),
These sinks are then later consumed in a streamBuilders inside Dashboard.dart(line 145). The problem is that even after adding data to respective sinks(in this case, _totalDebitSink), the values are not updated in the stream builder inside Dashboard class. For investigating further, I attached a listener to the _totalDebitStream inside the UdhariBloc's constructor
totalDebitStream.listen((onData) {
print("CURRENT DEBIT: $onData");
}, onError: (error) {
print("Error listening Debit :$error");
}, onDone: () {
print("Done listening to Debit values");
});
and every time there was a change in the value, I saw this log in the console.
CURRENT DEBIT: 100
CURRENT DEBIT: 0
============INSIDE DEBIT STREAM BLDR============
Conn State: ConnectionState.active
Has Data: true
Data: 0.0
Has Error: false
Error: null
Here, 100 was the updated value from Firestore and 0 was the initialValue which was provided to the StreamBuilder and also assigned to the variables _totalDebit, _totalCredit.
I used a similar technique in dashboardBloc.dart(line 88) and Dashboard.dart(line 212) and it works like a charm.
I haven't been able to find any solutions so far.
Finally, found the solution. Turns out it was a stupid fault on my part.
I accidentally mapped wrong streams and sinks to a wrong controller.
In this case,
Stream<double> get totalCreditStream => _totalDebitController.stream;
StreamSink<double> get _totalCreditSink => _totalDebitController.sink;
Here I accidentally mapped the totalCredtiStream with debitController's stream.
Also, thanks to #pskink for pointing it out.

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>