Widget should be refresh when its data belongs to StreamBuilder's stream in flutter - flutter

I am creating a simple EXPENSE MANAGER app
I have divided screen in two section
Top Section for showing Two card of TotalIncome and TotalExpense
and other section is showing All Transactions
Here, I have taken Streambuilder for showing all transaction, and with the help of this stream builder I have created Tow Global Variable totalincome and totalexpense
and showing total income and totalexpense to top section's Card
When I add any transaction, List of transaction refresh properly as it is due to Stream Builder but total income and expense card not refreshing...
here I want the proper way to do it...(
like creating a method that fetch records from firebase and store into a List and to use this list for various needs...
here Is my code
Widget headerSummary(Size size) {
return Container(
height: size.height * 0.15,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(30), bottomLeft: Radius.circular(30)),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: SummaryCard(
color: Colors.green,
amount: totalincome.toString(),
icondata: Icons.arrow_upward,
title: 'Income',
),
),
Expanded(
child: SummaryCard(
color: Colors.red,
amount: totalexpense.toString(),
icondata: Icons.arrow_downward,
title: 'Expense',
),
),
],
),
);
}
transaction
Widget showTransactions(Size size) {
return Container(
height: size.height * .65,
// color: Colors.red,
child: StreamBuilder(
stream: FirebaseFirestore.instance
.collection('users')
.doc(widget.loggeduser.userid)
.collection('expenses').orderBy("date",descending: true)
.snapshots(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.active) {
if (snapshot.hasData) {
QuerySnapshot querysnapshot =
snapshot.data as QuerySnapshot;
if (querysnapshot.docs.length > 0) {
List<Map<String, dynamic>> transactionlist = [];
for (int x = 0; x < querysnapshot.docs.length; x++) {
Map<String, dynamic> expensemap = querysnapshot.docs[x]
.data() as Map<String, dynamic>;
transactionlist.add(expensemap);
}
var x=transactionlist.where((element) => element['isexpense']==true).toList();
totalexpense=x.fold(0, (previousValue, element) => previousValue+element['amount']);
var y=transactionlist.where((element) => element['isexpense']==false).toList();
totalincome=y.fold(0, (previousValue, element) => previousValue+element['amount']);
//I have edited this lines...
return ListView.builder(
//reverse: true,
padding: EdgeInsets.symmetric(vertical: 10),
itemCount: transactionlist.length,
itemBuilder: (context, index) {
final trans=TransactionModel.fromjson(transactionlist[index]);
print(trans.toString());
return TransactionCard(
amount: trans.amount.toStringAsFixed(2),
datetime: trans.date.toString(),
paymentby: trans.paymentmode,
category: trans.category.title,
categoryicon: trans.category.iconurl,
isexpense: trans.isexpense,
);
});//listview end
} else {
return Container(
child: Center(
child: Text('No Transaction Found...')));
}
} else {
if (snapshot.hasError) {
return Text('error found');
} else {
return Text('empty..');
}
}
} else {
return Center(child: CircularProgressIndicator());
}
}),
);
}

StreamBuilder will refresh its child UI, not the upper widget.
You can use ValueNotifier with ValueListenableBuilder. This snippet will help you to clear the concept.
class MyHomePage extends StatefulWidget {
MyHomePage({
Key? key,
}) : super(key: key);
#override
_MyHomePageState createState() => _MyHomePageState();
}
int? myGlobalValue1;
ValueNotifier<int?> globalValueNotifier = ValueNotifier(null);
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("myGlobalValue1 $myGlobalValue1"),
ValueListenableBuilder<int?>(
valueListenable: globalValueNotifier,
builder: (context, value, child) {
return Text("globalValueNotifier $value");
}),
StreamBuilder<int>(
stream: Stream<int>.periodic(const Duration(seconds: 1), (x) => x)
.take(15),
builder: (BuildContext context, AsyncSnapshot snapshot) {
myGlobalValue1 = snapshot.data;
WidgetsBinding.instance.addPostFrameCallback((_) {
globalValueNotifier.value = snapshot.data; // to skip initOn frame build
});
return Column(
children: [
Text(
snapshot.data != null ? snapshot.data.toString() : "0",
),
Text("myGlobalValue1 inside streamB $myGlobalValue1"),
],
);
},
)
],
),
),
);
}
}

You could move StreamBuilder further up in the widget tree, so that showTransactions and headerSummary get built within the Stream Builder.
If you want to keep the layout as it is, then you could look into ValueNotifier to update Income and Expense variables when stream builder has an update.

Related

Flutter dropdownbutton changed value is not displaying

I'm new in flutter. I've created a simple project.
It is fetching documents of person collection from cloud firestore.
There is a modal screen to create new person document (it is opening When I touch the + button)
I have a problem In that modalBottomSheet
I can see the new value of department dropDownButton on the log screen but user interface are not changing.
I think it is related to 'context' but I couldn't solve the problem
Here is my code:
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
class Person extends StatefulWidget {
const Person({Key? key}) : super(key: key);
#override
_PersonState createState() => _PersonState();
}
class _PersonState extends State<Person> {
final TextEditingController _nameController = TextEditingController();
final CollectionReference _person = FirebaseFirestore.instance.collection('person');
final CollectionReference _department = FirebaseFirestore.instance.collection('department');
String? _usersDeptName;
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
Expanded(
child: StreamBuilder(
stream: _person.snapshots(),
builder: (context, AsyncSnapshot<QuerySnapshot> streamSnapshot) {
if (streamSnapshot.hasData) {
return ListView.builder(
itemCount: streamSnapshot.data!.docs.length,
itemBuilder: (context, index) {
final DocumentSnapshot documentSnapshot = streamSnapshot.data!.docs[index];
return Card(
margin: const EdgeInsets.all(10),
child: ListTile(
title: Text(documentSnapshot['personName']),
subtitle: Text(documentSnapshot['departmentName'] ?? '?'),
),
);
},
);
}
return const Center(
child: CircularProgressIndicator(),
);
},
),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => {_create()},
child: const Icon(Icons.add),
),
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat);
}
Future<void> _create() async {
_usersDeptName = null;
await showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext ctx) {
return Padding(
padding: EdgeInsets.only(top: 20, left: 20, right: 20, bottom: MediaQuery.of(context).viewInsets.bottom + 20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextField(controller: _nameController, decoration: InputDecoration(labelText: 'person_name'.tr())),
const SizedBox(height: 10),
StreamBuilder<QuerySnapshot>(
stream: _department.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Text("loading").tr();
} else {
List<DropdownMenuItem> departments = [];
int? howManyRecords = snapshot.data?.size;
for (int i = 0; i < howManyRecords!; i++) {
DocumentSnapshot snap = snapshot.data?.docs[i] as DocumentSnapshot<Object?>;
departments.add(DropdownMenuItem(child: Text(snap.get('departmentName')), value: snap.get('departmentName')));
}
return Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Expanded(
child: DropdownButton(
value: _usersDeptName,
items: departments,
onChanged: (newValue) {
setState(() {
_usersDeptName = newValue.toString();
print('$_usersDeptName is selected');
});
},
isExpanded: true,
),
),
],
);
}
},
),
ElevatedButton(
child: const Text('save').tr(),
onPressed: () async {
final String name = _nameController.text;
if (name != null) {
await _person.add({"personName": name, 'departmentName': _usersDeptName});
_nameController.text = '';
Navigator.of(context).pop();
}
},
)
],
),
);
},
);
}
}
Try using StatefulBuilder to update the bottomSheet state.
Future<void> _create() async {
_usersDeptName = null;
await showModalBottomSheet(
isScrollControlled: true,
context: context,
builder: (BuildContext ctx) {
return StatefulBuilder(
builder: (context, setState) => Padding(
padding: EdgeInsets.only(
If you like to change the widget-state(main UI) at the same time, you can rename the StatefulBuilder's setState and call both on onChanged.
when setting up the value for the _usersDeptName, you are converting it to string, this is not right because now the items and the selected items is 2 different thing,
so if you want it to be the department name, then when setting up the value for _usersDeptName, make it bind to the department name:
example
setState(() {
_usersDeptName = newValue.departmentName;
print('$_usersDeptName is selected');
});

Flutter - Provider - Add Map to List Replacing

Here I need to create a list of maps.
I'm using provider to keep data.
But when I commanded to list.add() it also replacing the 1st element.
Here's my code.
I'm fetching data from firestore collection.
add_inv_stream.dart
class AddInvStream extends StatelessWidget {
const AddInvStream({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
FirebaseServices services = FirebaseServices();
final provider = Provider.of<InventoryProvider>(context);
return StreamBuilder<QuerySnapshot>(
stream: services.inventory
.where('fishType', isEqualTo: provider.inventoryData['fishType'])
.snapshots(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Center(child: Text('Something wrong!'));
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}
if (snapshot.data!.size == 0) {
return const Center(
child: Text('No inventories'),
);
}
return AddInvData(snapshot: snapshot, services: services);
},
);
}
}
add_inv_data.dart
class AddInvData extends StatefulWidget {
final AsyncSnapshot<QuerySnapshot<Object?>> snapshot;
final FirebaseServices services;
const AddInvData({Key? key, required this.snapshot, required this.services})
: super(key: key);
#override
State<AddInvData> createState() => _AddInvDataState();
}
class _AddInvDataState extends State<AddInvData> {
List abc = [];
#override
Widget build(BuildContext context) {
final providerr = Provider.of<InventoryProvider>(context);
return ListView.builder(
padding: const EdgeInsets.all(15.0),
physics: const ScrollPhysics(),
shrinkWrap: true,
itemCount: widget.snapshot.data!.size,
itemBuilder: (context, index) {
Map<String, dynamic> sellerData =
widget.snapshot.data!.docs[index].data() as Map<String, dynamic>;
return InkWell(
onTap: () {
print('Date: ${sellerData['date']} | QTY: ${sellerData['qty']}');
showDialog(
context: context,
builder: (context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40),
),
elevation: 16,
child: Form(
key: _formkey,
child: Container(
padding: const EdgeInsets.all(20),
child: TextButton(
onPressed: () {
setState(() {
providerr.getInvSellerData(
sellerDate1: sellerData['date'],
sellerQty1: sellerData['qty'],
);
});
print(providerr.inventorySellerData);
abc.add(providerr.inventorySellerData);
print(abc);
},
child: const Text('ASSIGN'),
),
),
),
);
},
);
},
child: Padding(
padding: const EdgeInsets.fromLTRB(10, 0, 10, 30),
child: Container(
padding: const EdgeInsets.all(15),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(15),
border: Border.all(color: Colors.black),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(sellerData['date']),
Text('${sellerData['qty']} kg'),
],
),
),
),
);
},
);
}
}
inv_provider.dart
class InventoryProvider with ChangeNotifier {
Map<String, dynamic> inventorySellerData = {};
getInvSellerData({
String? sellerDate1,
String? sellerQty1,
}) {
if (sellerDate1 != null) {
inventorySellerData['sellerDate1'] = sellerDate1;
}
if (sellerQty1 != null) {
inventorySellerData['sellerQty1'] = sellerQty1;
}
notifyListeners();
}
}
There are only two collections I have created and when I tapped on first container it's printingDate: 10/31/2022 | QTY: 50and a dialog is showing.
Then I clicked assign button it's printing
{sellerDate1: 10/31/2022, sellerQty1: 50}
[{sellerDate1: 10/31/2022, sellerQty1: 50}]
After that I clicked on second container it's printing
Date: 11/25/2022 | QTY: 54and a dialog is showing same as first container.
Then I clicked assign button it's printing
{sellerDate1: 11/25/2022, sellerQty1: 54}
[{sellerDate1: 11/25/2022, sellerQty1: 54}, {sellerDate1: 11/25/2022, sellerQty1: 54}]
I need to get printed when clicked assign button
[{sellerDate1: 10/31/2022, sellerQty1: 50}, {sellerDate1: 11/25/2022, sellerQty1: 54}]
How can I do that?
Why is this replacing all the elements in the list?
how about add map to list abc outside of dialog.
onTap: () {
print('Date: ${sellerData['date']} | QTY: ${sellerData['qty']}');
showDialog(
context: context,
builder: (context) {
return Dialog(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(40),
),
elevation: 16,
child: Form(
key: _formkey,
child: Container(
padding: const EdgeInsets.all(20),
child: TextButton(
onPressed: () {
// no need call setState. bacause you already update state by provider
providerr.getInvSellerData(
sellerDate1: sellerData['date'],
sellerQty1: sellerData['qty'],
);
print(providerr.inventorySellerData);
},
child: const Text('ASSIGN'),
),
),
),
);
},
);
// add here
abc.add(providerr.inventorySellerData);
print(abc);
},

How to pass Stream data through Navigator in Flutter? (Flutter, Dart, Stream Firebase)

I'm building a chatapp which displays all the available chats as ChatTiles.
A ChatTile shows the name and the last message from the chat using a Stream from Firebase.
By clicking on a ChatTile the Navigator pushes a ConversationScreen Widget.
I would like to somehow pass the stream data along the widget tree to the ConversationScreen. So whenever a message is inserted, the ChatTile shows the last message and the ConversationScreen as well.
But my error is this:
Stream has already been listened to.
Here's a picture:
ChatTiles
ChatTile:
class _ChatRoomTileState extends State<ChatRoomTile> {
Stream<QuerySnapshot> chatRoomStream;
DataFromMessages dataFromMessages;
List<Message> messages;
#override
void initState() {
chatRoomStream =
DB_Service.streamChatRooms(context, widget.dataFromChatRoom.chatRoomId);
getUser();
super.initState();
}
#override
Widget build(BuildContext context) {
if (chatRoomStream == null) return Container();
return StreamProvider<QuerySnapshot>.value(
value: chatRoomStream,
initialData: null,
builder: (context, f) {
QuerySnapshot snapshot = Provider.of<QuerySnapshot>(context);
if (snapshot == null) return Container();
dataFromMessages =
dataFromMessagesFromJson(json.encode(snapshot.docs.first.data()));
messages = dataFromMessages.messages.entries
.map((e) => e.value)
.toList()
.reversed
.toList();
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => ConversationScreen(
dataFromChatRoom: widget.dataFromChatRoom,
uid: widget.uid,
chatRoomStream: chatRoomStream,
),
),
);
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 8),
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
border: Border.all(color: Colors.grey, width: 0.5),
borderRadius: BorderRadius.circular(8),
),
child: Row(
...
),
),
);
},
);
}
}
The pushed ConversationScreen:
class _ConversationScreenState extends State<ConversationScreen> {
TextEditingController messageController = TextEditingController();
DataFromMessages dataFromMessages;
List<Message> messages;
#override
Widget build(BuildContext context) {
print("Building ConversationScreen");
if (widget.chatRoomStream == null) return Container();
return StreamBuilder(
stream: widget.chatRoomStream.asBroadcastStream(),
initialData: null,
builder: (context, snap) {
//QuerySnapshot snap = Provider.of<QuerySnapshot>(context);
if (snap == null) return Text(" empty");
dataFromMessages = dataFromMessagesFromJson(
json.encode(snap.data.docs.first.data()));
messages = dataFromMessages.messages.entries
.map((e) => e.value)
.toList()
.reversed
.toList();
return GestureDetector(
onTap: () => FocusScope.of(context).unfocus(),
child: Scaffold(
appBar: buildAppBar(context),
body: Column(
children: [
Expanded(
child: messages != null
? ListView.builder(
shrinkWrap: true,
itemCount: messages.length,
itemBuilder: (context, index) {
return ChatTile(
data:
messages[index].data == widget.uid ? 1 : 0,
message: messages[index].message,
timestamp: messages[index].timestamp,
bubbleNip: BubbleNip.no,
);
})
: Container(),
),
TextInput(
uid: widget.uid,
chatRoomId: widget.dataFromChatRoom.chatRoomId,
messages: messages,
),
],
),
bottomNavigationBar: const SizedBox(height: 50),
),
);
});
}
AppBar buildAppBar(BuildContext context) {
return AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Text(
"Chat with Somebody",
style: TextStyle(
color: Theme.of(context).indicatorColor,
),
),
leading: BackButton(color: Theme.of(context).indicatorColor),
);
}
}

Passing data to another screen with Flutter Provider

I'm trying to pass the data to another screen using Provider, but it seems I'm always passing on the same data unless I sort the List and then pass the different data (meaning I'm probably switching the index by sorting the list so that is why it's passing different data now). In short, I call the API, populate the list, setting up the provider too for the next page, and on click I list out the the information from the previous screen, but the problem is I display the same item always unless I sort the list. Here is the code:
Calling the API and displaying the list:
var posts = <RideData>[];
var streamController = StreamController<List<RideData>>();
#override
void initState() {
_getRideStreamList();
super.initState();
}
_getRideStreamList() async {
await Future.delayed(Duration(seconds: 3));
var _vehicleStreamData = await APICalls.instance.getRides();
var provider = Provider.of<RideStore>(context, listen: false);
posts = await _vehicleStreamData
.map<RideData>((e) => RideData.fromJson(e))
.toList();
streamController.add(posts);
provider.setRideList(posts, notify: false);
}
bool isSwitched = true;
void toggleSwitch(bool value) {
if (isSwitched == false) {
posts.sort((k1, k2) => k1.rideId.compareTo(k2.rideId));
} else {
posts.sort((k1, k2) => k2.rideId.compareTo(k1.rideId));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
scrollDirection: Axis.vertical,
child: Column(
children: [
TextButton(
child: Text('sort ascending'),
onPressed: () {
setState(() {
toggleSwitch(isSwitched = !isSwitched);
});
}),
Container(
height: 1000,
child: StreamBuilder<List<RideData>>(
initialData: posts,
stream: streamController.stream,
builder: (context, snapshot) {
return ListView.builder(
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Column(
children: [
Row(
children: [
Padding(
padding: const EdgeInsets.only(left: 15.0),
child: Text(
'Ride #${snapshot.data[index].rideId}',
),
),
FlatButton(
textColor: Colors.blue[700],
minWidth: 0,
child: Text('View'),
onPressed: () {
// here is where I pass the data to the RideInfo screen
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => RideInfo(
rideId: snapshot
.data[index].rideId,
)));
},
),
],
),
Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
Text(
'${snapshot.data[index].pickupTime}',
),
Text(
'${snapshot.data[index].jobArrived}',
),
],
),
],
);
},
);
}),
),
],
),
),
),
);
}
After pressing the View button and passing the data to another screen (RideInfo):
class RideInfo extends StatefulWidget {
static const String id = 'ride_info_screen';
String rideId;
RideInfo({#required this.rideId});
#override
_RideInfoState createState() => _RideInfoState();
}
class _RideInfoState extends State<RideInfo> {
String rideID = '';
#override
void initState() {
super.initState();
setState(() {
rideID = widget.rideId;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.white,
title: Text(
'Ride #$rideID',
),
),
body: SafeArea(
child: SingleChildScrollView(
child: Consumer<RideStore>(
builder: (context, rideStore, child) {
return Column(
children: [
ListView.builder(
shrinkWrap: true,
itemCount: 1,
itemBuilder: (context, index) {
RideData rides = rideStore.getRideByIndex(index);
return Column(
children: [
Expanded(
flex: 2,
child: Column(
children: [
Text(
"PICK UP",
),
// here I display the pickUpTime but it is always the same and I wanted to display the time based on the ID
Text(
'${rides.pickupTime}AM',
),
],
),
),
],
);
}),
],
);
},
),
),
),
);
}
}
The data (pickUpTime in this case) doesn't change when I press to see the View of a single item, but like I said, when I change the order of the list with the sort method, then I get the different data.
Here is the Provider model:
class RideStore extends ChangeNotifier {
List<RideData> _rideList = [];
List<RideData> get rideList => _rideList;
setRideList(List<RideData> list, {bool notify = true}) {
_rideList = list;
if (notify) notifyListeners();
}
RideData getRideByIndex(int index) => _rideList[index];
int get rideListLength => _rideList.length;
}
How do I display the correct information based on the ID from the List that I pressed and passed in the Ride Info screen so it doesn't give back always the same data? Thanks in advance for the help!
The offending code is in RideInfo:
ListView.builder(
shrinkWrap: true,
itemCount: 1,
itemBuilder: (context, index) {
RideData rides = rideStore.getRideByIndex(index);
The index is always 1, so you are always showing the first RideData. There are various options to fix it, e.g. pass the index, or even pass the RideData, to the RideInfo constructor:
class RideInfo extends StatefulWidget {
static const String id = 'ride_info_screen';
String rideId;
final int index;
RideInfo({#required this.rideId, #required this.index, Key key})
: super(key: key) {
and:
RideData rides = rideStore.getRideByIndex(widget.index);
I have some additional comments on the code. Firstly, the ListView is serving no purpose in RideInfo, so remove it.
Secondly, there is no need to construct the streamController and to use StreamBuilder in the parent form. Your list is available in the RideStore. So your parent form could have:
Widget build(BuildContext context) {
var data = Provider.of<RideStore>(context).rideList;
...
Container(
height: 1000,
child:
// StreamBuilder<List<RideData>>(
// initialData: posts,
// stream: streamController.stream,
// builder: (context, snapshot) {
// return
ListView.builder(
shrinkWrap: true,
itemCount: data.length,
I hope these comments help.
Edit:
It is simple to edit your code to use FutureBuilder. Firstly, make _getRideStreamList return the data it read:
_getRideStreamList() async {
...
return posts;
}
Remove the call to _getRideStreamList in initState and wrap the ListView in the FutureBuilder that invokes _getRideStreamList:
Container(
height: 1000,
child: FutureBuilder(
future: _getRideStreamList(),
builder: (ctx, snapshot) {
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else {
var data = snapshot.data;
return ListView.builder(
...
);
}
},
),
),
This displays the CircularProgressIndicator while waiting for the data.
Note that this is a quick hack - you do not want to read the data everytime that the widget rebuilds. So _getRideStreamList could check if the data has already been read and just return it rather than rereading.

problems when displaying information in a ListView.builder

I am trying to show some information brought from firebase in web flutter but I have problems implementing FutureBuilder, since it does not show the information, the widget is not rendering anything the page is completely blank, attached I send the complete code of the page that is presenting the problem since I have followed all the tutorials that I have found but I can not make it show the data, in the same way I also attach the Json that returns the complement that I am using to obtain the data from firebase, I hope for the collaboration, thank you very much
Complete Code
class DriverPages extends StatefulWidget {
static final routeName = 'DriverPage';
DriverPages({Key key}) : super(key: key);
#override
_DriverPagesState createState() => _DriverPagesState();
}
DatabaseRef driverRef =
FirebaseDatabaseWeb.instance.reference().child('drivers');
class _DriverPagesState extends State<DriverPages> {
GlobalKey<ScaffoldState> scaffoldKey = GlobalKey<ScaffoldState>();
List prueba;
Future<List> driverItem() async {
DatabaseSnapshot snapshot = await driverRef.once();
Map<String, dynamic> json = snapshot.value;
return json.values.toList();
}
Widget driverList() {
return FutureBuilder<List>(
future: driverItem(),
builder: (contetx, snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
final value = snapshot.data[index];
return ListTile(
title: Text(value['fullname']),
subtitle: Text(value['email']),
);
},
);
}
},
);
}
#override
Widget build(BuildContext context) {
if (userSnapshot != null) {
return Scaffold(
key: scaffoldKey,
drawer: Container(
width: 320,
color: Colors.white,
child: Drawer(
child: PatimovilMenu(),
),
),
body: Container(
width: double.infinity,
height: double.infinity,
padding: EdgeInsets.symmetric(
vertical: 25,
horizontal: 30,
),
child: Column(
children: <Widget>[
PatimovilHeader(
onPressed: () {
scaffoldKey.currentState.openDrawer();
},
titlePage: 'Driver Manager',
),
Container(
width: MediaQuery.of(context).size.width,
height: (MediaQuery.of(context).size.height) - 260,
alignment: Alignment.center,
child: Center(
child: driverList(),
),
),
PatimovilFooder(),
],
),
),
);
} else {
Navigator.pushNamedAndRemoveUntil(
context,
LoginPage.routeName,
(route) => false,
);
}
}
}
This is the Json that returns the dependency I am using
{VOVQv28SU2c0GgXJKcSp8UFchPz2:
{email: bibiana206#gmail.com, fullname: bibiana, identification: e8104970, phone: 2095770, status: Waiting,
vehicle_details:
{car_brand: Ferrari, car_color: rojo, car_model: cretta, car_plate: az12}
},
dBncphEFOZbzGEt5qn44sD9BYVK2:
{email: bolivia20192019#gmail.com, fullname: Gustavo Barrios, identification: 14326048, phone: 60697350, status: Active,
vehicle_details:
{car_brand: Renault, car_color: Gris, car_model: Duster, car_plate: AY6787,}
}
}
I can't get the data to show me, it doesn't render anything
You should check the current state of the future as there are four connection states:
ConnectionState.none
ConnectionState.waiting
ConnectionState.active
ConnectionState.done
You were returning a widget only when snapshot.hasData which will occur only when the connection state is done & there are no errors.
In all other cases, your method driverList will be returning nothing.
So, update your method as follows:
Widget driverList() {
return FutureBuilder<List>(
future: driverItem(),
builder: (contetx, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return Center(child: Text('Something went wrong'));
}
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
final value = snapshot.data[index];
return ListTile(
title: Text(value['fullname']),
subtitle: Text(value['email']),
);
},
);
}
}
return Center(child: CircularProgressIndicator());
},
);
}