Flutter One time Intro Screen? - flutter

I have an intro screen for my app, but it shows every time I open the app,
I need to show that for the 1st time only.
How to do that?
//THIS IS THE SCREEN COMES 1ST WHEN OPENING THE APP (SPLASHSCREEN)
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
//After 2seconds of time the Introscreen will e opened by bellow code
Timer(Duration(seconds: 2), () => MyNavigator.goToIntroscreen(context));
}
//The below code has the text to show for the spalshing screen
#override
Widget build(BuildContext context) {
return Scaffold(
body: new Center(
child: Text('SPLASH SCREEN'),
),
);
}
}
Every time this screen opens the intro screen with 2 seconds delay.
but I want for the first time only How to do that with sharedpreference??
Please add the required code.

If you wish to show the intro screen only for the first time, you will need to save locally that this user has already seen intro.
For such thing you may use Shared Preference. There is a flutter package for Shared Preference which you can use
EDITED:
Please refer to the below complete tested code to understand how to use it:
import 'dart:async';
import 'package:after_layout/after_layout.dart';
import 'package:flutter/material.dart';
import 'package:shared_preferences/shared_preferences.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
color: Colors.blue,
home: new Splash(),
);
}
}
class Splash extends StatefulWidget {
#override
SplashState createState() => new SplashState();
}
class SplashState extends State<Splash> with AfterLayoutMixin<Splash> {
Future checkFirstSeen() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool _seen = (prefs.getBool('seen') ?? false);
if (_seen) {
Navigator.of(context).pushReplacement(
new MaterialPageRoute(builder: (context) => new Home()));
} else {
await prefs.setBool('seen', true);
Navigator.of(context).pushReplacement(
new MaterialPageRoute(builder: (context) => new IntroScreen()));
}
}
#override
void afterFirstLayout(BuildContext context) => checkFirstSeen();
#override
Widget build(BuildContext context) {
return new Scaffold(
body: new Center(
child: new Text('Loading...'),
),
);
}
}
class Home extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Hello'),
),
body: new Center(
child: new Text('This is the second page'),
),
);
}
}
class IntroScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('IntroScreen'),
),
body: new Center(
child: new Text('This is the IntroScreen'),
),
);
}
}
Thanks to Ben B for noticing the incorrect use of delay in initState. I had used a delay because sometimes the context is not ready immediately inside initState.
So now I have replaced that with afterFirstLayout which is ready with the context. You will need to install the package after_layout.

I was able to do without using after_layout package and Mixins and instead I have used FutureBuilder.
class SplashState extends State<Splash> {
Future checkFirstSeen() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool _seen = (prefs.getBool('seen') ?? false);
if (_seen) {
return HomeScreen.id;
} else {
// Set the flag to true at the end of onboarding screen if everything is successfull and so I am commenting it out
// await prefs.setBool('seen', true);
return IntroScreen.id;
}
}
#override
Widget build(BuildContext context) {
return FutureBuilder(
future: checkFirstSeen(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return MaterialApp(
initialRoute: snapshot.data,
routes: {
IntroScreen.id: (context) => IntroScreen(),
HomeScreen.id: (context) => HomeScreen(),
},
);
}
});
}
}
class HomeScreen extends StatelessWidget {
static String id = 'HomeScreen';
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Hello'),
),
body: new Center(
child: new Text('This is the second page'),
),
);
}
}
class IntroScreen extends StatelessWidget {
static String id = 'IntroScreen';
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('IntroScreen'),
),
body: new Center(
child: new Text('This is the IntroScreen'),
),
);
}
}

I always try to use minimum count of packages, because in future it can conflict with ios or android. So my simple solution without any package:
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
final splashDelay = 2;
#override
void initState() {
super.initState();
_loadWidget();
}
_loadWidget() async {
var _duration = Duration(seconds: splashDelay);
return Timer(_duration, checkFirstSeen);
}
Future checkFirstSeen() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool _introSeen = (prefs.getBool('intro_seen') ?? false);
Navigator.pop(context);
if (_introSeen) {
Navigator.pushNamed(context, Routing.HomeViewRoute);
} else {
await prefs.setBool('intro_seen', true);
Navigator.pushNamed(context, Routing.IntroViewRoute);
}
}
#override
Widget build(BuildContext context) {
//your splash screen code
}
}

Use shared_preferences:
Full code:
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var prefs = await SharedPreferences.getInstance();
var boolKey = 'isFirstTime';
var isFirstTime = prefs.getBool(boolKey) ?? true;
runApp(MaterialApp(home: isFirstTime ? IntroScreen(prefs, boolKey) : RegularScreen()));
}
class IntroScreen extends StatelessWidget {
final SharedPreferences prefs;
final String boolKey;
IntroScreen(this.prefs, this.boolKey);
Widget build(BuildContext context) {
prefs.setBool(boolKey, false); // You might want to save this on a callback.
return Scaffold();
}
}
class RegularScreen extends StatelessWidget {
Widget build(BuildContext context) => Scaffold();
}

I just had to do exactly the same thing, here's how I did it:
First, in my main method, I open the normal main page and the tutorial:
MaterialApp(
title: 'myApp',
onGenerateInitialRoutes: (_) => [MaterialPageRoute(builder: mainPageRoute), MaterialPageRoute(builder: tutorialSliderRoute)],
)
...and then I use a FutureBuilder to build the tutorial only if necessary:
var tutorialSliderRoute = (context) => FutureBuilder(
future: Provider.of<UserConfiguration>(context, listen: false).loadShowTutorial() // does a lookup using Shared Preferences
.timeout(Duration(seconds: 3), onTimeout: () => false),
initialData: null,
builder: (context, snapshot){
if (snapshot.data == null){
return CircularProgressIndicator(); // This is displayed for up to 3 seconds, in case data loading doesn't return for some reason...
} else if (snapshot.data == true){
return TutorialSlider(); // The Tutorial, implemented using IntroSlider()
} else {
// In case the tutorial shouldn't be shown, just return an empty Container and immediately pop it again so that the app's main page becomes visible.
SchedulerBinding.instance.addPostFrameCallback((_){Navigator.of(context).pop();});
return Container(width: 0, height: 0);
}
},
);
Also, I think the tutorial should be shown again in case the user does not finish it, so I set only set the variable showTutorial to false once the user has completed (or skipped) the tutorial:
class TutorialSlider extends StatefulWidget {
#override
State<StatefulWidget> createState() => TutorialSliderState();
}
class TutorialSliderState extends State<TutorialSlider> {
...
#override
Widget build(BuildContext context) => IntroSlider(
...
onDonePress: (){
Provider.of<UserConfiguration>(context, listen: false).setShowTutorial(false);
Navigator.of(context).pop();
}
);
}

I took a different approach. I agree with the other answers that you should save your isFirstRun status via SharedPreferences. The tricky part then is how to show the correct widget in such a way that when you hit back you close out of the app correctly, etc. I first tried doing this by launching a my SplashWidget while building my HomePageWidget, but this turned out to lead to some weird Navigator errors.
Instead, I wound up calling runApp() multiple times with my different widget as appropriate. When I need to close the SplashWidget, rather than pop it, I just call runApp() again, this time with my HomePageWidget as the child property. It is safe to call runApp() multiple times according to this issue, indeed even for splash screens.
So it looks something like this (simplified obviously):
Future<void> main() async {
bool needsFirstRun = await retrieveNeedsFirstRunFromPrefs();
if (needsFirstRun) {
// This is will probably be an async method but no need to
// delay the first widget.
saveFirstRunSeen();
runApp(child: SplashScreenWidget(isFirstRun: true));
} else {
runApp(child: HomePageWidget());
}
}
I have an isFirstRun property on SplashScreenWidget because I can launch it in two ways--once as a true splash screen, and once from settings so that users can see it again if they want. I then inspect that in SplashScreenWidget to determine how I should return to the app.
class SplashScreenWidget extends StatefulWidget {
final bool isFirstRun;
// <snip> the constructor and getState()
}
class _SplashScreenWidgetState extends State<SplashScreenWidget> {
// This is invoked either by a 'skip' button or by completing the
// splash screen experience. If they just hit back, they'll be
// kicked out of the app (which seems like the correct behavior
// to me), but if you wanted to prevent that you could build a
// WillPopScope widget that instead launches the home screen if
// you want to make sure they always see it.
void dismissSplashScreen(BuildContext ctx) {
if (widget.isFirstRun) {
// Then we can't just Navigator.pop, because that will leave
// the user with nothing to go back to. Instead, we will
// call runApp() again, setting the base app widget to be
// our home screen.
runApp(child: HomePageWidget());
} else {
// It was launched via a MaterialRoute elsewhere in the
// app. We want the dismissal to just return them to where
// they were before.
Navigator.of(ctx).pop();
}
}
}

Related

Flutter how to get data from the previous screen in which the data is refreshed

There is a FirstScreen screen and there is a second SecondScreen called from the first screen, in the first screen a web socket is launched, data arrives on it, on the second screen, let's say there is a Text () widget into which you need to write data from the first screen when SecondScreen is already running. Which way to look?
You can manage this issue using state management like provider or bloc
var _channel;
var _screenShow = 1;
class FirstScreen extends StatefulWidget {
FirstScreen();
#override
_FirstScreenState createState() => _FirstScreenState();
}
class _FirstScreenState extends State<FirstScreen> {
void _serverListen() {
_channel.stream.listen((message) {
String res = message.trim();
if (_screenShow == 1) {
// update widjet SecondScreen
} else if (_screenShow == 2) {
// update widjet ThirdScreen
} else if (_screenShow == 3) {
// update widjet FourthScreen
} else {
// update widjet FifthScreen
}
},
onDone: () {
print('ws channel closed');
},
onError: (error) {
print('ws error $error');
},
);
}
#override
void initState() {
super.initState();
_channel = IOWebSocketChannel.connect(Uri.parse('ws address'));
_serverListen();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('first screen'),
),
body: Text('FirstScreen'),
floatingActionButton: FloatingActionButton(
elevation: 5,
child: Icon(Icons.grid_on,),
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => SecondScreen()),
);
},
),
);
}
// SecondScreen
class SecondScreen extends StatefulWidget {
SecondScreen();
#override
_SecondScreenState createState() => _SecondScreenState();
}
class _SecondScreenState extends State<SecondScreen> {
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('SecondScreen'),
),
body: Text('update here from first screen'),
);
}
}
Use Getx Storage:
dependencies:
get_storage: ^2.0.3
Example:
In 1st Screen You Need to Store the value :
Initialize storage driver with await:
# main() async { await
GetStorage.init(); runApp(App()); }
use GetStorage through an instance or use directly GetStorage().read('key')
final box = GetStorage();
To read values you use read:
print(box.read('quote'));
// out: GetX is the best
In the 2nd Page you can remove the key:
To remove a key, you can use remove:
box.remove('quote');
If anyone is interested, implemented using this
https://pub.dev/packages/fbroadcast

Flutter login control in splash screen

I want to make a small login application. When entering the application, I want to inquire whether the user has a token code or not on the splash screen. How can do this? thank you for help.
main.dart file
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(),
);
}
}
My splash screen.
I want to know if the user has a token or not
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
void initState() {
super.initState();
loginControl();
}
// ignore: missing_return
Future<bool> loginControl() async {
bool status = AuthController.isLoginUser() as bool;
print(status);
if (status) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (BuildContext context) => HomeScreen()));
} else {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (BuildContext context) => LoginScreen()));
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Text('welcome my app'),
),
);
}
}
my auth controller like this;
class AuthController {
static Future<bool> isLoginUser() async {
SharedPreferences sharedPreferences = await SharedPreferences.getInstance();
String token = sharedPreferences.getString("token");
if (token == null) {
return false;
} else {
return true;
}
}
}
Your isLoginUser is actually returning a Future<bool> means that it returns a Future that will later resolve to a bool value.
So, when you use it like this in your loginControl,
bool status = AuthController.isLoginUser() as bool;
AuthController.isLoginUser() return Future<bool> and it can't be directly converted to a bool using as bool.
Instead you should await that Future to resolve, like this.
bool status = await AuthController.isLoginUser(); // This will work.
Now, your code will pause at this line, until it gets a return value from isLoginUser and then resume to next line with status being an actual bool value. i.e., true or false.

Can't find the stream provider when using named route navigation

I have a Stream Provider (connected to firebase) that is not working. I am guessing that the problem lies in the fact that I am using a named navigator [Navigator.pushNamed(context, '/route',)]. I guess this makes the 'route' widget to not be the son of the widget that calls it. Let me show it better below.
My app structure is as follows:
My main widget which handles routing and receives the Stream with user authentication (there is no problem here):
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider<User>.value(
value: AuthService().user,
child: MaterialApp(
debugShowCheckedModeBanner: false,
home: Wrapper(),
routes: {
'/home': (context) => Wrapper(),
'/edit_profile': (context) => UserProfile() //This is where I am having trouble.
}
),
);
}
}
The Wrapper that validates if the user is authenticated and acts accordingly:
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
// return either the Home or Authenticate widget
if (user == null){
return Authenticate();
} else {
return HomeWrapper();
}
}
}
The HomeWrapper which receives the second stream and redirects to the widget I am having trouble with:
class HomeWrapper extends StatefulWidget {
#override
_HomeWrapperState createState() => _HomeWrapperState();
}
class _HomeWrapperState extends State<HomeWrapper> {
String currentBodyName = 'home';
Widget currentBodyWidget = Home();
#override
Widget build(BuildContext context) {
Widget _drawerOptions = Row(
children: [
FlatButton(child: someChild, onPressed: () {Navigator.pushNamed(context, '/edit_profile',);},), //This is the actual call to the navigator.
],
);
return StreamProvider<Map>.value( //This is the problematic Stream!
value: DatabaseService().userDetail,
child: Scaffold(
//Body
body: currentBodyWidget,
//I am simplifying this to show the most important parts
bottomNavigationBar: myBottomNavigationBar(
buttons: <Widget>[
FlatButton(
icon: someIcon,
onPressed: () => _onItemTapped('home'),
),
FlatButton(
icon: otherIcon,
onPressed: () => _onItemTapped('second_screen'),
),
],)
//Drawer
drawer: Drawer(child: _drawerOptions,), //This one has the call to the problematic edit_profile route.
);
}
void _onItemTapped(String newBodyName) {
if (newBodyName != currentBodyName){
setState(() {
currentBodyName = newBodyName;
switch(newBodyName) {
case 'home': {
currentBodyWidget = Home();
}
break;
case 'second_screen': {
currentBodyWidget = SecondScreen();
}
break;
default: {
currentBodyWidget = Home();
}
break;
}
});
}
}
}
Finally the edit_profile route calls the UserProfile Widget which looks like this:
class UserProfile extends StatefulWidget {
#override
_UserProfileState createState() => _UserProfileState();
}
class _UserProfileState extends State<UserProfile> {
#override
Widget build(BuildContext context) {
//This is where the error occurs!!
final userDocument = Provider.of<Map>(context) ?? [];
print(userDocument);
return Scaffold(body: Container());
}
}
This is the error that it throws:
The following ProviderNotFoundError was thrown building UserProfile(dirty, state: _UserProfileState#09125):
Error: Could not find the correct Provider<Map<dynamic, dynamic>> above this UserProfile Widget
Thank you very much!!
Turns out my approach was wrong.
Instead of wrapping the HomeWrapper with the StreamProvider, hoping that it would pass the data to the next route (UserProfile ), what I did was to wrap the UserProfile widget with a StreamProvider, as follows:
(Note: I changed the Map StreamProvider for a UserData StreamProvider.)
class UserProfile extends StatefulWidget {
#override
_UserProfileState createState() => _UserProfileState();
}
class _UserProfileState extends State<UserProfile> {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
return StreamBuilder<UserData>(
stream: DatabaseService(uid: user.uid).userData,
builder: (context, snapshot) {
if (snapshot.hasData) {
UserData userData = snapshot.data;
return Scaffold(
body: Container(
//My Widget here
);
} else
return Loading();
});
}
}
This series was very helpful: https://www.youtube.com/playlist?list=PL4cUxeGkcC9j--TKIdkb3ISfRbJeJYQwC

Using FutureBuilder in main.dart

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.

flutter make raised button change after future return value

I have a raised button that kicks off my fingerprint authentication, when the Future returns I want to be able to change the Raised Button to new text and new onPressed method to complete the required authentication. I have given the Raised Button a key but can not find how to act upon that button to change it. Is it possible? Anyone have examples of it?
I tried to create new Raised Button with same key based on if the user is authenticated, but it did not change anything.
Any help would be great.
I would recommend reviewing the Flutter Interactivity Tutorial.
Once the Future completes you can call setState to tell Flutter to rebuild your StatefulWidget. And in your build() method, you can use the authenticated status of the user to construct a different RaisedButton.
Here's some example code that does this:
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:local_auth/local_auth.dart';
void main() {
runApp(new MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Flutter Local Auth Demo',
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
MyHomePage({Key key}) : super(key: key);
#override
_MyHomePageState createState() => new _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
bool _authenticated = false;
Future<Null> _authenticate() async {
final LocalAuthentication auth = new LocalAuthentication();
bool authenticated = false;
try {
authenticated = await auth.authenticateWithBiometrics(
localizedReason: 'Scan your fingerprint to authenticate',
useErrorDialogs: true);
} on PlatformException catch (e) {
print(e);
}
if (!mounted) return;
setState(() {
_authenticated = authenticated;
});
}
Widget _buildAuthButton() {
assert(!_authenticated);
return new RaisedButton(
child: new Text('Authenticate'),
onPressed: _authenticate,
);
}
Widget _buildContinueButton() {
assert(_authenticated);
return new RaisedButton(
child: new Text('Continue'),
onPressed: () {
// Do something now that the user is authenticated
},
);
}
#override
Widget build(BuildContext context) {
return new Scaffold(
appBar: new AppBar(
title: new Text('Interactivity Tutoral'),
),
body: new Center(
child: _authenticated ? _buildContinueButton() : _buildAuthButton(),
),
);
}
}
I would use FutureBuilder, just return one widget or the other based on whether the Future is complete
new FutureBuilder<String>(
future: your_future,
builder: (_, AsyncSnapshot<String> snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
default:
if (snapshot.hasError)
return new Text('Error: ${snapshot.error}');
else
return new Text(
'Your data: ${snapshot.data}',
);
}
})