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");
},
),
],
));
}
}
Related
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);
}
)
Since I use multiple StreamBuilder in my screen I get a Bad state error.
I know that I have to use a StreamController and use it with .broadcast().
Because I dont create the streams by myself I dont know how to change the controller of these streams.
This is my code:
class MyScreen extends StatefulWidget {
#override
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: [
StreamBuilder<List<int>>(
stream: streamOne?.value,
builder: (c, snapshot) {
final newValueOne = snapshot.data;
return Text(newValueOne);
}),
StreamBuilder<List<int>>(
stream: streamTwo?.value,
builder: (c, snapshot) {
final newValueTwo = snapshot.data;
return Text(newValueTwo);
}),
StreamBuilder<List<int>>(
stream: streamThree?.value,
builder: (c, snapshot) {
final newValueThree = snapshot.data;
return Text(newValueThree);
}),
],
),
),
);
}
}
I tried to have it as BroadcastStreams:
class MyScreen extends StatefulWidget {
#override
_MyScreenState createState() => _MyScreenState();
}
class _MyScreenState extends State<MyScreen> {
#override
Widget build(BuildContext context) {
return SafeArea(
child: Scaffold(
body: Column(
children: [
StreamBuilder<List<int>>(
stream: streamOne?.asBroadcastStream(),
builder: (c, snapshot) {
final newValueOne = snapshot.data;
return Text(newValueOne);
}),
StreamBuilder<List<int>>(
stream: streamTwo?.asBroadcastStream(),
builder: (c, snapshot) {
final newValueTwo = snapshot.data;
return Text(newValueTwo);
}),
StreamBuilder<List<int>>(
stream: streamThree?.asBroadcastStream(),
builder: (c, snapshot) {
final newValueThree = snapshot.data;
return Text(newValueThree);
}),
],
),
),
);
}
}
This didnt work and gave me still a bad state error.
Would be great if somone could help me here.
Thank you very much!
Inside your streamBuilder builder, you have to check that the snapshot has actually received the data, otherwise your Text widget is receiving null, thus, throwing a bad state error:
StreamBuilder<List<int>>(
stream: streamThree.asBroadcastStream(),
builder: (c, snapshot) {
if(snapshot.hasData){
final newValueThree = snapshot.data;
return Text(newValueThree);
} else {
// return any other widget like CircularProgressIndicator
}
}),
You can also check on
snpashot.connectionState == ConnectionState.done
and
snpashot.connectionState == ConnectionState.active
and
snpashot.connectionState == ConnectionState.waiting
Thank you #Arnaud Delubac. I also had to check if the array I get from the stream is not empty:
StreamBuilder<List<int>>(
stream: streamThree.asBroadcastStream(),
builder: (c, snapshot) {
if (snapshot.hasData && snapshot.data.isNotEmpty && snapshot.connectionState == ConnectionState.active) {
final newValueThree = snapshot.data;
return Text(newValueThree);
} else {
// return any other widget like CircularProgressIndicator
}
}),
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
While fetching data from database in flutter snapShot.ConnectionState is always waiting and the circular progress indicator keeps on loading.
I am not getting any errors and I am using FutureBuilder to build my widget.
Class where I build my widget
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../providers/event_provider.dart';
class HomeScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: Provider.of<EventProviders>(context).fetchAndSetEvents(),
builder: (ctx, dataSnapshot) {
if (dataSnapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return Consumer<EventProviders>(
child: Text('Not found'),
builder: (ctx, eventData, ch) {
if (eventData.events.length <= 0) {
return ch;
} else {
return ListView.builder(
itemCount: eventData.events.length,
itemBuilder: (ctx, index) {
return Container(
child: Text(eventData.events[index].eventName),
);
},
);
}
},
);
}
},
);
}
}
My future class
Future<void> fetchAndSetEvents() async {
final dataList = await DBHelper.getData('user_events');
_events = dataList
.map(
(data) => EventProvider(
eventName: data['event'],
eventDate: data['date'],
id: data['id'],
),
)
.toList();
notifyListeners();
}
}
Some help will be highly appreciated
Set listen: false
future: Provider.of<EventProviders>(context, listen: false).fetchAndSetEvents(),
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;
}
}