Save Data to Firebase Realtime Database in Flutter - firebase

In my code the user is Sign in through phone authentication.After login i want to get the user id of the user and add it to the Firebase database along with its personal details.
But when i have done this the Path in database is direct rather than it should be via first UserId then under that the personal details.
I have also provide the image with the database output.
Code:
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/cupertino.dart';
import 'package:udharibook/Screens/SignInPage.dart';
import 'package:udharibook/Screens/dashboard.dart';
class AuthService {
String UserId ='';
final DBRef = FirebaseDatabase.instance.reference().child('Users');
final FirebaseAuth _auth = FirebaseAuth.instance;
handleAuth(){
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext, snapshot){
if(snapshot.hasData){
getCurrentUser();
writeData();
return DashboardPage();
}
else {
return SignIn();
}
},
);
}
getCurrentUser() async{
final FirebaseUser user = await _auth.currentUser();
final uid = user.uid;
// Similarly we can get email as well
//final uemail = user.email;
UserId = uid;
print('User ID: '+UserId);
//print(uemail);
}
void writeData(){
DBRef.child(UserId).set({
'id':'ID1',
'Name':'Mehul Jain',
'Phone':'8856061841'
});
}
signOut(){
FirebaseAuth.instance.signOut();
}
signIn(AuthCredential authCreds){
FirebaseAuth.instance.signInWithCredential(authCreds);
}
signInWithOTP(smsCode,verId){
AuthCredential authCreds = PhoneAuthProvider.getCredential(
verificationId: verId,
smsCode: smsCode
);
signIn(authCreds);
}
}
Database Image

Try the following:
void writeData() async{
final FirebaseUser user = await _auth.currentUser();
final uid = user.uid;
DBRef.child(uid).set({
'id':'ID1',
'Name':'Mehul Jain',
'Phone':'8856061841'
});
}
Remove the getCurrentUser() method and retrieve the uid in writeData()

getCurrentUser() method is an async method, so it not sure that your userId will be having value before the writeData() method is called.
so what you can do is call writeData() after completion of getCurrentUser() method i.e. :
import 'package:firebase_auth/firebase_auth.dart';
import 'package:firebase_database/firebase_database.dart';
import 'package:flutter/cupertino.dart';
import 'package:udharibook/Screens/SignInPage.dart';
import 'package:udharibook/Screens/dashboard.dart';
class AuthService {
String UserId ='';
final DBRef = FirebaseDatabase.instance.reference().child('Users');
final FirebaseAuth _auth = FirebaseAuth.instance;
handleAuth(){
return StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (BuildContext, snapshot){
if(snapshot.hasData){
getCurrentUser();
//call writeData() from inside the above method
return DashboardPage();
}
else {
return SignIn();
}
},
);
}
getCurrentUser() async{
final FirebaseUser user = await _auth.currentUser();
final uid = user.uid;
// Similarly we can get email as well
//final uemail = user.email;
UserId = uid;
print('User ID: '+UserId);
//print(uemail);
//Here you add the method calling
writeData();
}
void writeData(){
DBRef.child(UserId).set({
'id':'ID1',
'Name':'Mehul Jain',
'Phone':'8856061841'
});
}
signOut(){
FirebaseAuth.instance.signOut();
}
signIn(AuthCredential authCreds){
FirebaseAuth.instance.signInWithCredential(authCreds);
}
signInWithOTP(smsCode,verId){
AuthCredential authCreds = PhoneAuthProvider.getCredential(
verificationId: verId,
smsCode: smsCode
);
signIn(authCreds);
}
}

Related

Google authentication problem (Flutter and Firebase)

I am flutter-newbie and I have one problem: I want to add Google authentication to my flutter app with firebase. This is my code for login screen:
import 'package:PixiCall/resources/firebase_repository.dart';
import 'package:PixiCall/screens/home_screen.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
class LoginScreen extends StatefulWidget {
#override
_LoginScreenState createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
FirebaseRepository _repository = FirebaseRepository();
#override
Widget build(BuildContext context) {
return Scaffold(
body: loginButton(),
);
}
Widget loginButton() {
return FlatButton(
padding: EdgeInsets.all(35),
child: Text(
'Login',
style: TextStyle(
fontSize: 25,
fontWeight: FontWeight.w900,
letterSpacing: 1.2,
),
),
onPressed: () => performLogin,
);
}
void performLogin() {
_repository.signIn().then((User user) {
if (user != null) {
authenticateUser(user);
} else {
print('There was an error');
}
});
}
void authenticateUser(User user, BuildContext context) {
_repository.authenticateUser(user).then((isNewUser) {
if (isNewUser) {
_repository.addDataToDb(user).then((value) {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) {
return HomeScreen();
}));
});
} else {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) {
return HomeScreen();
}));
}
});
}
}
I have this error here:
lib/screens/login_screen.dart:39:25: Error: Too few positional arguments: 2 required, 1 given.
authenticateUser(user);
What is the other parameter which I have to add?
Also I think that I have one more mistake in other file. This is the code from other file:
import 'package:PixiCall/models/user.dart';
import 'package:PixiCall/utils/utilities.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:google_sign_in/google_sign_in.dart';
class FirebaseMethods {
final FirebaseAuth _auth = FirebaseAuth.instance;
GoogleSignIn _googleSignIn = GoogleSignIn();
static final FirebaseFirestore firestore = FirebaseFirestore.instance;
//user class
User1 user = User1();
Future<User> getCurrentUser() async {
User currentUser;
currentUser = await _auth.currentUser;
return currentUser;
}
Future<User> signIn() async {
GoogleSignInAccount _signInAccount = await _googleSignIn.signIn();
GoogleSignInAuthentication _signInAuthentication =
await _signInAccount.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: _signInAuthentication.accessToken,
idToken: _signInAuthentication.idToken,
);
User user = await _auth.signInWithCredential(credential);
return user;
}
Future<bool> authenicateUser(User user) async {
QuerySnapshot result = await firestore
.collection('users')
.where('email', isEqualTo: user.email)
.get();
final List<DocumentSnapshot> docs = result.docs;
//if user is registered then length of list > 0 or else less than 0
return docs.length == 0 ? true : false;
}
Future<void> addDataToDb(User currentUser) async {
String username = Utils.getUsername(currentUser.email);
user = User1(
uid: currentUser.uid,
email: currentUser.email,
name: currentUser.displayName,
profilePhoto: currentUser.photoURL,
username: username);
firestore.collection('users').doc(currentUser.uid).set(user.toMap(user));
}
}
This is the mistake in console:
lib/resources/firebase_methods.dart:32:17: Error: A value of type 'UserCredential' can't be assigned to a variable of type 'User'.
Sorry if I confused you, as I said, I am newbie. If you want any other informations please ask here.
For the first mistake , you defined a function void authenticateUser(User user, BuildContext context) so when you use it, it expects 2 arguments a User type object and a BuildContext object
Then you are calling this function in
void performLogin() {
_repository.signIn().then((User user) {
if (user != null) {
authenticateUser(user);
} else {
print('There was an error');
}
});
}
You are passing only the User object, missing the BuildContext
For GoogleSingIn this was my solution:
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn _googleSignIn = GoogleSignIn();
//create user object based on FB json
User _userFromFirebaseUser(FirebaseUser user) {
return user != null
? User(uid: user.uid, emailVerified: user.isEmailVerified)
: null;
}
//auth change user stream
Stream<User> get user {
return _auth.onAuthStateChanged.map(_userFromFirebaseUser);
}
Future<User> singInUsingGoogle() async {
int age;
String email;
String name;
String lastname;
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
idToken: googleAuth.idToken, accessToken: googleAuth.accessToken);
final result = await _auth.signInWithCredential(credential);
FirebaseUser user = result.user;
print(result.user.providerData);
await DatabaseService(uid: user.uid).createUserData(
//user info
)

How to check whether the user email id exists?

I am using the below code for google sign in flutter app with firebase, which is working successfully.
How should I check that whether the email id used is already existing in the firebase authentication?
This I need to ensure that I update the user information in the firestore database accordingly.
Future<User> _handleSignIn() async {
User user;
bool userSignedIn = await _googleSignIn.isSignedIn();
setState(() {
isUserSignedIn = userSignedIn;
});
if (isUserSignedIn) {
user = _auth.currentUser;
}
else {
final GoogleSignInAccount googleUser = await _googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth = await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
user = (await _auth.signInWithCredential(credential)).user;
userSignedIn = await _googleSignIn.isSignedIn();
setState(() {
isUserSignedIn = userSignedIn;
});
}
return user;
}
Please guide me for this
There is a Generic exception related to Firebase Authentication. which is the class FirebaseAuthException. It comes with codes related to errors including email already exists so you would write something like:
try {
//your signIn method here
//i assumed handleSignIn
handleSignIn();
} on FirebaseAuthException catch (e) {
//exception that occurs if e-mail already exists
if (e.code == 'email-already-in-use') {
// the rest of your code here
}
}
Then this is the method you should use I have used to solve my problem
this is my auth.dart file
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:gfd_official/User/User.dart';
import 'package:gfd_official/services/database.dart';
import 'package:google_sign_in/google_sign_in.dart';
String photoUrl;
class AuthService {
final FirebaseAuth _auth = FirebaseAuth.instance;
// create user obj based on firebase user
Userdat _userFromFirebaseUser(User user) {
return user != null ? Userdat(uid: user.uid) : null;
}
// auth change user stream
Stream<Userdat> get user {
return _auth
.authStateChanges()
//.map((FirebaseUser user) => _userFromFirebaseUser(user));
.map(_userFromFirebaseUser);
}
Future signInWithGoogle() async {
GoogleSignIn googleSignIn = GoogleSignIn();
final acc = await googleSignIn.signIn();
final auth = await acc.authentication;
final credential = GoogleAuthProvider.credential(
accessToken: auth.accessToken, idToken: auth.idToken);
try {
final res = await _auth.signInWithCredential(credential);
User user = res.user;
photoUrl = user.photoURL;
UserHelper.saveUser(user);
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
Future logOut() {
try {
GoogleSignIn().signOut();
return _auth.signOut();
} catch (e) {
print(e.toString());
return null;
}
}
}
class UserHelper {
static FirebaseFirestore _db = FirebaseFirestore.instance;
static saveUser(User user) async {
Map<String, dynamic> userData = {
"name": user.displayName,
"email": user.email,
"role": "basic",
};
try {
final userRef = _db.collection("users").doc(user.uid);
if ((await userRef.get()).exists) {
await userRef.update({});
} else {
await _db
.collection("users")
.doc(user.uid)
.set(userData, SetOptions(merge: true));
}
} catch (e) {
print(e.toString());
}
}
}
In this the save user function will create database for new user and if user already exist then it will let it remain same as it is.
I am creating a document for every user with his/her uid.

Flutter App stuck at splash screen not moving to login page or homepage

I tried to add splash screen to add splash screen to my app but it gets stuck at splash screen itsalf and does not move to next screens
I have added the code here:-
void main() => runApp(MyApp());
class MyApp extends StatefulWidget {
#override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
#override
void initState() {
super.initState();
getUserInfo();
}
Future getUserInfo() async {
await getUser();
setState(() {});
print(uid);
navigateUser();
}
navigateUser()
{
if(uid!=null && authSignedIn != false)
{
Timer(Duration(seconds: 2),
()=>Navigator.pushReplacementNamed(context, "/toprofilepage")
);
}
else{
Timer(Duration(seconds: 2),
()=>Navigator.pushReplacementNamed(context, "/tologinpage")
);
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Login',
initial route:'/',
routes: {
'/toprofilepage':(context)=>FirstScreen(),
'/tologinpage':(context)=>LoginPage(),
},
home: Scaffold(
body: Center(
child: Text("Saraswat",style: TextStyle(fontSize: 40,fontWeight: FontWeight.bold,fontStyle: FontStyle.italic),),
),
)
);
}
}
I am getting this following message in console also:-
E/flutter ( 5947): [ERROR:flutter/lib/ui/ui_dart_state.cc(166)] Unhandled Exception: Navigator operation requested with a context that does not include a Navigator.
Code for sign in:-
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = GoogleSignIn();
bool authSignedIn;
String uid;
String name;
String imageUrl;
Future getUser() async {
// Initialize Firebase
await Firebase.initializeApp();
SharedPreferences prefs = await SharedPreferences.getInstance();
bool authSignedIn = prefs.getBool('auth') ?? false;
final User user = _auth.currentUser;
if (authSignedIn == true) {
if (user != null) {
uid = user.uid;
name = user.displayName;
imageUrl = user.photoURL;
}
}
}
Future<String> signInWithGoogle() async {
// Initialize Firebase
await Firebase.initializeApp();
final GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn();
final GoogleSignInAuthentication googleSignInAuthentication = await googleSignInAccount.authentication;
final AuthCredential credential = GoogleAuthProvider.credential(
accessToken: googleSignInAuthentication.accessToken,
idToken: googleSignInAuthentication.idToken,
);
final UserCredential userCredential = await _auth.signInWithCredential(credential);
final User user = userCredential.user;
if (user != null) {
// Checking if email and name is null
assert(user.uid != null);
assert(user.displayName != null);
assert(user.photoURL != null);
uid = user.uid;
name = user.displayName;
imageUrl = user.photoURL;
assert(!user.isAnonymous);
assert(await user.getIdToken() != null);
final User currentUser = _auth.currentUser;
assert(user.uid == currentUser.uid);
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('auth', true);
return 'Google sign in successful, User UID: ${user.uid}';
}
return null;
}
void signOutGoogle() async {
await googleSignIn.signOut();
await _auth.signOut();
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setBool('auth', false);
uid = null;
name = null;
imageUrl = null;
print("User signed out of Google account");
}
I tried a lot of things but did not get any solution pls help!.Should I place the navigator function anywhere else or is there some other error pls help!.
Try doing the flow conditions in initialRoute only
Since the firebase has updated the way we check the user is logged in or not. Its not a async task so you can use directly in the MyApp class.
initialRoute: FirebaseAuth.instance.currentUser != null
? HomeScreen.route_name
: AuthScreen.route_name
Or you can use the listener for auth change
FirebaseAuth.instance.onAuthStateChanged.listen((firebaseUser) {
// do whatever you want based on the firebaseUser state
});
so when the auth is changed it will re-direct it to the page you want to, like this
home: StreamBuilder(
stream: FirebaseAuth.instance.onAuthStateChanged,
builder: (streamContext, userSnapshot) {
if (userSnapshot.connectionState == ConnectionState.waiting)
return SplashScreen();
if (userSnapshot.hasData) {
return HomeScreen();
}
return AuthScreen();
},
),

Using Flutter ChangeNotifierProvider for authentication

I am using ChangeNotifierProvider to handle app state for my flutter app.
My main.dart file
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:flutter_app/services/auth.dart';
Future<void> main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
class MyApp extends StatelessWidget {
#override
Widget build(BuildContext context) {
return ChangeNotifierProvider<AuthService>(
create: (context) => AuthService(), // initializes auth.dart file here
child: MaterialApp(
initialRoute: '/',
onGenerateRoute: RouteGenerator.generateRoute,
debugShowCheckedModeBanner: false,
title: '...',
home: WelcomeScreen(),
));
}
}
I am trying to change the value of the uid field here in auth.dart
auth.dart file
import 'package:flutter/material.dart';
import 'package:firebase_auth/firebase_auth.dart';
class AuthService with ChangeNotifier {
final FirebaseAuth _auth = FirebaseAuth.instance;
String uid = ""; //initial value of uid
UserM _userFromFirebaseUser(User user) {
return user != null ? UserM(uid: user.uid) : null;
}
Stream<UserM> get user {
return null;
}
Future signInWithEmail(String email, String password) async {
try {
UserCredential result = await _auth.signInWithEmailAndPassword(
email: email, password: password);
User user = result.user;
uid = user.uid; //trying to change uid here
print('user id: $uid'); //new value is printed here
notifyListeners();
return _userFromFirebaseUser(user);
} catch (e) {
print(e.toString());
return null;
}
}
uid changes but then when i try to get the new value in another file, i still get the old value which is the empty string declared on top.
This is how i am trying to access it
final auth = Provider.of<AuthService>(context, listen: true).uid;
print(auth);
What am I doing wrong please?
I don't get why there is a need to use Provider to get the uid of a user in Firebase. You can get the uid synchronously by doing currentUser.uid.
Here is an example:
print(FirebaseAuth.instance.currentUser.uid);

error: The operator '[]' isn't defined for the type 'DocumentSnapshot'

The code was working fine , but when I updated my dependencies I started getting the following errors:
error: The operator '[]' isn't defined for the type 'DocumentSnapshot'
error: The expression doesn't evaluate to a function, so it can't be invoked. (invocation_of_non_function_expression
here is the code:
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:za/pages/signup.dart';
import 'home.dart';
class Login extends StatefulWidget {
#override
_LoginState createState() => _LoginState();
}
class _LoginState extends State<Login> {
final GoogleSignIn googleSignIn = new GoogleSignIn();
final FirebaseAuth firebaseAuth = FirebaseAuth.instance;
final _formKey = GlobalKey<FormState>();
TextEditingController _emailTextController = TextEditingController();
TextEditingController _passwordTextController = TextEditingController();
SharedPreferences preferences;
bool loading = false;
bool islogedin = false;
bool hidepass = true;
#override
void initState() {
super.initState();
isSignedIn();
}
void isSignedIn() async {
setState(() {
loading = true;
});
await firebaseAuth.currentUser().then((user){
if (user!=null){
setState(() => islogedin= true);
}
});
if (islogedin == true) {
Navigator.pushReplacement(
context, MaterialPageRoute(builder: (context) => HomePage()));
}
setState(() {
loading = false;
});
}
Future handleSignIn() async {
preferences = await SharedPreferences.getInstance();
setState(() {
loading = true;
});
final GoogleSignInAccount googleUser = await googleSignIn.signIn();
final GoogleSignInAuthentication googleAuth =
await googleUser.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleAuth.accessToken,
idToken: googleAuth.idToken,
);
User firebaseUser =
(await firebaseAuth.signInWithCredential(credential)).user;
if (firebaseUser != null) {
final QuerySnapshot result = await Firestore.instance
.collection("users")
.where("id", isEqualTo: firebaseUser.uid)
.get();
final List<DocumentSnapshot> documents = result.docs;
if (documents.length == 0) {
// insert the user to our collection
FirebaseFirestore.instance
.collection("users")
.doc(firebaseUser.uid)
.set({
"id": firebaseUser.uid,
"username": firebaseUser.displayName,
"profilePicture": firebaseUser.photoURL
});
await preferences.setString("id", firebaseUser.uid);
await preferences.setString("username", firebaseUser.displayName);
await preferences.setString("photoUrl", firebaseUser.displayName);
} else {
await preferences.setString("id", documents[0].data['id']);
await preferences.setString("username", documents[0].data['username']);
await preferences.setString("photoUrl", documents[0].data['photoUrl']);
}
Fluttertoast.showToast(msg: "Login was successful");
setState(() {
loading = false;
});
} else {
Fluttertoast.showToast(msg: "Login failed");
}
}
DocumentSnapshot doesn't have [] operator, as error says explicitly. It has data field, which has.
Replace this code:
await preferences.setString("id", documents[0]['id']);
await preferences.setString("username", documents[0]['username']);
await preferences.setString("photoUrl", documents[0]['photoUrl']);
With this:
await preferences.setString("id", documents[0].data['id']);
await preferences.setString("username", documents[0].data['username']);
await preferences.setString("photoUrl", documents[0].data['photoUrl']);
According to the source:
/// Returns the current [User] if they are currently signed-in, or `null` if
/// not.
///
/// You should not use this getter to determine the users current state,
/// instead use [authStateChanges], [idTokenChanges] or [userChanges] to
/// subscribe to updates.
User get currentUser {
if (_delegate.currentUser != null) {
return User._(this, _delegate.currentUser);
}
return null;
}
Try await preferences.setString("username", documents[0].data()['username']);
Btw, did you update your firebase packages? if yes, follow this link to check for the updated changes https://firebase.flutter.dev/docs/migration/
fixed 1, i changed
await preferences.setString("id", documents[0]['id']);
await preferences.setString("username", documents[0]['username']);
await preferences.setString("photoUrl", documents[0]['photoUrl']);
to
await preferences.setString("id", documents[0].data()['id']);
await preferences.setString("username", documents[0].data()['username']);
await preferences.setString("photoUrl", documents[0].data()['photoUrl']);
aparently, Getting a snapshots data via the data getter is now done via the data() method.
I'm only left with error
on
await firebaseAuth.currentUser().then((user){
if (user!=null){
setState(() => islogedin= true);
}
according to the documentation(https://firebase.flutter.dev/docs/migration/), Accessing the current user via currentUser() is now synchronous via the currentUser getter.

Resources