Retrieve realtime data from firebase - firebase

I have a flutter project set up to receive moisture sensor data from firebase. I am uploading data to firebase and trying to retrieve it on my app but it only retrieves data after some time. Moreover, the value is null till then. And also, when changed there is no change in data on the app. How do I get the data so that it shows data from the moment I start the app and changes when data on the firebase updates? Here is my code.
double moistureData;
#override
bool _isLoading = false;
void initState() {
super.initState();
databaseReference.child('Data').once().then((DataSnapshot snapshot) {
int moisture = snapshot.value['Moisture'];
moistureData = moisture.toDouble();
print(moisture);
setState(() {
if (moistureData != null) {
_isLoading = true;
}
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Moisture'),
),
body: StreamBuilder(
stream: databaseReference.child('Data').child('Moisture').onValue,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return CircularProgressIndicator();
} else {
return Center(
child: Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
Text('$moistureData'),
RaisedButton(
child: Text('Refresh'),
onPressed: () {
setState(() {
databaseReference
.child('Data')
.once()
.then((DataSnapshot snapshot) {
int moisture = snapshot.value['Moisture'];
moistureData = moisture.toDouble();
});
});
},
)
],
),
),
);
}
}));

of course, it takes some time to load data from firebase, create one bool variable and set it false.
double moistureData;
bool _isLoading=false;
#override
void initState() {
super.initState();
databaseReference.child('Data').once().then((DataSnapshot snapshot) {
final int moisture = snapshot.value['Moisture'];
moistureData=moisture.toDouble();
print(moisture);
setState((){
if(moistureData!=null)
_isLoading=true;
});
});
}
And inside your widget pass, circular progress indicator or an empty container when _isLoading is false. when it's true called your widget inside else.

To listen for both the current value and updates, you need to observe one of the on... stream properties of the query object. The closest to your current code is onValue stream.
An example from the FlutterFire example app:
_counterRef.onValue.listen((Event event) {
setState(() {
_error = null;
_counter = event.snapshot.value ?? 0;
});
}
So you see that this also uses the setState(...) that #satish's answer explains.

Related

How to retrieve a Firebase Storage image stream in flutter?

I've got a few photo's I've uploaded into my firebase storage under a file called 'photos' and I want to be able to retrieve them onto my app through a stream. I have done this before through Firebase cloud database by tapping into the Firestore.instance.collection('messages').snapshots() property in my StreamBuilder, but I don't know how to access the firebase storage snapshots and upload them as a stream into my app.
This was my code for the messages snapshot, I hope it helps:
final _firestore = Firestore.instance;
void messagesStream() async {
await for (var message in _firestore.collection('messages').snapshots()){
for (var snapshot in message.documents){
print(snapshot.data);
}
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('messages').snapshots(),
builder: (context, snapshot){
if (!snapshot.hasData){
return Center(
child: CircularProgressIndicator(backgroundColor: Colors.lightBlueAccent,),
);
} else {
final messages = snapshot.data.documents;
List<Text> messageWidgets = [];
for (var message in messages){
final messageText = message.data['text'];
final messageSender = message.data['sender'];
final messageWidget = Text('$messageText from $messageSender');
messageWidgets.add(messageWidget);
}
return Column(children: messageWidgets,);
}
}
),
),
},
So I figured out you can't create a stream from the firebase storage, but what I could do was, in my firebase cloud database, start a new collection called 'my_collection' and in a new document, create an auto-ID, with a field called 'image' which is a string, with an http reference to an image that is on the internet, or one you can upload to the internet (this is what I did on imgur.com, credit to them)! Here is my code below, I hope it helps others! If it doesn't, have a look at this code written by iampawan, he helped me a tonne!
https://github.com/iampawan/FlutterWithFirebase
class MyList extends StatefulWidget {
#override
_MyListState createState() => _MyListState();
}
class _MyListState extends State<MyList> {
StreamSubscription<QuerySnapshot> subscription;
List <DocumentSnapshot> myList;
final CollectionReference collectionReference = Firestore.instance.collection('my_collection');
final DocumentReference documentReference = Firestore.instance.collection('my_collection').document('GFWRerw45DW5GB54p');
#override
void initState() {
super.initState();
subscription = collectionReference.snapshots().listen((datasnapshot) {
setState(() {
myList = datasnapshot.documents;
});
});
}
#override
void dispose() {
subscription?.cancel();
super.dispose();
}
#override
Widget build(BuildContext context) {
return myList != null ?
ListView.builder(
itemCount: myList.length,
itemBuilder: (context, index){
String imgPath = myList[index].data['image'];
return MyCard(assetImage: Image.network(imgPath), function:
(){
if (imgPath == myList[0].data['image']){
Navigator.pushNamed(context, MyMenu.id);
} else if (imgPath == myList[1].data['image']){
Navigator.pushNamed(context, YourMenu.id);
} else if (imgPath == myList[2].data['image']){
Navigator.pushNamed(context, HisMenu.id);
} else if (imgPath == myList[3].data['image']){
Navigator.pushNamed(context, HerMenu.id);
}
},);
})
: Center(child: CircularProgressIndicator(),
);
}
}
Just to note, MyCard is it's own page with it's own constructor that requires an assetImage and a function for the user to be pushed to a new screen:
MyCard({#required this.assetImage, #required this.function});
final Image assetImage;
final Function function;

Getting null value (Firestore query result) in Flutter app

I have an application and In this i'm making a query for get user details by the e-mail account.
I'm using Future class to get data and fill my variable but the widget Text always show null value.
Please let me now if i am doing something wrong.
class _HomePageAppState extends State<HomePageApp> {
String _emailUsuario;
Usuario usuario;
void initState() {
super.initState();
Autenticacao().getCurrentUser().then((user) {
setState(() {
if (user != null) {
_emailUsuario = user.email.toString(); //the user email is returnig correctly
recuperarDadosUsuarioFirebase().then((ds) {
usuario = Usuario(
email: _emailUsuario,
nome: ds['nome'] != null ? ds['nome'] : null,
);
});
}
});
});
}
Future<DocumentSnapshot> recuperarDadosUsuarioFirebase() async {
DocumentSnapshot ds;
await Firestore.instance
.collection('usuarios')
.document(_emailUsuario)
.get()
.then((DocumentSnapshot _ds) {
ds = _ds;
});
return ds;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Container(
color: Colors.white10,
child: ListView(
children: <Widget>[
Text('Bem vindo ${usuario.nome} !!!'),
],
),
),
);
}
}
U might want to use Future Builder for such async work cause build method was called before usuario is assign so like this :
FutureBuilder(
future: getCurrentUser(),
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.data == null) {
return Center(child: CircularProgressIndicator());
}
// after getting data
},
);
Method getCurrentUser() needs to be created :)

How can I Paginate Firestore Data in ListView.Builder and Still Get Realtime Updates in Flutter?

I can't figure out how to paginate Firestore data without breaking the realtime listener. The data is passed to a StreamBuilder and displayed in a ListView.builder. I'm trying to fetch the next set of data when the user reaches maxScrollExtent.
I understand how to to use startAfter and limit with Firestore to paginate. If I pass the fetched data to a StreamController and use that in the StreamBuilder, pagination works fine but Firestore doesn't send any updates to the device.
If I pass Firestore.instance.(...).snapshots() directly to the StreamBuilder (without using a StreamController), then I get updates from the server, but pagination is all screwed up. The UI is rebuilt and I'm sent to the top of the list.
Using StreamController
final int limit = 2;
final _list = List<DocumentSnapshot>();
final _listController = StreamController<List<DocumentSnapshot>>.broadcast();
DateTime startAt;
bool _isAllDataFetched = false;
Stream<List<DocumentSnapshot>> get listStream => _listController.stream;
void initState() {
super.initState();
_eventDao
?.getAllEventMedia(event?.id ?? "",
startAfter: startAt?.millisecondsSinceEpoch, limit: limit)
?.then((QuerySnapshot querySnapshot) {
_list.addAll(querySnapshot.documents);
_listController.sink.add(_list);
})?.catchError((error) {
print(error);
});
}
Widget build(BuildContext context) {
return StreamBuilder<List<DocumentSnapshot>>(
stream: listStream,
builder: (BuildContext context, AsyncSnapshot<List<DocumentSnapshot>> snapshot) {
if (snapshot.hasError) {
return ErrorPage(imageAsset: AssetResources.failed);
}
if (snapshot.hasData &&
snapshot.data.isNotEmpty &&
snapshot.connectionState == ConnectionState.active) {
startAt = DateTime.fromMillisecondsSinceEpoch(
snapshot.data.last.data[EventMediaSchema.creationDate],
);
return MediaDisplayManagerAlt(
documents: snapshot.data,
atBottom: _fetchNextDocumentSet,
);
}
if (snapshot.hasData && snapshot.connectionState == ConnectionState.active) {
_isAllDataFetched = true;
}
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: Text("NO MEDIA YET"),
),
],
),
);
},
);
void _fetchNextDocumentSet() async {
if (!_isAllDataFetched) {
QuerySnapshot querySnapshot = await _eventDao?.getAllEventMedia(event?.id ?? "",
startAfter: startAt?.millisecondsSinceEpoch, limit: limit);
_list.addAll(querySnapshot.documents);
_listController.sink.add(_list);
}
}
Passing snapshots to StreamBuilder
final int limit = 2;
DateTime startAt;
bool _isAllDataFetched = false;
Widget build(BuildContext context) {
return StreamBuilder<QuerySnapshot>(
stream: _eventDao?.(event?.id ?? "",
startAfter: startAt?.millisecondsSinceEpoch, limit: limit),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) {
return ErrorPage(imageAsset: AssetResources.failed);
}
if (snapshot.hasData && snapshot.data.documents.isNotEmpty) {
return MediaDisplayManagerAlt(
documents: snapshot.data.documents,
atBottom: _fetchNextDocumentSet,
);
}
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Center(
child: Text("NO MEDIA YET"),
),
],
),
);
},
);
}
void _fetchNextDocumentSet() async {
if (!_isAllDataFetched) {
QuerySnapshot querySnapshot = await _eventDao?.getAllEventMedia(event?.id ?? "",
startAfter: startAt?.millisecondsSinceEpoch, limit: limit);
_list.addAll(querySnapshot.documents);
_listController.sink.add(_list);
}
}
It seems like these two approaches need to be combined in some way to get the desired effect, but after two days of trying I can't figure how to do that.

waiting for async funtion to finish

I want to read some txts and store their text in an array. But because I need this array for my GUI it should wait until all is done.
Future<String> getFileData(String path) async {
return await rootBundle.loadString(path);
}
int topicNr = 3;
int finished = 0;
for (int topic = 1; topic <= topicNr; topic++) {
getFileData('assets/topic' + topic.toString() + '.txt').then(
(text) {
topics.add(text);
},
).whenComplete(() {
finished++;
});
}
while (finished < topicNr)
But when I run this code, finished won't update (I think because it is because the while loop runs on the main thread and so the async funtion can't run at the same time)
I could do this by just waiting, but this isn't really a good solution:
Future.delayed(const Duration(milliseconds: 10), () {
runApp(MaterialApp(
title: 'Navigation Basics',
home: MainMenu(),
));
});
How can I now just wait until all of those async Funtions have finished?
(sorry, I am new to Flutter)
One thing you could do is use a stateful widget and a loading modal. When the page is initialized, you set the view to be the loading modal and then call the function that gets the data and populate the data using set state. When you are done/when you are sure the final data has been loaded then you set the loading to false. See the example below:
class Page extends StatefulWidget {
page();
#override
State<StatefulWidget> createState() => new _Page();
}
class _Page extends State<Page>{
bool _loading = true; //used to show if the page is loading or not
#override
void initState() {
getFileData(path); //Call the method to get the data
super.initState();
}
Future<String> getFileData(String path) async {
return await rootBundle.loadString(path).then((onValue){
setState(() { //Call the data and then set loading to false when you are done
data = on value.data;
_loading = false;
});
})
}
//You could also use this widget if you want the loading modal ontop your page.
Widget IsloadingWidget() {
if (_loading) {
return Stack(
children: [
new Opacity(
opacity: 0.3,
child: const ModalBarrier(
dismissible: false,
color: Colors.grey,
),
),
new Center(
child: new CircularProgressIndicator(
valueColor:
new AlwaysStoppedAnimation<Color>(Colors.green),
strokeWidth: 4.0,
),
),
],
);
} else {
return Container();
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: <Widget>[
//If loading, return a loading widget, else return the page.
_loading ?
Container(
child: Center(
child: CircularProgressIndicator(
valueColor: new AlwaysStoppedAnimation<Color>(
Colors.blue))))
:Column(
children:<Widget>[
//Rest of your page.
]
)
]))
}
}
You could also set the fields of the initial data to empty values and the use set state to give them their actual values when you get the data.
so for example
string myvalue = " ";
#override
void initState() {
getFileData(path); //Call the method to get the data
super.initState();
}
//then
Future<String> getFileData(String path) async {
return await rootBundle.loadString(path).then((onValue){
setState(() { //Call the data and then set loading to false when you are done
data = on value.data;
myValue = onValue.data['val'];
_loading = false;
});
})
}
Let me know if this helps.
Use the FutureBuilder to wait for the API call to complete before building the widget.
See this example: https://flutter.dev/docs/cookbook/networking/fetch-data
runApp(MaterialApp(
title: 'Navigation Basics',
home: FutureBuilder(
future: getFileData(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MainMenu()
} else {
return CircularProgressIndicator();
}
));

Implementing pull Down to Referesh

How would you implement a pull-down to refresh in Flutter app that gets data from a collection in Firestore preferable using a StreamBuilder or a FutureBuilder and displays it in a ListView ?
I ended up using information from here. How to refresh or reload a flutter firestore streambuilder manually?
I added the Refresh indicator and made the my stream get its data from a function see code below.
var stream;
#override
void initState() {
setState(() {
stream = mouvesStream();
});
super.initState();
}
Stream<QuerySnapshot> stream() {
return Firestore.instance.collection(_locationState).snapshots();
}
#override
Widget build(BuildContext context) {
body: StreamBuilder(
stream: stream,
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasError) {
return Text(snapshot.error);
}
if (snapshot.connectionState == ConnectionState.active) {
List aList = new List();
aList.clear();
for (DocumentSnapshot _doc in snapshot.data.documents) {
Model _add = new Model.from(_doc);
aList.add(_add);
}
return TabBarView(
children: <Widget>[
RefreshIndicator(
onRefresh: _handleRefresh,
child: ListView.builder(
itemCount: aList.length,
itemBuilder: (context, index) {
return Card(aList[index]);
},
),
),
Icon(Icons.directions_transit),
],
);
} else {
return Container(
child: Center(child: CircularProgressIndicator()));
}
})));
}

Resources