I'm experimenting with firebase on flutter. Found this initialRoute property in the documentation. I'm using it like this:
#override
Widget build(BuildContext context) {
// TODO: implement build
return new MaterialApp(
color: Colors.yellow,
routes: <String, WidgetBuilder>{
"/login":(BuildContext context) => new SignIn(),
},
initialRoute: "/login",
home: DefaultTabController(
length: 4,
...
In this SignIn Activity is the GoogleLogin and I was initially using Navigator to switch between the two activities. I want to know what triggers will be used in the SignIn activity to switch between screens? Do I still use a Navigator?
In the SignIn activity I have a proceed button to perform this switch:
new MaterialButton(onPressed: (){
Navigator.pop(context);
Navigator.push(context,
MaterialPageRoute(builder: (context)=> TabLayoutDemo.fromTabLayoutDemo(_auth, _googleSignIn)));
},
color: Colors.green,
splashColor: Colors.greenAccent,
child: const Text("Proceed"),
),
But what this does is launch the SignIn screen again after login completes.
You can still use Navigator on your SignIn Activity to navigate between screens. However, you may need to check if there's a user already signed-in to skip the SignIn page. To check the currently logged in user, you can use FirebaseAuth.instance.currentUser.
Related
the whole purpose of this is that i want to signout the current user using app, immediately when i open firebase console and delete his account from Authentication tab.
i want the signout process to be done smoothly without any errors.
what i've tried so far:
in my main function():
runApp(MyApp());
and this is myApp class:
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
Provider<AuthService>(
create: (_) => AuthService(),
),
StreamProvider(
create: (context) => context.read<AuthService>().onAuthStateChanged,
),
],
child: MaterialApp(
debugShowCheckedModeBanner: false,
routes: <String, WidgetBuilder>{
'/home': (BuildContext context) => HomeController(),
'/signUp': (BuildContext context) => SignUpView(
authFormType: AuthFormType.signUp,
),
'/signIn': (BuildContext context) => SignUpView(
authFormType: AuthFormType.signIn,
),
'/addGig': (BuildContext context) => Home(passedSelectedIndex: 1),
},
home: HomeController(),
));
}
}
and this is the HomeController():
class _HomeControllerState extends State<HomeController> {
AuthService authService = locator.get<AuthService>();
#override
void initState() {
super.initState();
}
#override
Widget build(BuildContext context) {
final firebaseUserUid = context.watch<String>();
if (firebaseUserUid != null) {
return HomePage();
return MaterialApp(
debugShowCheckedModeBanner: false,
routes: <String, WidgetBuilder>{
'/home': (BuildContext context) => HomeController(),
'/signUp': (BuildContext context) => SignUpView(
authFormType: AuthFormType.signUp,
),
'/signIn': (BuildContext context) => SignUpView(
authFormType: AuthFormType.signIn,
),
'/addGig': (BuildContext context) => Home(passedSelectedIndex: 1),
},
theme: fyreworkTheme(),
builder: EasyLoading.init(),
home: Home(passedSelectedIndex: 0),
);
} else {
return StartPage();
}
}
}
in the build function of MyApp class, the provider is listening to this stream from the AuthService class:
Stream<String> get onAuthStateChanged => _firebaseAuth.onAuthStateChanged.map(
(FirebaseUser user) => user?.uid,
);
so far so good...when i start or restart the App...every thing works as intended...no probs.
what i wanted to achieve is that if i open the firebase console / Authentication tab and i know the identifier of a specific user i want to delete his account and i delete it.
i thought that would signout that user from the app and navigates him to StartPage()..as the whole app is listening to onAuthStateChanged stream from the Provider.
but that didn't achieve what i was trying to do.
how can i sign out a specific user from the App after i delete his data from Authentication tab in firebase console ?
i hope i've described the problem and the desired goal well....
any help would be much appreciated.
Whenever the users sends any request to the firebase, revalidate the user to ensure that the user exists in the database or not. You may only if the user exists in the tables or not (not check validity of the tokens). Incase it does not redirect the user to login page. If it does then the user must have authenticated successfully before. This is a very simple solution.
For a complex one, you could use triggers in firebase and send a push notification to the app on deletion of the record of the user. Such that whenever the app receives the push notification that tells it to reauthenticate, the app should assume that the authentication data has been deleted or moved and would redirect the user to the login screen.
As an alternative approach, you could use firebase functions to trigger on deleteUser to delete the data server-side... this has the benefit of smaller-client, plus reduces the risk of some 'stuck' conditions should either the data or user delete fail.
exports.onAuthUserDelete = functions.auth.user().onDelete(onAuthUserDelete);
async function onAuthUserDelete(user: admin.auth.UserRecord) {
await admin.firestore().doc(`/users/${user.uid}`).delete();
functions.logger.info(`User firestore record deleted: uid=${user.uid}, displayName=${user.displayName}`);
}
I am trying to hide a Container in my app when a user logs in, but my UI won't change even if I use onAuthStateChanged or setState()
I am using a StreamBuilder in my main.dart and I am using a dependency that is similar to a Hamburger Menu (https://pub.dev/packages/kf_drawer) that passes a variable which decides the Visibility of my Container
return MaterialApp(
home: StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (_, snap) {
if (snap.connectionState == ConnectionState.active) {
if (snap.data != null) {
return DrawerMenu(firebaseUser: snap.data, visibleLogin: false);
} else {
return DrawerMenu(firebaseUser: null, visibleLogin: true);
}
} else {
return CircularProgressIndicator();
}
},
),
);
This is my code where the firebase user first passes to the DrawerMenu() and then to my Home(),
KFDrawerItem.initWithPage(
text: Text(
'Home',
style: TextStyle(color: Colors.white),
),
icon: Icon(Icons.settings, color: Colors.white),
page: Home(
firebaseUser: widget.firebaseUser,
visibleLogin: widget.visibleLogin,
),
),
This is the code for my Container, however my UI won't update as soon as my user logs in but only when I navigate through the other pages in my app, nor does the state of the app get saved when I re-open my app after quitting it.
//Simplified code for example purposes
Widget login(bool visibleLogin) {
return Visibility(
visible: visibleLogin,
child: Align(
alignment: Alignment.bottomCenter,
child: Container(),
),
);
}
I want to achieve something like this, the bottom login Container disappearing as soon as the user logs in.
BEFORE LOGGING IN - https://i.stack.imgur.com/BKfby.png
AFTER LOGGING IN - https://i.stack.imgur.com/5zMHB.png
Something might not be right with your stream, are you sure you are getting stream reacting to change if a stream is fine, you can then look to your hamburger it might somehow not working propper.
create a StreamSubscription from FirebaseAuth.instance.onAuthStateChanged in the initState() function inside the DrawerMenu
to setState((){}) of the DrawerMenu screen depending on the user state;
and remove the StreamBuilder from the MaterialApp
This was working initially and it just stopped and I cannot figure out where the problem lies.
I am working with streams on my flutter project in provider package. Streams are being emitted from services files and listening is happening on the widgets file. Firebase onAuthStateChanged stream is working but mine are not working.
I have alot of code in my files so am not going to post everything here.
I have a problem with AuthStatus stream
I tried subscribing to the stream on the widget class but it seems like no streams are getting emitted
MyApp(){
auth.authStateStream.listen((d){print("$d is data");});
}
This how firebase streams are getting emiited from services file
Stream<UserModel> get onAuthStateChanged{
return _firebaseAuth.onAuthStateChanged.map(_userFromFirebase);
}
I have a problem with AuthStatus stream. This was working initially
This is how AuthStatus stream is getting emmited from services file
//Services file
final StreamController<AuthStatus> _currentAuthStateController =
StreamController<AuthStatus>.broadcast();
Stream<AuthStatus> get authStateStream{
return _currentAuthStateController.stream;
}
void testStremas() {
//Stoast.setMessage("Test Message");
_currentAuthStateController.add(AuthStatus.ACTIVE);
}
This is how provider is litening to streams as a parent of the MaterialAPP widget
class MyApp extends StatelessWidget {
//I was trying if i my widget could subscribe to the stream
MyApp (){
auth.authStateStream.listen((d){print("$d is data");});
}
final ToastHelper toast = ToastHelper();
final ThemeHelper theme = ThemeHelper();
final AuthService auth = AuthService();
#override
Widget build(BuildContext context) {
return MultiProvider(
providers: [
StreamProvider<UserModel>.value(value: auth.onAuthStateChanged),
StreamProvider<ToastMessage>.value(value: toast.onNewMessage),
StreamProvider<AuthStatus>.value(
value: auth.authStateStream, initialData: AuthStatus.NONE),
],
child: MaterialApp(
title: Strings.appName,
theme: theme.darkThemeData(),
home: Loader(),
routes: {
'home': (context) => Home(),
},
debugShowCheckedModeBanner: false,
),
);
}
}
This is how the above method is getting called on a the widget on a click of a button
//Widgets file
onTap: () => auth.testStremas(),
The expected result should be when the AuthStatus change from the services file, The widgets should be notified via the provider package. Thanks in advance
Widget _body(BuildContext context) {
final AuthStatus _authStatus = Provider.of<AuthStatus>(context);
return Center(
child: Container(
constraints: BoxConstraints(maxWidth: 300),
child: SingleChildScrollView(
child: Center(
child: _authStatus == AuthStatus.ACTIVE
? Padding(
padding: const EdgeInsets.all(8.0),
child:CircularProgressIndicator(strokeWidth: 2,)
)
: _buildScreen(context),
)),
),
);
}
I'm not sure - try to change
final StreamController<AuthStatus> _currentAuthStateController =
StreamController<AuthStatus>.broadcast();
to
final StreamController<AuthStatus> _currentAuthStateController =
BehaviorSubject<AuthStatus>();
This BehaviorSubject from rxdart library https://pub.dev/packages/rxdart, so, you should import it. BehaviorSubject is keep last state of stream. You can read more here https://pub.dev/documentation/rxdart/latest/rx/BehaviorSubject-class.html
import 'package:rxdart/rxdart.dart';
The last few days I spend a lot of time to read through several SO-questions and tutorials. What I'm trying to achieve is, that a user of my flutter app can choose a firebase project and log in with email/password. After the login, obviously, the correct data of the corresponding database should be shown. And that is where I fail.
After a while of reading some sites and questions from SO, I went with the following site to get the first part of the login.
https://firebase.googleblog.com/2016/12/working-with-multiple-firebase-projects-in-an-android-app.html
After working through this article, I was able to successfully log in to my defined firebase projects.
How did I know that the login was successful? I compared the user-uids from the projects with the print statement from my app in the console. That was the prove my configuration for the non-default project is correct.
But now the main problem which I can't solve.
After the login, the data is always of the default firebase project from the google-service.json.
For state management, I choose the provider package, as they mentioned in the I/O '19. So inside my main.dart, I wrap the whole application with MultipleProvider:
Widget build(BuildContext context) {
return MultiProvider(
providers: [
ChangeNotifierProvider<LoginModel>(
builder: (_) => LoginModel(),
),
ChangeNotifierProvider<Auth>(
builder: (_) => Auth(),
),
],
child: MaterialApp(
title: 'Breaking News Tool',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: RootPage(),
),
);
}
The provided Auth class is a service that connects to firebase sdk and also configure non-default apps to create the needed firebase auth
abstract class BaseAuth {
getDefaultAuth();
getAbnAuth();
...
}
class Auth with ChangeNotifier implements BaseAuth {
...
Auth() {
_configureAbnApp();
_configureProdApp();
}
getDefaultAuth() {
_firebaseAuth = FirebaseAuth.instance;
}
getAbnAuth() {
_firebaseAuth = FirebaseAuth.fromApp(_abnApp);
}
_configureAbnApp() {
FirebaseOptions abnOptions = FirebaseOptions(
databaseURL: 'https://[project-id].firebaseio.com',
apiKey: 'AIzaSxxxxxxxxxxxxxxxx,
googleAppID: '1:10591xxxxxxxxxxxxxxxxxxx');
FirebaseApp.configure(name: 'abn_database', options: abnOptions)
.then((result) {
_abnApp = result;
});
}
...
}
After a log in the app redirects the user to the home_page (StatefulWidget). Here I use a snapshot of the database to show data.
_stream = Firestore.instance.collection(collection).snapshots();
...
Center(
child: Container(
padding: const EdgeInsets.all(10.0),
child: StreamBuilder<QuerySnapshot>(
stream: _stream,
builder:
(BuildContext context, AsyncSnapshot<QuerySnapshot> snapshot) {
if (snapshot.hasError)
return Text('Error: ${snapshot.error}');
switch (snapshot.connectionState) {
case ConnectionState.waiting:
return Text('Loading...');
default:
return ListView(
children: snapshot.data.documents
.map((DocumentSnapshot document) {
return CustomCard(
docID: document.documentID,
title: document[title],
message: document[message],
fromDate: document[fromDate],
endDate: document[endDate],
disableApp: document[disableApp],
);
}).toList(),
);
}
},
),
),
),
In the beginning, I only had one project to connect to and the data was correct. But now I successfully connect to another project with the correct user-uid, but the data is always from the default project which is defined by the google-service.json.
And at this point, I have no clue why this happens.
Did anyone have an advice or idea?
You create your _stream based on Firestore.instance, which will give you the default firebase app, as documented in the docs:
/// Gets the instance of Firestore for the default Firebase app.
static Firestore get instance => Firestore();
Therefore you always get the data from the default project.
To fix this you need to create your firestore using the app created by FirebaseApp.configure().
So replace:
_stream = Firestore.instance.collection(collection).snapshots();
with
_stream = Firestore(app: _abnApp).collection(collection).snapshots();
I'm writing a Flutter application which integrates Firebase Authentication.
The problem
I would like to integrate in the best and most optimal way possible the authentication checking if the user is authenticated in the moment the app is launched. If the user is authenticated the app opens the normal home page, otherwise the authentication page is shown. After the authentication the app should redirect the user to the normal home page. For obvious reasons the user mustn't have the possibility to tap the back button and go back to the authentication page.
What I've done so far
At the moment the application checks in the main() if the user is authenticated, and, if it is so, it creates a MaterialApp with, as home, the main page of the application. In that case, the '/' of the app is the home page. If it is not, the app creates a MaterialApp with, as home, the authentication screen. In that case, however, the '/' is the welcome screen, and so I can't use
Navigator.of(context).popUntil(ModalRoute.withName('/'))
(which, in fact, happens to be quite necessary and useful), because the '/' is not the home page, and, moreover, the user could tap the back button and get back to the welcome screen.
The question
What am I losing? Am I completely wrong, and there is a totally different way of doing what I want to do, or the base is correct? If so, how can I implement what I would like to?
Thanks in advance.
You're looking for Navigator.pushReplacement or Navigator.pushReplacementNamed.
https://docs.flutter.io/flutter/widgets/Navigator-class.html
https://docs.flutter.io/flutter/widgets/Navigator/pushReplacement.html
https://docs.flutter.io/flutter/widgets/Navigator/pushReplacementNamed.html
Here's a quick sample code.
class FirstScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
Text("First screen"),
RaisedButton(
child: Text("Go to second screen"),
onPressed: () => _goToSecondScreen(context),
),
],
);
}
_goToSecondScreen(context) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => SecondScreen()));
}
}
class SecondScreen extends StatelessWidget {
#override
Widget build(BuildContext context) {
return Column(
children: [
Text("Second screen"),
RaisedButton(
child: Text("Go to first screen"),
onPressed: () => _goToFirstScreen(context),
),
],
);
}
_goToFirstScreen(context) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => FirstScreen()));
}
}