I noticed my read requests increased significantly since I added a new Stream using StreamProvider, I managed to confirm this by removing the stream and no repeating requests where made. But I can't seem to track down why its repeating.
Note: The code functions fine, just these read requests have gone through the roof.
Stream
//get list of all open enquiries
Stream<List<EnquiryData>> get centreDashboardOpenEnquiries {
return centresCollection.document(centreID).collection('enquiries').where('enquiryStatus', whereIn: ['New', 'Contacted', 'Tour Scheduled', 'Tour Completed']).snapshots().map(_enquiryList);
}
//Map QuerySnapshot to List of EnquiryData
List<EnquiryData> _enquiryList(QuerySnapshot enquiry){
return enquiry.documents.map((doc) {
return EnquiryData(
enquiryID: doc.data['enquiryID'].toString(),
parentFirstName: doc.data['firstName'],
parentLastName: doc.data['lastName'],
parentPhoneNumber: doc.data['phoneNumber'],
parentEmail: doc.data['email'],
parentAddress: doc.data['address'],
methodOfEnquiry: doc.data['methodOfEnquiry'],
hearAboutUs: doc.data['hearAboutUs'],
specificHearAboutUs: doc.data['specificHearAboutUs'],
impressionOfCentre: doc.data['firstImpression'],
enquiryStatus: doc.data['enquiryStatus'],
created: doc.data['created'],
lastUpdated: doc.data['lastUpdated'],
);
}).toList();
}
Call Stream with CurrentOpenDataTable child
child: StreamProvider<List<EnquiryData>>.value(
value: EnquiryDatabaseService(centreID: widget.centreData.centreID).centreDashboardOpenEnquiries,
child: CurrentOpenDataTable(),
),
CurrentOpenDataTable()
class CurrentOpenDataTable extends StatefulWidget {
#override
_CurrentOpenDataTableState createState() => _CurrentOpenDataTableState();
}
class _CurrentOpenDataTableState extends State<CurrentOpenDataTable> {
#override
Widget build(BuildContext context) {
final enquiryData = Provider.of<List<EnquiryData>>(context) ?? [];
return DataTable(
showCheckboxColumn: false,
sortColumnIndex: 1,
sortAscending: true,
columns: [
DataColumn(
label: Text('Date'),
),
DataColumn(
label: Text('Name'),
//numeric: true,
),
DataColumn(
label: Text('Status'),
),
],
rows: _enquiryRow(context, enquiryData),
);
}
}
_dateFormat(DateTime dateTime) {
String formattedDate = DateFormat('dd/MM/yyyy').format(dateTime);
return formattedDate;
}
_enquiryRow(BuildContext context, List<EnquiryData> enquiryData) {
List<DataRow> listOfDataRows = List.generate(
enquiryData.length,
(index) => DataRow(
cells: [
DataCell(Text(_dateFormat(DateTime.fromMillisecondsSinceEpoch(
enquiryData[index].created)))),
DataCell(Text(enquiryData[index].parentFirstName +
' ' +
enquiryData[index].parentLastName)),
DataCell(Text(enquiryData[index].enquiryStatus)),
],
onSelectChanged: (bool selected) {
if (selected) {
showEnquiryDialog(context, enquiryData[index]);
}
}));
return listOfDataRows;
}
The widget (let's call it MyWidget) that contains the StreamProvider is constantly being rebuilt.
You shouldn't be creating the EnquiryDatabaseService inside StreamProvider<List<EnquiryData>>.value, instead, get the stream inside initState.
Example
class _MyWidgetState extends State<MyWidget> {
Stream<List<EnquiryData>> _stream;
#override
initState() {
_stream = EnquiryDatabaseService(centreID: widget.centreData.centreID). centreDashboardOpenEnquiries;
}
// ... in build
StreamProvider.value(
value: _stream,
child: Container(/* ... */),
),
}
Or don't use the .value constructor.
StreamProvider(
create: (_) => EnquiryDatabaseService(centreID: widget.centreData.centreID). centreDashboardOpenEnquiries,
child: Container(/* ... */),
),
Read more on the do's and don'ts here
More reference here - How to deal with unwanted widget build?
Related
I'm working on this activity-chart using firebase and I wanted to implement the logic for retrieving the data from Firebase. To get the data I wrote the following method:
static Future<double> spotData(String day, queryDocumentSnapshot) async {
final documentSnapshot = await FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid.toString())
.collection('activity')
.doc('${globals.year}')
.collection('week${globals.week}')
.doc(day.toString())
.get();
if (documentSnapshot.exists) {
print('Document exists');
return documentSnapshot['usage'].toDouble();
} else {
print('Document doesnt exist');
return 0;
}
// return 0; // you don't need this anymore
}
I thought it should work but turns out 0 is returned... I think that's because of the "return 0;" at the end of the method... I tried removing it but I can't since I the method could return null and flutter null safety, rightfully so, prevents that from happening... In my Console I get the Output "Document exists" - that's why I believe, that the basic if-statement logic works but I need some help with the return statement...
I'm very thankful for all kinds of help:)
class SomellierChart extends StatefulWidget {
SomellierChart(
{Key? key,
required this.color,
required this.textColor,
required this.queryDocumentSnapshot})
: super(key: key);
Color textColor;
Color color;
List<QueryDocumentSnapshot> queryDocumentSnapshot;
#override
_SomellierChartState createState() => _SomellierChartState();
}
class _SomellierChartState extends State<SomellierChart> {
static Future<double> spotData(String day, queryDocumentSnapshot) async {
final documentSnapshot = await FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid.toString())
.collection('activity')
.doc('${globals.year}')
.collection('week${globals.week}')
.doc(day.toString())
.get();
if (documentSnapshot.exists) {
print('Document exists');
return documentSnapshot['usage'].toDouble();
} else {
print('Document doesnt exist');
return 0;
}
}
#override
Widget build(BuildContext context) {
return LineChart(
LineChartData(
gridData: FlGridData(
show: false,
),
titlesData: FlTitlesData(
show: true,
rightTitles: SideTitles(showTitles: false),
topTitles: SideTitles(showTitles: false),
leftTitles: SideTitles(showTitles: false),
bottomTitles: SideTitles(
showTitles: true,
reservedSize: 20,
interval: 1,
getTextStyles: (context, value) => GoogleFonts.poppins(
textStyle: TextStyle(
color: Theme.of(context).indicatorColor,
fontWeight: FontWeight.w600,
fontSize: 12,
),
),
getTitles: (value) {
switch (value.toInt()) {
case 0:
return 'Mon';
case 2:
return 'Tue';
case 4:
return 'Wed';
case 6:
return 'Thu';
case 8:
return 'Fri';
case 10:
return 'Sat';
case 12:
return 'Sun';
}
return '';
},
margin: 5,
),
),
borderData: FlBorderData(show: false),
minX: 0,
maxX: 12,
minY: 0,
maxY: 3,
lineBarsData: [
LineChartBarData(
spots: [
FlSpot(0, spotData('1', widget.queryDocumentSnapshot)),
FlSpot(2, spotData('2', widget.queryDocumentSnapshot)),
FlSpot(4, spotData('3', widget.queryDocumentSnapshot)),
FlSpot(6, spotData('4', widget.queryDocumentSnapshot)),
FlSpot(8, spotData('5', widget.queryDocumentSnapshot)),
FlSpot(10, spotData('6', widget.queryDocumentSnapshot)),
FlSpot(12, spotData('7', widget.queryDocumentSnapshot)),
],
isCurved: true,
curveSmoothness: 0.5,
preventCurveOverShooting: true,
colors: [widget.textColor],
barWidth: 3,
isStrokeCapRound: true,
dotData: FlDotData(
show: false,
),
belowBarData: BarAreaData(
show: true,
colors: [
widget.color.withOpacity(0.4),
],
),
),
],
),
);
}
}
this is the code im currently using... I made it stateful because I thought maybe I could update the UI that way but I dont believe that's necessary... How should I implement the FutureBuilder?
You are making a request to firebase, all requests to firebase are asynchronous, meaning they might be processed immediately, or after a while or at some point or another, which is why you are using the .then.
So basically, your function runs, gets to the asynchronous calculation, continues, reaches the return 0. returns, and only then, the .then method calls itself and you get to work with the result, by that time, your function is long over and it already returned 0, because your original function is synchronous.
To fix this, you must make your original function asynchronous, and use the await keyword:
static Future<double> spotData(String day, queryDocumentSnapshot) async {
final documentSnapshot = await FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid.toString())
.collection('activity')
.doc('${globals.year}')
.collection('week${globals.week}')
.doc(day.toString())
.get();
if (documentSnapshot.exists) {
print('Document exists');
return documentSnapshot['usage'].toDouble();
} else {
print('Document doesnt exist');
return 0;
}
// return 0; // you don't need this anymore
}
The problem of course it that you now return a future instead of the double, to use the future on UI you probably will need a FutureBuilder
since Firebase was updated I have been having some issues, first it was because now, instead of a map, an Object? is returned and that is now fixed, but I can't seem to get any data from the database. I can put it there fine but the reading is not working.
This is on my firebase_utils .dart file
FirebaseDatabase data = FirebaseDatabase.instance;
DatabaseReference database = data.ref();
Future<void> init() async {
FirebaseAuth.instance.userChanges().listen((user) {
if (user != null) {
//_loginState = ApplicationLoginState.loggedIn;
} else {
//_loginState = ApplicationLoginState.loggedOut;
}
});
if (!kIsWeb) {
data.setPersistenceEnabled(true);
data.setPersistenceCacheSizeBytes(10000000);
}
}
I have this class:
class ReservationStreamPublisher {
Stream<List<Reservation>> getReservationStream() {
final stream = database.child('reservations').onValue;
final streamToPublish = stream
.map((event) {
List<Reservation> reservationList = [];
Map<String, dynamic>.from(event.snapshot.value as dynamic)
.forEach((key, value) => reservationList.add(Reservation.fromRTDB(value)));
print(reservationList);
return reservationList;
});
return streamToPublish;
}
}
Next is my Reservation.fromRTDB
factory Reservation.fromRTDB(Map<String, dynamic> data) {
return Reservation(
pin: data['pin'],
day: data['day'],
hour: data['hour'],
duration: data['duration'],
state: data['state'],
userEmail: data['client_email'],
completed: data['completed'],
id: data['id'],
dateMade: '',
timeMade: '');
}
And this is one of the places where I am supposed to show data
Text('Slots Reservados neste dia:'),
_selectedDate != null
? StreamBuilder(
stream:
ReservationStreamPublisher().getReservationStream(),
builder: (context, snapshot) {
final tilesList = <ListTile>[];
if (snapshot.hasData) {
List reservations =
snapshot.data as List<Reservation>;
int i = 0;
do {
if (reservations.isNotEmpty) {
if (reservations[i].day !=
(DateFormat('dd/MM/yyyy')
.format(_selectedDate!))) {
reservations.removeAt(i);
i = i;
} else
i++;
}
} while (i < reservations.length);
try {
tilesList
.addAll(reservations.map((nextReservation) {
return ListTile(
leading: Icon(Icons.lock_clock),
title: Text(
"Das ${nextReservation.hour} as ${nextReservation.duration}"),
);
}));
} catch (e) {
return Text(
'Ainda nao existem reservas neste dia');
}
}
// }
if (tilesList.isNotEmpty) {
return Expanded(
child: ListView(
children: tilesList,
),
);
}
return Text('Ainda nao existem reservas neste dia');
})
: SizedBox(),
I am not getting any error at the moment, but no data is returned.This is a Reservation example on my RTDB
I am doing a project where I have to keep an favorite icon and a selected fvrt list... Now using sqlflite .. I have done it.. when the user presses the favorite border icon it get changed to red color and the data saves in the favorite list.. when user pressses again in the same button .. the data gets delated from the list and the favorite button change to ist default color... but what i am not able to do is.. the favorite button is default false.. so even if the data is collected in the fvrt list .. all the fvrt button shows _fvrt default favorite btn when i start the app ...
i was wondering how can i check the data in the initState() , if the data already exit in database it fvrt btn will remain red..
here's a little code of the conditon that i haved used .
Widget _buildRow(String pair) {
final bool alreadySaved = _saved.contains(pair);
print("Already saved $alreadySaved");
print(pair);
return IconButton(
icon: new Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color:alreadySaved? Colors.red : Colors.white,
),onPressed: (){
setState(() {
if (alreadySaved) {
_saved.remove(pair);
_deleteEmployee(pair);
} else {
_saved.add(pair);
_insert(pair);
}
});
},
);
}
Reading data from your database is an async function - it takes some time. What you can do, is to create a loading state, and show a loading indicator, until the async function finishes.
import 'package:flutter/material.dart';
class MyClass extends StatefulWidget {
#override
_MyClassState createState() => _MyClassState();
}
class _MyClassState extends State<MyClass> {
bool isLoading = false;
List _saved = [];
#override
void initState() {
// Note that you cannot use `async await` in initState
isLoading = true;
_readFromDataBase().then((savedStuff) {
_saved = savedStuff;
isLoading = false;
});
super.initState();
}
#override
Widget build(BuildContext context) {
return !isLoading ? _buildRow("myPair") : CircularProgressIndicator();
}
Widget _buildRow(String pair) {
final bool alreadySaved = _saved.contains(pair);
print("Already saved $alreadySaved");
print(pair);
return IconButton(
icon: new Icon(
alreadySaved ? Icons.favorite : Icons.favorite_border,
color:alreadySaved? Colors.red : Colors.white,
),onPressed: (){
setState(() {
if (alreadySaved) {
_saved.remove(pair);
_deleteEmployee(pair);
} else {
_saved.add(pair);
_insert(pair);
}
});
},
);
}
}
Alternatively you can check the FutureBuilder Widget. Here is the official documentation: https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
I'm using a drop-down list (DropDown), whose elements are obtained from Firebase. The form works right, however when the internet connection is lost the Firebase Offline Persistence property doesn't work and the CircularProgressIndicator stays active. Reading some responses such as Using Offline Persistence in Firestore in a Flutter App, it is indicated that awaits should not be handled, however it is not clear to me how to achieve it:
class EstanqueAlimentarPage extends StatefulWidget {
#override
_EstanqueAlimentarPageState createState() => _EstanqueAlimentarPageState();
}
class _EstanqueAlimentarPageState extends State<EstanqueAlimentarPage> {
final formKey = GlobalKey<FormState>();
AlimentoBloc alimentoBloc = new AlimentoBloc();
AlimentoModel _alimento = new AlimentoModel();
AlimentarModel alimentar = new AlimentarModel();
List<AlimentoModel> _alimentoList;
bool _alimentoDisponible = true;
#override
void dispose() {
alimentoBloc.dispose();
super.dispose();
}
#override
void initState() {
_obtenerListaAlimentoUnaVez();
super.initState();
}
Future<void> _obtenerListaAlimentoUnaVez() async {
_alimentoList = await alimentoBloc.cargarAlimento(idEmpresa); // Await that I want to eliminate
if (_alimentoList.length > 0) { // Here appears a BAD STATE error when the internet connection goes from off to on
_alimento = _alimentoList[0];
_alimentoDisponible = true;
} else {
_alimentoDisponible = false;
}
_cargando = false;
setState(() {});
}
#override
Widget build(BuildContext context) {
return Form(
key: formKey,
child: Column(
children: <Widget> [
_crearTipoAlimento(_alimentoList),
SizedBox(height: 8.0),
_crearComentarios(),
]
)
),
_crearBoton('Guardar'),
}
Widget _crearTipoAlimento(List<AlimentoModel> lista) {
return Container(
decoration: _cajaBlanca,
child:
!_cargando // If it isn't loading, Dropdown must be displayed
? DropdownButtonFormField<AlimentoModel>(
decoration: InputDecoration(
labelText: 'Nombre del Alimento',
contentPadding: EdgeInsets.only(top:5.0),
prefixIcon: Icon(FontAwesomeIcons.boxOpen, color: Theme.of(context).primaryColor,),
border: InputBorder.none,
),
value: _alimento,
items: lista.map((AlimentoModel value) {
return DropdownMenuItem<AlimentoModel>(
child: Text(value.nombre),
value: value,
);
}).toList(),
onChanged: (_alimentoDisponible) ? (AlimentoModel _alimentoSeleccionado) {
print(_alimentoSeleccionado.nombre);
_alimento = _alimentoSeleccionado;
setState(() {});
} : null,
disabledHint: Text('No hay Alimento en Bodega'),
onSaved: (value) {
alimentar.idAlimento = _alimento.idAlimento;
alimentar.nombreAlimento = _alimento.nombreRef;
}
)
: Center (child: CircularProgressIndicator(strokeWidth: 1.0,))
);
}
Widget _crearComentarios() {
return TextFormField(
// -- DESIGN OTHER FIELDS -- //
onSaved: (value) {
alimentar.comentarios = value;
}
),
);
}
Widget _crearBoton(String texto) {
return RaisedButton(
// -- DESIGN -- //
onPressed: (_guardando) ? null : _submit,
),
);
}
void _submit() {
// CODE TO WRITE FORM IN FIREBASE
}
}
The function code from my BLOC is:
Future<List<AlimentoModel>> cargarAlimento(String idEmpresa, [String filtro]) async {
final alimento = await _alimentoProvider.cargarAlimento(idEmpresa, filtro); //It's one await more
_alimentoController.sink.add(alimento);
return alimento;
}
And the Query from PROVIDER is:
Future<List<AlimentoModel>> cargarAlimento(String idEmpresa, [String filtro]) async {
Query resp;
final List<AlimentoModel> alimento = new List();
resp = db.child('empresas').child(idEmpresa).child('bodega/1').child('alimento')
.orderByChild('cantidad').startAt(0.000001);
return resp.once().then((snapshot) {
if (snapshot.value == null) return [];
if (snapshot.value['error'] != null) return [];
snapshot.value.forEach((id, alim){
final temp = AlimentoModel.fromJson(Map<String,dynamic>.from(alim));
temp.idAlimento = id;
alimento.add(temp);
});
return alimento;
});
When using Firebase offline, you omit the await only on things that change the server (e.g., creating or updating a record). So you won't wait for the server to say "yes I wrote it", you assume that it's written.
In your case, however, you are not writing data, you are reading data. You will have to keep await in your example. The way you load your data has orderByChild and startAt, maybe those are preventing offline loading. Normally, you get it if it's already in the cache: https://firebase.google.com/docs/firestore/manage-data/enable-offline#get_offline_data
You mention a BAD STATE error, maybe if you provide that, we may be able to pinpoint the issue a bit better.
i am creating a flutter app where the user has a canvas which he can draw on and then he can save the canvas to Firestore and can recall and edit whenever he wants.I have seen tutorials in creating a canvas and drawing on it but i dont know how to save it to firebase, i have seen some say to convert it as an image and save it to firebase storage but after saving it as an image can the user recall and edit it , and is it possible to save all the points the user has drawn on canvas in the form of a list
Below is a code i am working on,
In this i am trying to save all the points in a list and update it to firebase
class _HomePageState extends State<HomePage> {
List<Offset> _points = <Offset>[];
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Container(
child: new GestureDetector(
onPanUpdate: (DragUpdateDetails details) {
setState(() {
RenderBox object = context.findRenderObject();
Offset _localPosition =
object.globalToLocal(details.globalPosition);
_points = new List.from(_points)..add(_localPosition);
});
},
onPanEnd: (DragEndDetails details) => _points.add(null),
child: new CustomPaint(
painter: new Signature(points: _points),
size: Size.infinite,
),
),
),
floatingActionButton: new FloatingActionButton(
child: new Icon(Icons.save),
onPressed: () => Firestore.instance.collection('points').document('XcX3MbUWt3hiBJyiPMIO'
).updateData({'points':FieldValue.arrayUnion(_points)})
),
);
}
}
class Signature extends CustomPainter {
List<Offset> points;
Signature({this.points});
#override
void paint(Canvas canvas, Size size) {
Paint paint = new Paint()
..color = Colors.blue
..strokeCap = StrokeCap.round
..strokeWidth = 10.0;
for (int i = 0; i < points.length - 1; i++) {
if (points[i] != null && points[i + 1] != null) {
canvas.drawLine(points[i], points[i + 1], paint);
}
}
}
#override
bool shouldRepaint(Signature oldDelegate) => oldDelegate.points != points;
}
It gives an error
Unhandled Exception: Invalid argument: Instance of 'Offset'
Is this method of saving possible of so how to do
I am able to print the offsets of the canvas