Flutter how to wait until Future function complete - flutter

I wrote a short flutter app that have a variable that need to be initialize before I send him to another function, so I wrote a function that initializing the variable as the app started. but for some reason the code isn't waiting for the function to end and I get the "LateInitializeError" error. Someone know how can I make the code wait until the function is finished and the variable has updated?.
this is my code:
// imports...
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: AuthenticationWrapper(),
);
}
}
class AuthenticationWrapper extends StatefulWidget {
const AuthenticationWrapper({Key? key}) : super(key: key);
#override
_AuthenticationWrapperState createState() => _AuthenticationWrapperState();
}
class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
final FirebaseAuth _auth = FirebaseAuth.instance;
late Map<String, dynamic> data;
#override
void initState() {
super.initState();
getUserDataFunc().then((Map<String, dynamic> value) {
data = value; // here I am updating the variable
});
}
Future<Map<String, dynamic>> getUserDataFunc() async {
return getUserData.getUserInfo("GGji5pJyJHQevdtlhqKDKjrVOwq1");
}
#override
Widget build(BuildContext context) {
if (_auth.currentUser != null) {
return FutureBuilder<bool>(
future: loginFunctions.isBarber(_auth.currentUser!.uid),
builder: (BuildContext context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const LoadingPage();
case ConnectionState.none:
return const Text("");
case ConnectionState.active:
case ConnectionState.done:
if (snapshot.data == true) {
return const BarberHomePage();
} else {
return ClientHomePage(
uid: _auth.currentUser!.uid,
);
}
}
},
);
} else {
return ClientHomePage(
uid: "GGji5pJyJHQevdtlhqKDKjrVOwq1", userData: data); // here I am want to send that variable, and I get the error
}
}
}

The build function will run at least once before any async task. That means that ClientHomePage will always be built before data is initialized. I would just pass it as a future and have a future builder in ClientHomePage as well.
class AuthenticationWrapper extends StatefulWidget {
const AuthenticationWrapper({Key? key}) : super(key: key);
#override
_AuthenticationWrapperState createState() => _AuthenticationWrapperState();
}
class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<Map<String, dynamic>> getUserDataFunc() async {
return getUserData.getUserInfo("GGji5pJyJHQevdtlhqKDKjrVOwq1");
}
#override
Widget build(BuildContext context) {
if (_auth.currentUser != null) {
return FutureBuilder<bool>(
future: loginFunctions.isBarber(_auth.currentUser!.uid),
builder: (BuildContext context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const LoadingPage();
case ConnectionState.none:
return const Text("");
case ConnectionState.active:
case ConnectionState.done:
if (snapshot.data == true) {
return const BarberHomePage();
} else {
return ClientHomePage(
uid: _auth.currentUser!.uid,
);
}
}
},
);
} else {
return ClientHomePage(
uid: "GGji5pJyJHQevdtlhqKDKjrVOwq1", userData: getUserDataFunc());
}
}
}
class ClientHomePage extends StatefulWidget {
const ClientHomePage({Key? key, this.data}) : super(key: key);
Future<Map<String, dynamic>> data;
#override
_ClientHomePageState createState() => _ClientHomePageState();
}
class _ClientHomePageState extends State<ClientHomePage> {
#override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: data,
builder: (BuildContext context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const LoadingPage();
case ConnectionState.none:
return const Text("");
case ConnectionState.active:
case ConnectionState.done:
return ClientHomePageContent(
data: data,
);
}
},
);
}
}

Related

i cannot solve this error. Flutter - rest api

/* The method '[]' can't be unconditionally invoked because the receiver can be 'null'.
Try making the call conditional (using '?.') or adding a null check to the target ('!').*/
import 'dart:core';
import 'package:flutter/material.dart';
import 'package:rest_api/data/fetch_data.dart';
class MyWidget extends StatefulWidget {
const MyWidget({Key? key}) : super(key: key);
#override
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget> {
int index =0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder(
future: getPost(),
builder: (context, snapshot) {
return ListView.builder(
itemBuilder: (context,index){
return Text(snapshot.**data[index]**);
});
})
);
}
}
The API might not give the proper response so the part of it you are trying to read might be null or non existent and Flutter has no way of knowing that the API will return a string. Try a default value if it's null like this
Text(res.body.text ?? "Default")
Try adding an exclamation mark (!):
return Text(snapshot.data![index]);
or:
return Text(snapshot.data[index] ?? "Data is null");
You can have error from API or getting null/empty data. Check this Demo widget and configure your case. Also, the current is object is not detecting and possible to get null from. You can do.
I gues the for your case it will be
Text(snapshot.data?[index]?? "default");
Follow the snippet and make changes
class MyHomePage extends StatefulWidget {
const MyHomePage({Key? key}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
late final Future<List<int>?> myFuture;
#override
void initState() {
super.initState();
myFuture = getCatData();
}
Future<List<int>?> getCatData() async {
await Future.delayed(Duration(seconds: 2));
//your operations
return [1, 2, 5];
}
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () {
setState(() {});
}),
body: FutureBuilder<List<int>?>(
future: myFuture,
builder: (context, AsyncSnapshot snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
}
if (snapshot.hasError) {
return Text("Error ${snapshot.error}");
}
if (!snapshot.hasData) {
return Text("no Data found");
}
if (snapshot.data!.isEmpty) {
return Text("Empty found");
}
if (snapshot.hasData) {
return ListView.builder(
itemCount: snapshot.data.length,
itemBuilder: (context, index) {
return Container(child: Text(snapshot.data[index].toString()));
},
);
}
return Text("NA");
},
),
);
}
}

What is the purpose of authentication state unknown in flutter_bloc?

I was going through flutter_bloc documentation https://bloclibrary.dev/#/flutterlogintutorial, I saw that AuthenticationState has 3 states
enum AuthenticationStatus { authenticated, unauthenticated, unknown }
I couldn't understand the purpose of unknown state.
Here is the full code
bloc state
enum AuthenticationStatus { authenticated, unauthenticated, unknown }
class AuthenticationState extends Equatable {
const AuthenticationState._({
this.status = AuthenticationStatus.unknown,
this.user = User.empty,
});
const AuthenticationState.unknown() : this._();
const AuthenticationState.authenticated(User user)
: this._(status: AuthenticationStatus.authenticated, user: user);
const AuthenticationState.unauthenticated()
: this._(status: AuthenticationStatus.unauthenticated, user: User.empty);
final AuthenticationStatus status;
final User user;
#override
List<Object> get props => [status, user];
}
bloc_implementation
class AuthenticationBloc
extends Bloc<AuthenticationEvent, AuthenticationState> {
AuthenticationBloc({
#required AuthenticationRepository authenticationRepository,
}) : assert(authenticationRepository != null),
_authenticationRepository = authenticationRepository,
super(const AuthenticationState.unknown()) {
_userSubscription = _authenticationRepository.userStream.listen(
(user) => add(AuthenticationUserChanged(user)),
);
}
final AuthenticationRepository _authenticationRepository;
StreamSubscription<User> _userSubscription;
#override
Stream<AuthenticationState> mapEventToState(
AuthenticationEvent event,
) async* {
if (event is AuthenticationUserChanged) {
yield _mapAuthenticationUserChangedToState(event);
} else if (event is AuthenticationLogoutRequested) {
unawaited(_authenticationRepository.logOut());
}
}
#override
Future<void> close() {
_userSubscription?.cancel();
return super.close();
}
AuthenticationState _mapAuthenticationUserChangedToState(
AuthenticationUserChanged event,
) =>
event.user != User.empty
? AuthenticationState.authenticated(event.user)
: const AuthenticationState.unauthenticated();
}
UI
import 'package:authentication_repository/authentication_repository.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:flutter_login/authentication/authentication.dart';
import 'package:flutter_login/home/home.dart';
// import 'package:flutter_login/login/login.dart';
import 'package:flutter_login/splash/splash.dart';
import 'package:flutter_login/splash/view/spash_page.dart';
import 'package:user_repository/user_repository.dart';
import 'authentication/bloc/authentication_bloc.dart';
import 'home/view/home_page.dart';
import 'login/view/login_page.dart';
class App extends StatelessWidget {
const App({
Key? key,
required this.authenticationRepository,
required this.userRepository,
}) : super(key: key);
final AuthenticationRepository authenticationRepository;
final UserRepository userRepository;
#override
Widget build(BuildContext context) {
return RepositoryProvider.value(
value: authenticationRepository,
child: BlocProvider(
create: (_) => AuthenticationBloc(
authenticationRepository: authenticationRepository,
userRepository: userRepository,
),
child: AppView(),
),
);
}
}
class AppView extends StatefulWidget {
#override
_AppViewState createState() => _AppViewState();
}
class _AppViewState extends State<AppView> {
final _navigatorKey = GlobalKey<NavigatorState>();
NavigatorState get _navigator => _navigatorKey.currentState!;
#override
Widget build(BuildContext context) {
return MaterialApp(
navigatorKey: _navigatorKey,
builder: (context, child) {
return BlocListener<AuthenticationBloc, AuthenticationState>(
listener: (context, state) {
switch (state.status) {
case AuthenticationStatus.authenticated:
_navigator.pushAndRemoveUntil<void>(
HomePage.route(),
(route) => false,
);
break;
case AuthenticationStatus.unauthenticated:
_navigator.pushAndRemoveUntil<void>(
LoginPage.route(),
(route) => false,
);
break;
default:
break;
}
},
child: child,
);
},
onGenerateRoute: (_) => SplashPage.route(),
);
}
}
I think it is to display a splash screen, but we need to edit android and ios native code to display splash screen right?
Please help me to understand its purpose.

How to use Future<bool> return value from function without async function (Flutter)

I wrote short flutter app that have a async function that get value from cloud firestore and return Future bool type according to the information from the database.
now in my main code I want to return a Widget according to the value that got return form that mention function, but I didn't succeed using the returned value right.
I tried to write an external async function that will call the first function and will put the returned value inside a shared variable. but it didn't work for me and got sometimes error about that variable isn't been initialized.
it's looked like the code is not stopping line by line and wait for my function to return the value when been called and continue on the if statement.
Someone know how can I return a widget according to the value that the function returning and fix that issue?.
I tried using block of code using then but I never use it before so it doesn't worked as well.
my fist function:
Future<bool> firstFunc(String uid) async {
DocumentSnapshot userData =
await FirebaseFirestore.instance.collection('Users').doc(uid).get();
bool value = userData["param"];
return value;
}
and this is my main code:
// imports
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
Widget build(BuildContext context) {
return const MaterialApp(
debugShowCheckedModeBanner: false,
home: AuthenticationWrapper(),
);
}
}
class AuthenticationWrapper extends StatefulWidget {
const AuthenticationWrapper({Key? key}) : super(key: key);
#override
_AuthenticationWrapperState createState() => _AuthenticationWrapperState();
}
class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
final FirebaseAuth _auth = FirebaseAuth.instance;
late bool sharedVar;
void getValue(String uid) async {
bool value = await firstFunc(uid);
sharedVar = value;
}
#override
Widget build(BuildContext context) {
if (_auth.currentUser != null) {
getValue(_auth.currentUser!.uid);
if (sharedVar == true)
{
return BossHomePage();
}
else {
return ClientHomePage();
}
} else {
return const EnterPhonePage();
}
}
}
The reason it doesn't wait for getValue is that getValue is of type void.
Try this;
class AuthenticationWrapper extends StatefulWidget {
const AuthenticationWrapper({Key? key}) : super(key: key);
#override
_AuthenticationWrapperState createState() => _AuthenticationWrapperState();
}
class _AuthenticationWrapperState extends State<AuthenticationWrapper> {
final FirebaseAuth _auth = FirebaseAuth.instance;
#override
Widget build(BuildContext context) {
if (_auth.currentUser != null) {
return FutureBuilder<bool>(
future: firstFunc(uid),
builder: (BuildContext context, snapshot){
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return const CircularProgressIndicator();
case ConnectionState.none:
return const Text('No data');
case ConnectionState.active:
case ConnectionState.done:
if (snapshot.data == true) {
return BossHomePage();
} else {
return ClientHomePage();
}
default:
return const Text('No data');
}
},
)
} else {
return const EnterPhonePage();
}
}
}
getValue is a future method, You need to use async at some point. You can convert this to
Future<bool> getValue(String uid) async {
bool value = ....;
return value;
}
And use FutureBuilder (or initState is better SatefullWidget) for this case
late bool sharedVar;
Future<void> getValue(String uid) async {
bool value =...;
sharedVar = value;
}
bool? currentUser;
_initData()async{
getValue("");
///all others methods
}
#override
void initState() {
super.initState();
_initData();
}
More about async-await

getting data after building widgets

I am trying to get data from Database, but my widget is built before I can get them...
class CategoriesWidget extends StatefulWidget {
#override
_CategoriesWidgetState createState() => _CategoriesWidgetState();
}
class _CategoriesWidgetState extends State<CategoriesWidget> {
SharedPreferences prefs;
String token;
var _isInit = false;
#override
void initState() {
if (!_isInit) {
super.initState();
fetchCat();
_isInit = true;
}
}
var categories = {};
fetchCat() async {
final prefs = await SharedPreferences.getInstance();
setState(() {
token = prefs.getString('api_token');
});
await fetchCategories(token).then((result) {
categories = result[1];
print(categories);
print(result[1]);
});
}
#override
Widget build(BuildContext context) {
final deviceSize = MediaQuery.of(context).size;
print('2');
return Column(
// code here
);
}
}
you can see that I print 1 and 2 to see which one is getting the first and I got as result 2 then 1.
You should use a FutureBuilder.
#override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: _fetchCat(),
builder: (context, snapshot) => snapshot.hasData
? MyWidget(data: snapshot.data)
: Text('Loading...'),
);
}
And with a FutureBuilder, your Widget could probably stay Stateless.
Here is a Minimal Working Example:
Full source code:
import 'package:flutter/material.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'StackOverflow Answer',
home: HomePage(),
);
}
}
class HomePage extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(child: CategoriesWidget()),
);
}
}
class CategoriesWidget extends StatefulWidget {
#override
_CategoriesWidgetState createState() => _CategoriesWidgetState();
}
class _CategoriesWidgetState extends State<CategoriesWidget> {
Future<String> _fetchCat() async {
await Future.delayed(Duration(seconds: 2));
return 'Category';
}
#override
Widget build(BuildContext context) {
return FutureBuilder<String>(
future: _fetchCat(),
builder: (context, snapshot) => Text(
snapshot.hasData ? snapshot.data ?? 'NO CATEGORY' : 'Loading...'),
);
}
}

How to render home page after authenticating user in flutter?

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) {
...
}
}