How to call a Future on a Firebase snapshot in Flutter? - asynchronous

I'm getting a repeating error that only lasts a second or two when I return a snapshot using StreamBuilder that says "snapshot called on null". I believe I need to make my snapshot async, but I could be completely wrong. Here is my code...
child: new StreamBuilder(
stream: fb.child('UserVideo/${fUser.uid}').orderByKey().onValue,
builder: (BuildContext context, AsyncSnapshot<Event> event) {
if (event.data.snapshot.value == null) {
return new Card(
child: new Center(
child: new Text('SEARCH',
textAlign: TextAlign.center,
style: new TextStyle(
fontSize: 30.0,
fontFamily: 'Chewy',
color: Colors.black)),
),
);
} else if (event.data.snapshot.value != null) {
Map unsorted =
event.data.snapshot.value; //store each map
final myMap =
new SplayTreeMap<String, dynamic>.from(
unsorted,
(a, b) => unsorted[a]['rank']
.compareTo(unsorted[b]['rank']));
//Map myMap = event.data.snapshot.value; //store each map
var titles = myMap.values;
List onesTitles = new List();
List onesIds = new List();
List onesImages = new List();
List onesRank = new List();
for (var items in titles) {
onesTitles.add(items['title']);
onesIds.add(items['videoID']);
onesImages.add(items['imageString']);
onesRank.add(items['rank'].toString());
}
names = onesTitles;
ids = onesIds;
numbers = onesRank;
vidImages = onesImages;
I'm arranging my user info into Lists then populating Card info from that.

Using the safe-navigation operator should prevent that error
event.data?.snapshot?.value
It eveluates the succeeding parts only if the parts before evaluate to != null

Related

How to make StreamBuilder stop listening for events

I am using a StreamBuilder to access the contents of my Firestore database and create a list of List Tiles for such content. My problem is that whenever I click on a List Tile, it's like my StreamBuilder method gets called again and doubles the amount of List Tiles that I need in my list (since I am using the List.add() method).
How do I make StreamBuilders stop "re-building" or "listening"? I'm fairly new to Dart so I'm not sure how to approach this.
Here is the code that keeps getting repeated. (I have censored some information in places that contain "..." for the purpose of this post):
Widget showContent(BuildContext context) {
var topics = new List();
var messages = new List();
var dates = new List();
List<Widget> content = new List<Widget>();
return new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("collection-id").snapshots(),
builder: (context, snap) {
//just add this line
if (snap.data == null) return CircularProgressIndicator();
for (int i = 0; i < snap.data.documents.length; i++) {
topics.add(...);
messages.add(snap.data.documents[i]["message"].toString());
dates.add(...);
}
for (var i = 0; i < topics.length; i++) {
content.add(new Container(
child: new ListTile(
leading: Icon(Icons.events, color: Colors.black, size: 30,),
title: Text(topics[i].toString()),
subtitle: Text(messages[i].toString()),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => ReadContent(topics[i], messages[i],dates[i])),
);
},
),
decoration: new BoxDecoration(border: new Border(bottom: new BorderSide()))));
}//for
return new Column(children: content);
});//StreamBuilder
}//showContent()
This is a snippet from Stateful widget where showContent() is called:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('App'),
),
body: Center(
child: new Container(
child: new SingleChildScrollView(
child: new Column(
children: <Widget>[
showContent(context),
],
),
),
),
),
);//Scaffold
}
You didn't post any code so I will have to guess how your code works.
I assume you are using your Firebase Stream right inside the StreamBuilder.stream, so every time build is called this Firebase call is being invoked again and again.
build() functions are supposed to have no side effects, you should call Firebase on initState() and save that call as a Future or a Stream and then use that variable on your build() function's StreamBuilder. This way the Future/Stream is only created once.
Again, you didn't post any code, so I am assuming a lot here.

Dart - How to make StreamBuilder stop listening for events

I am using a StreamBuilder to access the contents of my Firestore database and create a list of List Tiles for such content. My problem is that whenever I click on a List Tile, it's like my StreamBuilder method gets called again and doubles the amount of List Tiles that I need in my list (since I am using the List.add() method).
How do I make StreamBuilders stop "re-building" or "listening"? I'm fairly new to Dart so I'm not sure how to approach this.
Here is the code that keeps getting repeated. (I have censored some information in places that contain "..." for the purpose of this post):
var topics = new List();
var messages = new List();
var dates = new List();
List<Widget> content = new List<Widget>();
return new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection("collection-id").snapshots(),
builder: (context, snap) {
//just add this line
if (snap.data == null) return CircularProgressIndicator();
for (int i = 0; i < snap.data.documents.length; i++) {
topics.add(...);
messages.add(snap.data.documents[i]["message"].toString());
dates.add(...);
}
for (var i = 0; i < topics.length; i++) {
content.add(new Container(
child: new ListTile(
leading: Icon(Icons.events, color: Colors.black, size: 30,),
title: Text(topics[i].toString()),
subtitle: Text(messages[i].toString()),
onTap: () {
Navigator.push(context,
MaterialPageRoute(builder: (context) => ReadContent(topics[i], messages[i],dates[i])),
);
},
),
decoration: new BoxDecoration(border: new Border(bottom: new BorderSide()))));
}//for
return new Column(children: content);
});//StreamBuilder
}//showContent()
This is a snippet from Stateful widget where showContent() is called:
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('App'),
),
body: Center(
child: new Container(
child: new SingleChildScrollView(
child: new Column(
children: <Widget>[
showContent(context),
],
),
),
),
),
);//Scaffold
}
One way to do that would be to clear your List when you are adding data to it.
With your current approach if some new data also arrives the data will be appended to the List and result in data duplication.
for (int i = 0; i < snap.data.documents.length; i++) {
topics.clear();//clear the items in list
topics.add(...);
messages.add(snap.data.documents[i]["message"].toString());
dates.add(...);
}

What is the method to show data from a list on screen in flutter?

I stored some data from firestore instance in a list and now I want to display the data on my screen.
When I print my list in terminal, I see this:
I/flutter (11351): [{num: 100, title: This is test 2}]
I defined a list like this:
var list = new List();
I am fetching data on a click of button.
RaisedButton(
onPressed: () async {
var documentReference = await Firestore.instance.collection('insults').document('vv4YjpyRFFEknbCyC9o7');
documentReference.get().then((documentSnapshot){
setState(() {
list.add(documentSnapshot.data);
});
print(list);
}) ;
},
child: Text('Press me'),
),
Please guide me a little. I just started learning flutter.
I do not want to loop through document but I want to store it in a list using a suitable method.
There is no need to show a list, since you are only retrieving data under one document, if you want to show the retrieved data, then you can do the following:
First create an instance variable under your State:
Map<String, dynamic> retrievedData = Map<String, dynamic>();
Then inside setState assign the data to retrievedData:
RaisedButton(
onPressed: () async {
var documentReference = await Firestore.instance.collection('insults').document('vv4YjpyRFFEknbCyC9o7');
documentReference.get().then((documentSnapshot){
setState(() {
retrieveData = documentSnapshot.data;
});
}) ;
},
child: Text('Press me'),
),
Inside the build method you can have a Column with multiple children for example:
children: <Widget>[
Text(
retrievedData["title"] ?? "",
),
Text(
retrievedData["num"] ?? "",
),
],
When clicking the button, setState will call the build method and you will have your data on the screen.

Flutter: type 'Future<dynamic>' is not a subtype of type 'Widget'

I am using sqlite to store data in my Flutter App. I have a modal bottomsheet which opens with filterchips and when you select one it adds the item to the database. If the item already exists the filterchip is checked. When I call the database function to check if the item already exist I get the below error.
I have tried using both async and await.
Database query code:
// FIND TAG
findTag(int tagId) async {
var dbConnection = await db;
var res = await dbConnection.query("$TABLE_NAME", where: "tagId = ?", whereArgs: [tagId]);
return res.isNotEmpty ? true : false ;
}
Modal bottomsheet widget container code:
Widget build(BuildContext context) {
setState(() {
_index = widget.index;
_list =widget.list;
});
return new Container(
padding: new EdgeInsets.all(27.0),
child: new Column(
mainAxisAlignment: MainAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget> [
new Text(_list[_index]["title"], style: new TextStyle( fontWeight: FontWeight.bold, fontSize: 22),),
new Text(_list[_index]["description"]),
getFilterChipsWidgets(),
],
),
);
}
getFilterChipsWidgets()
async {
List<Widget> tagsList = new List<Widget>();
for(var i=0; i<_list[_index]["tags"].length; i++) {
var dbHelper = DBHelper();
int id = int.parse(_list[_index]["tags"][i]["id"]);
var exist = await dbHelper.findTag(id);
FilterChip item = new FilterChip(
label: Text(_list[_index]["tags"][i]["name"].toString(),),
selected: exist,
onSelected: (bool newValue) {
if(newValue) {
dbHelper.addNewTag(id);
} else {
dbHelper.deleteNewTag(id);
}
},
);
tagsList.add(item);
}
return Wrap(
spacing: 8.0, // gap between adjacent chips
runSpacing: 4.0, // gap between lines
children: tagsList,
);
}
Your getFilterChipsWidgets() is async function so it will return Future.
You could await for the future and save widgets to list and call setState once it is compleated.
Or just wrap it with FutureBuilder like so:
children: <Widget> [
new Text(_list[_index]["title"], style: new TextStyle(fontWeight: FontWeight.bold, fontSize: 22),),
new Text(_list[_index]["description"]),
FutureBuilder<Widget>(
future: getFilterChipsWidgets,
builder: (BuildContext context, AsyncSnapshot<Widget> snapshot){
if(snapshot.hasData)
return snapshot.data;
return Container(child: CircularProgressIndicator());
}
),
],
Sometimes it also has been seen, that mistakenly you have assigned the function async and inside the function block no where you are using the await processes,
Similar scenario have been already happened with me, and by removing this unused async over the function resolved this above issue.
when you make any function async, the function requester treats that in Future<T> way, so be careful.

Getter "Snapshot" called on null with StreamBuilder in Flutter

I'm using a Flutter Streambuilder and my stream calls null for a second before it loads. I'm trying to load a "Loading..." Card but its not working. My simulator briefly shows a red error screen before displaying my desired card list. How can I get it to stop doing this? Here is my code...
new Expanded(
child: new StreamBuilder(
stream: streamQuery,
builder:
(BuildContext context, AsyncSnapshot<Event> event) {
if (event.data.snapshot.value == null) {
return new Card(
child: new Text('Loading...',
style: new TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)),
);
}
Map myMap = event.data.snapshot.value; //store each map
var titles = myMap.values;
List onesTitles = new List();
List onesIds = new List();
List onesImages = new List();
List onesRank = new List();
for (var items in titles) {
onesTitles.add(items['vidTitle']);
onesIds.add(items['vidId']);
onesImages.add(items['vidImage']);
onesRank.add(items['Value']);
}
names = onesTitles;
ids = onesIds;
numbers = onesRank;
vidImages = onesImages;
switch (event.connectionState) {
case ConnectionState.none:
return new Card(
child: new Text('Loading...',
style: new TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)),
);
case ConnectionState.waiting:
return new Card(
child: new Text('Awaiting Results...',
style: new TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)),
);
default:
if (event.hasError)
return new Card(
child: new Text('Error: ${event.error}',
style: new TextStyle(
fontSize: 12.0,
fontWeight: FontWeight.bold,
fontStyle: FontStyle.italic)),
);
else
return new InkWell(
You need to explicitly state all your data manipulation inside the else that has the build layout for your if (snapshot!=null) {//do something}
Something like this:
else //event.data.snapshot.value != null
{
Map myMap = event.data.snapshot.value; //store each map
var titles = myMap.values;
List onesTitles = new List();
List onesIds = new List();
List onesImages = new List();
List onesRank = new List();
for (var items in titles) {
onesTitles.add(items['vidTitle']);
onesIds.add(items['vidId']);
onesImages.add(items['vidImage']);
onesRank.add(items['Value']);
}
names = onesTitles;
ids = onesIds;
numbers = onesRank;
vidImages = onesImages;
//return my layout
}
So do no leave anything that is using your snapshot.value outside your if statement that works when snapshot!=null.

Resources