How to filter's by id in Dart? Not searching bar - flutter

guys, I am trying to make filter by using id's of item. I have researched a lot, but I think I am missing something obvious. So as I said i need to make a filtration, I have API where two different models Transport and Marshes. Each clasess contains id of bus, tram, subway. For the bus id = 1, for tram = 2, for sunbway id = 3 and for each of these separate id's API contains the 3 separate list of bus' numbers, tram's numbers and subway's train numbers.
I create two models and two screen:
The models
class TransportType {
TransportType({
this.ttId,
this.ttTitle,
this.ttNote,
});
final int ttId;
final String ttTitle;
String ttNote;
}
Also I also clearly indicated that I need the ID of the route in the link from the API. Now it look like so:
For Marshes it is:
Future<List<Marshes>> fetchMarshes() async {
final response = await http.post(
Uri.parse(
'http:/getMarshes.php?fmt=json&tt_id'),
body: {'tt_id': '1'}, headers: <String, String>{'authorization': basicAuth}, );
For fetching Transport model it looks like this:
Future<List<TransportType>> fetchTranspotes() async {
var response = await http.get(
Uri.parse(
'http://getTransportTypes.php?fmt=json'),
headers: <String, String>{'authorization': basicAuth});
var jsonResponse = convert.jsonDecode(response.body) as List;
return jsonResponse
.map((transport) => TransportType.fromJson(transport))
.toList();
}
For fetching the both list: List of Transport types and List of Numbers I am using FutureBuilder:
The srceen one for Transport Types looks like so:
body: FutureBuilder<List<TransportType>>(
future: futureTransposrt,
builder: (context, snapshot) {
if(snapshot.hasData){
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (builder, index){
TransportType project = snapshot.data[index];
return ListTile(
title: Text(project.ttId.toString()),
onTap: (){
Navigator.of(context).pushAndRemoveUntil(MaterialPageRoute(builder: (context)=>MarshId(ttId: 1,)), (route) => false);
},
In the conctructor of MarshId I put the values I want, so the second sreen also has the same conctructor and it looks like this:
class MarshId extends StatefulWidget {
final int ttId;
const MarshId({Key key, this.ttId}) : super(key: key);
#override
_MarshIdState createState() => _MarshIdState();
}
So my qiestions is pretty dumb: What am I doing wrong? I did everything as docs say and I reserched here on SO, but it dosen't work and I copmpletly don't understand what I am doing wrong. I also used this expression as here reccomend but now it seems like it is not work:marshes = marshes.where((marsh) => marsh.ttId == ttId).toList(); I understand this task: I need to filter the data by category and when the user clicks on certain transport type, for example, "bus", he gets to the screen where there is only a list of bus numbers. But I can't figure out what exactly I am doing wrong when I write filtering.
I really appreciate your help, guys!

you can pass id to other page by using constructor in your ListBus() class the you simply use where like this
ie. let's say you parameter name is ttId;
then your filter will be
marshes = marshes.where((marsh) => marsh.ttId == ttId).toList();

Related

firestore doesnt show documents even though they are available

I have following code to add data to firebasefirestore
Future<void> sendMessage({
required String msg,
required String id,
}) async {
var docId = getDocId(id); // returns sth like "AbcDe-FghiJ"
DocumentReference documentReferencer = chat.doc(docId).collection('chatMsg').doc();
Map<String, dynamic> data = <String, dynamic>{
"message": msg,
"sentBy": ownId,
"sentAt": DateFormat('yyyy-MM-dd – kk:mm:ss').format(DateTime.now())
};
await documentReferencer.set(data);
}
I used following code to get the data
StreamBuilder<QuerySnapshot>(
stream: firebaseInstance.collection('Messages').snapshots(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError || !snapshot.hasData) {
return const Center(
child: CircularProgressIndicator()
);
} else {
var data = snapshot.data.docs;
return listBuilder(data);
}
}
)
listBuilder(listData) {
return ListView.builder(
shrinkWrap: true,
itemCount: listData.length,
itemBuilder: (BuildContext context, int index) {
return Text(listData[index].id);
}
)
}
However, data show 0 items even though there is a document present.
My question is how can I get the list of documents from Messages?
I was having the same exact problem with subcollections on Firestore and even asked a question here to get some help over it. Though, it seems like the snapshots won't show the documents having a subcollection in them as there is no field inside any of them. So what I did to counter this was to just add anything (just a random variable) and then it was able to find the documents.
This is my current layout:
I've just added another line of code to just add this whenever I'm inserting a new subcollection.
collection
.set({
'dummy': 'data'
})
.then((_) => print('Added'))
.catchError((error) => print('Add failed: $error'));

Importing an SQFlite database from Flutter app's assets and using rawQuery to display specific rows

I've built an app using Flutter. Part of its functionality is that users can search through data which is in the assets area of the app. This data was originally in JSON format, although I have converted it into an SQLite database to save storage space. That has actually helped me to save around 90%, which is great. The problem is, the search delegate no longer works. It simply returns an empty list, although no errors are produced in the console.
I have created a model class to help read the data from the SQLite database table, which looks like this:
/// Class to handle the country data in the database
class CountriesDB {
/// Defining the variables to be pulled from the json file
late int id;
late String continent;
late String continentISO;
late String country;
late String countryISO;
late String flagIconLocation;
CountriesDB({
required this.id,
required this.continent,
required this.continentISO,
required this.country,
required this.countryISO,
required this.flagIconLocation,
});
CountriesDB.fromMap(dynamic obj) {
this.id = obj[id];
this.continent = obj[continent];
this.continentISO = obj[continentISO];
this.country = obj[country];
this.countryISO = obj[countryISO];
this.flagIconLocation = obj[flagIconLocation];
}
Map<String, dynamic> toMap() {
var map = <String, dynamic>{
'id': id,
'continent': continent,
'continentISO': continentISO,
'country': country,
'countryISO': countryISO,
'flagIconLocation': flagIconLocation,
};
return map;
}
}
As far as I am aware, to read data in a database that is stored within the assets folder of the app, I need to programatically convert it into a working database. I have written the following code, to sort that:
/// Creating the database values
static final DatabaseClientData instance = DatabaseClientData._init();
static Database? _database;
DatabaseClientData._init();
/// Calling the database
Future<Database> get database async {
if (_database != null) return _database!;
_database = await _initDB('databaseWorking.db');
return _database!;
}
/// Future function to open the database
Future<Database> _initDB(String filePath) async {
/// Getting the data from the database in 'assets'
var databasesPath = await getDatabasesPath();
var path = join(databasesPath, filePath);
/// Check if the database exists
var exists = await databaseExists(path);
if (!exists) {
/// Should happen only the first time the application is launched
print('Creating new copy from asset');
/// Make sure the parent directory exists
try {
await Directory(dirname(path)).create(recursive: true);
} catch (_) {}
/// Copy from the asset
ByteData data =
await rootBundle.load('assets/data/database.db');
List<int> bytes =
data.buffer.asUint8List(data.offsetInBytes, data.lengthInBytes);
/// Write and flush the bytes written
await File(path).writeAsBytes(bytes, flush: true);
} else {
print('Opening existing database');
}
return await openDatabase(path, readOnly: true);
}
The next thing I have done is to create a Future function that searches the database using a rawQuery. The code for this is:
/// Functions to search for specific database entries
/// Countries
static Future<List<CountriesDB>> searchCountries(String keyword) async {
final db = await instance.database;
List<Map<String, dynamic>> allCountries = await db.rawQuery(
'SELECT * FROM availableISOCountries WHERE continent=? OR continentISO=? OR country=? OR countryISO=?',
['%keyword%']);
List<CountriesDB> countries =
allCountries.map((country) => CountriesDB.fromMap(country)).toList();
return countries;
}
Finally, I am using the Flutter Search Delegate class to allow the user to interact with the database and search for specific rows. This is the widget I have built for that:
/// Checks to see if suggestions can be made and returns error if not
Widget buildSuggestions(BuildContext context) => Container(
color: Color(0xFFF7F7F7),
child: FutureBuilder<List<CountriesDB>>(
future: DatabaseClientData.searchCountries(query),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Center(
child: PlatformCircularProgressIndicator(
material: (_, __) => MaterialProgressIndicatorData(
color: Color(0xFF287AD3),
),
cupertino: (_, __) => CupertinoProgressIndicatorData(),
));
default:
if (query.isEmpty) {
return buildAllSuggestionsNoSearch(snapshot.data!);
} else if (snapshot.hasError || snapshot.data!.isEmpty) {
return buildNoSuggestionsError(context);
} else {
return buildSuggestionsSuccess(snapshot.data!);
}
}
},
),
);
The idea is that the functionality I have built will return the whole list before a user searches and once a users starts typing, they will only be shown any rows that match their search query. This worked fine when I was using JSON data but it is returning an empty list, yet there are no errors printed in the console, at all. That makes it quite hard to know where my code is going wrong.
Where have I gone wrong with my code, such that it is not returning any data? How can I correct this? Thanks!

How can I display a logged in user details in flutter

I have used get method to retrieve user details and have got 200 status as well. I am having confusion how to show the details in UI. In my homepage I have a floating action button which leads to the profile page. Any help would be much appreciated Thank you.
Future getProfile() async {
String? token = await getToken();
final response = await http.get(Uri.parse('$API_URL/user'), headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token'
});
print(response.statusCode);
if (response.statusCode == 200) {
if (response.body != "") {
var results = json.decode(response.body);
var resultData = results['data']['name'];
print(resultData);
}
}
}
you can use a FutureBuilder like this:
FutureBuilder<dynamic>(
future: getProfile,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting: return Text('Loading....');
default:
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
else
return Text('Result: ${snapshot.data}');
}
},
);
...
Future getProfile() async {
String? token = await getToken();
final response = await http.get(Uri.parse('$API_URL/user'), headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token'
});
dynamic resultData;
if (response.statusCode == 200) {
if (response.body != "") {
var results = json.decode(response.body);
resultData = results['data']['name'];
print(resultData);
}
}
return resultData;
}
When you are working with network data (i.e. API responses), the best practice states that you should convert the received data into Dart objects. You will then be able to easily access your data.
Quick and easy approach (not recommended)
For a quick and dirty approach, you could do the following:
1- create a model for your user. Create new file and name it user_model.dart
class User{
String id;
String name;
// Add whatever other properties you need to pull from the server here
User({
this.id,
this.name,
});
// This function will help you convert the deata you receive from the server
// into an instance of User
factory User.fromJson(Map<String, dynamic> json) => User({
id: json['id'],
namne: json['name']
})
}
2- Instanciate a new user in your getProfile() function
Future<User?> getProfile() async { // you want to get a Future<User>
String? token = await getToken();
final response = await http.get(Uri.parse('$API_URL/user'), headers: {
'Accept': 'application/json',
'Authorization': 'Bearer $token'
});
print(response.statusCode);
if (response.statusCode == 200) {
if (response.body != "") {
var result = json.decode(response.body)['data']; // select the data you need here
final user = User.fromJson(result) // create a new User instance
return user // return it
}
}
// in case something went wrong you want to return null
// you can always check the nullity of your instance later in your code
return null;
}
3- In your UI, you can consume the newly created instance like so. I am assuming you are inside a build() function of any widget!
//...
#override
Widget build(BuildContext context) {
return FutureBuilder<dynamic>(
future: getProfile,
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if(snapshot.hasData){
final user = snapshot;
// now you can access your user's data as you wish
print(user.id);
print(user.name);
}
);
}
//...
Better Approach (recommended)
The above approach, although seems to work, won't be ideal for a more complex project. For that, you want to follow a road map that could look like the following:
Automate object serialization/deserialization using packages like freezed. This will offload you from any unwanted error injection by building toJson and fromJson methods, among others, for you ;). Check their documentation for more details.
Manage data streams using a state management library like bloc. You can access your state, in your case the user's profile data, from anywhere in the widget tree without having to use FutureBuilder everywhere. It will also help you keep in sync with your data. Check their well-written documentation for more details on how to use it.
I have mentioned these two libraries here because they are the ones I work with all the time and that I am familiar with. They might be others out there that do more or less the same. It's up to you to pick whichever you feel comfortable with ;)
Once you get familiar with a state management library you could architect your app as follow:
/...
-lib
|- model #build your data instance and return object
|- repository #call API methods and convert received data to model instance
|- api #make HTTP calls
|- ui #build UI elements
|- bloc #receive events from UI and call repository functions then return datastreams to UI

Running Multiple Async Requests at Once in Dart. Proper way

I am trying to work with concurrency in Flutter, so I have three get-request from server and I need to get values from them at the same time. Each request was parsed into a model. Now I'm trying to figure out how I can combine all the models into one list with three get-request and run this the final list in ListView.builder. Also my task is quite hard for such noobie as I am, beacuse besides parsed concurrently all three lists I need to filter them out, because as said in API all requests are nested and depends on id. How can i resolve this?
This is my models:
ScheduleVariants{
final int mrId;
final int mvId;
ScheduleVariants({this.mrId, this.mvId});
}
FlightCard{
final int mvId;
final int stId;
FlightCard({this.mrId, this.stId});
}
Stop{
final int stId;
Stop({this.stId})
}
I need to get final values from Stop models. As you can see all models have nested stucture and I can't avoid this.
Now I am trying to make concurrent call like this:
class Dire extends StatefulWidget {
final int mrId;
final int mvId;
final int stId;
const Dire({Key key, this.mrId, this.mvId, this.stId}) : super(key: key);
#override
_DireState createState() => _DireState();
}
class _DireState extends State<Dire> {
#override
void initState() {
fetchData();
super.initState();
stops.where((element) => element.stId == widget.stId).toList();
card.where((element) => element.mvId == widget.mvId).toList();
sheduler.where((element) => element.mrId == widget.mrId).toList();
}
List<ScheduleVariants> sheduler;
List<FlightCard> card;
List<Stop> stops;
Future fetchData() async {
String username = '';
String password = '';
String basicAuth =
'Basic ' + base64Encode(utf8.encode('$username:$password'));
print(basicAuth);
final result = await Future.wait([
http.get(
Uri.parse(
"http://mysecurelink/getMarshVariants.php?fmt=json"),
headers: <String, String>{'authorization': basicAuth}),
http.get(
Uri.parse(
"http://mysecurelink/getFlightCard.php?fmt=json&mv_id"),
headers: <String, String>{'authorization': basicAuth}),
http.get(
Uri.parse(
"http://mysecurelink/getStops.php?fmt=json"),
headers: <String, String>{'authorization': basicAuth}),
]);
setState(() {
sheduler = json.decode(result[0].body) as List;
card = json.decode(result[1].body) as List;
stops = json.decode(result[2].body) as List;
});
}
#override
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: FutureBuilder(
future: fetchData(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: stops.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(stops[index].stTitle),
);
});
} else {
return CircularProgressIndicator();
}
}));
}
}
At the end of the main task is to run three parallel request filtered by ID and get data from the Stops model. How can you do it right?
I am noob and don't get how properly to do it and I will really glad if someone help me to figure this task out.
I am going to make some assumptions, because there is not enough information:
Dire is a combination of the three classes ScheduleVariants, FlightCard and Stop where ScheduleVariants.mvId == FlightCard.mvId and FlightCard.stId == Stop.stId.
All three APIs will return a list as their response.
All ScheduleVariants have unique mvId, all FlightCards have unique mvId and stId, and all Stops have unique stId.
There is nothing wrong with the way you execute multiple asynchronous requests. Future.wait in this case takes in a list of futures and returns a list of responses. The problem you are facing is just that you do not know how to merge the responses from the three API requests.
You seem to also be mixing up the use of state with the use of futures. At least in the code snippet you provided, it does not seem like you ever need to change the state after you initialize it, which means you do not need to use state at all.
Dire should just be a model class.
class Dire {
final ScheduleVariants scheduleVariant;
final FlightCard flightCard;
final Stop stop;
Dire(this.scheduleVariant, this.flightCard, this.stop);
}
In your widget where you want to get the Dires from the APIs, you can use this function in the FutureBuilder:
Future<List<Dire>> fetchData() async {
String username = '';
String password = '';
String basicAuth =
'Basic ' + base64Encode(utf8.encode('$username:$password'));
print(basicAuth);
final result = await Future.wait([
http.get(
Uri.parse(
"http://mysecurelink/getMarshVariants.php?fmt=json"),
headers: <String, String>{'authorization': basicAuth}),
http.get(
Uri.parse(
"http://mysecurelink/getFlightCard.php?fmt=json&mv_id"),
headers: <String, String>{'authorization': basicAuth}),
http.get(
Uri.parse(
"http://mysecurelink/getStops.php?fmt=json"),
headers: <String, String>{'authorization': basicAuth}),
]);
flightCardMap = HashMap.fromIterable(json.decode(result[1].body), (fc) => fc["mvId"], (fc) => FlightCard(fc));
stopMap = HashMap.fromIterable(json.decode(result[2].body), (s) => s["stId"], (s) => Stop(s));
return json.decode(result[0].body).map((sv) => {
flightCard = flightCardMap[sv["mvId"]];
return Dire(ScheduleVariants(sv), flightCard, stopMap[flightCard["stId"]]);
}).toList();
}
A disclaimer: I did not check this code snippet for syntax errors, so there might be some but the general idea is there.

The error from API request "The method '[]' was called on null. Receiver: null Tried calling: [](0)" Update

I'm trying to use data from the API, but the data I need is found for different requests. In flutter there is future.waite and with this I can make a request for the necessary parts of the API. The idea is that I am trying to create schedule for the train. In this train schedule, I need train number, platform name and arrival time. I'm trying to do everything as described in the documentation and on the video, but I can't do it, because I get an error 'The method '[]' was called on null. Receiver: null Tried calling: []("st_title")'
Before asking here I tried to make something like this:
body:ListView.builder(itemBuilder: (context, index){
return ListTile(
title: Text(stops[index]['st_title']),
but it dosen't work and gives me an error:
The method '[]' was called on null. Receiver: null Tried calling:
I saw a solution to a similar error here, but I've tried all the solutions and can't figure out what exactly I'm doing wrong. Could you please point me out?
May be I don't actually realize the concepts of what exactly I should do. Can you, guys, help me?
My full code is:
Map <String, dynamic> stops;
Map <String, dynamic> marhe;
Map <String, dynamic> arrival;
#override
void initState() {
// TODO: implement initState
super.initState();
fetchData();
}
Future fetchData() async{
String username = '***';
String password = '***';
String basicAuth =
'Basic ' + base64Encode(utf8.encode('$username:$password'));
print(basicAuth);
final result = await Future.wait([
http.get( 'http://link/getTableCur.php?fmt=json',
headers: <String, String>{'authorization': basicAuth}),
http.get( 'http://link//getStops.php?fmt=json',
headers: <String, String>{'authorization': basicAuth}),
http.get( 'http://link//getMarshes.php?fmt=json',
headers: <String, String>{'authorization': basicAuth}),
]);
setState(() {
stops = json.decode(result[0].body);
marhe = json.decode(result[1].body);
arrival = json.decode(result[2].body);
});
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
),
body:ListView.builder(itemBuilder: (context, index){
return ListTile(
title: Text(stops[index]['st_title']),
leading: Text(marhe['mr_num']),
subtitle: Text(arrival['ta_arrivetime'].toString()),
);
}
//title: Text(arrival[index]['tc_arrivetime']?? ''),
),
UPDATE!
Thanks for helping me #Patrick Freitas, I was trying to make everything he said but now I get an error:
The getter 'length' was called on null.
Receiver: null
Tried calling: length
for the following code:
body: stops.length > 0 ? ListView.builder(
//itemCount: stops.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
trailing: Text(marhe[index]["mr_num"]),
title: Text(stops[index]['st_title']),
// subtitle: Text(arrival[index]["ta_arrivetime"]),
);
},
) : CircularProgressIndicator()
)
also when I use isDataObtained ? ListView.builder{...}:CircularProgressIndicator()
then it gives me endless loading and no data appears on the screen. It also gives an error "Error: List<dynamic> is not a subtype of type Map<String, dynamic>" also I have found the similiar problem solving on this question - Error: List<dynamic> is not a subtype of type Map<String, dynamic>
and my code looks like this:
setState(() {
stops = json.decode(result[0].body[0]);
marhe = json.decode(result[1].body[0]);
arrival = json.decode(result[2].body[0]);
isDataObtained = true;
});
}
Also my json's for all of this links looks like this:
for **getTableAll**
[
{
"ta_id": 1,
"srv_id": 1,
"st_id": 3026,
"mr_id": 305,
"rl_racetype": "B",
"uniqueid": 21,
"ta_systime": "2021-03-11 15:01:47",
"ta_arrivetime": "2021-03-11 15:06:11",
"mv_id": 957,
"rc_kkp": "",
"ta_len2target": 4.996839,
"u_inv": false
},
{
for **getStops**
{
"st_id": 1,
"ok_id": "18410",
"sr_id": 0,
"st_title": "Station1",
"st_desc": "Station1",
},
for **Marshes:**
[
{
"mr_id": 1,
"tt_id": 1,
"mt_id": 2,
"mr_num": "112",
},
These are three arrays and I need data from all three arrays at the same time: as I described above, I need data on arrival time, train number and platform name. I tried to use classic data retrieval as described in the flutter documentation, but nothing worked for me. Here on Stackoverflow, I was told that I need to use Future.wait, but now I stuck and got confused.
Flutter doesn't know if your async request already returned, so it is trying to render a array without any information. To prevent that, you can add a IF validation to verify if your array it's already populated.
Using a ternary if:
body:
stops.length > 0 ? ​
ListView.builder(...) :
CircularProgressIndicator()
As you have three arrays to be populated, you can create a boolean to control if your request already populated all three, like isDataObtained.
Map <String, dynamic> stops;
Map <String, dynamic> marhe;
Map <String, dynamic> arrival;
bool isDataObtained = false;
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(),
body:
isDataObtained ? ​
ListView.builder(...) :
CircularProgressIndicator()
)
)
}
Future fetchData() async{
final result = await Future.wait([...]);
setState(() {
stops = json.decode(result[0].body);
marhe = json.decode(result[1].body);
arrival = json.decode(result[2].body);
isDataObtained = true;
});
}