Checking for Firebase uID then routing - flutter

So my main.dart looking like this, I just want to check if the user already loggedIn or not. If true then route him directly to Homescreen and passing the UID else to the SignIn screen.
But somehow im getting a black screen without any error. Why? the debug print statements are working...
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
//User logged in?
final FirebaseAuth auth = FirebaseAuth.instance;
final User? user = auth.currentUser;
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
FirebaseAuth.instance.authStateChanges().listen((User? user) {
if (user == null) {
print('User is currently signed out!');
MaterialPageRoute(builder: (context) => const SignIn());
} else {
String myUid = user.uid;
MaterialPageRoute(builder: (context) => HomeScreen(userId: myUid));
print('User is signed in!');
}
});
return const SizedBox.shrink(); //<-----here
}
}
Well my Code looking now like this:
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
//User logged in?
final FirebaseAuth auth = FirebaseAuth.instance;
//The stream for auth changee
Future<User?> data() async {
return FirebaseAuth.instance.currentUser;
}
final User? user = auth.currentUser;
class MyApp extends StatelessWidget {
MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
return StreamBuilder<User?>(
stream: FirebaseAuth.instance
.authStateChanges(), //FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
if (snapshot.connectionState == ConnectionState.active) {
if (user == null) {
print('User is currently signed out!');
Navigator.push(context,
MaterialPageRoute(builder: (context) => const SignIn()));
} else {
String myUid = user!.uid;
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => HomeScreen(
userId: myUid,
)));
}
}
return const CircularProgressIndicator();
});
}
}
Navigator operation requested with a context that does not include a Navigator.
The relevant error-causing widget was
StreamBuilder<User?>

You can't just insert a stream listener in the build method like that. The easiest way to do this, is to use a StreamBuilder which handles the stream for you. Similar to the example in the documentation on listening for Firestore updates that'd be something like:
StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User?> snapshot) {
if (snapshot.hasError) {
return const Text('Something went wrong');
}
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
if (user == null) {
print('User is currently signed out!');
return MaterialPageRoute(builder: (context) => const SignIn());
} else {
String myUid = user.uid;
return MaterialPageRoute(builder: (context) => HomeScreen(userId: myUid));
}
},

Related

I am unable to access document field from cloud firestore and show it in a statefulwidget class in flutter

I want to acess the uname field of the current loggedin user .
I added uname in the registration screen like this :
onPressed: () async {
try {
final newuser = await FirebaseAuth.instance
.createUserWithEmailAndPassword(
email: email ?? 'error',
password: password ?? 'error',
);
await FirebaseFirestore.instance
.collection('Users')
.add({' uname': username});
if (newuser != null) {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => home()),
);
}
} catch (e) {
print(e);
}
}
But I dont know how to acess it from another file or more specifically I want to acess it on the profile screen .
How can I acess the uname field from firestore in flutter?
You should call the document in the initState and set the value of string using setState
Code:
class ProfileScreen extends StatefulWidget {
const ProfileScreen({super.key});
#override
State<ProfileScreen> createState() => _ProfileScreenState();
}
class _ProfileScreenState extends State<ProfileScreen> {
String? name;
#override
void initState() {
FirebaseFirestore.instance
.collection('Users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.get()
.then((value) {
print(value.data()?[' uname'];
setState(() {
name = value.data()?[' uname'];
});
});
super.initState();
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child:
name != null ? Text(name!) : const CircularProgressIndicator()),
);
}
}
Note: This is not recommended ⚠
As future method is called inside the initState(){} it is heavily discouraged to do so as it slows down the process of building the widget and also not considered as a good practice.
Use FutureBuilder for this:
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: FutureBuilder<DocumentSnapshot<Map<String, dynamic>>>(
future: FirebaseFirestore.instance
.collection('users')
.doc(FirebaseAuth.instance.currentUser!.uid)
.get(),
builder: (_, snapshot) {
if (snapshot.hasError) return Text('Error = ${snapshot.error}');
if (snapshot.connectionState == ConnectionState.waiting) {
return const Text("Loading");
}
Map<String, dynamic> data = snapshot.data!.data()!;
return Text(data['fullName']); //👈 Your valid data here
},
)),
);
}
I think the better way to what you want is to set the document ID in the collection("Users") same as the uid of the user authenticated. So fetching details or in this case, uname will be easier.
For creating doc with docID same as user uid:
await FirebaseFirestore.instance
.collection('Users')
.doc(newUser.uid)
.add({' uname': username});
For fetching details:
final userData = await FirebaseFirestore.instance
.collection("Users")
.doc(FirebaseAuth.instance.currentUser!.uid)
.get();

Firebase check user already logged in and different screens

So in my main.dart i want to check if the user is logged in already then redirect the user to HomeScreen with his userID and if not redirect the user to SignIn page.
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
#override
Widget build(BuildContext context) {
// Get the firebase user
final firebaseUser = FirebaseAuth.instance.currentUser;
String id = firebaseUser!.uid;
// Assign widget based on availability of currentUser
if (firebaseUser != null) {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => HomeScreen(userId: id)));
print('User already logged in');
} else {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (context) => SignIn()));
print('User must SignIn');
}
}
}
I'm getting the error
"The body might complete normally, causing 'null' to be returned, but the return type, 'Widget', is a potentially non-nullable type.
Try adding either a return or a throw statement at the end."
This method should return a widget. In both the cases its only getting navigated, so you can write a return SizedBox.shrink() like
Widget build(BuildContext context) {
// Get the firebase user
final firebaseUser = FirebaseAuth.instance.currentUser;
String id = firebaseUser!.uid;
// Assign widget based on availability of currentUser
if (firebaseUser != null) {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => HomeScreen(userId: id)));
print('User already logged in');
} else {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (context) => SignIn()));
print('User must SignIn');
}
return SizedBox.shrink();//<-----here
}

Onboarding screen does not work correctly with auth Firebase in Flutter app

The logic of the application - onboading screen appears only once, after it goes to the authorization screen with Firebase. If the authorization is successful, we open the home page of the application.
But this doesn't work the first time you run the app. After hot restart app - the login is done and we are right back to the home page.
Is there any way to fix it? (And without onboarding this logic works)
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
final pref = await SharedPreferences.getInstance();
final showHome = pref.getBool('showHome') ?? false;
runApp(MyApp(showHome: showHome));
}
class MyApp extends StatelessWidget {
bool showHome;
MyApp({Key? key, required this.showHome}) : super(key: key);
#override
Widget build(BuildContext context) {
return MultiProvider(
...
child: MaterialApp(
home: StreamBuilder<User?>(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
} else if (snapshot.hasError) {
return errorMessage();
} else if (snapshot.hasData) {
return const BottomPage();
} else {
return showHome ? const LoginPage() : const Onboarding();
}
},
),
}
LoginPage()
try {
await FirebaseAuth.instance.signInWithEmailAndPassword(
email: _loginController.text.trim(),
password: _passwordContoller.text.trim(),
);
}
Onboarding()
onTap: () async {
final pref = await SharedPreferences.getInstance();
pref.setBool('showHome', true);
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const LoginPage()));
},

App does not navigate to a different page when user authenticates

The issue is that my app does not navigate to another page automatically when user logs in or out.
class MyApp extends StatelessWidget {
final Future<FirebaseApp> _initialization = Firebase.initializeApp();
#override
Widget build(BuildContext context) {
return FutureBuilder(
// Initialize FlutterFire:
future: _initialization,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
return StreamProvider<User>.value(
value: AuthService().user,
child: MaterialApp(home: Wrapper()),
);
}
return Center(child: CircularProgressIndicator());
},
);
}
}
class Wrapper extends StatelessWidget {
const Wrapper({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if (user != null) {
return MaterialApp(initialRoute: '/', routes: {
'/': (context) => Home(),
'/profile': (context) => Profile()
});
}
return MaterialApp(initialRoute: '/', routes: {
'/': (context) => Welcome(),
'/signup': (context) => SignUp(),
'/signin': (context) => SignIn()
});
}
}
When the app starts it does show the Welcome() page. Then i am able to navigate to the signup page by pressing a signup button as such
onPressed: () {Navigator.pushNamed(context, "/signup");}),
but then when the user signs up, the app doesn't automatically navigate to Home()
class AuthService {
FirebaseAuth auth = FirebaseAuth.instance;
User _userFromFirebaseUser(User user) {
return user != null ? User(id: user.uid) : null;
}
Stream<User> get user {
return auth.authStateChanges().map(_userFromFirebaseUser);
}
Future<String> signUp(email, password) async {
try {
UserCredential user = await auth.createUserWithEmailAndPassword(
email: email, password: password);
await FirebaseFirestore.instance
.collection('users')
.doc(user.user.uid)
.set({'name': email, 'email': email});
_userFromFirebaseUser(user.user);
} on FirebaseAuthException catch (e) {
return e.code;
} catch (e) {
return e;
}
return "";
}
}
I am not sure what the issue is. Any help is appreciated.
First of all you need 1 MaterialApp not 3, then try to debug signUp method maybe there is an erorr for instance signUp returns Future<String> but in catch block you are returning an Exception and finally I suggest you to use Cubit whenever you need to listen state changes to navigate.

Error: Class 'String' has no instance getter 'token'. I/flutter ( 3268): Receiver: "dc9e0de8fa2eaa917657e810db06aad2458e4f65"

I have been struggling with this problem for like two days. My social media app should save its state, when signed in so that when you leave the app and come back again it should start from the home page, not the sign in page. I have found that it is possible to do this with StreamBuilder and FutureBuilder. I have tried some things with FutureBuilder and I have some errors.
Below is how my main page looks like:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider(
create: (BuildContext context) => UserData(),
child: MaterialApp(
title: 'Curtain App',
debugShowCheckedModeBanner: false,
home: FutureBuilder(
future: SharedPreferencesHelper.getPrefs(),
builder: (BuildContext context, AsyncSnapshot<dynamic> snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return CircularProgressIndicator();
}
if (snapshot.hasData) {
Provider.of<UserData>(context).currentUserId =
snapshot.data.token;
return HomeScreen();
} else {
return LoginScreen();
}
},
),
),
);
}
}
class SharedPreferencesHelper {
static final String _tokenCode = "token";
static Future<String> getPrefs() async {
final SharedPreferences preferences = await SharedPreferences.getInstance();
return preferences.getString(_tokenCode) ?? "empty";
}
}
And this is my LoginPage submit btn code:
_submit() async {
if (_formKey.currentState.validate()) {
_formKey.currentState.save();
// logging in the user w/ Firebase
//AuthService.login(_email, _password);
var user = await DatabaseService.loginUser(_username, _password);
final data = json.decode(user);
SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
print("Hi ${data['username']}");
print("Status ${data['status']}");
print("Token ${data['token']}");
if (data['username'] != null) {
setState(() {
_message = "Hi ${data['username']}";
sharedPreferences.setString('token', data['token']);
});
Navigator.of(context).pushAndRemoveUntil(
CupertinoPageRoute(
builder: (context) => HomeScreen(),
),
(Route<dynamic> route) => false);
}
}
}
Any ideas on how to solve this ?
Just remove the .token from the line where the error occurs. snapshot.data already is the token.