how to fetch data correctly with flutter - flutter

i have problem with fetch data from database (firestore)
im fetching data from firestore and listing them with listview.builder, when a card is tapped it routes to new page that show all the details of tapped item.
on the screenshot i have 2 item listed with header,body and date when i tapped on item routes to new page and fetch from firestore rest of the data (client,start date,finish date etc.)
my question is what is the best way to fetch tapped items datas
my idea is store somehow items store unique id to the builded item and when it tapped route to new page with id and query with id
my code block
Widget listener(Stream<QuerySnapshot> tasks) {
return Expanded(
child: StreamBuilder<QuerySnapshot>(
stream: tasks,
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (snapshot.hasError) {
return Center(
child: Text(
"something went wrong.",
style: TextStyle(
color: ColorConstants.instance.headerColor, fontSize: 20),
textAlign: TextAlign.center,
),
);
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.data!.size > 0) {
final data = snapshot.requireData;
return ListView.builder(
itemCount: data.size,
itemBuilder: (context, index) => buildNotificationCards(
context,
data.docs[index]['header'],
data.docs[index]['body'],
data.docs[index]['startDate']));
} else {
return Center(
child: SizedBox(
width: MediaQuery.of(context).size.width * .8,
child: Column(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
LineIcons.fileAlt,
size: 60,
color: ColorConstants.instance.headerColor,
),
SizedBox(
height: 20,
),
Text(
'Maalesef Kayıtlı Bir Veri Bulunamadı',
textAlign: TextAlign.center,
style: TextStyle(
color: ColorConstants.instance.headerColor,
fontSize: 20,
height: 2),
),
]),
),
);
}
},
));
}

The normal practice is under ListView.builder, you create a ListTile and on its onTap argument, you use the Navigator to switch to another page for the detail view:
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => DetailPage(data[index])),
);
where data[index] is the particular element of the data list you want to display the contents of.

Related

Display sub-collection in flutter Firebase

I want to display the data of a sub-collection named "Profile". I get it that we need to query it differently, and I tried it, but it is not working out for me. First, I displayed the information from the documents of the mother collection "mentors", using StreamBuilder. Then passed it's data to a Widget I created. Then on the Widget I created, I performed another streamBuilder query for the subcollection of each document of the Mother Collection "mentors".
This is the code I used to display the documents on "mentors" collection, and is working fine.
final mentors = Expanded(
child: Container(
height: 250,
margin: const EdgeInsets.only(left: 20, right: 20),
child: StreamBuilder<QuerySnapshot>(
stream: db_mentors,
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (snapshot.hasError) {
Fluttertoast.showToast(msg: "An Error Occured");
}
if (snapshot.connectionState == ConnectionState.waiting) {
Fluttertoast.showToast(msg: "Loading");
}
final data = snapshot.requireData;
return ListView.builder(
itemCount: data.size,
itemBuilder: ((context, index) {
return mentorsWidget(
"${data.docs[index]["uid"]}",
"${data.docs[index]['name']}",
"${data.docs[index]['specialty']}",
);
}),
);
}),
),
);
This here is the code I used to display the data from the subcollection of each document named "Profile". Which is also the widget I created.
Widget mentorsWidget(String uid, String name, String specialty) {
return Container(
margin: const EdgeInsets.all(5),
width: size.width,
decoration: const BoxDecoration(
color: Color.fromARGB(255, 3, 42, 134),
borderRadius: BorderRadius.only(
topRight: Radius.circular(20),
bottomRight: Radius.circular(20))),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
StreamBuilder(
stream: FirebaseFirestore.instance
.collection("mentors")
.doc(uid)
.collection("Profile")
.snapshots(),
builder: (
BuildContext context,
AsyncSnapshot<QuerySnapshot> snapshot,
) {
if (!snapshot.hasData) {
return SizedBox(
width: 80,
child: Image.asset("assets/Navigatu-icon.ico"),
);
} else {
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: ((context, index) {
String url = snapshot.data!.docs[index]['downloadURL'];
return SizedBox(
width: 80,
child: Image.network(url),
);
}),
);
}
}),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Container(
margin: const EdgeInsets.only(top: 10, left: 5),
child: Text(
name,
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontFamily: 'Roboto',
fontWeight: FontWeight.w500),
),
),
Container(
margin: const EdgeInsets.only(top: 15, bottom: 15, left: 5),
child: Text(
specialty,
style: const TextStyle(
color: Colors.white,
fontFamily: 'Roboto',
fontWeight: FontWeight.w400,
fontSize: 12,
),
),
),
],
)
],
),
);
}
Here is the Collection Tree in my firebase:
Firebase Collection Tree
Here is the display I want to achieve. The boat picture here supposedly must be a Image.network, with the url that is in the sub-collection, named "Profile".
Mentor Field
As you can see in the code, I performed first the "final mentors", then performing streambuilder inside of it. So that I can get the datas of each document from the mother collection. Now I passed those data to the "mentorwidget" to display them in a proper way, but then I wanna use a Image.network, containing the data inside the sub-collection of each document in the mother collection. That's why I performed another streambuilder inside the mentorwidget to display the picture, or get the data of the sub-collection which is the url of the said picture.
If the data doesn't get frequently updated or if you don't need to display the constant changes of it's value to the users then use FutureBuilder instead of StreamBuilder to query the value you want only once.
class Home extends StatelessWidget {
const Home({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance
.collection('mentors')
.doc('b23lt...[your desired document ID]')
.collection('Profile')
.get(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done &&
snapshot.hasData) {
return ListView.builder(
itemCount: , // lenght of snapshot data,
itemBuilder: (context, index) {
//Here you can retrieve the data of each document
},
);
}
return const Center(child: CircularProgressIndicator());
},
),
);
}
}
Update: I found the answer! thanks to Slender's answer, I managed to get the answer, here is the code.
FutureBuilder<QuerySnapshot>(
future: FirebaseFirestore.instance
.collection("mentors")
.doc(uid)
.collection("profile")
.get(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data!.docs.isEmpty) {
return SizedBox(
width: 80,
child: Image.asset("assets/Navigatu-icon.ico"),
);
} else if (snapshot.hasData) {
// print(snapshot);
return SizedBox(
width: 80,
child: Image.network(
"${snapshot.data!.docs[0]['downloadURL']}"),
);
}
}
// print(snapshot.data!.docs[0]['downloadURL']);
return const SizedBox(
width: 80,
child: Center(
child: CircularProgressIndicator(),
),
);
},
),

How to display something while the value is null/loading in Flutter?

I've created a real-time object detection with Tiny YOLOv2 using Flutter app. So far the app managed to detect the object and display its bounding box with the detectedClass and confidence. Then I pulled the detectedClass (the name of the object) and assigned it into my String _result variable because I need it to fetch data from Firebase later.
The main issue is when the app is not detecting anything I want to display something like maybe 'Loading...' until the _result return the name of the object bcus u see my custom Tiny YOLOv2 takes a lil bit of time before it detect the object. Then, I want to fetch data based on _result from the Firebase. So far, I've managed to fetch the data from the Firebase BUT ONLY if I hardcoded the name of the object detected. Otherwise the app would return null error if I'm fetching using _result variable.
Below is my attempt of displaying the the name of the food and its calorie (which is fetched from the Firebase) based on the _result variable but FAILED:
return MaterialApp(
debugShowCheckedModeBanner: false,
home: SafeArea(
child: Scaffold(
body: Container(
decoration: BoxDecoration(
image: DecorationImage(
image:AssetImage('assets/back.jpg'), fit: BoxFit.fill),
),
child: Column(
children: [
Stack(
children: [
Center(
child: Container(
margin: EdgeInsets.only(top: 10),
// child: Icon(Icons.photo_camera, color: Colors.orange, size: 40),
child: Text('Press on the camera icon',
style: TextStyle(
fontSize: 16.0,
color: Colors.orangeAccent,
fontWeight: FontWeight.bold
),
textAlign: TextAlign.center,
),
),
),
Center(
child: FlatButton(
onPressed: ()
{
initCamera();
},
child: Container(
margin: EdgeInsets.only(top: 35),
height: 270,
width: 360,
color: Colors.orange,
child: Stack(
children: list,
),
),
),
),
],
),
Center(
child: Container(
margin: EdgeInsets.only(top: 45.0),
child: SingleChildScrollView(
child: FutureBuilder(
future: dbRef.orderByChild("food_name").equalTo(_result).once(),
builder: (context, AsyncSnapshot snapshot) {
if (!snapshot.hasData) {
return Center(
child: Text("Loading..."),
);
} else {
lists.clear();
Map<dynamic, dynamic> values = snapshot.data.value;
values.forEach((key, values) {
lists.add(values);
});
return ListView.builder(
shrinkWrap: true,
itemCount: lists.length,
itemBuilder: (BuildContext context,
int index) {
return
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment
.start,
children: <Widget>[
Text("Name: " + lists[index]["food_name"]),
Text("Calorie: " + lists[index]["calorie"]),
],
),
);
});
}
})
),
),
),
],
),
),
),
),
);
}
}
The error from the above is:
Exception has occurred.
NoSuchMethodError (NoSuchMethodError: The method 'forEach' was called on null.
Receiver: null
Tried calling: forEach(Closure: (String, dynamic) => Null))
My issue is kinda similar with this user and the solution in there is not working in my case.
I dont know whether its possible to fetch the data from firebase based on the real time input? Otherwise how do I save the name of the object somewhere to make it static(?) I'm really new to Flutter so some guidance on how to code it is very much appreciated. Thank you in advance.
Edited Btw just want to add it here. This is how I declared my _result:
_recognitions.forEach((response)
{
_result = "${response["detectedClass"]}" + "\n\n";
});
Basically _result is just the name of the object detected.
Just wanna share how I resolved this in case anyone's having the same issue. All I did is just insert a while (values == null) inside my else statement like this:
else {
values = snapshot.data.value;
while (values == null){
return Center(
child: CircularProgressIndicator(color: Colors.orange),
);
}
lists.clear();
values.forEach((key, values){
lists.add(values);
});
}
and then followed by the ListView.builder() to display them.
Btw, thank you so much for those who responded!

Add OnEmpty Widget to ListView.builder() inside FutureBuilder using flutter?

I am using Flutter to develop small application with floor for the database.
I am getting the data from the database using Future then listing all items in UI using FutureBuild.
This is my code
Getting the data from database:
#Query('SELECT * FROM Doctor')
Future<List<Doctor>> findAllDoctor();
Getting data to UI
Future<List<Doctor>> findAllDoctor() async {
return await database.doctorDao.findAllDoctor();
}
Setting data into FutureBuilder:
return FutureBuilder(
future: findAllDoctor(),
builder: (BuildContext context, AsyncSnapshot<List<Doctor>> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data?.length,
itemBuilder: (BuildContext context, int index) {
return Card(
child: ListTile(
contentPadding: const EdgeInsets.all(8.0),
title: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
"${snapshot.data![index].firstName} ${snapshot
.data![index].lastName}"),
Text(
snapshot.data![index].phone,
style: const TextStyle(
fontSize: 14,
color: Colors.grey,
),
),
],
),
subtitle: Text(
"${snapshot.data![index].address} ${snapshot.data![index]
.nameOfTheClinic}"),
),
);
},
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
);
I want to add new widget that tells me no data if there is no data in the table.

ListView is not showing the result if empty list

I am loading data from a remote api:
This is the dart file that provides the connection and download:
clinica-api.dart
import 'package:flutter_capenergy/modelos/clinica.dart';
import 'package:http/http.dart' as http;
Future<List<Clinica>> fetchClinicas(String idUsuario) async {
String url ="https://..flutter_api/get_clinicas.php";
final response = await http.get(url);
if (response.body == "[]"){
}
return clinicaFromJson(response.body);
}
And this is the piece of code from misclinicas.dart where I am showing the list:
Expanded(
child: Container(
child: FutureBuilder(
future: fetchClinicas(miId),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, index) {
print(index.toString());
Clinica clinica = snapshot.data[index];
return new GestureDetector(
onTap: () {
clinicaProvider.setClinica(clinica.nombreClinica);
clinicaProvider.setClinicaId(clinica.idClinica);
} ,
child: new Card(
elevation: 6,
child: new Column(
children: [
new Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment
.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment
.center,
mainAxisAlignment: MainAxisAlignment
.center,
children: <Widget>[
Image.network(
'https://.../${clinica
.logoClinica}',
height: 180,
alignment: Alignment.center,),
],
),
Text(
'${clinica.nombreClinica}',
style: TextStyle(fontSize: 16,
fontWeight: FontWeight.bold,
color: Colors.blue),
),
Text(
'${clinica.direccionClinica}',
style: TextStyle(fontSize: 14,
color: Colors.grey,
fontStyle: FontStyle.italic),
),
],
),
),
],
),
),
);
},
);
}
else {
Text ("NO HAY CLINICAS");
}
return Text("Cargando clínicas");
},
),
),
),
If there are items on the list, they are shown, but if the list is empty I would like to show a text with a message reporting that the list is empty.
I am trying to do it putting this text widget if snapshot.hasdata is false:
Text ("NO HAY CLINICAS");
but it is not shown, I am only getting a blank listView.
In the empty list case, snapshot.hasData will be true and snapshot.data.length will be 0.
snapshot.hasData == false means it's either loading or an error has happened.
in the api call return empty list if response.body ="[]"
if (response.body == "[]"){
List<Clinica> emptyClinica = [];
return emptyClinica;
}
in misclinicas.dart
snapshot.data.lenth > 0 ? your list work : Text('No Data Found')

Flutter Show Data Once in ListView

I have listview in my app and in this listView I pull the book titles with API. Book titles are coming up without any problems. But if I press the button more than once, the titles increase as much as I press the button
Here is my code sample
_showData
? Container(
height: MediaQuery.of(context).size.height / 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
IconButton(
icon: Icon(Icons.close),
onPressed: () {
Navigator.pushNamed(
context, CountryScreen.routeName);
}),
Center(
child: Text(
'Please Select Book',
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 24),
),
),
],
),
Expanded(
child: ListView.builder(
itemCount: bookList.length,
itemBuilder: (BuildContext context, int index) {
return ListTile(
onTap: () {
Navigator.pushNamed(
context, MainScreen.routeName);
},
title: Text(bookList[index]),
);
),
],
),
),
)
: SizedBox()
I'm calling my data here,I'm calling in the button
else {
_showData = !_showData;
books.forEach((element) {
bookList.add(element.companyName);
book.setStringList(
'bookName', bookList);
});
}
To illustrate with a small example When I click once on the button I call the data
but if I click twice I see this (the more I click the more it gets), any idea?
The build() method is called whenever there is a change to redraw in the UI. But at that time your bookList state will not be reset.
I will give a trick code to fix this problem:
bookList = [];
books.forEach((element) {
bookList.add(element.companyName);
book.setStringList('bookName', bookList);
});