Hi I am using bloc named AuthenticationBloc to store the application auth status.
First of all here is the main.dart file.
void main() async{
runApp(MyApp());
}
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
#override
_MyAppState createState() => new _MyAppState();
}
class _MyAppState extends State<MyApp>{
#override
Widget build(BuildContext context) => BlocProvider(
create: (context) => AuthenticationBloc(),
child: Authentication(),
);
}
authentication.dart is like this.
#override
void initState() {
authenticationBloc = BlocProvider.of<AuthenticationBloc>(context);
authenticationBloc.add(AppLoaded());
super.initState();
}
#override
Widget build(BuildContext context) {
return MaterialApp(
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (BuildContext context, AuthenticationState state) {
if (state is AuthenticationInitial) {
return SplashScreen();
}
if (state is AuthenticationAuthenticated) {
return new Home();
}
if (state is AuthenticationNotAuthenticated) {
return MultiBlocProvider(
providers: [
BlocProvider<LoginBloc>(create: (context) => LoginBloc(authenticationBloc)),
],
child:new LoginPage(),
);
}
if (state is AuthenticationLoading) {
return SplashScreen();
}
return SplashScreen();
},
),
);
}
Now login_bloc.dart file is.
class LoginBloc extends Bloc<LoginEvent, LoginState> {
final AuthenticationBloc _authenticationBloc;
final repository = ApiRepository();
LoginBloc(AuthenticationBloc authenticationBloc)
: assert(authenticationBloc != null),
_authenticationBloc = authenticationBloc,
super(LoginInitial());
#override
Stream<LoginState> mapEventToState(LoginEvent event) async* {
if (event is LoginInWithEmailButtonPressed) {
yield* _mapLoginWithEmailToState(event);
}
}
Stream<LoginState> _mapLoginWithEmailToState(LoginInWithEmailButtonPressed event) async* {
yield LoginLoading();
try {
if (true) {
await Future.delayed(const Duration(seconds: 2), (){});
MySharedPreferences.instance.setStringValue('id', '1');
_authenticationBloc.add(UserLoggedIn(user: User(name:"abc",email:"abc#gmail.com")));
yield LoginSuccess();
yield LoginInitial();
} else {
yield LoginFailure(error: 'Something very weird just happened');
}
} on AuthenticationException catch (e) {
yield LoginFailure(error: e.message);
} catch (err) {
yield LoginFailure(error: err.message ?? 'An unknown error occured');
}
}
}
}
In login screen page when i log in then it successfully change the authentication-bloc state and navigated to home.
In home when i do like this
_authenticationBloc.add(UserLoggedOut()); the it successfully log out.
But after that when i click login it dispatch the event but the line
_authenticationBloc.add(UserLoggedIn(user: User(name:"abc",email:"abc#gmail.com"))); in login-bloc not working and so its not going to home.
Kindly help me why it is not dispatching the event to the bloc.
I am stucked at this point.
Thanks in advance.
Related
I'm just started with flutter bloc. I want to make a movie listing app, create your own lists and share your friends etc.
The problem is, when i tapped to logout button, UI does not update.
Here's where i try to change the UI. If the state is Authenticated(), I'm returning WatchlistNavBar(), if the state is Unauthenticated() I'm returning WelcomeView() for login or register.
If the user has submitted email and password correctly, WatchlistNavBar() is building. Everything works fine. But when the user tries the logout, WelcomeView() does not build.
By the way BlocNavigate() class is called in MaterialApp()'s home property.
class BlocNavigate extends StatelessWidget {
const BlocNavigate({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return BlocBuilder<AuthBloc, AuthState>(
builder: (context, state) {
if (state is Loading) {
return const LoadingWidget();
} else if (state is Authenticated) {
return const WatchlistNavBar();
} else if (state is Unauthenticated) {
return const WelcomeView();
} else {
return const SignInView();
}
},
);
}
}
AuthBloc:
class AuthBloc extends Bloc<AuthEvent, AuthState> {
AuthRepository authRepository = AuthRepository();
AuthBloc(this.authRepository) : super(AuthInitial()) {
on<AuthenticationStarted>(_onAuthStarted);
on<AuthenticationSignedOut>(_onSignOut);
}
_onAuthStarted(AuthenticationStarted event, Emitter<AuthState> emit) async {
UserModel user = await authRepository.getCurrentUser().first;
if (user.uid != "uid") {
emit(Authenticated());
} else {
emit(Unauthenticated());
}
}
_onSignOut(AuthenticationSignedOut event, Emitter<AuthState> emit) async {
authRepository.signOut();
emit(Unauthenticated());
}
}
AuthState:
abstract class AuthState extends Equatable {
const AuthState();
#override
List<Object> get props => [];
}
class AuthInitial extends AuthState {}
class Authenticated extends AuthState {}
class Unauthenticated extends AuthState {}
class Loading extends AuthState {}
And this is the logout button, where i add AuthenticationSignedOut() to AuthBloc():
class LogoutButton extends StatelessWidget {
const LogoutButton({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return IconButton(
icon: const Icon(Icons.exit_to_app, color: Colors.black),
onPressed: () {
context.read<AuthBloc>().add(AuthenticationSignedOut());
});
}
}
My main function and MaterialApp():
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
Bloc.observer = AppBlocObserver();
runApp(
MultiBlocProvider(providers: [
BlocProvider(create: (context) => FormBloc()),
BlocProvider(create: (context) => DatabaseBloc(DatabaseRepositoryImpl())),
BlocProvider(
create: (context) =>
AuthBloc(AuthRepository())..add(const AuthenticationStarted())),
BlocProvider(
create: (context) => FavoritesBloc()..add(const FavoritesLoad()))
], child: const WatchlistApp()),
);
}
class WatchlistApp extends StatelessWidget {
const WatchlistApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Watchlist',
theme: WatchlistTheme.mainTheme,
home: const BlocNavigate(),
);
}
}
As i said, i'm new to flutter bloc and don't know exactly what I'm doing wrong. If you need more information please let me know.
I've just started with Flutter recently. BLoC does indeed has a steep learning curve...
As it is clear from the title, the BlocBuilder logic gets correctly executed only once, when the app is started. Afterwards, however, UI doesn't get rebuilt on state changes. Although all the events get emitted, and states change.
Thanks in advance for any help!
Here's my 'main.dart':
import 'package:co_flutter/auth/authentication_bloc.dart';
import 'package:co_flutter/auth/authentication_event.dart';
import 'package:co_flutter/auth/authentication_state.dart';
import 'package:co_flutter/auth/login/login_bloc.dart';
import 'package:co_flutter/auth/signup/signup_page.dart';
import 'package:co_flutter/loading_indicator.dart';
import 'package:co_flutter/splash_page.dart';
import 'package:co_flutter/user_repository.dart';
import 'package:flutter/material.dart';
import 'package:bloc/bloc.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'auth/login/login_page.dart';
class SimpleBlocObserver extends BlocObserver {
#override
void onCreate(BlocBase bloc) {
super.onCreate(bloc);
print('onCreate -- ${bloc.runtimeType}');
}
#override
void onEvent(Bloc bloc, Object? event) {
super.onEvent(bloc, event);
print('onEvent -- ${bloc.runtimeType}, $event');
}
#override
void onTransition(Bloc bloc, Transition transition) {
super.onTransition(bloc, transition);
print('onTransition -- ${bloc.runtimeType}, $transition');
}
#override
void onChange(BlocBase bloc, Change change) {
super.onChange(bloc, change);
print('onChange -- ${bloc.runtimeType}, $change');
}
#override
void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
print('onError -- ${bloc.runtimeType}, $error');
super.onError(bloc, error, stackTrace);
}
#override
void onClose(BlocBase bloc) {
super.onClose(bloc);
print('onClose -- ${bloc.runtimeType}');
}
}
void main() {
Bloc.observer = SimpleBlocObserver();
runApp(MyApp(
userRepository: UserRepository(),
));
}
class MyApp extends StatefulWidget {
final UserRepository userRepository;
MyApp({Key? key, required this.userRepository}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
late AuthenticationBloc authenticationBloc;
UserRepository get userRepository => widget.userRepository;
#override
void initState() {
authenticationBloc = AuthenticationBloc(userRepository: userRepository);
authenticationBloc.add(AppStarted());
super.initState();
}
#override
void dispose() {
authenticationBloc.close();
super.dispose();
}
#override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider<AuthenticationBloc>(
create: (BuildContext context) => authenticationBloc,
),
BlocProvider<LoginBloc>(
create: (BuildContext context) => LoginBloc(
userRepository: userRepository,
authenticationBloc: authenticationBloc),
),
],
child: MaterialApp(
title: 'My App',
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: BlocBuilder<AuthenticationBloc, AuthenticationState>(
builder: (BuildContext context, AuthenticationState state) {
if (state is AuthenticationUninitialized) {
return SplashPage();
}
if (state is AuthenticationAuthenticated) {
return Dashboard(
title: 'Dashboard',
);
}
if (state is AuthenticationUnauthenticated) {
return LoginPage(
userRepository: userRepository,
);
}
if (state is AuthenticationLoading) {
return LoadingIndicator();
}
// else {
// return Text('Error');
// }
return BlocBuilder<LoginBloc, LoginState>(
builder: (context, state) {
if (state is LoginToSignup) {
return SignUpPage();
} else
return SizedBox.shrink();
},
);
},
),
),
);
}
}
class Dashboard extends StatelessWidget {
final String title;
const Dashboard({Key? key, required this.title}) : super(key: key);
#override
Widget build(BuildContext context) {
final AuthenticationBloc authenticationBloc =
BlocProvider.of<AuthenticationBloc>(context);
return Scaffold(
appBar: AppBar(
title: Text('Dashboard'),
),
body: Container(
child: Center(
child: ElevatedButton(
child: Text('logout'),
onPressed: () {
authenticationBloc.add(LoggedOut());
},
),
),
),
);
}
}
authentication_bloc
import 'dart:async';
import 'package:co_flutter/auth/authentication_event.dart';
import 'package:co_flutter/auth/authentication_state.dart';
import 'package:bloc/bloc.dart';
import '../user_repository.dart';
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
UserRepository userRepository;
AuthenticationBloc({required this.userRepository})
: super(AuthenticationUninitialized()) {
userRepository = UserRepository();
}
#override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event,
) async* {
if (event is AppStarted) {
final bool hasToken = await userRepository.hasToken();
if (hasToken) {
yield AuthenticationAuthenticated();
} else {
yield AuthenticationUnauthenticated();
}
}
if (event is LoggedIn) {
yield AuthenticationLoading();
await userRepository.persistToken(event.token, event.userId);
yield AuthenticationAuthenticated();
}
if (event is LoggedOut) {
yield AuthenticationLoading();
await userRepository.deleteToken();
yield AuthenticationUnauthenticated();
}
}
}
authentication_event
import 'package:equatable/equatable.dart';
abstract class AuthenticationEvent extends Equatable {
#override
List<Object> get props => [];
}
class AppStarted extends AuthenticationEvent {
#override
String toString() => 'AppStarted';
}
class LoggedIn extends AuthenticationEvent {
final String token;
final String userId;
LoggedIn({required this.token, required this.userId});
#override
String toString() => 'LoggedIn { token: $token}';
}
class LoggedOut extends AuthenticationEvent {
#override
String toString() => 'LoggedOut';
}
authentication_state
import 'package:equatable/equatable.dart';
abstract class AuthenticationState extends Equatable {
const AuthenticationState();
}
class AuthenticationUninitialized extends AuthenticationState {
#override
List<Object> get props => [];
}
class AuthenticationLoading extends AuthenticationState {
#override
List<Object> get props => [];
}
class AuthenticationAuthenticated extends AuthenticationState {
#override
List<Object> get props => [];
}
class AuthenticationUnauthenticated extends AuthenticationState {
#override
List<Object> get props => [];
}
The problem is here. You already created a bloc:
authenticationBloc = AuthenticationBloc(userRepository: userRepository);
And here you are trying to create it again.
To fix it replace this code:
BlocProvider<AuthenticationBloc>(
create: (BuildContext context) => authenticationBloc,
),
with this:
BlocProvider<AuthenticationBloc>.value(
value: authenticationBloc,
),
Pass bloc into bloc builder
BlocBuilder<AuthenticationBloc, AuthenticationState>(
bloc: authenticationBloc,
Don't forget to dispose bloc authenticationBloc after you used it in dispose function.
I'm using flutter to make a mobile app.
In app.dart, the code controls the main routing.
class App extends StatelessWidget {
const App({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(primaryColor: hexToColor("#6a1717")),
home: BlocBuilder<AuthenticationBloc, AuthenticationDataState>(
builder: (context, state) {
if (state.state == AuthenticationState.uninitialized) {
return SplashPage();
} else if (state.state == AuthenticationState.authenticated) {
print("now home page");
return HomePage();
} else if (state.state == AuthenticationState.unauthenticated) {
return LoginPage();
} else if (state.isLoading) {
return LoadingIndicator();
} else {
return null;
}
},
),
);
}
}
And in login bloc handler, yielded logged_in event after log in.
class LoginBloc extends Bloc<LoginEvent, LoginState> {
final AuthenticationBloc authenticationBloc;
LoginBloc({
#required this.authenticationBloc,
}) : assert(authenticationBloc != null),
super(LoginInitial());
#override
Stream<LoginState> mapEventToState(LoginEvent event) async* {
if (event is LoginButtonPressed){
yield LoginLoading();
try {
final user = await userController.authenticate(
email: event.email,
password: event.password
);
if(user != null){
if(user.accessToken != null){
authenticationBloc.add(LoggedIn(user: user));
yield LoginInitial();
}else{
yield NotValid();
}
}else{
yield NotRegistered();
}
}catch(error){
yield LoginFailure(error: AppException.unknown(message: error.toString()));
}
}
}
}
When debugging, I checked out that the command "print("now home page");" is executed, and the processor went in home page actually but the login screen does not disapper and the home page is not rendered.
I don't know what's going on in flutter. Please help me.
Login page:
class LoginPage extends StatelessWidget {
const LoginPage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
// resizeToAvoidBottomInset: false,
body: BlocProvider(
create: (context) {
return LoginBloc(
authenticationBloc: BlocProvider.of<AuthenticationBloc>(context),
);
},
child: LoginForm(),
),
);
}
}
Home Page:
class HomePage extends StatefulWidget {
HomePage({Key key}) : super(key: key);
#override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
#override
Widget build(BuildContext context) {
...
}
}
I want to subscribe to the purchaseUpdatedStream event after my app has initialised as I want to access localization text to display messages to the user if a purchase has failed. However I can't get the listen event to fire unless it's subscribed BEFORE the MaterialApp widget is built.
Working example:
class AppConfig extends InheritedWidget {
AppConfig({
#required this.appName,
#required Widget child,
#required this.prefs,
#required this.devMode
}) : super(child: child);
final String appName;
final SharedPreferences prefs;
final bool devMode;
static AppConfig of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var configuredApp = new AppConfig(
appName: 'app',
child: new MyApp(),
prefs: await SharedPreferences.getInstance(),
devMode: true,
);
InAppPurchaseConnection.enablePendingPurchases();
runApp(configuredApp);
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
StreamSubscription<List<PurchaseDetails>> _subscription;
#override
void initState() {
Stream purchaseUpdated =
InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList, context);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
super.initState();
}
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList, BuildContext context) {
var config = AppConfig.of(context);
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
print('pending');
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
print('error');
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
print('purchased');
}
if (purchaseDetails.pendingCompletePurchase) {
print('complete');
await InAppPurchaseConnection.instance
.completePurchase(purchaseDetails);
}
}
});
}
#override
Widget build(BuildContext context) {
var config = AppConfig.of(context);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return MultiProvider(
providers: [
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: config.appName,
home: Scaffold(
body: SomeWidget(); // whack a button in this widget that triggers a product purchase
),
)
);
}
}
Non-working example:
class AppConfig extends InheritedWidget {
AppConfig({
#required this.appName,
#required Widget child,
#required this.prefs,
#required this.devMode
}) : super(child: child);
final String appName;
final SharedPreferences prefs;
final bool devMode;
static AppConfig of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
#override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
var configuredApp = new AppConfig(
appName: 'app',
child: new AppScaffold(),
prefs: await SharedPreferences.getInstance(),
devMode: true,
);
InAppPurchaseConnection.enablePendingPurchases();
runApp(configuredApp);
}
class AppScaffold extends StatelessWidget {
#override
Widget build(BuildContext context) {
var config = AppConfig.of(context);
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
return MultiProvider(
providers: [
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
title: config.appName,
home: Scaffold(
body: MyApp()
),
)
);
}
}
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
StreamSubscription<List<PurchaseDetails>> _subscription;
#override
void initState() {
Stream purchaseUpdated =
InAppPurchaseConnection.instance.purchaseUpdatedStream;
_subscription = purchaseUpdated.listen((purchaseDetailsList) {
_listenToPurchaseUpdated(purchaseDetailsList, context);
}, onDone: () {
_subscription.cancel();
}, onError: (error) {
// handle error here.
});
super.initState();
}
#override
void dispose() {
_subscription.cancel();
super.dispose();
}
void _listenToPurchaseUpdated(List<PurchaseDetails> purchaseDetailsList, BuildContext context) {
var config = AppConfig.of(context);
purchaseDetailsList.forEach((PurchaseDetails purchaseDetails) async {
if (purchaseDetails.status == PurchaseStatus.pending) {
print('pending');
} else {
if (purchaseDetails.status == PurchaseStatus.error) {
print('error');
} else if (purchaseDetails.status == PurchaseStatus.purchased) {
print('purchased');
}
if (purchaseDetails.pendingCompletePurchase) {
print('complete');
await InAppPurchaseConnection.instance
.completePurchase(purchaseDetails);
}
}
});
}
#override
Widget build(BuildContext context) {
return SomeWidget(); // whack a button in this widget that triggers a product purchase
}
}
Can anyone see if I'm going about this the wrong way and/or explain why this doesn't work?
My own fault - I was using Navigator.pushReplacement(...); elsewhere in the app which was triggering the dispose method on the child widget. Obvious now I think about it.
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.