I am new to Flutter and Firebase and am working on an authentication app.
How do I make the app go to the HomePage if a user is signed in already or go to the login page if no user is signed in?
Forgive my rusty code.
FirebaseAuth auth = Firebase.instance;
void main() {
Future<bool> home() async {
if (await auth.currentUser() == null) {
return false;
}
return true;
}
runApp(
new MaterialApp(
title: 'DASH',
home: home() == null ? LoginScreen() : HomeScreen(),
theme: DashTheme.theme,
color: DashTheme.white,
routes: <String, WidgetBuilder>{
// Set routes for using the Navigator.
'/home': (BuildContext context) => new HomeScreen(),
'/login': (BuildContext context) => new LoginScreen(),
},
),
);
}
I'm using Future Builder
Widget _getLandingPage() {
return FutureBuilder<FirebaseUser>(
future: FirebaseAuth.instance.currentUser(),
builder: (BuildContext context, AsyncSnapshot<FirebaseUser> snapshot){
if (GlobalStore.state.userConfig != null && GlobalStore.state.userConfig.isDisplayIntroScreen == true) {
return routes.buildPage('intro_screen', null);
}
if (snapshot.hasData) {
return routes.buildPage('main', null);
} else {
return routes.buildPage('auth', null);
}
},);
home: _getLandingPage(),
While I never wrote anything in the Dart language, I can see that you wrote some good beginning. If we take a look at this line:
home: home() == null ? LoginScreen() : HomeScreen(),
You are checking in home() if the user is logged in. However, you are checking if the return value is null while the returned object of the method is Future<bool>. (This means that in this case the condition is always false).
Also note that this method is async. That means that you will not really know when this method is actually done executing. (This will be some moment in the future).
That being said, you should display an initial page once the app starts up. Then you should run and check the return value of the future method. According to the docs (https://www.dartlang.org/tutorials/language/futures) this can be done like this:
// Call this after the initialization of your app and pages
final future = home();
future.then((isLoggedIn) => ); // Inside here you need to do a navigation to the homepage or loginpage
A little modification is required in your code.
Instead of checking the current user in the main function like that, what you can do is check user in the initState() function.
#override
void initState()
{
super.initState();
final FirebaseAuth _auth = FirebaseAuth.instance;
if(_auth.currentUser() == null)
{
Navigator.of(context).pushNamedAndRemoveUntil("/LoginScreen" , (Route<dynamic> route) => false);
}
else
{
Navigator.of(context).pushNamedAndRemoveUntil("/HomeScreen" , (Route<dynamic> route) => false);
}
}
pushNamedAndRemoveUntil is used if you don't want the user to go back to the previous scene or route.
You can Read about Navigation in flutter here. And use the one you need.
Thanks alot for all your answers.
I was checking up on the answers and this is what I came across.
I don't know if it's the right thing to do but it is actually working.
Here is the code:
Widget _getLandingPage() {
return StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
if (snapshot.hasData) {
if (snapshot.data.providerData.length == 1) {
return snapshot.data.isEmailVerified ? HomeScreen() : VerifyEmailScreen();
} else {
return HomeScreen();
}
} else {
return LoginScreen();
}
},
);
}
home: _getLandingPage(),
Related
My problem is with Futures, because they should be obtained before build() method executed, as the documentation states:
The future must be obtained earlier, because 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.
I know that Futures should be called in initstate() function before the build method executed, but my case is different.
I want to get data from api as a Future, but the request I am sending to the api needs some parameters that user should select inside the screen's build() method.
And I don't know what the parameter of the request will be until user selects in build() method, and I have to call the api in the build() method and use FutureBuilder there, but that makes FutureBuilder to get constantly called, and I don't want that.
basically, I don't want to call FutureBuilder indefinetely, and I can't put my Future inside initState() because the Future needs some parameters that user later selects when the screen is shown inside build() method.
inside the build method:
FutureBuilder<List<LatLng>>(
builder: (context, snapshot) {
if (snapshot.hasData) {
return PolylineLayer(
polylines: [
Polyline(
points: snapshot.data!,
strokeWidth: 4,
color: Colors.purple),
],
);
} else if (snapshot.hasError) {
return Text("${snapshot.error}");
} else {
return Container();
}
},
future: Provider.of<NavigationProvider>(context)
.getNavigationPoints(pointToGoTo!),
),
now if you look at the code, at the final lines, I am sending the parameter pointToGoTo to the function which calls the backend.
simply, I want to get rid of calling api and getting data back as a Future inside build method, I want to do it in initState or somewhere else that prevents the build methods calling backend indefinitely.
is there any way to fix this problem?
Thanks in advance.
Firstly, create future state variable and a nullable params and use it with conditional if while using FutureBuilder.
I will recommend checking Fixing a common FutureBuilder and StreamBuilder problem
Now you can follow this example. It is missing progressBar on API recall, StreamBuilder might be better option in cases like this.
class Foo extends StatefulWidget {
const Foo({super.key});
#override
State<Foo> createState() => _FooState();
}
class _FooState extends State<Foo> {
int? params;
Future<int> fetch(int? data) async {
await Future.delayed(Duration(seconds: 1));
return (params ?? 0) * 2;
}
late Future<int> future = fetch(params);
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
children: [
DropdownButton<int?>(
value: params,
items: List.generate(
12,
(index) => DropdownMenuItem(
value: index,
child: Text("$index"),
)).toList(),
onChanged: (value) {
future =
fetch(params); // this will only call api with update data
setState(() {
params = value;
});
},
),
if (params != null)
FutureBuilder<int>(
future: future,
builder: (context, snapshot) {
if (snapshot.hasData) return Text("${snapshot.data}");
return CircularProgressIndicator();
},
)
],
),
);
}
}
class Testing extends StatefulWidget {
const Testing({super.key});
#override
State<Testing> createState() => _TestingState();
}
class _TestingState extends State<Testing> {
bool isFetched = false;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Consumer<SomethingProvider>(
builder: (context, prov, child) {
if (!isFetched) {
prov.getData("a", "b");
Future.delayed(const Duration(milliseconds: 200), () {
isFetched = true;
});
}
if (prov.newData.isNotEmpty) {
return Column(
// make widget tree from here
);
} else {
return const Center(
child: CircularProgressIndicator(),
);
}
},
),
);
}
}
class SomethingProvider extends ChangeNotifier {
List newData = [];
Future getData(param1, param2) async {
newData = ["testingdata"];
}
}
I do not want to use accesstoken or refreshtoken but it is not working. Does anyone help me to handle this situation? Here is my future =>
Future<bool> autoLogin() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
var user = prefs.getString('username');
var pwd = prefs.getString('password');
debugPrint(user.toString());
debugPrint(pwd.toString());
UserResponse account = await LoginApi().login(user, pwd);
// debugPrint(account.toString());
bool acc = prefs.containsKey('userInfo');
return acc;
}
This is my usage part =>
home: FutureBuilder<bool>(
future: autoLogin(),
builder: (context, snapshot){
if(snapshot.data == null){
Future.delayed(Duration(milliseconds: 1000), () {
setState(() {
snapshot.data == false;
});
});
return LoadingScreen();
}
return snapshot.data == true ? MainWidget() : LoginScreen();
},
),
When i first time to try login it stays on loading screen, snapshot.data stays null. It does not change.
Create an instance at widget that is int ctr=0;
Then you can change there like this:
home: FutureBuilder<bool>(
future: autoLogin(),
builder: (context, snapshot){
if(snapshot.data == null) {
ctr++;
if(ctr>1) {
return LoginScreen();
}
else {
return LoadingScreen();
}
}
return snapshot.data == true ? MainWidget() : LoginScreen();
Your Future.delayed() is not being awaited, so the time delay is effectively a no-op except to push that setState later.
When the layers start to be complicated with a series of async operations, it's time to look at something like RiverPod (which I prefer over Provider and BLoC) to give you wrappers around those async operations, which properly cascade the dependencies in a readable format.
I am making an bungalow reservation system with spring rest back end and flutter front end.
In this I want to get a list of bungalows.
So I decided to make a method to get the list of bungalows in a method using HttpService class that I made to handle the rest end points, That method is getBungalows() method.
Then I called this method by overriding initstate().
But the problem is that before my initstate() is completed. my build method starts.
To prove this I printed two lines 'print' and 'print build' as I thought I get 'print build' first. what am I doing wrong here. Please help.
Method to retrieve data from rest back end
When this happened I first checked this method but this works fine and return the desired result.
Future<List<Bungalow>> getBungalows() async {
Uri uri = Uri.parse('$url/bungalows/');
http.Response response = await http.get(uri);
if (response.statusCode == 200) {
List<Bungalow> bungalows = List<Bungalow>.from(
json.decode(response.body).map((x) => Bungalow.fromJson(x)));
// print(bungalows.first.address + 'asafafasfafdfgfgarfgargafvfrvaerg');
return bungalows;
} else {
throw 'Unable to retrieve data';
}
}
Code of the HomeScreen
class _HomeScreenState extends State<HomeScreen> {
HttpService httpService = HttpService();
late List<Bungalow> bungalows;
bool isLoggedIn = false;
User? user;
void getBungalows() async {
bungalows = await httpService.getBungalows();
print('done');
}
#override
Widget build(BuildContext context) {
if (widget.user != null) {
isLoggedIn = true;
user = widget.user;
}
print('done build');
return Scaffold(
backgroundColor: Colors.white,
body: Column(
children: [
Text(isLoggedIn ? user!.userDetail.username : 'No login'),
// TextButton(
// onPressed: () {
// setState(() {
// getBungalows();
// print(bungalows.first.address);
// });
// },
// child: Text('click'))
],
),
);
}
#override
void initState() {
getBungalows();
}
}
Console Output
I/flutter (22248): done build
I/flutter (22248): done
It is behaving correctly, initState function is not async and method getBungalows() is called in parallel.
You should either use setState in getBungalows, or add a listener, or use the then keyword, or use StreamBuilder.
Check this: https://stackoverflow.com/a/54647682/305135
void _myMatches() {
if (SignUp.userUid != null) {
FirebaseFirestore.instance
.collection("posts")
.where(
'owner id',
isEqualTo: SignUp.userUid,
)
.where("User Id", isNotEqualTo: [])
.where("rental status", isEqualTo: false)
.get()
.then((value) {
value.docs.forEach((result) {
print(result.data());
});
});
} else {
FirebaseFirestore.instance
.collection("posts")
.where(
'owner id',
isEqualTo: Loginpage.userUid,
)
.where("User Id", isNotEqualTo: [])
.where("rental status", isEqualTo: false)
.get()
.then((value) {
value.docs.forEach((result) {
print(result.data());
});
});
}
}
}
Hi, I am using flutter and firestore to write a program. My function that reads the data is as follows:(mentioned above)
which i call when a specific button is pressed. This leads to the data being read from firestore to be printed on the console. What do I do to display it on my emulator. How do I wrap this data in a widget so I can display it on the screen on whichever page i want?
The key is to use a FutureBuilder to render UI after you get the data, and show loading before that. Then inside builder of FutureBuilder, use ListView and ListTile(or anything you like) to render list items.
A minimum example might looks like this:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MaterialApp(
home: App(),
));
}
class App extends StatelessWidget {
Future<QuerySnapshot<Map<String, dynamic>>> getData() {
// Handle any data retrieval logic you want
return FirebaseFirestore.instance.collection('posts').get();
}
#override
Widget build(BuildContext context) {
return FutureBuilder<QuerySnapshot<Map<String, dynamic>>>(
// plug your future snapshot here
future: getData(),
builder: (context, snapshot) {
// Check loading
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
// Check error
final queryData = snapshot.data;
if (snapshot.hasError || queryData == null) {
return Icon(Icons.error);
}
return Scaffold(
// Use ListView.builder to render only visible items
body: ListView.builder(
itemCount: queryData.docs.length,
itemBuilder: (context, index) {
// Get data inside docs
final docData = queryData.docs[index].data();
return ListTile(
title: docData['title'],
subtitle: docData['subtitle'],
);
},
),
);
});
}
}
I am creating a chat app that uses Firebase for the backend.
Whenever I restart the app, it goes to the welcome screen where users have to login/register, every single time.
If you log in, then close the app without logging out, it still goes back to the login screen. I have added a method in the main method to check:
void getCurrentUser() async {
FirebaseAuth.instance.onAuthStateChanged.listen((firebaseUser) {
print(firebaseUser);
_user = firebaseUser;
});
}
So, if _user is null, then go to welcome page, otherwise go straight into the chat screen of the app:
Widget build(BuildContext context) {
getCurrentUser();
return MaterialApp(
initialRoute: _user == null ? '/' : '/chat',
routes: {
'/': (context) => WelcomeScreen(),
'/login': (context) => LoginScreen(),
'/registration': (context) => RegistrationScreen(),
'/chat': (context) => ChatScreen(),
},
);
}
Why doesn't it work? I have Googled this problem for a while and earlier I was using auth.currentUser(), which could have been the problem, but now I'm using a listener and it still doesn't work.
var user await FirebaseAuth.instance.currentUser();
if (user != null) {
Navigator.of(context).pushNamed("routeName");
}
Wrap the above code in an async function that takes BuildContext and call in the build method before the return statement.