I am trying to fetch some data from the internet and show it int a list.
Following is my bloc code
class StudentsBloc {
final _repository = Repository();
final _students = BehaviorSubject<StudentModel>();
final BehaviorSubject<bool> _showProgress = BehaviorSubject<bool>();
final BehaviorSubject<bool> _showNoInternetViews = BehaviorSubject<bool>();
Observable<StudentModel> get students => _students.stream;
Observable<bool> get showProgress => _showProgress.stream;
Observable<bool> get showNoInternetViews => _showNoInternetViews.stream;
//FetchStudent from my Api
fetchStudents(String disciplineId, String schoolId, String year_id,
String lastIndex) async {
final student = await _repository.fetchStudents(
disciplineId, schoolId, year_id, lastIndex);
_students.sink.add(student);
}
//Check to see if user has internet or not
isNetworkAvailable(String disciplineId, String schoolId, String year_id,
String lastIndex) async {
checkInternetConnection().then((isAvailable) {
if (isAvailable) {
fetchStudents(disciplineId, schoolId, year_id, lastIndex);
} else {
_students.sink.addError(NO_NETWORK_AVAILABLE);
}
});
}
Function(bool) get changeVisibilityOfProgress => _showProgress.sink.add;
Function(bool) get changeVisibilityOfNoInternetViews =>
_showNoInternetViews.sink.add;
dispose() {
_students.close();
_showProgress.close();
_showNoInternetViews.close();
}
}
Following is my main code to hide unide Widgets
Widget buildList(StudentsBloc bloc) {
return StreamBuilder(
stream: bloc.students,
builder: (context, AsyncSnapshot<StudentModel> snapshot) {
if (snapshot.hasError) {
bloc.changeVisibilityOfProgress(false);
bloc.changeVisibilityOfNoInternetViews(true);
return StreamBuilder(
stream: bloc.showNoInternetViews,
builder: (context, snapshot) {
bool showNoInternetView = snapshot.hasData ?? false;
return Visibility(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("No Network Available"),
RaisedButton(
onPressed: () {
fetchStudents();
},
child: Text("Retry"),
)
],
),
),
visible: showNoInternetView ? true : false,
);
},
);
}
if (snapshot.hasData) {
bloc.changeVisibilityOfProgress(false);
bloc.changeVisibilityOfNoInternetViews(false);
return Refresh(
year_id: "2",
schoolId: "1",
lastIndex: "0",
disciplineId: "1",
child: ListView.builder(
itemBuilder: (context, int index) {
return buildTile(
snapshot.data.toBuilder().data.studentData[index]);
},
itemCount: snapshot.data.toBuilder().data.studentData.length,
),
);
}
if (!snapshot.hasData) {
return StreamBuilder(
builder: (context, snapshot) {
bool showProgressIndicator = snapshot.data ?? false;
return Visibility(
child: Center(
child: CircularProgressIndicator(),
),
visible: showProgressIndicator ? true : false,
);
},
stream: bloc.showProgress,
);
}
},
);
}
The buildList method is called in the body of Scaffold
void fetchStudents() {
bloc?.changeVisibilityOfNoInternetViews(false);
bloc?.changeVisibilityOfProgress(true);
bloc?.isNetworkAvailable("1", "1", "2", "0");
}
Suppose the user has internet when app is opened then i am able to see circularprogressindicator and then the list of data is visible
but suppose at the start when app is opened and the user does not have internet then i am showing the No Network Available Text and a button to retry,
now if the user has connected to the internet and then click on button to retry i am directly seeing the list of data after few seconds instead of the circularprogressindicator.I am not able to understand why the NoInternetviews are not hiding and progressindicator is showing when retry button is clicked before showing list of data.
My stream is not getting updated on retry button called. Are there any caveats for StreamBuilder within StreamBuilder?
I tried changing the StreamBuilder order as mentioned by #ivenxu in the answer but it still does not work.
Following are the links of attached code
https://drive.google.com/file/d/15Z8jXw1OpwTB1CxDS8sHz8jKyHhLwJp7/view?usp=sharing
https://drive.google.com/open?id=1gIXV20S1o5jYRnno_NADabuIj4w163fF
in view layer you can use Visibility() widget and pass visible parameter true or false when load data from Internet.
let's think about how to change the visible variable from bloc.
The parent of Visibility() widget the StreamBuilder() to stream on changes data.
for your case you need a PublishSubject inside your bloc to stream and add new data, and Observable to stream on this data on your widget.
let's show a snippet code to help you how you can implement it
The bloc contains PublishSubject and Observable to stream and add data
//this Subject allows sending data, error and done events to the listener
final PublishSubject<bool> _progressStateSubject = new PublishSubject();
//the listener are streaming on changes
Observable<bool> get progressStateStream => _progressStateSubject.stream;
//to change your progress state
void changeProgressState({bool state}) => _progressStateSubject.sink.add(state);
Here you can change your state of your progress
void callWebService() async {
//when you call your func to fetch your data change your state to true
changeProgressState(state: true);
.
.
.
if(response != null){
//when your call is done change the state to false
changeProgressState(state: false);
}
}
Your progress widget is
// Loading Widget
Widget _buildLoadingWidget(Bloc bloc) {
return StreamBuilder<bool>(
stream: bloc.progressStateStream,//streaming on changes
builder: (context, snapshot) {
return Visibility(
visible: snapshot.data ?? false,//calling when data changes
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text("Loading data from API...",
textDirection: TextDirection.ltr),
CircularProgressIndicator()
],
),
),
);
});
}
It seems the cause is that CircularProgressIndicator is inside the students stream's update cycle. And the student steam only get next snapshot when the data returned from internet call in the case of retry.
Have a try on changing the order of stream builders, try putting the student streambuilder inside of the progress stream builder.
Widget buildList(StudentsBloc bloc) {
return StreamBuilder(
stream: bloc.showProgress,
builder: (context, snapshot) {
bool showProgressIndicator = snapshot.data ?? false;
if (!showProgressIndicator) {
StreamBuilder(
stream: bloc.students,
builder: (context, AsyncSnapshot<StudentModel> snapshot) {
....
//your original code without progress StreamBuilder
}
}
return Visibility(
child: Center(
child: CircularProgressIndicator(),
),
visible: showProgressIndicator ? true : false,
);
},
);
}
Related
I have an array called "Names" in the firestore. i want to display that array elements in a ListView.builder. i tried many ways but could not do it. i don't know how to access snapshot data.
FutureBuilder(
future: getList(),
builder: (context, AsyncSnapshot<List<dynamic>> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
} else {
return Center(
child: ListView.builder(
padding: const EdgeInsets.only(bottom: 20.0),
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Center(
child: ListTile(
title: Text(
snapshot.data[0].data), //snapshot data should dispaly in this text field
),
);
}),
);
}
},
),
this is my getList() method.
Future<List<dynamic>> getList() async {
var firestore = Firestore.instance;
DocumentReference docRef =
firestore.collection('RecodeBook').document('2019-05-04');
List<dynamic> info = new List<String>();
docRef.get().then((datasnapshot) {
if (datasnapshot.exists) {
info = datasnapshot.data['Names'].toList();
print('#');
print(info); //this line prints [aa, aghshs, fffg, fug, ghh, fggg, ghhh]
print(info.length); //this line prints 7
}
});
return info;
}
It seems to be because you are sending back a List<dynamic> and not a Future<List<dynamic>>. The following code should work
Future<List<dynamic>> getList() async {
var firestore = Firestore.instance;
DocumentReference docRef = firestore.collection('RecodeBook').document('2019-05-04');
return docRef.get().then((datasnapshot) {
if (datasnapshot.exists) {
List<dynamic> info = datasnapshot.data['Names'].toList();
print('#');
print(info); //this line prints [aa, aghshs, fffg, fug, ghh, fggg, ghhh]
print(info.length); //this line prints 7
return info;
}
});
}
Also, in your ListView.builder do take note of the index.
title: Text(
snapshot.data[index].data), //snapshot data should display in this text field
There are two dropdown button with the list of countries and types of sport. If on them somsething is chosen it is need to show listTile with the leagues on it is chosen to the country and/or sport and if on them nothing is chosen - show all leagues.
But I get:
Dart Error: Unhandled exception:
setState () called after dispose (): _SportLigPageState # b5830 (lifecycle state: defunct, not mounted)
This is what happens if you see the widget tree (e.g.). This error can occur when a call is made. Dispose () callback. It is necessary to ensure that the object is still in the tree.
This can be a memory card if it’s not. To avoid memory leaks, consider dispose ().
Api with leagues: https://www.thesportsdb.com/api/v1/json/1/all_leagues.php:
class LigList extends StatefulWidget {
#override
_LigListState createState() => _LigListState();
}
class _LigListState extends State<LigList> {
String sport;
String country;
List data;
Future<String> getJsonData() async {
http.Response response;
if (sport != null) {
if (country != null) response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php?c=$sport&s=$country'), headers: {"Accept": "application/json"});
else response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php?c=$sport'), headers: {"Accept": "application/json"});}
else if (country == null){ response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php'), headers: {"Accept": "application/json"});}
else response = await http
.get(Uri.encodeFull('https://www.thesportsdb.com/api/v1/json/1/all_leagues.php?c=$country'), headers: {"Accept": "application/json"});
var convertDatatoJson = json.decode(response.body);
data = convertDatatoJson['leagues'];
return "Success";
}
static const menuItems = countriesList;
final List<DropdownMenuItem<String>> _dropDownItems = menuItems
.map((String CountruValue) =>
DropdownMenuItem<String>(
value: CountruValue,
child: Text(CountruValue),
),
).toList();
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: Column(children: <Widget>[
FutureBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return DropdownButton(
value: country,
hint: Text("Choose a countre league of which you want to find"),
items: _dropDownItems,
onChanged: (value) {
country = value;
print(country);
setState(() {});
},
);}),
SizedBox(width: 5),
FutureBuilder(
future: _getSports(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return snapshot.hasData
? DropdownButton(
value: sport,
hint: Text("Choose a sport league of which you want to find"),
items: snapshot.data,
onChanged: (value) {
sport = value;
print(sport);
setState(() {});
},
)
: Padding(
padding: EdgeInsets.symmetric(vertical: 20),
child: CircularProgressIndicator());
}),
Flexible(
child:FutureBuilder(
future: getJsonData(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
return ListView.separated(
itemCount: data == null ? 0 : data.length,
itemBuilder: (BuildContext context, int i) {
return Container(
child: Center(
child: Column(
crossAxisAlignment: CrossAxisAlignment
.stretch,
children: <Widget>[
ListTile(
title: Text(data[i]['strLeague']),
subtitle: Text(
data[i]['strSport']),
onTap: () {
Navigator.push(
context,
new MaterialPageRoute(
builder: (
BuildContext context) =>
new ComandListScreen()
// (data[i])
));
},
),
]
)
)
);
});
}))
]),
),
);
}
}
Any assistance is very much appreciated.
There's a lot of things wrong with your code. The first child in your code is wrapped in a FutureBuilder but you're not using any Future functionality.
FutureBuilder(
builder: (BuildContext context, AsyncSnapshot snapshot) {
return DropdownButton(
value: country,
hint: Text("Choose a countre league of which you want to find"),
items: _dropDownItems,
onChanged: (value) {
country = value;
print(country);
setState(() {}); // Remove this line
},
);}),
In addition to that you also are calling setState() randomly in your onChanged callback with nothing inside of it. I'd suggest you take that widget out of the FutureBuilder and just use the DropdownButton on it's own.
Then also in this line
itemCount: data == null ? 0 : data.length,
You're using data, which is set in the future that you call there. You might want to read up on how to properly use the FutureBuilder widget. Just return the data object from your _getJsonData() Future because it's always returning "Success" anyway. Return the list you want from the Future and then access it using snapshot.data
And lastly there's literally only one setState call in there so remove it and you'll be fine. My assumption is that there's some additional dispose you're calling or navigating away and the app crashes. Will need a lot more info to figure out, but you'll have to fix the way you use Futures and the Future builder so we can ensure it's not because of latent threads coming back and setting the state once you've left the view you were on.
I'm trying to change some variables in different methos in Flutter, but the value isn't changed.
An example is something like:
enum UserPlaceStatusType { NONE, GOING, THERE, OUT, CANCELLED }
class PlaceCardState extends State<PlaceCard> {
UserPlaceStatusType _isOtherPlaceActive = UserPlaceStatusType.NONE;
Widget build(BuildContext context) {
return Card(
child: Scaffold(
body: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: this._getBody(),
),
bottomNavigationBar: this._getBottomNavigationBar()));
}
List<Widget> _getBody() {
return [
Expanded(child: Text('test'), flex: 3),
Expanded(child: Text('test'), flex: 6),
Expanded(child: this._getActionsMenu(), flex: 1)
];
}
Widget _getActionsMenu() {
return Container(
padding: EdgeInsets.fromLTRB(0.0, 0.0, 10.0, 0.0),
child: IconButton(
icon: Icon(Icons.arrow_forward_ios),
color: Colors.grey[400],
onPressed: () {
showModalBottomSheet<void>(
context: context,
builder: (BuildContext context) {
return new Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
new ListTile(
leading: new Icon(Icons.train),
title: new Text(Utility.format(
Language.of(context).takePlace, [_place.title])),
onTap: () {
showUserStatusDialog<DialogActions>(
context: context,
//It opens a simple dialog
child: this._getCurrentUserPlaceStatus());
},
),
],
);
});
},
));
}
Widget _getCurrentUserPlaceStatus() {
return new GraphqlProvider(
client: new ValueNotifier(
Client(endPoint: 'GraphQLUrl', cache: new InMemoryCache()),
),
child: new Query(
'The GraphQL Query',
variables: {},
builder: ({
bool loading,
var data,
var error,
}) {
if (data != null && data['getCurrentUserPlaceStatus'] != null) {
this._isOtherPlaceActive = UserPlaceStatusType.THERE;
Navigator.pop(context, DialogActions.cancel);
return Container();
} else {
this._isOtherPlaceActive = UserPlaceStatusType.GOING;
Navigator.pop(context, DialogActions.cancel);
return Container();
}
},
));
}
void showUserStatusDialog<T>({BuildContext context, Widget child}) async {
//here there is a validation but the variable value is the initial one, I mean NONE
if (this._isOtherPlaceActive == UserPlaceStatusType.GOING) {
//Cod to do
return;
}
showDialog<T>(
context: context,
builder: (BuildContext context) => child,
).then<void>((T value) {
if (value != null) {
this._isOtherPlaceActive = UserPlaceStatusType.NONE;
Navigator.pop(context);
}
});
}
}
I changed the variable value through some methods, but when I need to apply the validation, that's the initial value, it isn't changed, and I could not apply SetState method cuz it breaks the modal and throws an exception.
I will appreciate any feedback.
The method setState() can't be called inside a widget directly. I'm curious with your use of GrapQLProvider since it returns an empty Container() widget just to check the status of the data.
While I'm unfamiliar with the use of GraphQL, if the client that you're using inherits either a Stream or Future, it can be used to listen when the query is done.
Here's some snippets as demo. Let _testFuture() as the sample for a Future callback.
Future _testFuture() async{
return null;
}
Future can be listened to inside a Widget. When the request finishes, we have the opportunity to call setState().
_testFuture().then((value) {
// Check for values here
setState(() {
// Update values
});
});
Or if the request is set in a Stream, it's also possible to listen for Stream changes inside a Widget.
_streamController.add(_testFuture());
_streamController.stream.listen((event) {
// Check for values here
setState(() {
// Update values
});
});
This may not be the exact answer that you're looking for, but I hope this can guide you for a solution to your approach. I also found a GraphQL sample that uses ObservableQuery as a Stream that you can try.
Your code is very complex and should be refactored. Please notice how dialogs must be called.
enum DialogResult {ok, cancel}
caller_widget.dart
FlatButton(
child: Text('Open dialog'),
onPressed: () async {
// Call dialog and wait for result (async call)
final dialogResult = await showDialog<DialogResult>(
context: context,
builder: (context) => DialogWidget(),
);
if (dialogResult == DialogResult.ok) {
// do something
}
},
),
dialog_widget.dart
...
FlatButton(
child: Text('Ok'),
onPressed: () => Navigator.pop(context, DialogResult.ok), // DialogResult.ok returns
),
FlatButton(
child: Text('Cancel'),
OnPressed: () => Navigator.pop(context, DialogResult.cancel), // DialogResult.cancel returns
),
So you can return required value from dialog and set it to required variable.
P.S. Try to avoid use of old fashion then process of futures and use async/await.
The main concept is showing documents or fields which contains the searched alphabet.
The search bar gets the given input, it send to the _firebasesearch(), but in return nothing comes out, and the above image is my database structure, trying to figure out more than a week.
CODE
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter_search_bar/flutter_search_bar.dart';
SearchBar searchBar;
GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
class DisplayCourse extends StatefulWidget {
#override
_DisplayCourseState createState() => new _DisplayCourseState();
}
AppBar _buildAppBar(BuildContext context) {
return new AppBar(
title: new Text("FIREBASE QUERY"),
centerTitle: true,
actions: <Widget>[
searchBar.getSearchAction(context),
],
);
}
class _DisplayCourseState extends State<DisplayCourse> {
String _queryText;
_DisplayCourseState() {
searchBar = new SearchBar(
onSubmitted: onSubmitted,
inBar: true,
buildDefaultAppBar: _buildAppBar,
setState: setState,
);
}
void onSubmitted(String value) {
setState(() {
_queryText = value;
_scaffoldKey.currentState.showSnackBar(new SnackBar(
content: new Text('You have Searched something!'),
backgroundColor: Colors.yellow,
));
});
}
#override
Widget build(BuildContext context) {
return new Scaffold(
key: _scaffoldKey,
appBar: searchBar.build(context),
backgroundColor: Colors.red,
body: _fireSearch(_queryText),
);
}
}
Widget _fireSearch(String queryText) {
return new StreamBuilder(
stream: Firestore.instance
.collection('courses')
.where('title', isEqualTo: queryText)
.snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return new Text('Loading...');
return new ListView.builder(
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) =>
_buildListItem(snapshot.data.documents[index]),
);
},
);
}
Widget _buildListItem(DocumentSnapshot document) {
return new ListTile(
title: document['title'],
subtitle: document['subtitle'],
);
}
the main concept is showing document sor fields which contains the searched alphabet
the search bar gets the given input, it send to the _firebasesearch(),but in return nothing comes out, and the above image is my database structure, trying to figure out more than a week,
This might sound a ridiculous solution but actually works so well, It's almost like the Like '%' query from SQL
In the TextField as you type a value the inside where() isGreaterThanOrEqualTowill compare it and all the string values greater than the input and If you concatinate a 'Z'
At the end then isLessThan will end just after your search keyword and You get the desired Result from firestore.
// Declare your searchkey and Stream variables first
String searchKey;
Stream streamQuery;
TextField(
onChanged: (value){
setState(() {
searchKey = value;
streamQuery = _firestore.collection('Col-Name')
.where('fieldName', isGreaterThanOrEqualTo: searchKey)
.where('fieldName', isLessThan: searchKey +'z')
.snapshots();
});
}),
I used this Stream in StreamBuilder and It works exactly as expected.
Limitations:
The search is case sensitive(You can convert searchKey to specific case if your data is consistent like Type Case )
You have to start searching from the first letter, it can't search from mid
I'm a bit too late but I just want to share something on how I implement the search function without using third party app in my case. My solution is a bit straight forward querying using firestore. Here's the code:
Future<List<DocumentSnapshot>> getSuggestion(String suggestion) =>
Firestore.instance
.collection('your-collection')
.orderBy('your-document')
.startAt([searchkey])
.endAt([searchkey + '\uf8ff'])
.getDocuments()
.then((snapshot) {
return snapshot.documents;
});
example if you want to search all keywords containing "ab" then it will display all words containing "ab" (ex. abcd, abde, abwe). If you want to make auto suggest search function you can use typehead. which can be found in this link: https://pub.dev/packages/flutter_typeahead
Good luck.
You don't have to rebuild your whole stream, just filter the results from your stream depending on your search string.
Fast, does not need to rebuild the whole stream, finds occurences of the search string not only from the start of a word and is case-insensitive.
return StreamBuilder(
stream: FirebaseFirestore.instance.collection("shops").snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError) // TODO: show alert
return Text('Something went wrong');
if (snapshot.connectionState == ConnectionState.waiting)
return Column(
children: [
Center(
child: CupertinoActivityIndicator()
)
],
);
var len = snapshot.data.docs.length;
if(len == 0)
return Column(
children: [
SizedBox(height: 100),
Center(
child: Text("No shops available", style: TextStyle(fontSize: 20, color: Colors.grey)),
)
],
);
List<Shop> shops = snapshot.data.docs.map((doc) => Shop(
shopID: doc['shopID'],
name: doc['name'],
...
)).toList();
shops = shops.where((s) => s.name.toLowerCase().contains(searchString.text.toLowerCase())).toList();
return
Expanded(
child: ListView.builder(
padding: EdgeInsets.symmetric(vertical: 15),
scrollDirection: Axis.vertical,
shrinkWrap: true,
itemCount: shops.length,
itemBuilder: (context, index) {
return shopRow(shops[index]);
}
),
);
},
);
The issue is you are expecting results from firestore where title is equal to queryText not title contains queryText.
If you want the search feature, you can get and store the firestore documents in a variable something like List<Model> model instead of StreamBuilder and implement search manually from the above stored list of model.
The solution that i found:
List<String> listaProcura = List();
String temp = "";
for(var i=0;i<nomeProduto.length; i++) {
if(nomeProduto[i] == " ") {
temp = "";
} else {
temp = temp + nomeProduto[i];
listaProcura.add(temp);
}
}
The "listaProcura" is the name of the list.
the String "temp" is the name of a temporary string.
This way you will save this list of names in the firebase database.
Will be like:
[0] E
[1] Ex
[2] Exa
[3] Exam
[4] Examp
[5] Exampl
[6] Example
[7] o
[8] on
[9] one
For retrieving this info with the word you wanna search:
await Firestore.instance.collection('name of your collection').where('name of your list saved in the firebase', arrayContains: 'the name you are searching').getDocuments();
This way if you search for "one" and the name is "Example one" the search will return properly.
THIS IS ANOTHER SEARCH CODE THIS WILL SEARCH INSIDE FIREBASE DATABASE
import 'package:flutter/material.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:firebase_database/ui/firebase_animated_list.dart';
class Db extends StatefulWidget {
#override
HomeState createState() => HomeState();
}
class HomeState extends State<Db> {
List<Item> Remedios = List();
Item item;
DatabaseReference itemRef;
TextEditingController controller = new TextEditingController();
String filter;
final GlobalKey<FormState> formKey = GlobalKey<FormState>();
#override
void initState() {
super.initState();
item = Item("", "");
final FirebaseDatabase database = FirebaseDatabase.instance; //Rather then just writing FirebaseDatabase(), get the instance.
itemRef = database.reference().child('Remedios');
itemRef.onChildAdded.listen(_onEntryAdded);
itemRef.onChildChanged.listen(_onEntryChanged);
controller.addListener(() {
setState(() {
filter = controller.text;
});
});
}
_onEntryAdded(Event event) {
setState(() {
Remedios.add(Item.fromSnapshot(event.snapshot));
});
}
_onEntryChanged(Event event) {
var old = Remedios.singleWhere((entry) {
return entry.key == event.snapshot.key;
});
setState(() {
Remedios\[Remedios.indexOf(old)\] = Item.fromSnapshot(event.snapshot);
});
}
void handleSubmit() {
final FormState form = formKey.currentState;
if (form.validate()) {
form.save();
form.reset();
itemRef.push().set(item.toJson());
}
}
#override
void dispose() {
controller.dispose();
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: new AppBar(
centerTitle: true,
backgroundColor: new Color(0xFFE1564B),
),
resizeToAvoidBottomPadding: false,
body: Column(
children: <Widget>\[
new TextField(
decoration: new InputDecoration(
labelText: "Type something"
),
controller: controller,
),
Flexible(
child: FirebaseAnimatedList(
query: itemRef,
itemBuilder: (BuildContext context, DataSnapshot snapshot,
Animation<double> animation, int index) {
return Remedios\[index\].name.contains(filter) || Remedios\[index\].form.contains(filter) ? ListTile(
leading: Icon(Icons.message),
title: Text(Remedios\[index\].name),
subtitle: Text(Remedios\[index\].form),
) : new Container();
},
),
),
\],
),
);
}
}
class Item {
String key;
String form;
String name;
Item(this.form, this.name);
Item.fromSnapshot(DataSnapshot snapshot)
: key = snapshot.key,
form = snapshot.value\["form"\],
name = snapshot.value\["name"\];
toJson() {
return {
"form": form,
"name": name,
};
}
}
if Search list is case senstive like this :
Curaprox Be You Display
Curaprox Black is White Display
Curaprox Black is White Mini Display
Curaprox Hydrosonic Pro Display
Curaprox Large Interdental Brush Display
then :
response = await FirebaseFirestore.instance
.collection('pointOFSale')
.orderBy("title")
.startAt([val.capitalize()]).endAt(
[val[0].toUpperCase() + '\uf8ff']).get();
Extension code :
extension StringExtension on String {
String capitalize() {
return "${this[0].toUpperCase()}${this.substring(1)}";
}
}
if List is like :
curaprox be you display
curaprox black is white display
curaprox black is white mini display
then :
response = await FirebaseFirestore.instance
.collection('pointOFSale')
.orderBy("title")
.startAt([val]).endAt([val + '\uf8ff']).get();
soo simple and fast.
if (text.length > 1) {
setState(() {
tempSearchStore = _listPkh.documents.where((d) {
if (d['nama'].toLowerCase().indexOf(text) > -1) {
return true;
} else if (d['alamat'].toLowerCase().indexOf(text) > -1) {
return true;
}
return false;
}).toList();
});
} else {
setState(() {
tempSearchStore = _listPkh.documents;
});
}
I am currently using Cloud Firestore with the Streambuilder widget in order to populate a ListView widget with Firestore documents.
new StreamBuilder<QuerySnapshot>(
stream: Firestore.instance.collection('videos').limit(10).snapshots(),
builder: (BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (!snapshot.hasData) return new Center(
child: new CircularProgressIndicator(),
);
return new ListView(
children: snapshot.data.documents.map((DocumentSnapshot document) {
new Card(child: ...)
}).toList(),
);
},
);
This setup however only allows for the querying of the first x results (in this case x=10), with x being a fixed number that will sooner or later be exceeded by the number of Card widgets the user wants to see as he or she scrolls down.
Would it now be possible to query the first x results, and after the user hits a scroll threshold to query the next x+10 results from Cloud Firestore and so on?
This would allow for a dynamic list length which would also benefit the Firestore data consumption.
I am not sure whether it is possible or not with Streambuilder. I have integrated the similar functionality in my App using the startAfter method as shown below
class Feed extends StatefulWidget {
Feed({this.firestore});
final Firestore firestore;
#override
_FeedState createState() => _FeedState();
}
class _FeedState extends State<Feed> {
ScrollController controller;
DocumentSnapshot _lastVisible;
bool _isLoading;
CollectionReference get homeFeeds => widget.firestore.collection('homefeed');
List<DocumentSnapshot> _data = new List<DocumentSnapshot>();
final scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
controller = new ScrollController()..addListener(_scrollListener);
super.initState();
_isLoading = true;
_getData();
}
Future<Null> _getData() async {
// await new Future.delayed(new Duration(seconds: 5));
QuerySnapshot data;
if (_lastVisible == null)
data = await widget.firestore
.collection('homefeed')
.orderBy('created_at', descending: true)
.limit(3)
.getDocuments();
else
data = await widget.firestore
.collection('homefeed')
.orderBy('created_at', descending: true)
.startAfter([_lastVisible['created_at']])
.limit(3)
.getDocuments();
if (data != null && data.documents.length > 0) {
_lastVisible = data.documents[data.documents.length - 1];
if (mounted) {
setState(() {
_isLoading = false;
_data.addAll(data.documents);
});
}
} else {
setState(() => _isLoading = false);
scaffoldKey.currentState?.showSnackBar(
SnackBar(
content: Text('No more posts!'),
),
);
}
return null;
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: scaffoldKey,
appBar: new AppBar(),
body: RefreshIndicator(
child: ListView.builder(
controller: controller,
itemCount: _data.length + 1,
itemBuilder: (_, int index) {
if (index < _data.length) {
final DocumentSnapshot document = _data[index];
return new Container(
height: 200.0,
child: new Text(document['question']),
);
}
return Center(
child: new Opacity(
opacity: _isLoading ? 1.0 : 0.0,
child: new SizedBox(
width: 32.0,
height: 32.0,
child: new CircularProgressIndicator()),
),
);
},
),
onRefresh: ()async{
_data.clear();
_lastVisible=null;
await _getData();
},
),
);
}
#override
void dispose() {
controller.removeListener(_scrollListener);
super.dispose();
}
void _scrollListener() {
if (!_isLoading) {
if (controller.position.pixels == controller.position.maxScrollExtent) {
setState(() => _isLoading = true);
_getData();
}
}
}
}
Hope it helps!
That's definitely possible, but there's nothing pre-built in the API.
You'll have to remember the last document on the first page, and then startAfter() with that document to get the second page of documents.
See the documentation on Paginating Data with Query Cursors.