We have a Laravel + Flutter based platform.
When a search query is returned in our Flutter application, it prints the products on the screen by making an API call as follows.
api/foods?search=EXAMPLE;&searchFields=name:like;&limit:70;
Our search is sorted by name.
When I search for "EXAMPLE", I want a listing based on the name, followed by products with "EXAMPLE" in the description. So this query;
api/foods?search=EXAMPLE;&searchFields=description:like;&limit:70;
I mean, having these two queries done one after the other; How can I list it in the search results so that it lists the name first and then the description?
yani EXAMPLE aramasının sonucunda
önce "api/foods?search=EXAMPLE;&searchFields=name:like;&limit:70;" sorgusu,
altında da birleşik bir şekilde "api/foods?search=EXAMPLE;&searchFields=description:like;&limit:70;" sorgusunun yer almasını istiyorum.
Flutter Codes;
Future<Stream<Food>> searchFoods(String search) async {
Uri uri = Helper.getUri('api/foods');
Map<String, dynamic> _queryParams = {};
_queryParams['search'] = '$search';
_queryParams['searchFields'] = 'name:like;';
_queryParams['limit'] = '70';
uri = uri.replace(queryParameters: _queryParams);
try {
final client = new http.Client();
final streamedRest = await client.send(http.Request('get', uri));
return streamedRest.stream.transform(utf8.decoder).transform(json.decoder).map((data) => Helper.getData(data)).expand((data) => (data as List)).map((data) {
return Food.fromJSON(data);
});
} catch (e) {
print(CustomTrace(StackTrace.current, message: uri.toString()).toString());
return new Stream.value(new Food.fromJSON({}));
}
}
One option is to have 2 ListViews in the Column so that you can display results from 2 lists. The other option is to merge the list into one and then send it to your UI to display in single ListView.
Related
I have a Riverpod Streamprovider that manages how a number of different Firebase documents are presented to the user. The user can then access each document, make some changes and return to the list of their documents. Once they have made, changes the row for that document should have a tick showing. The only wrinkle is that these documents in a different collection, each with their own identifier. So its not as easy as just streaming a whole collection, my function needs to get the identifier for each item and then get a list of documents to send to the user.
I have the code so it 'just works' but what I can't work out is why updating the record works when all the code is inside the provider vs when the provider calls it a external code. For example this StreamProvider works as I want and updated documents are recognised
final outputStreamProvider = StreamProvider.autoDispose((ref) async* {
final List<itemModelTest> itemList = [];
final user = ref.watch(loggedInUserProvider);
final uid = ref.watch(authStateProvider).value!.uid;
for (String ident in user.value!.idents) {
# get each item by its own identifier
final item = FirebaseFirestore.instance
.collection('items')
.where("ident", isEqualTo: ident)
.limit(1)
.snapshots();
final result = await item.first;
final test = result.docs[0];
final itemItem = itemModelTest.fromFirebaseQuery(test, uid);
itemList.add(itemItem);
# Listen for changes in the items
item.listen((event) async {
dev.log('event changed');
for (var change in event.docChanges) {
if (change.type == DocumentChangeType.modified) {
itemModelTest updatedModel =
itemModelTest.fromFirebaseQuery(test, uid);
itemList
.removeWhere((element) => element.title == updatedModel.title);
itemList.add(updatedModel);
}
}
});
}
yield itemList;
});
But as you can see it contains a lot of logic that doesn't belong there and should be with my firebase database class. So I tried to split it so now in my firebase crud class I have almost identical code:
Stream<List<itemModelTest>> itemsToReviewStream(LoggedInUser user, String uid) async*{
final List<itemModelTest> itemList = [];
for (String ident in user.idents) {
final item = FirebaseFirestore.instance
.collection('items')
.where("ident", isEqualTo: ident)
.limit(1)
.snapshots();
final result = await item.first;
final test = result.docs[0];
final itemItem = itemModelTest.fromFirebaseQuery(test, uid);
itemList.add(itemItem);
item.listen((event) async {
dev.log('event changed ${event.docChanges.first.doc}');
for(var change in event.docChanges){
if(change.type == DocumentChangeType.modified){
itemModelTest updatedModel = itemModelTest.fromFirebaseQuery(test, uid);
itemList.removeWhere((element) => element.title == updatedModel.title);
itemList.add(updatedModel);
}
}
});
}yield itemList;
}
and my StreamProvider now looks like this
// Get a list of the currently logged in users papers to review
final testitemStreamProvider = StreamProvider.autoDispose((ref) {
final user = ref.watch(loggedInUserProvider).value;
final uid = ref.watch(authStateProvider).value!.uid;
return DataBase().itemsToReviewStream(user!, uid);
});
The only problem is using this second approach the updates to firebase don't trigger any updates to the ui so when the user returns to their list of documents they cant see which have been processed already. I have been round the houses trying to work out what I am doing wrong but cant see it.
Edit: just a quick edit in case it matters but this is for FlutterWeb not iOS or Android
I am leaving this in case anyone else has the same problem. The real problem with this project was that the database structure was not fit for purpose and a further restriction was to not duplicate data on the database.
The simplest solution (and if you happen to be reading this because you fixing a similar problem) is to make a copy of the documents the user is supposed to have access to in their own collection, this can then be streamed as an entire collection. Checking which documents have and have not been looked at by users was always going to have to be done via an admin account anyway, so it's not as though this would have incurred a penalty.
All the same to manage my particular data repo i ended up
1 make a simple provider to stream a single document
final getSinglePageProvider = StreamProvider.family((ref, String pageId){
return DataBase().getSinglePage(pageId);});
Then once you have a list of all the documents the user has access to make a provider that provides a list of providers above
final simpleOutputsStreamsProvier = StreamProvider((ref) async* {
final user = ref.watch(loggedInUserProvider);
final items = user.value!.items;
yield items.map((e) => ref.watch(getSinglePageProvider(e))).toList();
});
You can then use this in a consumer as normal, but it has to be 'consumed' twice. In my case, I watched the ListProvider in the build method of a ConsumerWidget. That gives you a list of StreamProviders for individual pages. Finally I used ListView to get each StreamProvide (listofProviders[index]) and unwrapped that with provider.when(...
I have no idea how brittle this approach will turn out to be however!
how I can show DeliveryBoys in a specific location, in my realtime database I have a value that I need to compare drivers with which is "City" I would like to have all DeliveryBoys that are in a specific city. How can I do that? Using flutter
Am only able to get all drivers without a conditional statement
**This is my Function that i want to modify **
retrieveOnlineDriversInformation(List onlineNearestDriversList) async {
DatabaseReference ref =
FirebaseDatabase.instance.ref().child("DeliveryBoys");
for (int i = 0; i < onlineNearestDriversList.length; i++) {
await ref
.child(onlineNearestDriversList[i].driverId.toString())
.once()
.then((dataSnapshot) {
var driverKeyInfo = dataSnapshot.snapshot.value;
dList.add(driverKeyInfo);
});
}
}
Database Structure
Based on your responses and as far as I can see, you don't need the loop where you have it. Therefore, I am going to ignore it and simply show you the code that will return the list of driver ids of all drivers for city 'Lusaka'.
Future<List<String>> retrieveOnlineDriversInformation() async {
final driverIds = <String>[];
DatabaseReference ref = FirebaseDatabase.instance.ref().child("drivers");
try {
await ref.orderByChild("city")
.equalTo("Lusaka")
.once()
.then(
(event) {
if (event.snapshot.value != null) {
final driverListData =
Map<String, dynamic>.from(event.snapshot.value! as Map);
driverListData.forEach((key, value) {
driverIds.add(key);
});
}
},
} on FirebaseException catch (error, stackTrace) {
// < Some code here to print database error details or otherwise deal with it >
} catch (error, stackTrace) {
// < Some code here to print other error details or otherwise deal with it >
}
return driverIds;
}
You could instead modify this to just return the Map 'driverListData' which contains each driver's id and associated driver data.
A couple of other points:
You don't stick to a standard naming convention for your database node and field names. I suggest that you always use lowerCamelCase as the standard (so for example, change DriverLicense to driverLicense) as it will match what you typically name the variables within the Flutter/Dart code.
You don't need to hold the driver id as a separate field in the driver node. It is a duplicate (and therefore wastes space on the database) of the driver record key, which is already accessible to you.
As you see, you should always wrap your database call logic in a try / catch clauses in order to handle any errors that the call to the database may return. There are specific exceptions that can be tested for with the on clause.
[This image contains the part of code wherEin I am getting the data from the website https://arprogramming.blogspot.com/ and storing the data in 3 separate lists. The link list is used to store the link of the blog so that I can use it as a link afterwards to redirect to the site from the app]2
These are all my imports
Thia is my pubspec.yaml file
This is the part of code where I am using the scraped data
THIS IS MY ERROR
Below is my main code
Future<void> _getDataFromWeb() async{
var uri =Uri.parse('https://arprogramming.blogspot.com/');
final response = await http.get(uri);
dom.Document document = parser.parse(response.body);
final elements = document.getElementsByClassName('entry-title');
final content = document.getElementsByClassName('entry-content');
final link1 = document.getElementsByClassName('entry-header blog-entry-header');
setState(() {
title = elements.map((elements)=> elements.getElementsByTagName("a")[0].innerHtml.toString()).toList();
post = content.map((content)=> content.getElementsByTagName("p")[0].innerHtml.toString()).toList();
link = link1.map((link1) async => link1.getElementsByTagName("a")[0].attributes['href']).cast<String>().toList();
});
}
You can not use the Future as string. Because these may not be available when you want to use. Use "then", it allows us to know when the async function ends and we have variable to use.
NOTE: Please be more careful further repositories. Community should upload their code as code snippet and error messages clearly. Welcome
http.get(uri).then((String response){
dom.Document document = parser.parse(response.body);
final elements = document.getElementsByClassName('entry-title');
final content = document.getElementsByClassName('entry-content');
final link1 = document.getElementsByClassName('entry-header blog-entry-header');
setState(() {
title = elements.map((elements)=> elements.getElementsByTagName("a")[0].innerHtml.toString()).toList();
post = content.map((content)=> content.getElementsByTagName("p")[0].innerHtml.toString()).toList();
link = link1.map((link1) async => link1.getElementsByTagName("a")[0].attributes['href']).cast<String>().toList();
});
});
I am trying to using the mobile_number plugin. Basically, I am fetching the SimCard number.
Here is the code.
String _mobileNumber = '';
List<SimCard> _simCard = <SimCard>[];
Future<void> initMobileNumberState() async {
if (!await MobileNumber.hasPhonePermission) {
await MobileNumber.requestPhonePermission;
return;
}
String mobileNumber = '';
// Platform messages may fail, so we use a try/catch PlatformException.
try {
mobileNumber = await MobileNumber.mobileNumber;
_simCard = await MobileNumber.getSimCards;
} on PlatformException catch (e) {
debugPrint("Failed to get mobile number because of '${e.message}'");
}
// If the widget was removed from the tree while the asynchronous platform
// message was in flight, we want to discard the reply rather than calling
// setState to update our non-existent appearance.
if (!mounted) return;
setState(() {
_mobileNumber = mobileNumber;
});
}
Widget fillCards() {
List<Widget> widgets = _simCard
.map((SimCard sim) => Text(
'Sim Card Number: (${sim.countryPhonePrefix}) - ${sim.number}\nCarrier Name: ${sim.carrierName}\nCountry Iso: ${sim.countryIso}\nDisplay Name: ${sim.displayName}\nSim Slot Index: ${sim.slotIndex}\n\n'))
.toList();
return Column(children: widgets);
}
This is working fine I can see the mobile number.
But now I need to search the number inside the list.
I checked some of the questions on SO to get it done but I am not good with lists.
I tried using some of the examples and comes up with below. But this is incorrect with so many errors.
var comparenumber = _simCard.map((SimCard sim).where((sim.number) => sim.number.toLowerCase().contains(mobile.toLowerCase()).toList();
If you want to search if the list has a number or part of a number (say the result would be true searching for 641 in a list that has a number 641-819-xxxx)
you can use this code
bool searchNumber(String number) {
return _simCard.where((element) => element.number.contains(number)).isNotEmpty;
}
if you want to search for an exact number match replace element.number.contains(number) with element.number == number as in dart == for strings compare the string value, not pointers as in Java for example.
So, what is where?
where is a function in the abstract class Iterable (and lists in dart are iterable) that take a predicate or a test function and returns an iterable that has only elements that match the predicate.
returned iterable can be collected to a list or you can use some other functions or properties directly on it like isNotEmpty in our case
I have a simple table from which I'm fetching a list of records. Once I get the records, then I have to get information online for each of the records. The code to do this is as follows:
class UserStationList {
List<UserStationListItem> _userStations = [];
final StreamController<HomeViewState> stateController;
UserStationList({#required this.stateController});
Future fetchUserStations() async {
stateController.add(HomeViewState.Busy);
//Fetch stations from table.
List<Map<String, dynamic>> stations =
await UserStationDatabase.instance.queryAllRows();
//If there are no stations, return and tell the screen to display the no data message.
if (stations.length == 0) {
stateController.add(HomeViewState.NoData);
return;
}
//Loop through each of the stations in the list and build the collection.
stations.forEach((station) async {
UserStationListItem newItem =
await _getPurpleAirSiteData(station['_id'], station['stationid']);
_userStations.add(newItem);
});
//When done, let the screen know.
stateController.add(HomeViewState.DataRetrieved);
}
Future<UserStationListItem> _getPurpleAirSiteData(
int id, int stationId) async {
var response = await http.get('$kURL$stationId');
var data = json.decode(response.body);
return UserStationListItem(
id: id, stationId: stationId, stationName: data['results'][0]['Label']);
}
}
The problem that I am running into involves the futures. I am processing the loop in a forEach and calling into the _getPurpleAirSiteData function for each. Within that function I have to await on the http.get to bring in the data. The stateController.add(HomeViewState.DataRetrieved) function is being called and the function exits long before the loop is completed. This is resulting in the data not being available when the StreamBuilder that I have receiving the data is run.
How can I set this up so that the loop runs completely before calling stateController.add?
I would change this part of code to a list of Futures and await-ing on it.
//Loop through each of the stations in the list and build the collection.
stations.forEach((station) async {
UserStationListItem newItem =
await _getPurpleAirSiteData(station['_id'], station['stationid']);
_userStations.add(newItem);
});
To:
List<Future<UserStationListItem>> listOfFutures = [];
stations.forEach((station) {
listOfFutures.add(_getPurpleAirSiteData(station['_id'], station['stationid']));
});
var stationItems = await Future.wait(listOfFutures);
stationItems.forEach((userStationListItem) {
_userStations.add(userStationListItem);
});
What I am essentially doing creating a list of Futures with your server request. Then await on it which returns a list of item result Maintaining index, which in turn ensures that requests are completed before you hit statecontroller.add. You also gain a performance gain since all request are not going one by one and instead asynchronously. Then you just iterate through the future result and add it to your item list.