Initialize App using FutureBuilder with a ListView.builder and have an onClick in each ListItem? - asynchronous

Im building an App with Flutter and have a problem regarding the use of FutureBuilder. The situation is that my HomePage in the App should make a request to my server and get some Json. The call to the getData-Method happens in the build-method of the Homescreen (not sure if this is right).
The next call in the build-Method has the snapshot and builds a ListView.
Here is the problem:
Each time I click a button or go to a different screen, the Future Builder is triggered! That means that I have a bunch of useless API calls.
Here is the question:
What do I have to change, to let the Future Builder only run when I come to the Homescreen?
class HomeState extends State<Home> {
int count = 0;
final homeScaffoldKey = GlobalKey<ScaffoldState>();
List compList = new List();
Future<List> getData() async {
final response = await http.get(
Uri.encodeFull("http://10.0.2.2:5000/foruser"),
headers: {
"Authorization":
"JWT eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE1NTk2NDM4ODcsImlhdCI6MTU1NzA1MTg4NywibmJmIjoxNTU3MDUxODg3LCJpZGVudGl0eSI6MX0.OhuUgX9IIYFX7u0o_6MXlrMYwk7oMCywlmHLw-vbNSY",
"charset": "utf-8"
},
);
if (response.statusCode == 200) {
compList = jsonDecode(response.body);
List<Comp> result = [];
count++;
for (var c in compList) {
Comp comp = Comp.fromJson(c);
result.add(comp);
}
return result;
} else {
throw Exception('Failed to load');
}
}
#override
Widget build(BuildContext context) {
return new Scaffold(
backgroundColor: Colors.white10,
body: Stack(
children: <Widget>[
new Container(
child: FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return new Container(
child: Text("whoops"),
);
}
if (snapshot.hasData) {
if (snapshot.data != null) {
if (snapshot.data.toString() == "[]") {
print("no comps - called API: $count");
return new ListView(
key: Key("1"),
children: <Widget>[
new Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
SizedBox(
height: 30.0,
),
Card(
color: Colors.blue,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text(
"Welcome, you have no comps",
style:
TextStyle(color: Colors.white),
),
),
],
),
),
],
),
],
);
}
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (BuildContext context, int index) {
print(index);
if (index == 0) {
return new Column(
children: <Widget>[
Card(
color: Colors.blue,
child: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
ListTile(
title: Text(
"Welcome back, these are your comps",
style:
TextStyle(color: Colors.white),
),
),
],
),
),
SizedBox(
height: 10.0,
),
new CompListItem(
new Comp(
snapshot.data[index].left_name,
snapshot.data[index].right_name,
snapshot.data[index].left,
snapshot.data[index].right,
snapshot.data[index].left_city,
snapshot.data[index].right_city,
snapshot.data[index].latitude_left,
snapshot.data[index].longitude_left,
snapshot.data[index].latitude_right,
snapshot.data[index].longitude_right,
snapshot.data[index].points,
),
"left")
],
);
}
Comp tmp = new Comp(
snapshot.data[index].left_name,
snapshot.data[index].right_name,
snapshot.data[index].left,
snapshot.data[index].right,
snapshot.data[index].left_city,
snapshot.data[index].right_city,
snapshot.data[index].latitude_left,
snapshot.data[index].longitude_left,
snapshot.data[index].latitude_right,
snapshot.data[index].longitude_right,
snapshot.data[index].points,
);
return new CompListItem(tmp, "left");
},
);
} else if (snapshot.data == null) {
return new Container(
child: Text("Sorry, there seems to be a problem :("),
);
}
}
} else {
return CircularProgressIndicator();
}
},
),
),
],
),
floatingActionButton: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
mainAxisSize: MainAxisSize.min,
children: <Widget>[
FloatingActionButton(
heroTag: null,
child: Icon(
Icons.add_location,
color: Colors.white,
),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => MakeComp(),
),
);
},
backgroundColor: Colors.blue,
),
SizedBox(
height: 10.0,
),
FloatingActionButton(
heroTag: null,
child: Icon(Icons.compare_arrows),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => GetComps(),
),
);
},
backgroundColor: Colors.blue,
),
],
));
}
}
Actual results:
Open the App -> Future Builder runs -> List of data is shown -> Navigate to another Widget -> Future Builder runs -> click some button -> Future Builder runs
Expected results:
Open the App -> Future Builder runs -> List of data is shown -> Navigate to another Widget -> click some button -> do something on another screen -> return to homescreen -> Future Builder runs

And in the moment I posted this question, I found my answer :D
Thanks to Rémi Rousselet, who answerd this question here:
How to deal with unwanted widget build?
The answer was simply to put the call for the Future into the initState Method, which is exactly called, when I need the data to be loaded.
Happy Fluttering everyone!

Related

The method '[]' can't be unconditionally invoked because the receiver can be 'null'

I'm new to Flutter. I am trying to develop an application.
I want to show the staff list in the Firebase database. However, I am getting the following error.
Error :
The method '[]' can't be unconditionally invoked because the receiver
can be 'null'. Try making the call conditional (using '?.') or adding
a null check to the target ('!').
Kodlarım :
`import 'package:calendar/page/mainPage.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class Staff extends StatefulWidget {
#override
_StaffState createState() => _StaffState();
}
class _StaffState extends State<Staff> {
final _firestore = FirebaseFirestore.instance;
#override
Widget build(BuildContext context) {
// ignore: unused_local_variable
CollectionReference staffRef = _firestore.collection('staff');
return Scaffold(
appBar: AppBar(
title: Text("Personel Listesi"),
backgroundColor: Colors.redAccent[400],
actions: <Widget>[
IconButton(
icon: Icon(Icons.home),
onPressed: () {
Navigator.pushAndRemoveUntil(
context,
MaterialPageRoute(builder: (_) => MainPage()),
(route) => true);
},
),
],
),
body: Container(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Center(
child: Column(
children: [
StreamBuilder<QuerySnapshot>(
stream: staffRef.snapshots(),
builder: (BuildContext context, AsyncSnapshot asyncSnapshot) {
if (asyncSnapshot.hasError) {
return Center(
child: Text(
"Bir hata oluştu, lütfen tekrar deneyiniz."));
} else {
if (asyncSnapshot.hasData) {
List<DocumentSnapshot> listStaff =
asyncSnapshot.data.docs;
return Flexible(
child: ListView.builder(
itemBuilder: (context, index) {
return Card(
elevation: 20,
color: Colors.greenAccent[200],
child: ListTile(
trailing: IconButton(
icon: Icon(Icons.delete),
onPressed: () async {
await listStaff[index]
.reference
.delete();
},
),
title: Text(
'${listStaff[index].data['nameSurname']}',
style: TextStyle(fontSize: 20),
),
subtitle: Column(
children: [
Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Text(
'${listStaff[index].data['tip']}',
style: TextStyle(fontSize: 14),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Text(
'${listStaff[index].data['mail']}',
style: TextStyle(fontSize: 14),
),
],
),
Row(
mainAxisAlignment:
MainAxisAlignment.start,
children: [
Text(
'${listStaff[index].data['phone']}',
style: TextStyle(fontSize: 14),
),
],
),
],
),
),
);
},
itemCount: listStaff.length),
);
} else {
return Center(
child: CircularProgressIndicator(),
);
}
}
},
),
],
),
),
),
),
);
}
}
`
In the new flutter update, we don't need to add .data()
my codes below
title: Text(
**'${listStaff[index].data['nameSurname']}',**
style: TextStyle(fontSize: 20),
),
Changing it like this fixed the error.
title: Text(
**'${listPersonel[index]['nameSurname']}'**,
style: TextStyle(fontSize: 20),
),
Problem:
You get this error if you're accessing an element on a nullable List or Map. Let's understand it for a List and you can apply the same solutions for your Map.
For example:
List<int>? someList;
void main() {
int a = someList[0]; // Error
}
Solutions:
Use a local variable:
var list = someList;
if (list != null) {
int a = list[0]; // No error
}
Use ? and ??:
int a = someList?[0] ?? -1; // -1 is the default value if List was null
Use ! bang operator only if you're sure that the List isn't null.
int a = someList![0];
For those who are using FutureBuilder/StreamBuilder:
You can solve the error in two ways:
Specify a type to your FutureBuilder/StreamBuilder
FutureBuilder<List<int>>( // <-- type 'List<int>' is specified.
future: _listOfInt(),
builder: (_, snapshot) {
if (snapshot.hasData) {
List<int> myList = snapshot.data!; // <-- Your data
}
return Container();
},
)
Use as to downcast Object to your type, say a List or Map.
FutureBuilder(
future: _listOfInt(),
builder: (_, snapshot) {
if (snapshot.hasData) {
var myList = snapshot.data! as List<int>; // <-- Your data using 'as'
}
return Container();
},
)
I was having the same problem as the questioner and the solution came from this post. I leave it here in case anyone else has this problem.
https://fileidea.com/2021/05/05/method-cant-be-unconditionally-invoked-because-the-receiver-can-be-null-with-firestore/
before:
final mySnapStream = messagesCollection
.orderBy('date', descending: true)
.limit(100)
.snapshots()
.map((obj) => obj.docs
.map((e) => new MyItem(
e.data()['myFieldOne'],
e.data()['myFieldThree'],
e.data()['myFieldFour']))
.toList());
after:
final mySnapStream = messagesCollection
.orderBy('date', descending: true)
.limit(100)
.snapshots()
.map((obj) => obj.docs
.map((e) => new MyItem(
(e.data() as dynamic)['myFieldOne'],
(e.data() as dynamic)['myFieldThree'],
(e.data() as dynamic)['myFieldFour']))
.toList());
It is a typical null-safety related error. I did not try the code, by I guess that probably assigning asyncSnapshot.data.docs to listStaff possibly may return null, but your declared type List<DocumentSnapshot> doesn't allow for that. If you are 100% sure that this assignment won't return null value, you can add '!' to ensure compiler, that it will be List, so it will let you use methods. Although if you want this list to be nullabe, you can simply add '?' to show it, and than use '?.' to use methods. It works like: Check if the object is null and execute method on this method only if its not.

Show loading indicator /spinner when the page data isn't fully loaded from Firebase - Flutter

In my Flutter app, I am using ModalProgressHUD to show a spinner when I click on save buttons in my form screens and it stops spinner once data successfully writes to Firebase.
I have this screen that uses Listview.builder to display a list of all my expenses and I want to automatically show spinner as soon as the page displays, and to stop spinner once all the data from Firebase fully loads.
I need assistance in doing this. I've pasted excerpt of my code as shown below. Thanks in advance.
//class wide declaration
bool showSpinner = true;
Widget build(BuildContext context) {
ExpenseNotifier expenseNotifier = Provider.of<ExpenseNotifier>(context);
Future<void> _resfreshList() async {
expenseNotifier.getExpenses(expenseNotifier);
var expenseList = ExpenseNotifier.getExpenses(expenseNotifier);
if (expenseList != null) {
setState(() {
showSpinner = false;
});
}
return Scaffold(
body: ModalProgressHUD(
inAsyncCall: showSpinner,
child: RefreshIndicator(
onRefresh: _resfreshList,
child: Consumer<ExpenseNotifier>(
builder: (context, expense, child) {
return expense == null
? Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
PaddingClass(bodyImage: 'images/empty.png'),
SizedBox(
height: 20.0,
),
Text(
'You don\'t have any expenses',
style: kLabelTextStyle,
),
],
)
: ListView.separated(
itemBuilder: (context, int index) {
var myExpense = expense.expenseList[index];
return Card(
elevation: 8.0,
color: Colors.white70,
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
RegularExpenseTextPadding(
regText:
'${_formattedDate(myExpense.updatedAt)}',
),
Container(
margin: EdgeInsets.all(20.0),
padding: const EdgeInsets.all(15.0),
decoration: BoxDecoration(
borderRadius:
BorderRadius.all(Radius.circular(5.0)),
border: Border.all(
color: kThemeStyleBorderHighlightColour),
),
child: Row(
children: <Widget>[
Expanded(
flex: 5,
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Text(
'${myExpense.amount}',
style: kRegularTextStyle,
),
SizedBox(
height: 20.0,
),
Text(
myExpense.description,
style: kRegularTextStyle,
),
],
),
),
Expanded(
flex: 1,
child: GestureDetector(
onTap: () {
expenseNotifier.currentExpense =
expenseNotifier
.expenseList[index];
Navigator.of(context).push(
MaterialPageRoute(builder:
(BuildContext context) {
return ExpenseDetailsScreen();
}));
},
child: Icon(
FontAwesomeIcons.caretDown,
color: kThemeIconColour,
),
),
),
],
),
),
],
),
);
},
separatorBuilder: (BuildContext context, int index) {
return SizedBox(
height: 20.0,
);
},
itemCount: expenseNotifier.expenseList.length,
);
},
),
),
),
);
}
this is an example from my app:
bool _isLoading = false; <- default false
bool _isInit = true; <- to mae it only load once
#override
void initState() {
if (_isInit) {
// activating spinner
_isLoading = true;
// your function here <------
_isInit = false;
super.initState();
}
Initstate gets called before the user can see any kind of thin in your app, so this is the perfect place to make your firebase data load. with this logic from above the loading spinner shows as long you are receiving the data. And your body looks like the following then:
#override
Widget build(BuildContext context) {
return _isLoading <- is loading condition true? shows spinner
? Center(child: CircularProgressIndicator()) <- loading spinner
// else shows your content of the app
: SafeArea(
child: Container()
....

Flutter FutureBuilder show Progress indicator

I have a button which onpressed inserts something in DB and redirects user to another page. I am trying to implement FutureBuilder which should show CircularProgressIndicator until everything is done.
This is my function:
Future<bool> insertPhoneNumber(String phoneNumber) async {
String token = await getToken();
if (token.isNotEmpty) {
var body = jsonEncode({'token': token, 'userID': user.getUserID(), 'phoneNumber': phoneNumber});
print(body.toString());
var res = await http.post((baseUrl + "/insertPhoneNumber/" + user.getUserID()),
body: body,
headers: {
"Accept": "application/json",
"content-type": "application/json"
});
if (res.statusCode == 200) {
print("Insert Phone Number is OK");
notifyListeners();
return true;
} else {
print("Insert Phone Number not OK");
notifyListeners();
return false;
}
} else {
print("Insert Phone Number failed due to unexisting token");
}
}
and this is a button which triggers DB interaction:
RaisedButton(
color: Color.fromRGBO(105, 79, 150, 1),
onPressed: () {
var user = Provider.of<UserRepository>(context);
String completePhoneNumber = _selectedDialogCountry.phoneCode + phoneNumberController.text;
FutureBuilder(
future: user.insertPhoneNumber(completePhoneNumber),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return CircularProgressIndicator(
backgroundColor: Colors.blue);
} else {
return Dashboard();
}
},
);
},
textColor: Colors.white,
child: Text("SAVE"),
)
It updates DB but nothing else happens. There is no progress indicator nor redirection to Dashboard.
EDIT
This is my full build method:
#override
Widget build(BuildContext context) {
return Scaffold(
body: Container(
padding: EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
AutoSizeText(
'What is your phone number?',
style: TextStyle(fontSize: 30),
maxLines: 1,
),
Row(
children: <Widget>[
SizedBox(
width: 9.0,
child: Icon(Icons.arrow_downward),
),
SizedBox(width: 8.0),
SizedBox(
width: 120.0,
height: 65.0,
child: Card(
child: ListTile(
onTap: _openCountryPickerDialog,
title: _buildDialogItem(_selectedDialogCountry),
),
)),
Expanded(
child: TextField(
autofocus: true,
keyboardType: TextInputType.number,
decoration:
InputDecoration(border: OutlineInputBorder())),
),
],
),
SizedBox(
width: 100,
child: RaisedButton(
color: Color.fromRGBO(105, 79, 150, 1),
onPressed: () {
print("Test");
},
textColor: Colors.white,
child: Text("SAVE"),
),
)
],
)),
);
}
It is not working because the FutureBuilder isn't attached to the Widget tree.
The once the future is not in done state, it is just creating an instance Dashboard.You shouldn't be using FutureBuilder here instead you can set the child widget based on some variable and when future complete, you call setState on the that variable to update the state which will in-turn rebuild the widget with the new state value
Something like this
var isLoading = false;
void insertNumber(){
var user = Provider.of<UserRepository>(context);
String completePhoneNumber = _selectedDialogCountry.phoneCode + phoneNumberController.text;
setState(() => isLoading=true);
user.insertPhoneNumber(completePhoneNumber).then((result){
setState(() => isLoading=false);
})
}
Widget build(BuildContext context){
return RaisedButton(
color: Color.fromRGBO(105, 79, 150, 1),
onPressed: () {
var user = Provider.of<UserRepository>(context);
String completePhoneNumber = _selectedDialogCountry.phoneCode + phoneNumberController.text;
FutureBuilder(
future: user.insertPhoneNumber(completePhoneNumber),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return CircularProgressIndicator(
backgroundColor: Colors.blue);
} else {
return Dashboard();
}
},
);
},
textColor: Colors.white,
child: isLoading? CircularProgressIndicator(): Text("Save"),
);
}
For your use case, you will need more that two state to achieve the UI you are looking for .Example DEFAULT,LOADING,ERROR,SUCCESS

How to enable a button in Flutter,when dropdownbutton has value?

colleagues ! I'm noob in Flutter.This is my first app,and I'm trying to enable the button only when the user chooses a value from the dropdownbutton.
I was trying to find a similar question,but didn't find.
class Reserve extends StatefulWidget {
#override
_ReserveState createState() => _ReserveState();
}
class _ReserveState extends State<Reserve> {
var _category;
final formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text(
globals.salonname == "" ? "Reserve" : globals.salonname),
backgroundColor: Colors.deepOrange,
),
drawer: new Drawer(
child: new ListView(children: <Widget>[
new ListTile(
title: new Text("Close"),
trailing: new Icon(Icons.arrow_upward),
onTap: () {
Navigator.of(context).pop();
})
])),
body: Card(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Form(
key: formKey,
child: Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Calendar(
isExpandable: true,
onDateSelected: null,
),
Row(children: <Widget>[
new Container(
alignment: Alignment(1.0, 0.0),
child: new Center(
child: new StreamBuilder<QuerySnapshot>
(
stream: Firestore.instance
.collection('salons').document(
globals.salonkey).collection(
'employee')
.snapshots(),
builder: (context, snapshot) {
try {
if
(snapshot.data.documents.length ==
0) {
return new Text("No employees
found!");
}
else {
return new DropdownButton(
hint: new Text(
"Choose an employee"),
items: snapshot.data.documents
.map(
(
DocumentSnapshot document) {
return DropdownMenuItem(
value: document
.data["name"],
child: new Text(
document
.data["name"]));
}).toList(),
value: _category,
onChanged: (value) {
setState(() {
_category = value;
});
});
}
}
catch (ex) {
return new Text("Try again!");
}
})),
)
]),
new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
_buildButton(),
])
])))));
}
Widget _buildButton() {
return new RaisedButton(
padding: const EdgeInsets.all(8.0),
color: _category == true ? Colors.orangeAccent : Colors.grey,
child: Text("Choose employee"),
onPressed: _category==null ? null : () => setState(() => Navigator.of(context).push(
new MaterialPageRoute(
builder: (BuildContext context) =>
new ReserveTable()))));
}
}
So I want to enable the Raised button only,when an employee is chosen in dropdown button.
Thanks.
You will have to modify your code a little, if you need your button changes according to the Stream, move your StreamBuilder at your Column level and add a variable to check when the stream has data.
Here you have your code fixed:
class _ReserveState extends State<Reserve> {
var _category;
bool enableButton = false;
final formKey = GlobalKey<FormState>();
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title:
new Text(globals.salonname == "" ? "Reserve" : globals.salonname),
backgroundColor: Colors.deepOrange,
),
drawer: new Drawer(
child: new ListView(children: <Widget>[
new ListTile(
title: new Text("Close"),
trailing: new Icon(Icons.arrow_upward),
onTap: () {
Navigator.of(context).pop();
})
])),
body: Card(
child: Padding(
padding: EdgeInsets.all(8.0),
child: Form(
key: formKey,
child: new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance
.collection('salons')
.document(globals.salonkey)
.collection('employee')
.snapshots(),
builder: (context, snapshot) {
return Column(
mainAxisSize: MainAxisSize.max,
children: <Widget>[
new Calendar(
isExpandable: true,
onDateSelected: null,
),
Row(children: <Widget>[
new Container(
alignment: Alignment(1.0, 0.0),
child: new Center(
child: (!snapshot.hasData ||
snapshot.data.documents.length == 0)
? new Text("No employees found!")
: new DropdownButton(
hint:
new Text("Choose an employee"),
items: snapshot.data.documents.map(
(DocumentSnapshot document) {
return DropdownMenuItem(
value: document.data["name"],
child: new Text(
document.data["name"]));
}).toList(),
value: _category,
onChanged: (value) {
setState(() {
_category = value;
});
})))
]),
new Row(
mainAxisAlignment: MainAxisAlignment.end,
children: <Widget>[
_buildButton(),
])
]);
})),
)));
}
Widget _buildButton() {
return new RaisedButton(
padding: const EdgeInsets.all(8.0),
color: _category!=null ? Colors.orangeAccent : Colors.grey,
child: Text("Choose employee"),
onPressed: _category==null
? null
: () => setState(() => Navigator.of(context).push(
new MaterialPageRoute(
builder: (BuildContext context) => ReserveTable()))));
}
}

Stream builder from firestore to flutter

I am wondering how to get data from firestore to flutter app using the streambuilder. I created the necessary Boilerplate code I have the widget built and working and in the below code
headimageassetpath is nothing but a URL string which exists in the firestore.
#override
Widget build(BuildContext context) {
return Scaffold(
body:
new StreamBuilder(
stream: Firestore.instance.collection('Items').snapshots(),
builder: (_, AsyncSnapshot<QuerySnapshot> snapshot) {
var items = snapshot.data?.documents ?? [];
return new Lost_Card(
headImageAssetPath : snapshot.data.documents.map()(['url'],)
);
},
)
My firestore:
full code:
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class LostPage extends StatefulWidget {
#override
_LostPage createState() => new _LostPage();
}
class _LostPage extends State<LostPage> {
//temp vars
final String firebasetest = "Test";
//firestore vars
final DocumentReference documentReference =
Firestore.instance.document("Items/Rusty");
//CRUD operations
void _add() {
Map<String, String> data = <String, String>{
"name": firebasetest,
"desc": "Flutter Developer"
};
documentReference.setData(data).whenComplete(() {
print("Document Added");
}).catchError((e) => print(e));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body:
new StreamBuilder(
stream: Firestore.instance.collection('Items').snapshots(),
builder: (_, AsyncSnapshot<QuerySnapshot> snapshot) {
var items = snapshot.data?.documents ?? [];
return new Lost_Card(
headImageAssetPath : snapshot.data.documents.map()(['url'],)
);
},
)
/*new Lost_Card(
headImageAssetPath: "https://i.imgur.com/FtaGNck.jpg" ,
title: "Mega Dish",
noro: "old",
)*/,
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.add),
onPressed: _add),
);
}
}
class Lost_Card extends StatelessWidget
{
//All the card variables
final String headImageAssetPath;
final IconData icon;
final Color iconBackgroundColor;
final String title;
final String noro;
final int price;
final ShapeBorder shape;
Lost_Card({
this.headImageAssetPath, //used
this.icon,
this.iconBackgroundColor,
this.title, //used
this.noro, //used
this.price,
});
#override
Widget build(BuildContext context) {
// TODO: implement build
return GridView.count(
shrinkWrap: true,
crossAxisCount: 2,
children: <Widget>[
Card(
child: Column(
// mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: Stack(
fit: StackFit.expand,
children: <Widget>[
Container(
height: MediaQuery.of(context).size.height / 4,
width: MediaQuery.of(context).size.height / 2.5,
child: DecoratedBox(
decoration: BoxDecoration(
image: DecorationImage(
image: NetworkImage(
headImageAssetPath),
fit: BoxFit.cover),
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Align(
alignment: FractionalOffset.topLeft,
child: CircleAvatar(
backgroundColor: Colors.redAccent,
radius: 15.0,
child: Text(
noro,
textScaleFactor: 0.5,
),
),
),
),
Align(
alignment: FractionalOffset.topRight,
child: Container(
color: Colors.blueAccent,
height: 35.0,
width: 35.0,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Icon(Icons.account_circle),
Text(
"1P",
textScaleFactor: 0.5,
),
],
),
),
),
),
],
),
),
Center(
child: Container(
padding: const EdgeInsets.all(8.0),
alignment: FractionalOffset.bottomCenter,
child: Text(
title,
style: TextStyle(
fontWeight: FontWeight.w700,
),
),
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
FlatButton(
child: Text(
"Add To Cart",
style: TextStyle(color: Colors.grey[500]),
),
onPressed: () => null,
),
Text(
"\$5",
style: TextStyle(color: Colors.grey[500]),
)
],
)
],
),
),
],
);
}
}
Actual App
Please shed some light on this. Tks.
This should work for one item
body: new StreamBuilder(
stream: Firestore.instance.collection("collection").snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text(
'No Data...',
);
} else {
<DocumentSnapshot> items = snapshot.data.documents;
return new Lost_Card(
headImageAssetPath : items[0]["url"]
);
}
If you want to create list builder from many documents use it like this
return new ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
DocumentSnapshot ds = snapshot.data.documents[index];
return new Lost_Card(
headImageAssetPath : ds["url"];
);
Accessing documents using StreamBuilder in Flutter 2
StreamBuilder<QuerySnapshot>(
stream: FirebaseFirestore.instance.collection('products').snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data!.docs.length,
itemBuilder: (context, index) {
DocumentSnapshot doc = snapshot.data!.docs[index];
return Text(doc['name']);
});
} else {
return Text("No data");
}
},
)
As per new changes 2021 in Firebase FireStore you can retrieve data from collection using StreamBuilder as below
final _mFirestore = FirebaseFirestore.instance;
return StreamBuilder<QuerySnapshot>(
stream:
_mFirestore.collection(kFirebaseCollectionName).snapshots(),
builder: (context, snapshots) {
if (!snapshots.hasData) {
return Center(
child: Text('Data not available',),
);
}
final messages = snapshots.data.docs;
List<Text> textWidgets = [];
messages.forEach((element) {
final messageText = element['text'];
final messageSender = element['sender'];
final textWidget = Text('$messageText, $messageSender');
textWidgets.add(messageBubbleWidget);
});
},
);
Card buildItem(DocumentSnapshot doc) {
return Card(
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Text(
'name: ${doc.data['name']}',
style: TextStyle(fontSize: 24),
),
Text(
'todo: ${doc.data['todo']}',
style: TextStyle(fontSize: 20),
),
Text(
'Age: ${doc.data['age']}',
style: TextStyle(fontSize: 10),
),
SizedBox(
height: 12,
),
],
)
],
),
),
); }
For other persons who will face the same problem, the card and stream builder will represent a solution. The Widget has the Card just before it declaration and has inside the body the next part:
body: ListView(
padding: EdgeInsets.all(8),
children: <Widget>[
Form(
key: _formKey,
child: buildTextFormField(),
),
StreamBuilder<QuerySnapshot>(
stream: db
.collection('CRUD')
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(
children: snapshot.data.documents
.map((doc) => buildItem(doc))
.toList());
} else {
return SizedBox();
}
},
)
],
),
Update for 2022, Flutter 2.10, cloud_firestore: ^3.1.11. You can retrieve data from collection using StreamBuilder
Stream collectionStream = FirebaseFirestore.instance.collection('users').snapshots();
StreamBuilder<QuerySnapshot>(
builder: (context, snapshot) {
if (snapshot.hasData) {
final messages = snapshot.data!.docs;
List<Text> messageWidgets = [];
for (var element in messages) {
final messageText = element['text'];
final messageSender = element['sender'];
final messageWidget =
Text('$messageText from $messageSender');
messageWidgets.add(messageWidget);
}
return Column(
children: messageWidgets,
);
}
return const Text('Error');
},
stream:collectionStream),
StreamBuilder<List<UData>>(
stream: AdminData().getDrivers,
builder: (context, snapshot) {
return ListView(
children: snapshot.data.map((document) {
return hadCard(
widget: Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
hadText(title: document.name),
hadText(title: document.phone),
hadText(title: document.Driver),
],
),
);
}).toList(),
);
}),

Resources