Firebase Streambuilder is not changing login state after navigating - firebase

Here, i am trying to implement Firebase login and signup system. i am trying to change screen base on user login or not.
Basically, i want to show feed screen when user is login and when user is not login i want to show login scree. if i do login in login screen it is working fine, so i did not added that code here. but issue come when i navigate from login screen to sign up scree and even if i successfully sign up it is not showing me feed screen. When i hot reload it show me feed screen.
Moreover, i also make sure that it is reaching where i am changing screen by print in console.
Note: i know i can using function to change between login screen and signup screen, so i don't need Navigator, which will again work for me. but i want to know why after navigating using navigator it is not working.
class DeleteWidget extends StatefulWidget {
#override
_DeleteWidgetState createState() => _DeleteWidgetState();
}
class _DeleteWidgetState extends State<DeleteWidget> {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: StreamBuilder<FirebaseUser>(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext context, snapshot) {
print(snapshot.hasData);
print(snapshot.connectionState);
if (ConnectionState.active == snapshot.connectionState) {
print("object 1");
if (snapshot.hasData) {
print("object 2");
return Feed();
} else {
print("object 3");
return LoginScreen();
}
} else {
return LoginScreen();
}
}),
);
}
}
class LoginScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: RaisedButton(
child: Text("login"),
onPressed: () async {
Navigator.push(
context, MaterialPageRoute(builder: (context) => SignUp()));
},
),
),
),
);
}
}
class SignUp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: RaisedButton(
child: Text("Sign up"),
onPressed: () async {
await FirebaseAuth.instance.signInAnonymously();
},
),
),
),
);
}
}
class Feed extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Container(
child: RaisedButton(
child: Text("feed"),
onPressed: () async {
await FirebaseAuth.instance.signOut();
},
),
),
),
);
}
}

You can use the Provider Package to Listen if user is logged in and use a Wrapper to direct the user to the correct screen. If the user logs out at any stage, they will be automatically redirected to the Login Screen.
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
// This widget is the root of your application.
User _userFromFirebaseUser(FirebaseUser user) {
return user != null ? User(uid: user.uid) : null;
}
#override
Widget build(BuildContext context) {
return StreamProvider<User>.value(
value: FirebaseAuth.instance.onAuthStateChanged.map(_userFromFirebaseUser),
child: MaterialApp(
home: Wrapper(),
),
);
}
}
Wrapper
class Wrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<User>(context);
if(user == null) {
return LoginScreen();
} else {
return Feed();
}
}
}

Related

Provider in different route - Flutter

I'm new in Flutter development, i'm making an app with Firebase Auth, in where I'm using an Authentication Wrapper class that, if user is logged in, goes to Home Screen, else goes to SignIn Screen.
The problem is that, when I want to navigate to AuthWrapper, I get this error message in a red screen:
Error: Could not find the correct Provider<UserFirebaseModel> above this Builder Widget
This happens because you used a `BuildContext` that does not include the provider
of your choice. There are a few common scenarios:
- You added a new provider in your `main.dart` and performed a hot-reload.
To fix, perform a hot-restart.
- The provider you are trying to read is in a different route.
Providers are "scoped". So if you insert of provider inside a route, then
other routes will not be able to access that provider.
- You used a `BuildContext` that is an ancestor of the provider you are trying to read.
Make sure that Builder is under your MultiProvider/Provider<UserFirebaseModel>.
This usually happens when you are creating a provider and trying to read it immediately.
Here there are the most important classes of my code, where I think the problem is.
main.dart
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider.value(
value: Authentication().user,
initialData: UserFirebaseModel.initialData(),
child: MaterialApp(
title: 'AccessCity',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
fontFamily: 'Montserrat',
),
routes: getAppRoutes(),
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
return MaterialPageRoute(
builder: (BuildContext context) => HomeTempPage(),
);
},
),
);
}
}
authWrapper.dart
class AuthWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final user = Provider.of<UserFirebaseModel>(context);
print(user);
// ignore: unnecessary_null_comparison
if (user == null) {
return LoginPage();
} else {
return Home();
}
}
}
routes.dart
Map<String, WidgetBuilder> getAppRoutes() {
return <String, WidgetBuilder>{
// Home Temporal Page
'/': (BuildContext context) => HomeTempPage(),
// Components Pages
'generalBigButton': (BuildContext context) => GeneralBigButtonPage(),
'textEntryField': (BuildContext context) => TextEntryFieldPage(),
'secureTextEntryField': (BuildContext context) =>
SecureTextEntryFieldPage(),
'underlinedButton': (BuildContext context) => UnderlinedButtonPage(),
// Modules
'login': (BuildContext context) => LoginPage(),
'authWrapper': (BuildContext context) => AuthWrapper(),
};
}
homeTempPage.dart
const String _title = 'Home Temporal';
const String _goAccessCityButton = 'Ir a AccessCity';
const String _goAccessCityLabel = 'Ir a pantalla Login de la app';
class HomeTempPage extends StatefulWidget {
#override
_HomeTempPageState createState() => _HomeTempPageState();
}
class _HomeTempPageState extends State<HomeTempPage> {
final model = HomeTempModel();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(_title),
backgroundColor: mainBlue,
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SizedBox(height: 10),
MaterialButton(
child: Text(_goAccessCityButton),
color: Colors.blue,
textColor: Colors.white,
onPressed: () {
model.navigateToStart(context);
},
),
Text(_goAccessCityLabel),
SizedBox(height: 30),
Expanded(
child: Container(
child: ListView(
children: model.getComponents(context),
),
),
),
],
),
),
);
}
}
In this last class, the line
navigateToStart() method
goes to route 'authWrapper'.
UserFirebaseModel
class UserFirebaseModel {
final String id;
final String email;
UserFirebaseModel(this.id, this.email);
factory UserFirebaseModel.initialData() {
return UserFirebaseModel('', '');
}
}
Authentication class
class Authentication {
final FirebaseAuth _firebaseAuth = FirebaseAuth.instance;
// Create user based on User (ex FirebaseUser)
UserFirebaseModel? _userFromFirebaseUser(User? user) {
final _mail;
if (user != null) {
_mail = user.email;
if (_mail != null) {
return UserFirebaseModel(user.uid, _mail);
}
} else {
return null;
}
}
// Auth change user stream
Stream<UserFirebaseModel?> get user {
return _firebaseAuth.authStateChanges().map(_userFromFirebaseUser);
}
// Sign in with email and password
Future<String?> signIn({
required String email,
required String password,
}) async {
try {
await _firebaseAuth.signInWithEmailAndPassword(
email: email,
password: password,
);
print("Signed in");
return "";
} on FirebaseAuthException catch (e) {
return e.message;
}
}
User? getUser() {
try {
return _firebaseAuth.currentUser;
} on FirebaseAuthException {
return null;
}
}
}
Thanks for your help, i need to solve it!

Check authentication state using stream not working in Flutter

I'm using GoogleSignIn.
On my splash screen, I'm using StreamBuilder to check whether the user is logged in or not, and based on that data I'm trying to show the Login Screen or Home Screen.
When I uninstall and reinstall the app it shows the Login Screen for the first time. And then after it always shows me the Home Screen even if I logged out from the app.
Below is my code for Splash Screen:
class SplashScreen extends StatefulWidget {
#override
_SplashScreenState createState() => _SplashScreenState();
}
class _SplashScreenState extends State<SplashScreen> {
#override
Widget build(BuildContext context) {
SizeConfig().init(context);
return StreamBuilder(
stream: FirebaseAuth.instance.authStateChanges(),
builder: (BuildContext context, AsyncSnapshot<User> snapshot) {
if (snapshot.hasData) {
print(snapshot.hasData);
return AnimatedSplashScreen(
splashIconSize: SizeConfig.blockSizeVertical * 16,
splashTransition: SplashTransition.fadeTransition,
nextScreen: HomeScreen(),
splash: kLogoImage,
duration: 800,
);
} else {
print(snapshot.hasData);
return AnimatedSplashScreen(
splashIconSize: SizeConfig.blockSizeVertical * 16,
splashTransition: SplashTransition.fadeTransition,
nextScreen: LoginScreen(),
splash: kLogoImage,
duration: 800,
);
}
},
);
}
}
Below is my code for Home Screen:
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final GoogleSignIn googleSignIn = GoogleSignIn();
User firebaseUser;
signOut() async {
await googleSignIn.signOut();
Navigator.pushReplacementNamed(context, MyRoutes.loginScreen);
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Center(
child: Text('Hello'),
),
MaterialButton(
child: Text('Log out'),
color: Colors.red,
onPressed: () {
signOut();
})
],
),
);
}
}
Could it be that GoogleSignIn is not the same as Firebase?
Try replacing googleSignIn.signOut() with FirebaseAuth.instance.signOut() on your home screen.
I don't know why it doesn't,t work I think your code is correct but I have another way to do that you can use stream provider instead of stream builder
wrap the stream provider above your material app or cupirtinoapp :
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return StreamProvider.value(
value: firebaseauht.inistance.onauthchange,
child: MaterialApp(
home: Wholescreen(),
),
);
}
}
then call the provider in the wholescreen :
class Wholescreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
var provider = Provider.of<User>(context);
if (provider!=null) {
return AnimatedSplashScreen(
splashIconSize: SizeConfig.blockSizeVertical * 16,
splashTransition: SplashTransition.fadeTransition,
nextScreen: HomeScreen(),
splash: kLogoImage,
duration: 800,
} else {
return AnimatedSplashScreen(
splashIconSize: SizeConfig.blockSizeVertical * 16,
splashTransition: SplashTransition.fadeTransition,
nextScreen: LoginScreen(),
splash: kLogoImage,
duration: 800,
);
}
}
}
Change this:
signOut() async {
await googleSignIn.signOut();
Navigator.pushReplacementNamed(context, MyRoutes.loginScreen);
}
Into this:
signOut() async {
await googleSignIn.disconnect();
Navigator.pushReplacementNamed(context, MyRoutes.loginScreen);
}
According to the docs the disconnect method is what revokes the authentication/signs the user out.

Flutter Firebase Login not returning the right screen, until reload, how to programatically reload?

The following is the code for my main.dart, here I am trying to return the Login screen if the user is not currently logged in or the home screen if the user is logged in.
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AuthenticationService>(
create: (_) => AuthenticationService(FirebaseAuth.instance),
),
StreamProvider(
create: (context) =>
context.read<AuthenticationService>().authStateChanges,
)
],
child: MaterialApp(
title: 'SBR+',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primaryColor: kPrimaryColor,
scaffoldBackgroundColor: Colors.white,
),
home: AuthenticationWrapper(),
),
);
}
}
class AuthenticationWrapper extends StatelessWidget {
#override
Widget build(BuildContext context) {
final firebaseUser = context.watch<User>();
print('here');
if (firebaseUser != null) {
print('Logged In!');
return HomeScreen();
}
return LoginScreen();
}
}
But as the use logs in (and the 'Logged In!' does print), it does not change automatically to the home screen. Not until I do a hard reset... anyone knows how to fix this and make it go to the home screen automatically?
Thanks in advance
Use below code to check logged User:
FirebaseAuth _auth = FirebaseAuth.instance;
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
home: StreamBuilder<User>(
stream: _auth.userChanges(),
builder: (context, snapshot) => snapshot.hasData ? HomePage() : LoginPage(),
));
}
}

How can I update a Network Image in Flutter

I'm currently learning Flutter and I wanted to try out Network Requests and working with Futures.
I want to show a random image from unsplash.com using their API and I want to change the image every time I press a certain button.
I tried implementing a function to change the image, but it doesn't work.
My code looks like this:
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: RandomImage(),
);
}
}
class RandomImage extends StatefulWidget {
#override
_RandomImageState createState() => _RandomImageState();
}
class _RandomImageState extends State<RandomImage> {
static String imageUrl = 'https://source.unsplash.com/random/300x200';
Future _imgFuture = http.get(imageUrl);
void _changeImage() async {
_imgFuture = http.put(imageUrl);
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: (Text('Hello')),
),
body: Center(
child: Column(
children: [
Spacer(),
FutureBuilder(
future: _imgFuture,
builder: (context, snapshot) {
if (snapshot.hasError) {
return Text('Oops, there was an error');
} else if (snapshot.hasData) {
return Image.network(imageUrl);
} else {
return Text('No value yet');
}
},
),
RaisedButton(
child: Text('Change Image!'),
onPressed: () => setState(() {
_changeImage();
}),
),
Spacer(),
],
),
),
);
}
}
Actually, Image.network is keeping your image, for more detail to see It here. The solution for this issue is make a simple useless query to api, so the image will be identical differently in flutter.
RaisedButton(
child: Text('Change Image!'),
onPressed: () => setState(() {
// _changeImage();
imageUrl="https://source.unsplash.com/random/300x200?v=${DateTime.now().millisecondsSinceEpoch}";
}),
),
The image won't change If you call the api too frequently, you might want to add a timer to prevent user from clicking too often.
I think the problem is in the _changeImage() method, try replace http.put with http.get.

How to cancel StreamBuilder's assigned stream on navigating to different screen?

Code:
final ref = Firestore.instance.document('some/path');
class MainPage extends StatefulWidget {
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
Stream _stream;
#override
void initState() {
super.initState();
_stream = ref.snapshots().map((snapshot) {
// <--------------------------------------------------------------- line 1
return snapshot.data;
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
body: StreamBuilder(
stream: _stream,
builder: (_, snapshot) {
if (snapshot.hasData) {
return RaisedButton(
onPressed: () => Navigator.push(context, MaterialPageRoute(builder: (_) => Page2())),
child: Text("Navigate"),
);
}
return CircularProgressIndicator();
},
),
);
}
}
class Page2 extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Scaffold(
body: RaisedButton(
onPressed: () => ref.updateData({'key': "value"}), // <------ line 2
child: Text('Update data'),
),
);
}
}
This code is reproducible, no compile time error. Let me give you an overview.
There are 2 screen Screen1 and Screen2. On first screen, I have set up a StreamBuilder which listens for value provided using _stream. So far so good.
But when I navigate to Screen2 (see line 2), and update the server from it, the Screen1 snapshot gets built again (see line 1). How can I cancel that Stream so that I don't have to be worry about when Screen1 isn't in view?

Resources