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(...);
}
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.
I'm trying to create markers with a JSON, response, the JSON y working correctly, and the map is also working, but the markers are not showed in the map.
I know something is wrong with the code, but I can't find the solution, or something similar to fix it `
var farmcias = Farmacia.fromJson(jsonDecode(response.body));
for (var result in farmcias.result.toList()) {
titulo = result?.title;
calle = result?.calle;
lat = result?.geometry.coordinates[0];
long = result?.geometry.coordinates[1];
markers.add( Marker(
width: 80.0,
height: 80.0,
point: new LatLng(lat, long),
builder: (ctx) =>
Container(
child: FlutterLogo(colors: Colors.purple),
),
));
return markers;
}
}
#override
Widget build(BuildContext context) {
FetchJSON();
return new Scaffold(
body: new Padding(
padding: new EdgeInsets.all(8.0),
child: new Column(
children: [
new Padding(
padding: new EdgeInsets.only(top: 18.0, bottom: 18.0),
),
new Flexible(
child: new FlutterMap(
options: new MapOptions(
center: new LatLng(41.652833, -0.889830),
zoom: 12.0,
),
layers: [
new TileLayerOptions(
urlTemplate:
"https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png",
subdomains: ['a', 'b', 'c']),
new MarkerLayerOptions(markers: markers)
],`
Thanks for all.
I am trying to change the query according to the selected community.
When you select the community you must show the content of this community.
I have found a solution but I do not think it is the right one, because the application has 39 countries.
Those 39 require you to have a query for each one and there are many lines of code.
I hope there is some better solution than what I found.
GIF APP
- The error that appears is because there is no other country.
SearchClub.dart
This is the function that returns a query related to the countries.
I want to use one and not make several queries for each country.
searchClubs(TextEditingController countryChanged) {
var widgetFireUpdate;
setState(() {
if(countryChanged.text == 'SPAIN') {
widgetFireUpdate = new FirebaseAnimatedList(
query: FirebaseDatabase.instance.reference().child(widget.player.platform).child("CLUB").orderByChild('country').equalTo(countryChanged.text),
sort: (a, b) => a.value['createdDate'].compareTo(b.value['createdDate']),
reverse: true,
shrinkWrap: true,
defaultChild: new CircularProgressIndicator(),
itemBuilder: (BuildContext context, DataSnapshot snapshot,
Animation<double> animation, int index) {
return new StreamBuilder<Event>(
stream: itemRef2.orderByKey().onValue,
builder: (context, AsyncSnapshot<Event> snapshot2){
if(snapshot2.hasData) {
try {
return new Container(
decoration: new BoxDecoration(
color: Colors.grey[300],
),
child: new ListTile(
leading: snapshot.value['logoURL'].toString().indexOf('images/assets/logo_notfound.png') == -1 ? new CachedNetworkImage(imageUrl: snapshot.value['logoURL'], width: MediaQuery.of(context).size.width/8) : new Image.asset(snapshot.value['logoURL'], width: MediaQuery.of(context).size.width/8),
title: new Text(snapshot.value['name'].toUpperCase(), style: new TextStyle(color: Colors.black, fontWeight: FontWeight.bold, fontSize: MediaQuery.of(context).size.width/30)),
subtitle: new RichText(
text: new TextSpan(
children: <TextSpan>[
new TextSpan(text: "CAPITÁN:", style: new TextStyle(color: Colors.black, fontSize: MediaQuery.of(context).size.width/35, fontWeight: FontWeight.bold)),
new TextSpan(text: " ${snapshot.value['captain'].toUpperCase()}", style: new TextStyle(color: Colors.black, fontSize: MediaQuery.of(context).size.width/35)),
]
),
),
),
);
}catch(e) {
return new Container();
}
} else if(snapshot2.hasError){
return new Container();
} else {
return new Container(
child: new Center(
child: new CircularProgressIndicator(
backgroundColor: Colors.red,
),
),
);
}
},
);
},
);
} else if(countryChanged.text == 'BRAZIL') {
widgetFireUpdate = new FirebaseAnimatedList(
query: FirebaseDatabase.instance.reference().child(widget.player.platform).child("CLUB").orderByChild('country').equalTo(countryChanged.text),
sort: (a, b) => a.value['createdDate'].compareTo(b.value['createdDate']),
reverse: true,
shrinkWrap: true,
defaultChild: new CircularProgressIndicator(),
itemBuilder: (BuildContext context, DataSnapshot snapshot,
Animation<double> animation, int index) {
return new StreamBuilder<Event>(
stream: itemRef2.orderByKey().onValue,
builder: (context, AsyncSnapshot<Event> snapshot2){
if(snapshot2.hasData) {
try {
return new Container(
decoration: new BoxDecoration(
color: Colors.grey[300],
),
child: new ListTile(
leading: snapshot.value['logoURL'].toString().indexOf('images/assets/logo_notfound.png') == -1 ? new CachedNetworkImage(imageUrl: snapshot.value['logoURL'], width: MediaQuery.of(context).size.width/8) : new Image.asset(snapshot.value['logoURL'], width: MediaQuery.of(context).size.width/8),
title: new Text(snapshot.value['name'].toUpperCase(), style: new TextStyle(color: Colors.black, fontWeight: FontWeight.bold, fontSize: MediaQuery.of(context).size.width/30)),
subtitle: new RichText(
text: new TextSpan(
children: <TextSpan>[
new TextSpan(text: "CAPITÁN:", style: new TextStyle(color: Colors.black, fontSize: MediaQuery.of(context).size.width/35, fontWeight: FontWeight.bold)),
new TextSpan(text: " ${snapshot.value['captain'].toUpperCase()}", style: new TextStyle(color: Colors.black, fontSize: MediaQuery.of(context).size.width/35)),
]
),
),
),
);
}catch(e) {
return new Container();
}
} else if(snapshot2.hasError){
return new Container();
} else {
return new Container(
child: new Center(
child: new CircularProgressIndicator(
backgroundColor: Colors.red,
),
),
);
}
},
);
},
);
}
});
return widgetFireUpdate;
}
// OPEN LIST COMMUNITY
Widget _buildBottomPicker() {
final FixedExtentScrollController scrollController = new FixedExtentScrollController();
return new Container(
height: MediaQuery.of(context).size.height/3.5,
color: CupertinoColors.white,
child: new DefaultTextStyle(
style: const TextStyle(
color: CupertinoColors.black,
fontSize: 22.0,
),
child: new SafeArea(
child: new CupertinoPicker(
scrollController: scrollController,
itemExtent: MediaQuery.of(context).size.height/15,
magnification: 0.7,
diameterRatio: 0.5,
backgroundColor: CupertinoColors.white,
onSelectedItemChanged: (int index) {
setState(() {
_comunidad.text = _comunidades[index];
_imgComunidad = _imgComunidades[index];
});
},
children: new List<Widget>.generate(_comunidades.length, (int index) {
return new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Text(_comunidades[index]),
],
);
}),
),
),
),
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
appBar: searchBar.build(context),
body: new Container(
color: widget.themeConsole,
child: new Column(
children: <Widget>[
new Card(
elevation: 0.0,
color: Colors.grey[50],
child: new Container(
child: new Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
new Container(
width: MediaQuery.of(context).size.width/1.35,
child: new TextFormField(
controller: _comunidad,
style: new TextStyle(color: Colors.white),
enabled: false,
decoration: new InputDecoration(
labelText: 'Community:',
labelStyle: new TextStyle(color: Colors.white),
icon: new Image.asset(_imgComunidad, width: 24.0),
filled: true,
fillColor: Colors.grey[800],
),
validator: (String value){
player.country = value;
},
),
),
new IconButton(
icon: new Icon(Icons.flag, color: Colors.grey[800], size: 30.0,),
color: Colors.black,
onPressed: ()async {
await showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return _buildBottomPicker();
},
);
},
),
],
),
),
),
new Flexible(child: searchClubs(_comunidad))
],
)
),
);
}
SearchClub Complete: https://pastebin.com/zbeU6M1u
From what I have understood, you are using firebase real time database where you have child name as country and have data inside that node. You want to make queries in such a way that when you select a country, that particular query should be made like when selecting Argentina everything inside Argentina in your database should be called.
Store value of country names in a list and make a string for calling countries.
String country="";
List<String> country_name=["Argentina","Brazil","Spain"....];
Now on press function can be like this-
onPressed: () {
setState(() {
country=country_name[1];//example
});
country_call();
}
This will help us when we change the country name.
Lastly make changes in your firebase reference-
Future<void> country_call()async{
final FirebaseDatabase database = FirebaseDatabase().instance();
setState(() {
itemRef = database.reference().child(country);
itemRef.onChildAdded.listen(_onEntryAdded);
itemRef.onChildChanged.listen(_onEntryChanged);
});
}
By changing you can call different queries without writing for 39 different countries. If you are using a list view to show our data, make sure to empty it in country_call function. That can be done by simply equating it to null list("[]").
I was searching for something similar but could not find anything on the website. I used this approach to fix my problem. I hope it was helpful for you too.
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