This is a simple code which display a different Text based in a random number. I want to show the CircularProgressIndicator when the user push 'next' button and the method 'getRandom' delays 5 secs.
CircularProgressIndicator never is shown...why?
import 'package:flutter/material.dart';
import 'dart:math';
void main() {
runApp(new MyApp());
}
class MyApp extends StatefulWidget {
#override
State<StatefulWidget> createState() {
return MyAppState();
}
}
class MyAppState extends State<MyApp> {
Future<String> random;
#override
void initState() {
super.initState();
random = getRandom();
}
Future<String> getRandom() async{
print("getRandom");
Future.delayed(const Duration(seconds: 5));
return "the number is"+Random().nextInt(100).toString();
}
Widget build(BuildContext context) {
return new MaterialApp(
home: new Scaffold(
appBar: AppBar(title: Text("Random Widget")),
body: Center(child:
FutureBuilder(
future:random,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Column(children: [
Text(snapshot.data,textScaleFactor: 4),
getNextButton()
]);
} else if (snapshot.hasError) {
return Text("ERROR");
}
return CircularProgressIndicator();
}
)
)),
);
}
Widget getNextButton(){
return RaisedButton(
child: Text("NEXT"),
color: Colors.red,
onPressed: () {
setState(() {
random=getRandom();
});
}
);
}
}
Thanks in advance!!
There are several mistakes in your code.
You are passing initialData. So the snapshot.hasData will be true in the beginning itself.
You didn't initialise the random for the first time.
You have to await your Future.delay
Change getRandom implementation:
Future<String> getRandom() async{
await Future.delayed(const Duration(seconds: 5));
return "the number is"+ Random().nextInt(100).toString();
}
Implement initState to initialise random future:
#override
void initState() {
super.initState();
random = getRandom();
}
Change future builder:
FutureBuilder(
future:random,
builder: (context, snapshot) {
if (snapshot.hasData && snapshot.connectionState == ConnectionState.done) {
return Column(children: [
Text(snapshot.data,textScaleFactor: 4),
getNextButton()
]);
} else if (snapshot.hasError) {
return Text("ERROR");
}
return CircularProgressIndicator();
}
)
If your not using your initialData field in you FutureBuilder than delete and your code will work. If you for some reason need that value add the following statement to the end:
if(snapshot.data != 'starting') {
return CircularProgressIndicator();
}
See the docs for an example of how to use it
Related
How do I cancel FutureBuilder operation when rebuilding the widget
Lets say I have a code like this... Every time i pressed the Floating button the widget rebuilds calling myFuture which waits five seconds and then the counter increments... Now I want that during that five seconds if I pressed the Floating button the current Future (which is still is delayed) should stop its operation and the new Future will be called...So at the end I should get a counter of 2 but instead I get 3...
class MyHomePage extends StatefulWidget {
#override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var counter = 0;
myFuture()async{
await Future.delayed(Duration(seconds:5));
counter++;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: FutureBuilder(
future: myFuture(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(child: CircularProgressIndicator());
else return Text(counter.toString());
}
),
floatingActionButton: FloatingActionButton(
onPressed: ()=>setState(() {}),
child: Icon(Icons.add),
),
);
}
}
In order to cancel a Future, you can use the CancelableOperation from the async package.
It's implementation would look like the following :
Future<dynamic> executeCancelable(Future<dynamic> futureMethod) async {
operation?.cancel();
operation = CancelableOperation.fromFuture(futureMethod, onCancel: () {
print('Future stopped');
});
return operation.value;
}
Future<dynamic> futureMethod() async {
return Future.delayed(const Duration(milliseconds: 3000), () {
return counter++;
});
}
Which can be called with the following method :
executeCancelable(futureMethod())
Note that in this example, I'm using a Future.delayed wich can't "really" be cancelled as explained here.
This snippet would work well with an API query for example.
int counter = 0;
fiveSeconds() async {
await Future.delayed(Duration(seconds: 5));
}
twoSeconds() async {
await Future.delayed(Duration(seconds: 2));
}
bool _futureTime = true;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: FutureBuilder(
future: _futureTime ? fiveSeconds() : twoSeconds(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting)
return Center(child: CircularProgressIndicator());
else counter++;
return Text(counter.toString());
}
),
floatingActionButton: FloatingActionButton(
onPressed: (){setState(() {
_futureTime = !_futureTime;
});
},
tooltip: 'Increment',
child: Icon(Icons.add),
),
);
}
}
Alternatively you can use a Stream and StreamBuilder instead of Future and FutureBuilder if that fits your use case.
void main() {
// keep a reference to your stream subscription
StreamSubscription<List> dataSub;
// convert the Future returned by getData() into a Stream
dataSub = getData().asStream().listen((List data) {
updateDisplay(data);
});
// user navigated away!
dataSub.cancel();
}
source: https://dart.academy/how_cancel_future/
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
I am creating a simple loading screen between two screens in which I load it until task is not completed but I am stuck only on loading screen.
Navigator.of(context).pushReplacementNamed('/loading');
doTask(context);
void doTask(BuildContext context){
Navigator.of(context).pushReplacementNamed('/secondScreen');
}
I would suggest using a future builder instead:
class SecondRoute extends StatelessWidget {
#override
Widget build(context) {
return Scaffold(
appBar: AppBar(),
body: FutureBuilder(
future: doTask(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Loading();
}
return SecondScreen();
}),
);
}
Future<void> doTask() async {
// Any future process here
await Future.delayed(
Duration(seconds: 3),
);
}
}
doTask won't be called, the code does not work after Navigator.of(context).push(). Try completing task in loading screen, it is better you do your task in second screen and show loading while task is not complete.
class SecondRoute extends StatelessWidget {
Future<void> doTask() async {
//Dummy Code, Replace it with your task
await Future.delayed(
Duration(seconds: 2),
);
}
#override
Widget build(context) {
return Scaffold(
appBar: AppBar(),
body: FutureBuilder(
future: doTask(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
return Container(); //Replace it with your widget
}),
);
}
}
I have call three web services in one page now for that i want show only one CircularProgressIndicator.How to show CircularProgressIndicator first when i open screen after CircularProgressIndicator show all UI.
Use Future.wait to merge three futures (web service calls) then use FutureBuilder to show CircularProgressIndicator while waiting for the merged Future to complete.
Try it like this,
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
String _data1;
String _data2;
String _data3;
Future<void> _webCall;
#override
void initState() {
_webCall = _callWebServices();
super.initState();
}
Future<bool> _callWebServices() async {
await Future.wait([_webService1(), _webService2(), _webService3()]);
return true;
}
Future<void> _webService1() async {
await Future.delayed(const Duration(seconds: 1)); //TODO: do webservice call `get` or `post`
_data1 = "This is data1"; //TODO: save the data
}
Future<void> _webService2() async {
await Future.delayed(const Duration(seconds: 5)); //TODO: do webservice call `get` or `post`
_data2 = "This is data2"; //TODO: save the data
}
Future<void> _webService3() async {
await Future.delayed(const Duration(seconds: 3)); //TODO: do webservice call `get` or `post`
_data3 = "This is data3"; //TODO: save the data
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Demo"),
),
body: FutureBuilder(
future: _webCall,
builder: (context, snapshot) {
print(snapshot);
if (snapshot.connectionState == ConnectionState.waiting)
return _buildProgressIndicator();
else if (snapshot.hasError)
return _buildError();
else
return _buildBody();
},
),
);
}
Widget _buildProgressIndicator() {
return Center(
child: CircularProgressIndicator(),
);
}
Widget _buildBody() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(_data1),
Text(_data2),
Text(_data3),
],
),
);
}
Widget _buildError() {
return Center(
child: Text("Error while loading Web Services"),
);
}
}
I am trying to update the value of totalPricewith the value that comes from the response from API. I have created a currentTotal methods that contains setState(). Then passed snapshot.data.price.totalAmountvalue to currentTotal in order to update the value of totalPrice.But, it doesnt update the value. Can you help?
double totalPrice = 0;
#override
Widget build(BuildContext context) {
currentTotal(double x) {
setState(() {
totalPrice += x;
});
}
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Center(
child: FutureBuilder<SearchResult>(
future: serviceOne.postCall(),
builder: (context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data != null) {
return new Material(
child: CustomScrollView(
slivers: <Widget>[
SliverList(
delegate: SliverChildListDelegate([
ListTile(
title: new Text(totalPrice.toString()),
)
]),
),
]
)
}
currentTotal(snapshot.data.price.totalAmount);
else if (snapshot.hasError) {
return Text("error....${snapshot.error}");
}
There are many things needs to be fixed in your build.
1 - Your widget is StatefulWidget, to use FutureBuilder inside StatefulWidget read this:
https://api.flutter.dev/flutter/widgets/FutureBuilder-class.html
Briefly, create Future future; instance field, then assign it inside the initState and use that future for FutureBuilder.
2 - your setState not inside a method, you have probably syntax error there. Create a void method and use setState inside it.
3 - You don't need to check twice like:
if (snapshot.hasData) {
if (snapshot.data != null) {
One of them enough, after the condition check, call your method includes setState, then display it.
Edit:
Here an example template for your solution:
class Test extends StatefulWidget {
#override
_TestState createState() => _TestState();
}
class _TestState extends State<Test> {
Future<int> future;
int price = 0;
#override
void initState() {
future = fetchPrice();
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: FutureBuilder<int>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) {
return Center(
child: Text(price.toString()),
);
}
return Center(child: CircularProgressIndicator());
},
),
),
);
}
Future<int> fetchPrice() async {
final response =
await http.get('https://jsonplaceholder.typicode.com/posts/1');
final data = json.decode(response.body);
setState(() {
price = data['userId'];
});
return data['userId'];
}
}