Flutter Firestore Pagination (add/remove items) - flutter

I'm struggling to get this done, after two days I decided to ask you guys for help.
I'm using Mobx as state management, the issue is related to adding/removing an item to/from the list, e.g. if I retrieve two queries of 5 items each, according to the limit and then remove an item from the first query the first item from the second query is duplicate and if I add a new item, the first item from the second query is hidden. I have also set a scrollListener to the ListView.builder to get the bottom of the list and call for more items.
Thanks in advance,
#override
Stream<QuerySnapshot> teste(DocumentSnapshot lastDoc) {
if (lastDoc == null) {
return firestore.collection('teste')
.orderBy('name')
.limit(5)
.snapshots();
} else {
return firestore.collection('teste')
.orderBy('name')
.limit(5)
.startAfterDocument(lastDoc)
.snapshots();
}
}
#observable
ObservableList<List<TesteModel>> allPagedResults = ObservableList<List<TesteModel>>();
#observable
ObservableList<TesteModel> listTeste = ObservableList<TesteModel>();
#observable
DocumentSnapshot lastDoc;
#observable
bool hasMoreItem;
#action
void teste() {
var _currentRequestIndex = allPagedResults.length;
primaryRepository.teste(lastDoc).listen((query) {
if (query.docs.isNotEmpty) {
var _query = query.docs.map((doc) => TesteModel.fromFirestore(doc))
.toList();
var _pageExists = _currentRequestIndex < allPagedResults.length;
if (_pageExists) allPagedResults[_currentRequestIndex] = _query;
else allPagedResults.add(_query);
listTeste = allPagedResults.fold<List<TesteModel>>(<TesteModel>[],
(initialValue, pageItems) => initialValue..addAll(pageItems)).asObservable();
if (_currentRequestIndex == allPagedResults.length - 1) lastDoc = query.docs.last;
hasMoreItem = _query.length == 5;
}
});
}

Any luck with this issue?
For adding new items and having multiple pages you could do something like this:
if (allPagedResults.contains(allPagedResults[0].last) == false) {
allPagedResults.last.add(allPagedResults[0].last);
allPagedResults.last.remove(allPagedResults[0].first);
}

Related

Flutter: filter a List<Map<String, dynamic>> to only contain certain items

I have a list of maps where one of the fields in the map is a boolean called "completed". I want to be able to filter the map so that I only get the items for which "completed" is true or false. My current code is the following:
try {
String? email = FirebaseAuth.instance.currentUser!.email;
for (int i = startingIndex; i < endingIndex; ++i) {
var doc = await FirebaseFirestore.instance
.collection(email!)
.doc("goals")
.collection(Utils.goalsCategoriesDropdownItems[i])
.get();
if (doc.size > 0) {
allData.addAll(doc.docs.map((doc) => doc.data()).toList());
}
}
allData.sort(((a, b) {
Timestamp one = a["dueDate"];
Timestamp two = b["dueDate"];
return one.compareTo(two);
}));
return allData;
}
but I don't want to return allData, I want to filter and see if all the items are true or false, so I want something like this:
if (completed == true) {
allData.map((e) => e["completed"] == true);
} else {
allData.map((e) => e["completed"] == false);
}
return allData;
It is not entirely clear what your expected outcome is, but here are two possibilities:
a) If you want to check if every Map in your List has the key completed with a value of true you can use every:
return allData.every((e) => e["completed"] === true)
b) If you want to have a list containing only Maps which contain the key completed with a value of true you can use where:
return allData.where((e) => e["completed"] === true)

Is this approach is an Anti-Pattern? - Flutter

strong textI'm implementing a contacts list search bar, where the user can search & select some contacts, but I need the selected contacts to be shown as selected while the user is still searching.
I achieved this by modifying the original list and the duplicate list which used in the searching process at the same time.
is this an Anti-Pattern and is there a better way to do it?
Here's what I'm doing with the search query:
void searchContacts([String? name]) {
if (name == null || name.isEmpty) {
searchedList.clear();
addAllContactsToSearchList();
return;
} else {
searchedList.clear();
originalContactsList!.forEach((contact) {
if (contact.name.toLowerCase().contains(name.toLowerCase())) {
searchedList.clear();
searchedList.add(contact);
return;
} else {
return;
}
});
return;
}
}
and here's the code for selecting a contact:
void _onChange(bool value, int index) {
final selectedContact = searchedList[index].copyWith(isSelected: value);
searchedList.removeAt(index);
setState(() {
searchedList.insert(index, selectedContact);
notifier.originalContactsList = notifier.originalContactsList!.map((e) {
if (e.number == selectedContact.number) {
return selectedContact;
} else {
return e;
}
}).toList();
});}
This is expected behavior: gif
Some assumptions I'm making is that (1) each contact has a unique identifier that isn't just a name, and (2) you don't need the selected contacts to be shown in the same list as a search with a different query (if you search H, select Hasan, then search Ha, you expect it to still show up as selected on the next page, but if you search He, Hasan shouldn't shouldn't be on that next list.
The best way to do this is to have one constant list, and one list with results:
Set<String> selectedContactIds = {};
List<Contact> searchedList = [];
void _onChange(bool value, int index) {
final clickedContact = searchedList[index];
bool itemAlreadySelected = selectedContactIds.contains(clickedContact.userID);
setState({
if(itemAlreadySelected) {
selectedContactIds.remove(clickedContact.userID);
} else {
selectedContactIds.add(clickedContact.userID);
}
});
}
Now once you set state, your ListView should be updating the appearance of selected objects by checking if it's selected the same way that the _onChange function is checking if the item was already selected, which was:
bool itemAlreadySelected = selectedContactIds.contains(clickedContact.userID);
And this way, you don't have a whole class for contacts with a dedicated isSelected member. You wouldn't want that anyways because you're only selecting contacts for very specific case by case uses. Hopefully this helps!

How to join two firebase queries in the same Stream?

I am using Streambuilder to display a list of tasks. For this I want to join two queries in Firebase: The tasks that have been completed in the last 7 days (alList1) and all the pending tasks (alList2)
The way I do the query is as follows:
Stream<List<AlarmaModel>> cargarAlarmasStream(int fechaMinima) {
final List<AlarmaModel> alarmaListVacia = [];
Query resp = db.child('alarmas').orderByChild('fechaCompleta').startAt(fechaMinima);
final streamPublish1 = resp.onValue.map((event) {
if(event.snapshot.value != null) {
final alList1 = Map<String,dynamic>.from(event.snapshot.value).entries.map((e) {
AlarmaModel alTemp = new AlarmaModel();
alTemp = AlarmaModel.fromJson(Map<String,dynamic>.from(e.value));
alTemp.idAlarma = e.key;
return alTemp;
}).toList();
return alList1;
} else return alarmaListVacia;
});
Query resp2 = db.child('alarmas').orderByChild('completa').equalTo(false);
final streamPublish2 = resp2.onValue.map((event) {
if(event.snapshot.value != null) {
final alList2 = Map<String,dynamic>.from(event.snapshot.value).entries.map((e) {
AlarmaModel alTemp2 = new AlarmaModel();
alTemp2 = AlarmaModel.fromJson(Map<String,dynamic>.from(e.value));
alTemp2.idAlarma = e.key;
return alTemp2;
}).toList();
return alList2;
} else return alarmaListVacia;
});
return streamPublish1;
}
However, at the time of displaying it on the screen with the StreamBuilder, it is only showing me alList1, which is the one contained in streamPublish1. How can I do in the return to output the result of StreamPublish1 added to StreamPublish2?
UPDATE
I was testing the solution in Stackoverflow 36571924 as follows:
Stream<List<AlarmaModel>> pubStream = StreamGroup.merge([streamPublish1, streamPublish2]);
return pubStream
It was close, but it didn't work. It only retrieves the last list (alList2) and not the combination of both.

How to map each item from observable to another one that comes from async function?

I want to
1.map item from observable to another one if it has already saved in database.
2.otherwise, use it as it is.
and keep their order in result.
Saved item has some property like tag, and item from observable is 'raw', it doesn't have any property.
I wrote code like this and run testMethod.
class Item {
final String key;
String tag;
Item(this.key);
#override
String toString() {
return ('key:$key,tag:$tag');
}
}
class Sample {
///this will generate observable with 'raw' items.
static Observable<Item> getItems() {
return Observable.range(1, 5).map((index) => Item(index.toString()));
}
///this will find saved item from repository if it exists.
static Future<Item> findItemByKey(String key) async {
//simulate database search
await Future.delayed(Duration(seconds: 1));
if (key == '1' || key == '4') {
final item = Item(key)..tag = 'saved';
return item;
} else
return null;
}
static void testMethod() {
getItems().map((item) async {
final savedItem = await findItemByKey(item.key);
if (savedItem == null) {
print('not saved:$item');
return item;
} else {
print('saved:$savedItem');
return savedItem;
}
}).listen((item) {});
}
The result is not expected one.
expected:
saved:key:1,tag:saved
not saved:key:2,tag:null
not saved:key:3,tag:null
saved:key:4,tag:saved
not saved:key:5,tag:null
actual:
not saved:key:2,tag:null
not saved:key:3,tag:null
not saved:key:5,tag:null
saved:key:1,tag:saved
saved:key:4,tag:saved
How to keep their order in result?
I answer myself to close this question.
According to pskink's comment, use asyncMap or concatMap solve my problem. Thanks!!
below is new implementation of testMethod.
asyncMap version:
getItems().asyncMap((item) {
final savedItem = findItemByKey(item.key);
if (savedItem != null)
return savedItem;
else
return Future.value(item);
}).listen(print);
concatMap version:
getItems().concatMap((item) {
final savedItem = findItemByKey(item.key);
if (savedItem != null)
return Observable.fromFuture(savedItem);
else
return Observable.just(item);
}).listen(print);

How to assign values from storage to a variable in ionic 3

I tried few methods to assign values to variable but could succeed please help.
Method 1:-
getData() {
return this.storage.get('products')
.then(res => {
return this.cart = res;
});;
}
Console.log shows undefined
Method 2:-
cart = [];
getData() {
return this.storage.get('products')
.then(res => {
return this.cart.push(res);
});;
}
Output :
How can i achieve
Cart variable as directly the array list from 0, 1,? [as shown in picture]
Found the Solution
//set Cart Storage
this.storage.get('products').then((data) => {
if (data == null) {
data = [];
}
this.cart = data;//re-initialize the items array equal to storage value
this.cart.push(this.cartItem());
this.storage.set('products', this.cart);
console.log("Cart" + this.cart);
});
On Another Page
// Retrieving data
public getData() {
return this.storage.get('products')
.then(res => {
this.cart = [];
this.cart = res;
console.log(this.cart);
});
}
Try logging the value of the res parameter in the console. From there, you can assign the value of cart to the correct property in the res object.