I'm trying to use FutureBuilder but i'm countering an issue, my future is called but not executed. And i couldn't understand why. Does someone has an idea why ?
This is my code:
class TesPage extends StatefulWidget {
#override
_TesPageState createState() => _TesPageState();
}
class _TesPageState extends State<TesPage> {
Future<String> future;
Future<String> futureFunction() async {
try {
await text();
return "Test"; // this is never called
} catch (e) {
return e.toString();
}
}
#override
#mustCallSuper
void initState() {
future = futureFunction();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: new FutureBuilder<String>(
future: future,
builder: (BuildContext context, snapshot) {
// returning a widget based on the future
},
),
);
}
}
Thank you !
Update
fix my code example
You are not properly waiting for a Future to be resolved.
I suggest you to do the following way:
class TesPage extends StatefulWidget {
#override
_TesPageState createState() => _TesPageState();
}
class _TesPageState extends State<TesPage> {
//Future<String> future;
Future<String> futureFunction() async {
try {
await text();
return "Test"; // this is never called
} catch (e) {
return e.toString();
}
}
//#override
//#mustCallSuper
//void initState() {
// future = futureFunction();
// super.initState();
//}
#override
Widget build(BuildContext context) {
return Scaffold(
body: new FutureBuilder<String>(
future: futureFunction(),
builder: (BuildContext context, snapshot) {
// returning a widget based on the future
},
),
);
}
}
I've commented the unused statements and variables
This way the FutureBuilder will invoke the futureFunction and wait for its result.
Be aware of the states snapshot can be in, check this for complete informations.
Usually you can just check if snapshot.hasData or not, but the class offers you more information about the status of the async computation (aka the futureFunction in this case).
Related
I am still pretty new to flutter and dart but I am currently trying to return a future widget that is my main game inside of a StatefulWidget and I am wondering if I need to use a future builder or if there is another way to do it?
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:untitled2/screens/game.dart';
class GamePlay extends StatefulWidget {
GamePlay({Key key}) : super(key: key);
#override
_GamePlayState createState() => _GamePlayState();
}
class _GamePlayState extends State<GamePlay> {
#override
Widget build(BuildContext context) async { // THIS IS WHERE IT SAYS THE PROBLEM IS
SharedPreferences storage = await SharedPreferences.getInstance();
MainGame mainGame = MainGame(storage);
return Scaffold(
body: mainGame.widget,
);
}
}
You can't use await inside the build method. You can use a FutureBuilder widget instead:
class _GamePlayState extends State<GamePlay> {
// Create a property variable to be updated
SharedPreferences _storage;
// Create a future to store the computation
Future<void> _future;
#override
void initState() {
super.initState();
// When creating the widget, initialize the future
// to a method that performs the computation
_future = _compute();
}
Future<void> _compute() async {
// Make your asynchronous computation here
_storage = await SharedPreferences.getInstance();
}
#override
Widget build(BuildContext context) async {
// Use a FutureBuilder to display to the user a progress
// indication while the computation is being done
return FutureBuilder(
future: _future,
builder: (context, snapshot) {
// If snapshot is not ready yet, display the progress indicator
if (snapshot.connectionState == ConnectionState.waiting)
return Center(child: CircularProgressIndicator());
// If it's ready, use the property
final SharedPreferences storage = _storage;
final MainGame mainGame = MainGame(storage);
return Scaffold(body: mainGame.widget);
},
);
}
}
I have a Listview.builder inside a FutureBuilder which taking some data from an http request.i have a bool closed i want to prevent some items from refreshing if status bool is true
how can I do that
You can achieve this by placing your call in initState. In this way you can make sure that it will get the data only once.
example:
class FutureSample extends StatefulWidget {
// Create instance variable
#override
_FutureSampleState createState() => _FutureSampleState();
}
class _FutureSampleState extends State<FutureSample> {
Future myFuture;
Future<String> _fetchData() async {
await Future.delayed(Duration(seconds: 10));
return 'DATA';
}
#override
void initState() {
// assign this variable your Future
myFuture = _fetchData();
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FutureBuilder(
future: myFuture,
builder: (ctx, snapshot) {
if (snapshot.hasData) {
return Text(snapshot.data.toString());
}
return CircularProgressIndicator();
},
),
),
);
}
}
In that way you don't need a bool value. There are also different ways to achieve or extend your request. You can check this article for more informations: https://medium.com/flutterworld/why-future-builder-called-multiple-times-9efeeaf38ba2
I have the following code, to get initial data for the screen and this SchedulerBinding seems to be a hack, but if I remove it, request data is lost.
I think it happens due to the fact widgets(streamBuilders etc.) are not yet built.
Any ideas how can I fix this?
Full screen code: https://gist.github.com/Turbozanik/7bdfc69b36fea3dd38b94d8c4fcdcc84
Full bloc code: https://gist.github.com/Turbozanik/266d3517a297b1d08e7a3d7ff6ff245f
SchedulerBining is not a hack,according to docs addPostFrame call callback only once and if you remove it your stream will never get the data
but you can call your stream loading in iniState
void initState(){
super.initState();
_mblock.loadSpotMock();
}
You can load your data asynchronously in the initState method, meanwhile you can show a loader or message. Once your data has loaded, call setState to redraw the widget.
Here is an example of this:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
createState() => new MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
String _data;
Future<String> loadData() async {
// Simulate a delay loading the data
await Future<void>.delayed(const Duration(seconds: 3));
// Return the data
return "This is your data!";
}
#override
initState() {
super.initState();
// Call loadData asynchronously
loadData().then((s) {
// Data has loaded, rebuild the widget
setState(() {
_data = s;
});
});
}
#override
Widget build(BuildContext context) {
if (null == _data) {
return Text("Loading...");
}
return Text(_data);
}
}
You can test it in https://dartpad.dartlang.org
It works like this:
initState will call loadData asynchronously, then the build method will draw the widget.
when loadData returns, the call to setState will redraw the widget.
Using StreamBuilder
The following example uses a StreamBuilder to show the data, once it's loaded:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
body: Center(
child: MyWidget(),
),
),
);
}
}
class MyWidget extends StatefulWidget {
#override
createState() => new MyWidgetState();
}
class MyWidgetState extends State<MyWidget> {
// Create a stream and execute it
final Stream<String> _myStream = (() async* {
// Simulate a delay loading the data
await Future<void>.delayed(const Duration(seconds: 3));
// Return the data
yield "This is your data!";
})();
#override
Widget build(BuildContext context) {
return StreamBuilder<String>(
stream: _myStream,
builder: (BuildContext context, s) {
String result;
if (s.hasError) {
result = "Error";
}
else {
if (s.connectionState == ConnectionState.done) {
result = s.data;
}
else {
result = "Loading...";
}
}
return Text(result);
}
);
}
}
Hope this helps :)
I tried to load data from the database by using FutureBuilder like this code.
// counter application
import 'package:flutter/material.dart';
class SetStateScreen extends StatefulWidget {
#override
_SetStateScreenState createState() => _SetStateScreenState();
}
class _SetStateScreenState extends State<SetStateScreen> {
Future<int> getData() async {
int initNumFromDB = 0; // fetch some asynchronous initial value
return initNumFromDB;
}
#override
Widget build(BuildContext context) {
int counter;
return Container(
color: Colors.white,
child: FutureBuilder(
future: getData(),
builder: (BuildContext context, AsyncSnapshot<int> snapshot) {
Widget childWidget;
if (snapshot.hasData) {
counter = snapshot.data;
childWidget = Center(
child: Container(
child: FlatButton(
onPressed: () {
setState(() {
counter += 1;
});
},
child: Text("$counter"),
)),
);
} else {
childWidget = Container();
}
return childWidget;
}),
);
}
}
But, when pushing the button and running the setState method, FutureBuilder loads initial data again and reset the counter number. How to load initial data once, and run this code correctly?
Try this is the proper way to use
class SetStateScreen extends StatefulWidget {
#override
_SetStateScreenState createState() => _SetStateScreenState();
}
class _SetStateScreenState extends State<SetStateScreen> {
Future<int> getData() async {
int initNumFromDB = 0; // fetch some asynchronous initial value
return initNumFromDB;
}
#override
void initState() {
super.initState();
getData();
}
}
You need to call you future method inside the initstate method . As such it will run only once when the widget is built and not everytime when setstate() is called.
Below code always show OnboardingScreen a little time (maybe miliseconds), after that display MyHomePage. I am sure that you all understand what i try to do. I am using FutureBuilder to check getString method has data. Whats my fault ? Or any other best way for this ?
saveString() async {
final prefs = await SharedPreferences.getInstance();
prefs.setString('firstOpen', '1');
}
getString() method always return string.
getString() async {
final prefs = await SharedPreferences.getInstance();
String txt = prefs.getString('firstOpen');
return txt;
}
main.dart
home: new FutureBuilder(
future: getString(),
builder: (context, snapshot) {
if (snapshot.hasData) {
return MyHomePage();
} else {
return OnboardingScreen();
}
})
Usually I'm using another route, rather than FutureBuilder. Because futurebuilder every hot reload will reset the futureBuilder.
There always will be some delay before the data loads, so you need to show something before the data will load.
Snapshot.hasData is showing only the return data of the resolved future.
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(primarySwatch: Colors.blue),
home: SplashScreen(),
);
}
}
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
const isOnboardingFinished = 'isOnboardingFinished';
class _SplashScreenState extends State<SplashScreen> {
Timer timer;
bool isLoading = true;
#override
void initState() {
_checkIfFirstOpen();
super.initState();
}
Future<void> _checkIfFirstOpen() async {
final prefs = await SharedPreferences.getInstance();
var hasOpened = prefs.getBool(isOnboardingFinished) ?? false;
if (hasOpened) {
_changePage();
} else {
setState(() {
isLoading = false;
});
}
}
_changePage() {
Navigator.of(context).pushReplacement(
// this is route builder without any animation
PageRouteBuilder(
pageBuilder: (context, animation1, animation2) => HomePage(),
),
);
}
#override
Widget build(BuildContext context) {
return isLoading ? Container() : OnBoarding();
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Container(child: Text('homePage'));
}
}
class OnBoarding extends StatelessWidget {
Future<void> handleClose(BuildContext context) async {
final prefs = await SharedPreferences.getInstance();
prefs.setBool(isOnboardingFinished, true);
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (_) => HomePage(),
),
);
}
#override
Widget build(BuildContext context) {
return Container(
child: Center(
child: RaisedButton(
onPressed: () => handleClose(context),
child: Text('finish on bording and never show again'),
),
),
);
}
}
From the FutureBuilder class documentation:
The future must have been obtained earlier, e.g. during State.initState, State.didUpdateConfig, or State.didChangeDependencies. It must not be created during the State.build or StatelessWidget.build method call when constructing the FutureBuilder. If the future is created at the same time as the FutureBuilder, then every time the FutureBuilder's parent is rebuilt, the asynchronous task will be restarted.
So you need to create a new Stateful widget to store this Future's as a State. With this state you can check which page to show. As suggested, you can start the future in the initState method:
class FirstPage extends StatefulWidget {
_FirstPageState createState() => _FirstPageState();
}
class _FirstPageState extends State<FirstPage> {
final Future<String> storedFuture;
#override
void initState() {
super.initState();
storedFuture = getString();
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: storedFuture,
builder: (context, snapshot) {
if (snapshot.hasData) {
return MyHomePage();
} else {
return OnboardingScreen();
}
});
}
}
So in your home property you can call it FirstPage:
home: FirstPage(),
Your mistake was calling getString() from within the build method, which would restart the async call everytime the screen gets rebuilt.