How can i use a Future Int using a Provider? - flutter

I am trying to show a live count of total documents in an Appbar. I get the right information in my console, but when i try to pass it with an Provider it returns an Instance of 'Future'. Can someone tell why i am getting still an Instence even if i await the result and the result is printed correctly in my console?
this is where i get the Future int and print the result to my console.
class AuthenticationService extends ChangeNotifier {
Future<int> totalJumps(jumpDict) async {
var respectsQuery = _db.collection(jumpDict);
var querySnapshot = await respectsQuery.get();
var result = querySnapshot.docs.length;
print(result);
// notifyListeners();
return result;
}
}
This is were it should show the result as a int in the title of the appBar
class LazyListOnline extends StatefulWidget {
static const String id = 'Lazy_list_online';
#override
_LazyListOnlineState createState() => _LazyListOnlineState();
}
class _LazyListOnlineState extends State<LazyListOnline> {
#override
Widget build(BuildContext context) {
String userDict = Provider.of<AuthenticationService>(context).findJumpDict;
var _firestoreDb =
FirebaseFirestore.instance.collection(userDict).snapshots();
var totalJump = Provider.of<AuthenticationService>(context)
.totalJumps(userDict)
.toString();
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.arrow_back),
onPressed: () {
Navigator.popAndPushNamed(context, HomeDrawer.id);
}),
title: Text('totalJumps'),
body: Stack(children: [
Padding(
padding: const EdgeInsets.all(18.0),
child: Container(
decoration: BoxDecoration(),
),
StreamBuilder<QuerySnapshot>(
stream: _firestoreDb,
builder: (context, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, int index) {
return JumpItem(
snapshot: snapshot.data,
index: index,
);
});
}),
]),
);
}
}

I think you just need to store only the amount of documents as an int in Provider like.
class DocumentData extends ChangeNotifier {
int documentLength;
void setCurrentLengthOfDocuments(int length) {
this. documentLength = length;
notifyListeners();
}
}
Then in StreamBuilder. Every time data has been changed. You just need to update. Regrading to your example be something like.
StreamBuilder<QuerySnapshot>(
stream: _firestoreDb,
builder: (context, snapshot) {
if (!snapshot.hasData) return CircularProgressIndicator();
// Update amount of documents length
Provider.of<DocumentData>(context, listen: false)
.setCurrentLengthOfDocuments(lengthOfCurrentDocuments);
return ListView.builder(
itemCount: snapshot.data.docs.length,
itemBuilder: (context, int index) {
return JumpItem(
snapshot: snapshot.data,
index: index,
);
});
}),
]),
Then when you can get the length of documents every where on this page by just use Consumer widget. Or get the value directly from Provider.

Related

ListView infinite loop when parsing data from API response

I'm trying to read data from some mock endpoint. Mock endpoint I'm invoking (HTTP GET) is here.
Essentially, the JSON structure is result > toolList[] > category > tools[]. I'd like to display these items on my page in such a way that the category name is displayed first, then items belonging to that category under it. I am trying to achieve this with ListView.builder but I somehow managed to get some sort of infinite loop and the items keep getting populated until my device freezes.
What I'm trying to achieve:
Category Title
Item 1
Item 2
Category Title 2
Item 1
Item 2
Itme 3
And finally the Widget:
class OpticsSelectorWidget extends StatefulWidget {
const OpticsSelectorWidget({Key key}) : super(key: key);
#override
_OpticsSelector createState() => _OpticsSelector();
}
class _OpticsSelector extends State<OpticsSelectorWidget> {
PageController pageViewController;
final scaffoldKey = GlobalKey<ScaffoldState>();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: StandardAppbarWidget(appBarTitle: "some title"),
body: SizedBox(
child: FutureBuilder<ApiCallResponse>(
future: ConfigurationController.getOpticsTools2(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color: Colors.red,
),
),
);
}
final gridViewGetToolsOpticsResponse = snapshot.data;
var toolCategories = getJsonField(
gridViewGetToolsOpticsResponse.jsonBody,
r'''$.result.toolList''',
).toList();
return Builder(
builder: (context) {
return ListView.builder(itemBuilder: (context, itemIndex) {
final widgets = <Widget>[];
for (int i = 0; i < toolCategories.length; i++) {
var currentToolCategory = getJsonField(
toolCategories[i],
r'''$.category''',
);
widgets.add(Text(
currentToolCategory,
style: Colors.white,
));
var toolListInCategory = getJsonField(
toolCategories[itemIndex],
r'''$.tools''',
);
for (int j = 0; j < toolListInCategory.length - 1; j++) {
var toolDisplayName = getJsonField(
toolListInCategory[j],
r'''$.displayName''',
);
widgets.add(Text(toolDisplayName));
}
}
return SingleChildScrollView(
child: Column(
children: widgets,
));
});
},
);
},
),
),
);
}
}
I'm especially confused about the itemIndex expression. That number I thought would be the item count that I receive from my API call, but I guess I'm mixing something badly.
If it helps, here's the bit where I'm making the API call. But feel free to just grab the JSON your way (from mock response)
static Future<ApiCallResponse> getOpticsTools2() async {
HttpOverrides.global = new MyHttpOverrides();
var client = http.Client();
try {
var response = await client.get(Uri.https('stoplight.io'
, "mocks/ragingtortoise/test/82311857/configuration/tools/optics"));
return createResponse(response, true);
} finally {
client.close();
}
}
static ApiCallResponse createResponse(http.Response response, bool returnBody) {
var jsonBody;
try {
jsonBody = returnBody ? json.decode(response.body) : null;
} catch (_) {}
return ApiCallResponse(jsonBody, response.statusCode);
}
And the return type, which is ApiCallResponse:
class ApiCallResponse {
const ApiCallResponse(this.jsonBody, this.statusCode);
final dynamic jsonBody;
final int statusCode;
bool get succeeded => statusCode >= 200 && statusCode < 300;
}
Finally adding the screen recording of what's happening, if it helps.
In here builder you should use,itemCount parameter
ListView.builder(
itemCount: list.length,
itemBuilder: (context, index) {
return Your Widget;
}),
Create a state variable for future and include itemCount: list.length,
final myFuture = ConfigurationController.getOpticsTools2();
And use it on
child: FutureBuilder<ApiCallResponse>(
future: myFuture ,
builder: (context, snapshot) {
I struggled for so long but clearly, the issue was not passing in the itemCount argument into the ListView.builder() method. Also, the outer loop was invalid as now I need to use the actual itemIndex within the builder. Thanks for pointing out the itemCount all! Here's the fixed code and the solution in case anyone needs it later.
#override
Widget build(BuildContext context) {
final opticsToolsMockResponse = ConfigurationController.getOpticsTools2();
return Scaffold(
backgroundColor: Colors.black,
appBar: StandardAppbarWidget(appBarTitle: "some title"),
body: SizedBox(
child: FutureBuilder<ApiCallResponse>(
future: opticsToolsMockResponse,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return Center(
child: SizedBox(
width: 50,
height: 50,
child: CircularProgressIndicator(
color: Colors.red,
),
),
);
}
final gridViewGetToolsOpticsResponse = snapshot.data;
var toolCategories = getJsonField(
gridViewGetToolsOpticsResponse.jsonBody,
r'''$.result.toolList''',
).toList();
return Builder(
builder: (context) {
return ListView.builder(
itemCount: toolCategories.length,
itemBuilder: (context, itemIndex) {
final widgets = <Widget>[];
var currentToolCategory = getJsonField(
toolCategories[itemIndex],
r'''$.category''',
);
widgets.add(Text(
currentToolCategory,
style: Colors.white,
));
var toolListInCategory = getJsonField(
toolCategories[itemIndex],
r'''$.tools''',
);
for (int j = 0; j < toolListInCategory.length; j++) {
var toolDisplayName = getJsonField(
toolListInCategory[j],
r'''$.displayName''',
);
widgets.add(Text(toolDisplayName));
}
return SingleChildScrollView(
child: Column(
children: widgets,
));
});
},
);
},
),
),
);
}
You just forgot to specify the size of the list, you should do it with the itemCount property in the ListView.builder widget
itemCount: list.length,

I am failing to get data from cloud firestore while using flutter

At first, when i started writing my calls to get data from firestore, it worked. But when i tried writing more docs to my collection, it failed to bring data for the docs i recently added. Then, when i deleted the first one i added, i stopped receiveing data from firestore all together. I have tried several methods, but have all ended in failure.
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class collect extends StatefulWidget {
#override
_collectState createState() => _collectState();
}
class _collectState extends State<collect>
{
Future _data;
void initState()
{
super.initState();
_data = getStuff();
}
Future getStuff()
async {
var firestore = FirebaseFirestore.instance;
QuerySnapshot qn = await firestore.collection("buses").get();
return qn.docs;
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: _data,
builder: (_, snapshot)
{
if(snapshot.connectionState == ConnectionState.waiting)
{
return Center(
child:Text("Loading")
);
}
else if(snapshot.connectionState == ConnectionState.done)
{
return ListView.builder(itemCount: snapshot.data.length,itemBuilder:(_, index)
{
return Container(
child: ListTile(
title: Text(snapshot.data[index].data()["name"].toString()),
subtitle: Text(snapshot.data[index].data()["price"].toString()),
),
);
});
}
},
),
);
}
}
```![enter image description here](https://i.stack.imgur.com/L7FqF.jpg)
Define your database call as,
Future getStuff() async {
var docs;
await FirebaseFirestore.instance
.collection("buses")
.get()
.then((querySnapshot) {
docs = querySnapshot.docs;
});
return docs;
}
Then use the FutureBuilder in the build() function as,
return Scaffold(
body: Center(
child: FutureBuilder<dynamic>(
future: getStuff(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (_, index) {
return Container(
child: ListTile(
title: Text(
snapshot.data[index].data()["name"].toString()),
subtitle: Text(
snapshot.data[index].data()["price"].toString()),
),
);
});
} else {
return CircularProgressIndicator();
}
},
),
),
);
I wrapped the FutureBuilder inside a Center just for clarity, you may remove that Center widget.

Future builder returning length as null

I am retreiving data from cloud firestore and using Future builder and Listview Builder to display the data. But i am getting null values in the Listview builder i.e displaying the CircularProgressIndicator always.Can't figure out the problem.Any solution will be of great help.
The print(values) function prints out: [9,8] successfully
This is the code i implemented:
Future<List> getassignment() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
final name = prefs.getString('orgname') ?? '';
print(name);
var query = FirebaseFirestore.instance.collection('Org').doc(name).collection('Login').doc(FirebaseAuth.instance.currentUser.uid);
query.snapshots().forEach((doc) {
List values = List.from(doc.data()['fields']['class']);
print(values);
return values;
});
}
// void getlist() async{
// await getassignment();
// }
#override
void initState() {
// getlist();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xFF1976D2),
body: FutureBuilder(
future: getassignment(),
builder: (context,snapshot){
List list = snapshot.data;
if (!snapshot.hasData) {
return Center(child: CircularProgressIndicator());
} else{
return Container(
child: ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, position) {
return GestureDetector(
onTap: (){
Navigator.of(context).push(MaterialPageRoute<Null>(
builder: (BuildContext context){
return new SubjectList(
clas: list[position].toString(),
);
}
));
},
child: Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(list[position].toString(), style: TextStyle(fontSize: 22.0),),
),
),
);
},
),
);
}
},
),
);
}
You are assigning and returning data inside of foreach loop. So that won't return anything.
// try adding await in this line.
var query = await FirebaseFirestore.instance.collection('Org').doc(name).collection('Login').doc(FirebaseAuth.instance.currentUser.uid);
List values = query.snapshots().forEach((doc) => List.from(doc.data()['fields']['class']));
print(values);
return values;
You need to do something like this.

Flutter BLoC implementation with streamBuilder

I have a problem with my BLoC implementation, I have this code in synchronize.dart:
...
class _SynchronizeState extends State<Synchronize> {
UserBloc userBloc;
//final dbRef = FirebaseDatabase.instance.reference();
#override
Widget build(BuildContext context) {
userBloc = BlocProvider.of(context);
return Scaffold(
resizeToAvoidBottomPadding: false,
body: Container(
...
),
child: StreamBuilder(
stream: dbRef.child('info_tekax').limitToLast(10).onValue,
builder: (context, snapshot) {
if(snapshot.hasData && !snapshot.hasError){
Map data = snapshot.data.snapshot.value;
List keys = [];
data.forEach( (index, data) => keys.add(index) );
return ListView.builder(
itemCount: data.length,
itemBuilder: (context, index) => SynchronizeItem(title: keys[index], bottom: 10, onPressed: (){ print(keys[index]); })
);
}else{
return Container(
child: Center(
child: Text('Loading...'),
),
);
}
}
),
),
);
}
}
The previos code, works correctly, but i want implemente bloc Pattern, i have userBloc then i want to put this
userBloc.getDevicesForSinchronized()
instead of
dbRef.child('info_tekax').limitToLast(10).onValue,
my problem is this:
void getDevicesForSynchronized() {
return dbRef.child(DEVICES).limitToLast(10).onValue;
}
i get this error **A vaue of type 'Stream' can't be returned from method 'getDevicesForSynchronized' because it has a return type of 'void'
The error is very clear, but i don't know what is type that i need return, try:
Furure<void> getDevicesForSynchronized() async {
return await dbRef.child(DEVICES).limitToLast(10).onValue;
}
or
Furure<void> getDevicesForSynchronized() async {
dynamic result = await dbRef.child(DEVICES).limitToLast(10).onValue;
}
and another solutions, but I don't know how return correctly value for use in the StreamBuilder
From the error message you can see that the return type is Stream. Change your method like:
Future<Stream> getDevicesForSynchronized() async {
return dbRef.child(DEVICES).limitToLast(10).onValue;
}

single document in same collection Firestore Flutter

I build a quiz app and i use firestore for the data, i need a code for this : when the user select answer 1 he goes to page A , but when he select answer 2 he goes to page B ... etc
This is where am i : all the answers go to the same page when i tap on it, i want for every answer has his own page
This is my code :
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:flutter/material.dart';
class question14 extends StatefulWidget {
#override
_question14State createState() => _question14State();
}
class _question14State extends State<question14> {
int selectedIndex
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: Firestore.instance.collection('numberzz').snapshots(),
builder: (context, snapshot) {
if (!snapshot.hasData) return const Text('Loading ...');
return ListView.builder(
scrollDirection: Axis.horizontal,
padding: EdgeInsets.fromLTRB(100.0, 0.0, 0.0, 0.0),
itemExtent: 200.0,
itemCount: snapshot.data.documents.length,
itemBuilder: (BuildContext context, int index) {
final DocumentSnapshot document =
snapshot.data.documents[index];
return Container(
padding: EdgeInsets.fromLTRB(0.0, 300.0, 0.0, 450.0),
child: ListTile(
contentPadding: selectedIndex == index
? EdgeInsets.all(0.0)
: EdgeInsets.all(25.0),
title: Image.network(
document['number'],
),
selected: selectedIndex == index,
onTap: () {
Firestore.instance.runTransaction((transaction) async {
DocumentSnapshot freshSnap =
await transaction.get(document.reference);
await transaction.update(freshSnap.reference, {
'vote': freshSnap['vote'] + 1,
});
});
Navigator.push(
context, MaterialPageRoute(
builder: (context) => new page()));
setState(() {
selectedIndex = index;
});
},
),
);
},
);
}));
}
}
thanks for your help !
You can check which index is selected before navigating. Kind of conditional navigation.
switch(selectedIndex){
case 0:
Navigator.of(context).push(.....(Page A));
break;
case 1:
.......
break;
I think you get the point.
But this code should come after
setState(() { selectedIndex = index };