Flutter Riverpod show CircularProgressIndicator while waiting for future ro resolve - flutter

I want to show CircularProgressIndicator while waiting for future to resolve using flutter_riverpod, here is my code snippet.
But it's not showing, I am using ConsumerStatefulWidget is this right way to do it?
ElevatedButton(
onPressed: () {
rejectResponse = ref
.read(notificationRepositoryProvider)
.approveDocument(1);
Navigator.of(context).pop();
setState(() {});
},
child: FutureBuilder(
future: rejectResponse,
builder: (context, snapshot) {
if (snapshot.connectionState ==
ConnectionState.done) {
if (snapshot.hasData) {
return Text('Yes');
} else if (snapshot.hasError) {
return Text('Error');
}
} else if (snapshot.connectionState ==
ConnectionState.waiting) {
return CircularProgressIndicator();
}
return Text('Yes');
}),
),

The preferred way of doing this in Riverpod is using a FutureProvider and AsyncValue:
final notificationRepositoryProvider = FutureProvider<bool?>((ref) async {
Future<bool> approveDocument() => Future.delayed(Duration(seconds: 2), () => Future.value(Random().nextBool()));
return approveDocument();
});
class HomeView extends ConsumerStatefulWidget {
const HomeView({Key? key}) : super(key: key);
#override
HomeViewState createState() => HomeViewState();
}
class HomeViewState extends ConsumerState<HomeView> {
#override
Widget build(BuildContext context) {
AsyncValue<bool?> rejectResponse = ref.watch(notificationRepositoryProvider);
return ElevatedButton(
onPressed: () {
ref.refresh(notificationRepositoryProvider.future);
},
child: rejectResponse.when(
loading: () => const CircularProgressIndicator(
color: Colors.white,
),
skipLoadingOnRefresh: false,
error: (err, stack) => Text('Error'),
data: (data) => Text('Yes: $data'),
));
}
}
Note that after the initial loading, the FutureProvider will return the previous value but will set AsyncValue.isRefreshing to true. To avoid this and always show the loader on refresh, you can set skipLoadingOnRefresh to false.

You can use ConnectionState, but I think it can also be in none state. So you can check if its done, and if not, you should probably show the loading indicator anyway.
builder: (context, snapshot) {
if (snapshot.connectionState != ConnectionState.done) {
return CircularProgressIndicator();
}
else
return Text('Yes');
}
}
I usually use the hasData property instead. This lets me reduce the number of states I have to deal with, and safely assume snapshot.data is not null. I use the below pattern.
// strongly type the builder so snapshot.data is typed as well.
FutureBuilder<MyType>(
future: myFuture
builder: (context, snapshot) {
if (!snapshot.hasData) return Loading();
final data = snapshot.data!; // '!' is safe since hasData is true
return DisplayData(data);
}
)

Related

How to get result of FutureBuilder from the parent FutureBuilder?

The entry point is _processError function. I expect to get a widdet there. And this _processError runs from a parent FutureBuilder.
Then another Future builder should be executed, at least I think it should... But it seems there is no result from there. Whats wrong with it?
FutureBuilder<List<ShortLetter>>(
future: fetchMessages(),
builder: (BuildContext context, AsyncSnapshot<List<ShortLetter>> snapshot) {
...
} else if (snapshot.hasError) {
return _processError(snapshot, context); // I want to get a widget when an error happens
...
},
);
Future<bool> checkConnection() async {
debugPrint('---checkConnection---');
var connectivityResult = await (Connectivity().checkConnectivity());
...
// and returs true or false
}
Widget _processError(AsyncSnapshot snapshot, BuildContext context) {
var errorType = snapshot.error.runtimeType;
debugPrint('AllMessagesView, snapshot error: $errorType');
debugPrint(snapshot.error.toString());
if (errorType == TimeoutException) {
debugPrint('0000000000000000');
//////////////////////////////////////////////////////
// there is any output in console from the FutureBuilder below
// but checkConnection() was executed
FutureBuilder<bool>(
future: checkConnection(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData) {
debugPrint('11111111111111 snapshot data: ${snapshot.data}');
if (snapshot.data == true) {
...
}
...
} else if (snapshot.hasError) {
debugPrint('2222222222222');
...
} else {
debugPrint('Error. This should not happen.');
...
}
},
);
...
}
...
}
here is a sample console output and any result from the second FutureBuilder
I/flutter (10556): AllMessagesView, snapshot error: TimeoutException
I/flutter (10556): TimeoutException after 0:00:10.000000: Future not completed
I/flutter (10556): 0000000000000000
I/flutter (10556): ---checkConnection---
Parent FutureBuilder is already been processed, I think we don't need to pass Async data.
This demo widget may help.
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Future<int> parentF() async {
return await Future.delayed(Duration(seconds: 2), () => 4);
}
Future<String> childF(int sec) async {
return await Future.delayed(Duration(seconds: sec), () => "got the child");
}
Widget childFB(final data) {
print(data.runtimeType);
return FutureBuilder(
future: childF(4),
builder: (context, snapshot) => snapshot.hasData
? Text("${snapshot.data!} parent data: $data ")
: const Text("loading second child"));
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
FutureBuilder(
future: parentF(),
builder: (context, parentSnapshot) {
return parentSnapshot.hasData
? FutureBuilder<String>(
future: childF(3),
builder: (context, snapshot) {
return snapshot.hasData
? Text(
"${snapshot.data!} parent data: ${parentSnapshot.data} ")
: const Text("loading child");
},
)
: const Text("loading parent");
},
),
FutureBuilder(
future: parentF(),
builder: (context, parentSnapshot) {
return parentSnapshot.hasData
? childFB(parentSnapshot
.data) // it already have normal data, not async
: const Text("loading parent");
},
),
],
));
}
}

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.

How To Work with Flutter Data Model and Future Builder

i have working with test app, its just display list of employees from api call, for that i have created data model for employee and calling it. but i get nothing i know somewhere it goes wrong help me to find out the problem(actually no errors but, its does not load the data).
here is the snippets
import 'package:flutter/material.dart';
import '../models/employee.dart';
import 'package:http/http.dart' as http;
import 'dart:convert';
class EmployeeListScreen extends StatefulWidget {
EmployeeListScreen({Key key}) : super(key: key);
#override
_EmployeeListScreenState createState() => _EmployeeListScreenState();
}
class _EmployeeListScreenState extends State<EmployeeListScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Employee List"),
),
body: FutureBuilder(
future: fetchEmployees(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.none) {
return Center(
child: Text("None"),
);
}
if (snapshot.connectionState == ConnectionState.active) {
return Center(
child: CircularProgressIndicator(),
);
}
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return Center(child: Text("No Employees"));
} else {
return Center(
child: ListView.builder(
itemCount: snapshot.data.length[![enter image description here][1]][1],
itemBuilder: (BuildContext context, int index) {
return Text(snapshot.data[index]["name"]);
},
),
);
}
}
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
}
return Container();
},
));
}
Future<List<Employee>> fetchEmployees() async {
final response = await http.get(
"http://192.168.1.199/projects/ci/employee/api/getEmployees",
headers: {"accept": "application/json"});
debugPrint("Api Finished...");
if (response.statusCode == 200) {
final result = jsonDecode(response.body);
Iterable list = result['employees'];
print(list);
return list.map((employee) => Employee.fromJson(employee)).toList();
} else {
throw Exception("Failed to Load Employees");
}
}
}
see the screen shots.
i have the result while am using traditional api call without using model and factory methods, its very confusing to me also suggest me for best sites to learn these things, even i saw the official document it not clear at all.
To help debug the issue, how about trying this simplified code below. Call your fetchEmployees() from inside loadSlowData() method.
(It's not good practice to make an async call directly in FutureBuilder future:. Instead, make the async call in initState of the StatefulWidget. Since FutureBuilder is inside the build() method, and build could be called up to 60 times a second, you can obviously see the problem. If you happen to use an animation on that part of the widget tree, which refresh at 60fps, you'll get that situation.)
import 'package:flutter/material.dart';
class FutureBuilderStatefulPage extends StatefulWidget {
#override
_FutureBuilderStatefulPageState createState() => _FutureBuilderStatefulPageState();
}
class _FutureBuilderStatefulPageState extends State<FutureBuilderStatefulPage> {
Future<String> _slowData;
#override
void initState() {
super.initState();
_slowData = loadSlowData();
}
Future<String> loadSlowData() async {
// replace with your async call ↓ ↓
return await Future.delayed(Duration(seconds: 2), () => 'The Future has arrived');
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('FutureBldr Stateful'),
),
body: FutureBuilder<String>(
future: _slowData,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(child: Text(snapshot.data));
}
return Center(child: Text('Loading...'));
},
),
);
}
}
You can possibly Try snapShot.hasData instead of snapshot.data

snapshot.data is null in Flutter

My snapshot.data is null. When I print the response it is displaying the retrieved data. But still snapshot.data is null.
Future _getUsers() async {
var data = await http.post("http://10.0.2.2/Flutter/abreport.php", body: {
{
"date": mydt,
});
var jsonData = json.decode(data.body); //edited
print(jsonData); // the data is printing here
return jsonData;
}
}
FutureBuilder(
future: _getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
debugPrint(snapshot.data);
if (snapshot.data == null) {
return Container(
child: Center(
child:Text("no data"),
)
);
} else {
//some code
}
)
You should use the format given in the documentation for FutureBuilder. You're not checking for the state of the future, so when the FutureBuilder is first built, it will display "no data". You haven't implemented your else branch, so by the time you have data, your build will probably not refresh anyway. Try this code instead:
FutureBuilder(
future: _getUsers(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.data == null) {
return Text('no data');
} else {
return Text('data present');
}
} else if (snapshot.connectionState == ConnectionState.error) {
return Text('Error'); // error
} else {
return CircularProgressIndicator(); // loading
}
}
)
with Flutter 2.2, this one returns an error
builder: (BuildContext context, AsyncSnapshot<String> snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data,);
Error: The argument type 'String?' can't be assigned to the parameter type 'String' because 'String?' is nullable and 'String' isn't.
return Text(snapshot.data,);
but this one dosen't
builder: (BuildContext context, AsyncSnapshot snapshot) {
When similar things happen, take the type "var" not "String" or other non-nullable type.
(If it was not Flutter, the compilers will do?)
Since i cannot see your complete code, i am assuming you are parsing your json data incorrectly after receiving it inside FutureBuilder. Below is an example which is similar to what you are doing. This example retrieves Date json data and displays using FutureBuilder,
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
MyAppState createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.only(left: 10.0, right: 10.0),
child: FutureBuilder(
future: _getDate(),
builder: (BuildContext context, AsyncSnapshot snapshot) {
if (snapshot.hasData) {
return Text('Date: ' + snapshot.data['date']
+ '\nMilliseconds Since Epoch: ' + snapshot.data['milliseconds_since_epoch'].toString()
+ '\nTime: ' + snapshot.data['time'],
style: TextStyle(fontSize: 18.0, fontWeight: FontWeight.bold, color: Colors.grey));
} else {
return Center(child: CircularProgressIndicator());
}
},
))
]))));
}
Future _getDate() async {
var data = await http.post("http://date.jsontest.com/");
var jsonData = json.decode(data.body);
print(jsonData);
return jsonData;
}
}
Test screenshot:
Hope this helps.
Because your async function doesnt return anything..
Change it like this:
Future _getUsers() async {
return await http.post("http://10.0.2.2/Flutter/abreport.php", body: {
{
"date": mydt,
});
var jsonData = json.decode(data.body); //edited
print(jsonData); // the data is printing here
return jsonData;
}
}

FutureBuilder only works in Debug

I have a FutureBuilder with a ListView to display custom items (Widgets) with values which are read from .txt files.
The problem is that these items are only displayed if I launch the app in Debug-mode or run-mode. When I try to open the app with the AppLauncher (like a "normal" user would do it) the listView is empty. I tried this on an AVD and on a "real" device.
the Future "listFuture" is used to read the values from the files and return a list of Widgets
class Home extends StatefulWidget {
final Future listFuture = setupList();
#protected
#mustCallSuper
void initState() {
print("init complete");
}
#override
State<StatefulWidget> createState() {
return HomeState();
}
}
If the FutureBuilder gets the data correctly a listView with the list of my widgets should be displayed
child: FutureBuilder<List<SubListItem>>(
future: widget.listFuture,
// ignore: missing_return
builder: (BuildContext context, AsyncSnapshot snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.none:
return new Text("None");
case ConnectionState.waiting:
return new Text("loading");
default:
if (snapshot.hasError) {
print("Error");
return Center(child: (Text("No data")));
} else {
return subListView(context, snapshot);
}
}
},
),
Widget subListView(BuildContext context, AsyncSnapshot snapshot) {
List<Widget> items = snapshot.data;
//This ScrollConfiguration is used to remove any animations while scrolling
return ScrollConfiguration(
behavior: CustomScrollBehavior(),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 4),
child: new ListView.builder(
itemCount: items.length,
itemBuilder: (BuildContext context, int index) {
return Column(
children: <Widget>[items[index]],
);
},
),
),
);
}
Thanks for helping!
Ok, I solved the problem. You just have to call "setState" when your Widget is built.
#protected
#mustCallSuper
void initState() {
super.initState();
Future.delayed(Duration.zero, () {
//This setState is necessary because it refreshes the listView
setState(() {});
});
}
It's looks like a async data issue, try these changes:
Remove listFuture from your StatefulWidget.
Add the listFuture var inside your State.
Move the setupList() method inside your State.
And finally call directly like this:
child: FutureBuilder<List<SubListItem>>(
future: setupList(),
// ignore: missing_return
builder: (BuildContext context, AsyncSnapshot snapshot) {
if(!snapshot.hasData) {
return new Text("loading");
}
else if (snapshot.hasError) {
print("Error");
return Center(child: (Text("No data")));
} else {
return subListView(context, snapshot);
}
}
},
),