Use the futureBuilder for main route - flutter

I set up my app with named routes:
class App extends StatelessWidget {
const App({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return MaterialApp(
initialRoute: "/",
routes: {
"/": (context) =>
FutureBuilder(
future: landingPage(),
builder: (BuildContext context, AsyncSnapshot<Widget> widget) {
if(widget.connectionState == ConnectionState.done){
if(!widget.hasData) {
return const Center(
child: Text("No Data exists"),
);
}
return widget.data!;
}
return const Center(
child: CircularProgressIndicator(
color: Colors.white,
),
);
},
),
"/email-verification":(context) => const EmailVerificationPage(),
"/login":(context) => const LoginPage(),
"/name-age":(context) => const AgePage(),
"/signup":(context) => const SignupPage(),
},
);
}
}
Then I have my functions where I decide which page should be the landing page depending on how far the user has added information to their account:
Future<Widget> landingPage() async {
if(FirebaseAuth.instance.currentUser == null){
return const LoginPage();
}
if(!FirebaseAuth.instance.currentUser!.emailVerified){
return const EmailVerificationPage();
}
if(! await checkAge(FirebaseAuth.instance.currentUser!.uid)){
return const AgePage();
}
return const HomePage();
}
Future<bool> checkAge(String uid) async {
QuerySnapshot snapshot = await FirebaseFirestore.instance.collection("users")
.where("uid", isEqualTo: FirebaseAuth.instance.currentUser!.uid)
.where("birtDate", isNull: false)
.get();
if(snapshot.size > 0){
return true;
}
return false;
}
However the futureBuilder always returns no data for some reason. Does anyone know why?

Related

Why can i see black screen when using BlocBuilder though i have taken all possible measures?

This is my main file, I am trying to check for internet connection.And showing dialog if there is no internet connection using flutter cubit.
But the only hurdle is for a flicker of second the screen goes black and then dialog is displayed , how can i avoid this?
main.file
void main() {
runApp(BlocProvider(
create: (BuildContext context) => ConnectivityCubit()..checkConnectivity(),
lazy: false,
child: MaterialApp(home: MyApp()),
));
}
class MyApp extends StatelessWidget {
MyApp({super.key});
bool _isDialogDisplayed = false;
#override
Widget build(BuildContext context) {
return BlocConsumer<ConnectivityCubit, ConnectivityState>(
listener: (context, state) {
if (state == ConnectivityState.disconnected) {
_isDialogDisplayed = true;
showDialog(
context: context,
builder: (context) => const AlertDialog(
title: Text('No Internet'),
content: Text('Please check your internet connection.'),
),
);
}
if (state == ConnectivityState.connected &&
_isDialogDisplayed == true) {
Navigator.of(context).pop();
_isDialogDisplayed = false;
}
},
builder: (context, state) {
if (state == ConnectivityState.init) {
return const CircularProgressIndicator();
}
return MaterialApp( // <-- This is causing problem
home: Scaffold(
body: state == ConnectivityState.connected
? const Center(
child: Text('Hello World'),
)
: const Center(child: CircularProgressIndicator()),
),
);
},
);
}
}
cubit.file
import 'dart:async';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
enum ConnectivityState { connected, disconnected, init }
class ConnectivityCubit extends Cubit<ConnectivityState> {
final Connectivity _connectivity = Connectivity();
StreamSubscription<ConnectivityResult>? _subscription;
late Stream<ConnectivityResult> streamValue;
ConnectivityCubit() : super(ConnectivityState.init) {
streamValue = _connectivity.onConnectivityChanged;
_subscription = _connectivity.onConnectivityChanged.listen((result) {
if (result == ConnectivityResult.none) {
emit(ConnectivityState.disconnected);
} else {
emit(ConnectivityState.connected);
}
});
}
checkConnectivity() async {
final result = await _connectivity.checkConnectivity();
if (result == ConnectivityResult.none) {
emit(ConnectivityState.disconnected);
} else {
emit(ConnectivityState.connected);
}
}
#override
Future<void> close() {
_subscription?.cancel();
return super.close();
}
}
I have tried to simply use this way
return const MaterialApp(
home: Scaffold(
body: Center(
child: Text('Hello World'),
)),
);
The above code solves black screen issue but it will show Hello World for fraction of second i.e because of the time taken to build dialog by the BlocListener. To overcome that I tried the above method. Though i have things wrapped inside the MaterialApp why do i see black screen?
you want builder part in check status and then showDialog()
MyApp({super.key});
bool _isDialogDisplayed = false;
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text(widget.title)),
body: BlocConsumer<ConnectivityCubit, ConnectivityState>(
listener: (context, state) {
if (state == ConnectivityState.disconnected) {
_isDialogDisplayed = true;
showDialog(
context: context,
builder: (context) => const AlertDialog(
title: Text('No Internet'),
content: Text('Please check your internet connection.'),
),
);
}
if (state == ConnectivityState.connected &&
_isDialogDisplayed == true) {
Navigator.of(context).pop();
_isDialogDisplayed = false;
}
},
builder: (context, state) {
if (state == ConnectivityState.init) {
return const CircularProgressIndicator();
}
else if(state == ConnectivityState.disconnected){
_isDialogDisplayed = true;
showDialog(
context: context,
builder: (context) => const AlertDialog(
title: Text('No Internet'),
content: Text('Please check your internet connection.'),
),
);
}
},
);
);
}
}

is there a better way to write a document stream builder?

Is there a better way to write this code?
The application is about jobs and job applications.
Each job can have multiple job applications.
if the job is fulfilled, all the job applications should be closed so that people are not applying to a job that is closed.
I don't like the way the code is written but it achieves the functionality that I wanted.
to get to the 'JobApplicationView' I have to go through a page that displays all the current job applications, once I click on one of the job application, the job application.
here is the snip of code from the 'job application list view'
StreamBuilder(
stream: _jobsService.allJobApplications(userId),
builder: (context, snapshot) {
switch (snapshot.connectionState) {
case ConnectionState.waiting:
case ConnectionState.active:
if (snapshot.hasData) {
final allJobApplications =
snapshot.data as Iterable<CloudJobApplication>;
return JobApplicationsListView(
allowScroll: false,
jobApplications: allJobApplications,
onTap: (job) {
Navigator.of(context).pushNamed(
myJobApplicationsRoute,
arguments: job,
);
},
);
} else {
return const CircularProgressIndicator();
}
default:
return const CircularProgressIndicator();
}
},
),
Below is the really ugly code that performs the functionality that i want
class JobApplicationView extends StatefulWidget {
const JobApplicationView({Key? key}) : super(key: key);
#override
_JobApplicationViewState createState() => _JobApplicationViewState();
}
class _JobApplicationViewState extends State<JobApplicationView> {
final _formKey = GlobalKey<FormState>();
late final FirebaseCloudStorage _cloudFunctions;
final currentUser = AuthService.firebase().currentUser!;
#override
void initState() {
super.initState();
_cloudFunctions = FirebaseCloudStorage();
}
getExistingJobApplication(BuildContext context) {
return FirebaseFirestore.instance
.collection('job application')
.doc(context.getArgument<CloudJobApplication>()!.documentId)
.snapshots();
}
submitProposal(localStates) {
submitProposal() async {
await _cloudFunctions.updateJobApplicationColumn(
documentId: context.getArgument<CloudJobApplication>()!.documentId,
fieldNameColumn: jobApplicationStateColumn,
fieldNameColumnValue: jobApplicationStateOpen);
await _cloudFunctions.updateJobApplicationColumn(
documentId: context.getArgument<CloudJobApplication>()!.documentId,
fieldNameColumn: jobApplicationSubStateColumn,
fieldNameColumnValue: 'Awaiting client proposal');
}
// job applicator can submit proposal if the state is new
if (localStates['jobApplicatotIsSameAsCurrentUser'] &&
localStates['jobApplication'][jobApplicationStateColumn] ==
jobApplicationStateNew) {
return ElevatedButton(
style: ElevatedButton.styleFrom(
textStyle: const TextStyle(fontSize: 13),
backgroundColor: Colors.blue,
),
onPressed: () => submitProposal(),
child: const Text('Submit proposal'),
);
// job creator can accept the proposal if state is open
// job creator can deny job proposal if state is open
} else {
return Container();
}
}
#override
void dispose() {
super.dispose();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('update job application'),
actions: [],
),
body: StreamBuilder(
stream: getExistingJobApplication(context),
builder: (context, AsyncSnapshot<DocumentSnapshot> snapshot) {
if (!snapshot.hasData) {
return const CircularProgressIndicator();
}
// this is where I saved the local states
var localStates = {};
localStates['width'] = MediaQuery.of(context).size.width;
localStates['jobApplication'] = snapshot.data;
localStates['formFieldsEditable'] = localStates['jobApplication']
[jobApplicationStateColumn] ==
jobApplicationStateNew &&
currentUser.id ==
localStates['jobApplication'][jobApplicatorIdColumn]
? true
: false;
localStates['jobApplicatotIsSameAsCurrentUser'] = currentUser.id ==
localStates['jobApplication'][jobApplicatorIdColumn];
localStates['jobCreatorIsSameAsCurrentUser'] = currentUser.id ==
localStates['jobApplication'][jobCreatorIdColumn];
return Form(
key: _formKey,
child: ListView(
padding: const EdgeInsets.all(32.0),
children: [
submitProposal(localStates),
],
),
);
}),
);
}
}

How to return a widget if I need to go to a new screen?

Why do this code in not properly set up? I get the error: This function has a return type of 'Widget', but doesn't end with a return statement.
Obviously, it doesn like the use of Navigator stuff in future builder. How to make it properly?
MaterialApp(
home: const Splash1(),
);
class Splash1 extends StatelessWidget {
const Splash1({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: FutureBuilder<bool>(
future: checkIsSeen(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData) {
if (snapshot.data == true) {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const HomeView()),
);
} else {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const IntroScreen()),
);
}
} else if (snapshot.hasError) {
return const Icon(
Icons.error_outline,
size: 60,
);
} else {
return CircularProgressIndicator();
}
}),
);
}
There is a statement about your issue (Obviously, it does not like the use of Navigator stuff in the future builder.). Future.builder shouldn't include logic beyond building widgets (e.g. don't call Navigator.push).
Instead of FutureBuilder, you can just put the async call in build().
Widget build(BuildContext context) {
check().then((success) {
if (success) {
Navigator.pushReplacementNamed(context, '/home');
} else {
Navigator.pushReplacementNamed(context, '/login');
}
});
You can learn more about this issue at this link: https://github.com/flutter/flutter/issues/16218
Since HomeView and IntroScreen both contain a Scaffold, you can reorganise your code like this, without using Navigator in the build method:
class Splash1 extends StatelessWidget {
const Splash1({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
return FutureBuilder<bool>(
future: checkIsSeen(),
builder: (BuildContext context, AsyncSnapshot<bool> snapshot) {
if (snapshot.hasData) {
return snapshot.data ? const HomeView() : const IntroScreen();
} else {
return Scaffold(
body: snapshot.hasError
? const Icon(
Icons.error_outline,
size: 60,
)
: const CircularProgressIndicator());
}
});
}
}

read user info from FirebaseFirestore in StreamBuilder

I'm trying to read document info from Database this my code
abstract class Database{
Future<void> sendText(SendOrder textData);
Stream<QuerySnapshot<Map<String, dynamic>>> readDataStream();
}
class FirestoreDatabase implements Database{
FirestoreDatabase({required this.uid});
final String uid;
#override
Future<void> sendText(SendOrder textData) async {
final documentRefer = FirebaseFirestore.instance.collection('userInfo').doc(uid);
await documentRefer.set(textData.toMap());
}
#override
Stream<QuerySnapshot<Map<String, dynamic>>> readDataStream() {
return FirebaseFirestore.instance.collection('userInfo').where('uid', isEqualTo: uid).snapshots();
}
}
with send text I got no problem but read data I got problem
getData() async{
return database.readDataStream().map((snapshot) => snapshot.docs);
}
Future<void> _dataSend(BuildContext context) async {
await database.sendText(SendOrder(userName: 'name', coins: '12', level: '12'));
}
body: StreamBuilder<Database>(
builder: (context, snapshot){
return Column(
children: [
ElevatedButton(onPressed: () {
_dataSend(context);
}, child:
const Text('Click')),
Text(
getData().toString()
, style: const TextStyle(color: Colors.white),
)
],
);
},
)
I need to read map data but I have problem becuase I'm new flutter developer , don't know how to convert data or where comes from
so how to read data?
Is my code right to read as StreamBuilder?
You had not set stream for your StreamBuilder. Check below code for simple StreamBuilder:
class HomePage extends StatelessWidget {
const HomePage({Key? key}) : super(key: key);
Stream<String> _generateNumbers() async* {
for (int i = 1; i <= 10; i++) {
await Future<void>.delayed(const Duration(seconds: 1));
yield "$i";
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: StreamBuilder<String>(
stream: _generateNumbers(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const CircularProgressIndicator();
} else if (snapshot.connectionState == ConnectionState.active
|| snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return const Text('Error');
} else if (snapshot.hasData) {
return Text(
snapshot.data ?? '',
style: const TextStyle(color: Colors.red, fontSize: 40)
);
} else {
return const Text('Empty data');
}
} else {
return Text('State: ${snapshot.connectionState}');
}
},
),
),
);
}
}

Why is flutter printing out widget name?

I have a problem with flutter printing out the name and rendering Widget name after running the application
class MyApp extends StatefulWidget {
const MyApp({Key? key}) : super(key: key);
#override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? loggedIn = prefs.getBool('loggedin');
if (loggedIn == true) {
Home();
} else {
return LoginOrSignup();
}
}
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(body:SafeArea(
child: FutureBuilder(
future: autoLogin(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return Text('${snapshot.data}');
} else {
return LoginOrSignup();
}
}),
))
);
}
}
After running the app the output is LoginOrSignup()
class LoginOrSignup extends StatelessWidget {
const LoginOrSignup({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Center(
child: MaterialButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Login()),
);
},
child: Text('Loginsss'),
),
),
Center(
child: MaterialButton(
onPressed: (){
Navigator.push(
context,
MaterialPageRoute(builder: (context) => Signup()),
);
},
child: Text('Signup'),
),
)
],
),
);
}
}
I have tried using another widget like Text() but it still prints out the same when i run the application on a mobile app. The problem seems to appear in the autoLogin() function that i have
The issue is your future return Widget itself, and when you use Text('${snapshot.data}') it print the widget, To simplfity this you can return data from Future(this is what mostly we do). Let say you like to return widget itself.
A little correction is needed on Future.
Future<Widget> autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? loggedIn = prefs.getBool('loggedin');
if (loggedIn == true) {
return Home();
} else {
return LoginOrSignup();
}
}
And
return MaterialApp(
home: Scaffold(
body: SafeArea(
child: FutureBuilder<Widget>(
future: autoLogin(),
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
return snapshot.data!;
} else {
return LoginOrSignup();
}
}),
)));
You are returning a Widget in autoLogin function. Instead you should return a bool.
Future<bool?> autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
bool? loggedIn = prefs.getBool('loggedin');
if (loggedIn == null) return null;
if (loggedIn == true) {
return true;
} else {
return false;
}
}
Then in the FutueBuilder you can check if it's then return Home()
if (snapshot.hasData && snapshot.data! == true) {
return Home();
} else {
return LoginOrSignup();