**I build a chat app and I am trying to apply the cloud function to my app so every time user sends a message the other one receives a notification but I have a problem I send the message twice so that I can show both messages on chat screen and also when user add friend I add twice for both user **
// chat provider
class ChatScreenProvider with ChangeNotifier {
final _chatAuth = FirebaseFirestore.instance;
// static String? friendPhone;
Future<void> addMesseges({
BuildContext? context,
String? messages,
String? userId,
String? userName,
chatUserId,
String? userPhone,
required String? freindPhone,
}) async {
enter image description herefinal friendUserId = await FirebaseFirestore.instance
.collection(conUserCollectios)
.doc(userPhone)
.collection(conFriendCollection)
.doc(freindPhone)
.get();
// final chatId = friendUserId[conChatId];
_chatAuth
.collection(conUserCollectios)
.doc(userPhone)
.collection(conFriendCollection)
.doc(freindPhone)
.collection(conChatCollectios)
.doc()
.set(
{
conUserId: userId,
conChatMesseges: messeges,
conuserName: userName,
conChatCreatedAt: Timestamp.now(),
conChatUserId: friendUserId[conFriendPhone],
conFriendUserName: friendUserId[conFriendUserName] == null
? freindPhone
: friendUserId[conFriendUserName]
},
).then((value) {
_chatAuth
.collection(conUserCollectios)
.doc(freindPhone)
.collection(conFriendCollection)
.doc(userPhone)
.collection(conChatCollectios)
.doc()
.set(
{
conUserId: userId,
conChatMesseges: messeges,
conuserName: userName,
conChatCreatedAt: Timestamp.now(),
conChatUserId: friendUserId[conFriendPhone],
conFriendUserName: friendUserId[conFriendUserName] == null
? freindPhone
: friendUserId[conFriendUserName]
},
);
});
}
}
// add friend provider
class AddFriendProvider with ChangeNotifier {
final _friendAuth = FirebaseFirestore.instance;
Future<void> addFriends({
// required String? friendName,
required String? friendNumber,
required userPhone,
// required userName,
BuildContext? context,
}) async {
final friendUser = await FirebaseFirestore.instance
.collection(conUserCollectios)
.doc(friendNumber)
.get();
final currentUser = await FirebaseFirestore.instance
.collection(conUserCollectios)
.doc(userPhone)
.get();
final users =
await FirebaseFirestore.instance.collection(conUserCollectios).get();
final userList = users.docs.map((e) => e.data()).toList();
final element = userList.firstWhere(
(element) => element[conuserPhone] == friendNumber,
orElse: () => {},
);
if (element[conuserPhone] == userPhone || element.isEmpty) {
AwesomeDialog(
padding: EdgeInsets.all(10),
context: context!,
dialogType: DialogType.ERROR,
body: Column(children: [
element[conuserPhone] != userPhone
? Text(
'the user is not found',
style: TextStyle(color: Colors.black, fontSize: 20),
)
: Text(
'يعم ده انت ',
style: TextStyle(color: Colors.black, fontSize: 20),
),
SizedBox(height: 20),
element[conuserPhone] != userPhone
? Text(
'invite your friend \n to use our app',
style: TextStyle(color: Colors.red, fontSize: 15),
)
: Text(
'بلاش هزارك الرخم ده',
style: TextStyle(color: Colors.red, fontSize: 15),
),
]),
btnOkText: 'Cancel',
btnOkOnPress: () {},
).show();
} else {
// String? chatId = friendNumber! + userPhone;
_friendAuth
.collection(conUserCollectios)
.doc(currentUser[conuserPhone])
.collection(conFriendCollection)
.doc(friendNumber)
.set(
{
conFriendUserName: friendUser[conuserName],
conFriendPhone: friendUser[conuserPhone],
conuserPhone: currentUser[conuserPhone],
conuserName: currentUser[conuserName],
conUserImageUrl: friendUser[conUserImageUrl],
conFriendId: friendUser[conUserId],
// conChatId: chatId
},
).then((value) async {
final userDoc = FirebaseFirestore.instance
.collection(conUserCollectios)
.doc(userPhone)
.collection(conFriendCollection)
.doc();
// final data = userDoc.docs.map((e) => e.data()).toList();
print(userDoc);
// print(userDoc.docs.toString());
// print(userDoc.docChanges);
_friendAuth
.collection(conUserCollectios)
.doc(friendUser[conuserPhone])
.collection(conFriendCollection)
.doc(currentUser[conuserPhone])
.set(
{
conFriendUserName: currentUser[conuserName],
conFriendPhone: currentUser[conuserPhone],
conuserPhone: friendUser[conuserPhone],
conuserName: friendUser[conuserName],
conUserImageUrl: currentUser[conUserImageUrl],
conFriendId: currentUser[conUserId],
// conChatId: chatId
},
);
});
Navigator.of(context!).pushReplacementNamed(MultiUserChats.routeNames);
}
}
}
Related
class SimpleDialog extends StatelessWidget {
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
final TextEditingController _textEditingController =
TextEditingController();
String? baseurl;
#override
Widget build(BuildContext context) {
return AlertDialog(
title: Text('Base URL'),
content: Form(
key: _formKey,
child: TextFormField(
keyboardType: TextInputType.multiline,
maxLines: null,
controller: _textEditingController,
decoration: InputDecoration(
hintText: "Please Enter Base Url",
border:
OutlineInputBorder(borderRadius: BorderRadius.circular(15)),
),
validator: (value) {
// return value!.isNotEmpty ? null : "Enter Base Url";
return Uri.parse(value.toString()).host == ''
? "Enter Base Url"
: null;
},
)),
actions: <Widget>[
Center(
child: InkWell(
onTap: () {
if (_formKey.currentState!.validate()) {
baseurl = _textEditingController.text.toString().trim();
checkBaseUrl(baseurl, context);
print('baseurl=====base------$baseurl');
}
},
child: Container(
width: 100,
height: 40,
padding: EdgeInsets.all(12.0),
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(20.0),
),
child: Text(
"Connect",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.white),
),
),
),
)
],
);
}
Future<void> checkBaseUrl(String baseurl, context) async {
Response response;
try {
response = await http.get(Uri.parse(baseurl));
print(await response);
if (response.statusCode == 200) {
var snackBar = SnackBar(content: Text('Connected Successfully'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
var name = await baseurl;
await Prefs().seturlBase(name);
print(await '---pred---prefp---$name');
baseurl = await Prefs().geturlBase().toString();
Navigator.of(context).pop();
} else {
var snackBar = SnackBar(content: Text('Connection failed'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
Navigator.of(context).pop();
}
} catch (e) {
var snackBar = SnackBar(content: Text('Connection failed'));
ScaffoldMessenger.of(context).showSnackBar(snackBar);
Navigator.of(context).pop();
}
Class Prefs:
Future<void> seturlBase(String urlBase) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
prefs.setString(this.urlBase, urlBase);
}
Future<String> geturlBase() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
String urlBase;
urlBase = await prefs.getString(this.urlBase) ?? '';
return urlBase;
}
Check out the image, I need to save the URL as an input. After closing the app the URL should save and when users again open their app URL should be there for easy going.
How to achieve it, I used shared preferences.
.....................................................Thank you...........................
firt create a const for your base url
const String keyBaseUrl = 'baseUrl'
you can save it like this.
final SharedPreferences sharedPrefs = await SharedPreferences.getInstance();
sharedPrefs.setString(keyBaseUrl, baseurl);
and you can get it like this.
SharedPreferences prefs = await SharedPreferences.getInstance();
String? baseurl = prefs.getString(keyBaseUrl);
i am beginner in flutter .
i have a probleme firebase auth with email work on all phones and emulators but on my new samsung phone it doesn't .
here is the error message
/FirebaseAuth( 2571): [FirebaseAuth:] Preparing to create service connection to fallback implementation
W/System ( 2571): Ignoring header X-Firebase-Locale because its value was null.
2
I/System.out( 2571): (HTTPLog)-Static: isSBSettingEnabled false
D/InputMethodManager( 2571): HSIFW - flag : 0
here is my auth screen where the user sign up or login.
import 'package:flutter/material.dart';
import 'package:m51/common/constants.dart';
import 'package:m51/common/loading.dart';
import 'package:m51/services/authentication.dart';
class AuthenticateScreen extends StatefulWidget {
#override
_AuthenticateScreenState createState() => _AuthenticateScreenState();
}
class _AuthenticateScreenState extends State<AuthenticateScreen> {
final AuthenticationService _auth = AuthenticationService();
final _formKey = GlobalKey<FormState>();
String error = '';
bool loading = false;
final emailController = TextEditingController();
final nameController = TextEditingController();
final passwordController = TextEditingController();
bool showSignIn = true;
#override
void dispose() {
nameController.dispose();
emailController.dispose();
passwordController.dispose();
super.dispose();
}
void toggleView() {
setState(() {
_formKey.currentState?.reset();
error = '';
emailController.text = '';
nameController.text = '';
passwordController.text = '';
showSignIn = !showSignIn;
});
}
#override
Widget build(BuildContext context) {
return loading
? Loading()
: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
backgroundColor: Colors.blueGrey,
elevation: 0.0,
title: Text(showSignIn ? 'Sign in to Water Social' : 'Register to Water Social'),
actions: <Widget>[
TextButton.icon(
icon: Icon(
Icons.person,
color: Colors.white,
),
label: Text(showSignIn ? "Register" : 'Sign In',
style: TextStyle(color: Colors.white)),
onPressed: () => toggleView(),
),
],
),
body: Container(
padding: EdgeInsets.symmetric(vertical: 15.0, horizontal: 30.0),
child: Form(
key: _formKey,
child: Column(
children: [
!showSignIn
? TextFormField(
controller: nameController,
decoration: textInputDecoration.copyWith(hintText: 'name'),
validator: (value) =>
value == null || value.isEmpty ? "Enter a name" : null,
)
: Container(),
!showSignIn ? SizedBox(height: 10.0) : Container(),
TextFormField(
controller: emailController,
decoration: textInputDecoration.copyWith(hintText: 'email'),
validator: (value) =>
value == null || value.isEmpty ? "Enter an email" : null,
),
SizedBox(height: 10.0),
TextFormField(
controller: passwordController,
decoration: textInputDecoration.copyWith(hintText: 'password'),
obscureText: true,
validator: (value) => value != null && value.length < 6
? "Enter a password with at least 6 characters"
: null,
),
SizedBox(height: 10.0),
ElevatedButton(
child: Text(
showSignIn ? "Sign In" : "Register",
style: TextStyle(color: Colors.white),
),
onPressed: () async {
if (_formKey.currentState?.validate() == true) {
setState(() => loading = true);
var password = passwordController.value.text;
var email = emailController.value.text;
var name = nameController.value.text;
dynamic result = showSignIn
? await _auth.signInWithEmailAndPassword(email, password)
: await _auth.registerWithEmailAndPassword(name, email, password);
if (result == null) {
print ('Please supply a valid email');
}
}
}
),
SizedBox(height: 10.0),
Text(
error,
style: TextStyle(color: Colors.red, fontSize: 15.0),
)
],
),
),
),
);
}
}
i also have that message
I/flutter ( 4866): [firebase_auth/invalid-email] The email address is
badly formatted.
the service code
import 'package:firebase_auth/firebase_auth.dart';
import 'package:urjob/models/user.dart';
import 'package:urjob/services/database.dart';
class AuthenticationService {
final FirebaseAuth _auth = FirebaseAuth.instance;
AppUser? _userFromFirebaseUser(User? user) {
return user != null ? AppUser(user.uid) : null;
}
Stream<AppUser?> get user {
return _auth.authStateChanges().map(_userFromFirebaseUser);
}
Future signInWithEmailAndPassword(String email, String password) async {
try {
UserCredential result =
await _auth.signInWithEmailAndPassword(email: email, password: password);
User? user = result.user;
return _userFromFirebaseUser(user);
} catch (exception) {
print(exception.toString());
return null;
}
}
Future registerWithEmailAndPassword(String name, String email, String password, String profilepic) async {
try {
UserCredential result =
await _auth.createUserWithEmailAndPassword(email: email, password: password);
User? user = result.user;
if (user == null) {
throw Exception("No user found");
} else {
await DatabaseService(user.uid).saveUser(name,profilepic,email);
return _userFromFirebaseUser(user);
}
} catch (exception) {
print(exception.toString());
return null;
}
}
Future signOut() async {
try {
return await _auth.signOut();
} catch (exception) {
print(exception.toString());
return null;
}
}
Future sendreset(String email) async {
try {
return await _auth.sendPasswordResetEmail(email: email);
} catch (exception) {
print(exception.toString());
return null;
}
}
}
Thank you for your help
i found the solution
we need in ma case to add .trim() functions to erase white spaces
https://github.com/FirebaseExtended/flutterfire/issues/1760
Trim text gotten from textfields.
emailController.text.trim(),
passwordController.text.trim()
Please read properly and then comment, hope this help full for software community.
I unable to auto detect firebase phone auth sms, when i test in android it's work without doing anything, but when i tried it in flutter sms not detected automatically, require manually type sms code, I did not find any documentation to do this process automatically.
I wan't to know how to detect automatically that sms code from Firebase phone auth
Future<void> verifyPhone() async {
starTimer();
phoneNumber = contryCode+mobile_controller.text;
final PhoneCodeAutoRetrievalTimeout autoReRetrive = (String verId) {
this.verificationId = verId;
};
final PhoneCodeSent smsCodesent = (String verId, [int forceCodeResent]) {
this.verificationId = verId;
pageController.animateToPage(2, duration: Duration(milliseconds: 300), curve: Curves.easeInBack);
/*smsCodeDialog(context).then((value) {
print("VERIFY PHONE SIGN IN");
});
*/
};
final PhoneVerificationCompleted phoneVerificationCompleted =
(PhoneAuthCredential creditional) {
print("VERIFY PHONE AUTH SUCESS");
this.mPhoneCreditional=creditional;
//_controller.animateToPage(3, duration: Duration(milliseconds: 300), curve: Curves.easeIn);
loginIn(creditional);
};
final PhoneVerificationFailed phoneVerificationFailed =
(FirebaseAuthException excepton) {
Fluttertoast.showToast(
msg: excepton.message,
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.CENTER,
timeInSecForIosWeb: 1,
backgroundColor: Colors.red,
textColor: Colors.white,
fontSize: 16.0
);
print("VERIFY PHONE AUTH FAILED " + excepton.message);
};
FirebaseAuth mAuth=await FirebaseAuth.instance;
mAuth.setLanguageCode("en");
await mAuth.verifyPhoneNumber(
phoneNumber: phoneNumber,
timeout: const Duration(seconds: 45),
verificationCompleted: phoneVerificationCompleted,
verificationFailed: phoneVerificationFailed,
codeSent: smsCodesent,
codeAutoRetrievalTimeout: autoReRetrive);
}
Make sure you have added the debugging, release, sha1, sha256 keys to your firebase project.
Make sure you have enabled appcheck enabled on your firebase project.
Make sure you have enabled the Android Device Verification api on your google cloud console.
The below code is slightly modified from the example code given in firebase auth plugin, for my projects the detecion is working well.
Some devices may fail to detect the otp, but most of the devices will login automatically.
The code which detects the sms is highlighted in sendOtpFn()
import 'package:firebase_auth/firebase_auth.dart';
import 'package:flutter/material.dart';
import 'package:projdebug/Widgets/Widgets.dart';
class LoginPage extends StatefulWidget {
const LoginPage({Key? key}) : super(key: key);
#override
State<StatefulWidget> createState() => LoginPageState();
}
class LoginPageState extends State<LoginPage> {
final FirebaseAuth _auth = FirebaseAuth.instance;
final _phoneNumberController = TextEditingController();
final _smsController = TextEditingController();
String _message = '';
late String _verificationId;
#override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(onPressed: () {
setState(() {});
}),
body: Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: <Widget>[
Container(
alignment: Alignment.center,
child: const Text(
'Test sign in with phone number',
style: TextStyle(fontWeight: FontWeight.bold),
),
),
TextFormField(
controller: _phoneNumberController,
decoration: const InputDecoration(
labelText: 'Phone number (+x xxx-xxx-xxxx)',
),
validator: (String? value) {
if (value!.isEmpty) {
return 'Phone number (+x xxx-xxx-xxxx)';
}
return null;
},
),
Container(
padding: const EdgeInsets.symmetric(vertical: 16),
alignment: Alignment.center,
child: Button(
icon: Icons.send,
buttonColor: Colors.deepOrangeAccent[700]!,
text: 'Send otp',
onPressed: sendOtpFn,
),
),
TextField(
controller: _smsController,
decoration:
const InputDecoration(labelText: 'Verification code'),
),
Container(
padding: const EdgeInsets.only(top: 16),
alignment: Alignment.center,
child: Button(
icon: Icons.done,
buttonColor: Colors.deepOrangeAccent[400]!,
onPressed: manualSignInFn,
text: 'Confirm otp',
),
),
CrossFade(
show: _auth.currentUser?.uid != null,
child: Button(
icon: Icons.logout,
buttonColor: Colors.deepOrangeAccent[400]!,
onPressed: () async{
await _auth.signOut();
setState(() {});
},
text: 'LogOut',
),
),
Visibility(
visible: _message.isNotEmpty,
child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Text(
_message,
style: const TextStyle(color: Colors.red),
),
),
)
],
),
),
),
);
}
// Example code of how to verify phone number
Future<void> sendOtpFn() async {
setState(() {
_message = '';
});
///This is the code snippet which [detects the code from sms automatically]
PhoneVerificationCompleted verificationCompleted =
(PhoneAuthCredential phoneAuthCredential) async {
setState(() {
_message = phoneAuthCredential.smsCode ?? "";
_smsController.text = _message;
});
await _auth.signInWithCredential(phoneAuthCredential);
Widgets.showToast(
'Phone number automatically verified and user signed in: $phoneAuthCredential');
setState(() {});
};
PhoneVerificationFailed verificationFailed =
(FirebaseAuthException authException) {
setState(() {
_message =
'Phone number verification failed. Code: ${authException.code}. '
'Message: ${authException.message}';
});
};
PhoneCodeSent codeSent =
(String verificationId, [int? forceResendingToken]) async {
Widgets.showToast('Please check your phone for the verification code.');
_verificationId = verificationId;
};
PhoneCodeAutoRetrievalTimeout codeAutoRetrievalTimeout =
(String verificationId) {
_verificationId = verificationId;
};
try {
await _auth.verifyPhoneNumber(
phoneNumber: _phoneNumberController.text,
timeout: const Duration(seconds: 120),
verificationCompleted: verificationCompleted,
verificationFailed: verificationFailed,
codeSent: codeSent,
codeAutoRetrievalTimeout: codeAutoRetrievalTimeout);
} catch (e) {
Widgets.showToast('Time out to Verify Phone Number: $e');
}
}
///Code which will be executed only when a confirm otp button is pressed, ie both phoneNumber & otp is typed by the user manually.
Future<void> manualSignInFn() async {
try {
final PhoneAuthCredential credential = PhoneAuthProvider.credential(
verificationId: _verificationId,
smsCode: _smsController.text,
);
final User user = (await _auth.signInWithCredential(credential)).user!;
Widgets.showToast('Successfully signed in UID: ${user.uid}');
} catch (e) {
print(e);
Widgets.showToast('Failed to sign in');
}
}
}
I am trying to write a program to check if the time selected by the user already exists in the firebase firestore or not. If it does then I navigate back to the page where they select time again.
But as of now, I am succeeded in sending the date and time to firebase and but not the latter part.
DateTime _eventDate;
bool processing;
String _time;
bool conditionsStatisfied ;
#override
void initState() {
super.initState();
_eventDate = DateTime.now();
processing = false ;
}
inside showDatePicker()
setState(() {
print('inside the setState of listTile');
_eventDate = picked ;
});
inside the button (SAVE):
onPressed: () async {
if (_eventDate != null) {
final QuerySnapshot result = await FirebaseFirestore
.instance
.collection('events')
.where('event_date', isEqualTo: this._eventDate)
.where('selected_time', isEqualTo: this._time)
.get();
final List <DocumentSnapshot> document = result.docs;
if (document.length > 0) {
setState(() {
print('inside the method matching conditions');
showAlertDialogue(context);
});
}else{
final data = {
// "title": _title.text,
'selected_time ': this._time,
"event_date": this._eventDate
};
if (widget.note != null) {
await eventDBS.updateData(widget.note.id, data);
} else {
await eventDBS.create(data);
}
Navigator.pop(context);
setState(() {
processing = false;
});
}
};
some guidance needed on how do I resolve this issue!
Also, because of the else statement now the program won't write the date into firestore.
After Alot of research, I came to realize that if you send the data from calendar in DateTime format then, because of the timestamp at the end of the Date it becomes impossible to match to dates. Hence I formatted the DateTime value into (DD/MM/YYYY).
Here is the rest of the code for reference:
class _AddEventPageState extends State<AddEventPage> {
String _eventDate;
bool processing;
String _time;
#override
void initState() {
super.initState();
// _eventDate = DateTime.now();
processing = false ;
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Please select a date'),),
body: Column(
children: [
hourMinute30Interval(),
Text('$_time'),
ListView(
scrollDirection: Axis.vertical,
shrinkWrap: true,
children: <Widget>[
ListTile(
title: Text(
'$_eventDate'),
onTap: () async {
DateTime picked = await showDatePicker(context: context,
initialDate: DateTime.now(),
firstDate: DateTime(DateTime.now().year - 1),
lastDate: DateTime(DateTime.now().year + 10),);
if (picked != null) {
setState(() {
print('inside the setState of listTile');
_eventDate = DateFormat('dd/MM/yyyy').format(picked) ;
});
}
},
),
SizedBox(height: 10.0),
ListTile(
title: Center(
child: Text('Select time for appointment!', style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 20,
),
),
),
),
processing
? Center(child: CircularProgressIndicator())
: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16.0),
child: Material(
elevation: 5.0,
borderRadius: BorderRadius.circular(30.0),
color: Theme
.of(context)
.primaryColor,
child:MaterialButton(
child: Text('SAVE', style: TextStyle(
fontSize: 20,
color: Colors.white,
fontWeight: FontWeight.bold,
)),
onPressed: () async {
if (_eventDate != null) {
AddingEventsUsingRajeshMethod().getAvailableSlots(
_eventDate, _time).then((QuerySnapshot docs) async {
if (docs.docs.length == 1) {
showAlertDialogue(context);
}
else{
final data = {
// "title": _title.text,
'selected_time': this._time,
"event_date": _eventDate,
};
if (widget.note != null) {
await eventDBS.updateData(widget.note.id, data);
} else {
await eventDBS.create(data);
}
Navigator.pop(context);
setState(() {
processing = false;
});
}
});
}
}
),
),
),
],
),
],
),
);
}
showAlertDialogue method :
showAlertDialogue(BuildContext context) {
Widget okButton = FlatButton(onPressed: (){
Timer(Duration(milliseconds: 500), () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => datePicker()),
);
});
}, child: Text(' OK! '));
AlertDialog alert = AlertDialog(
title: Text('Slot unavailable'),
content: Text('This slot is already booked please select another slot'),
actions: [
okButton,
],
);
showDialog(context: context ,
builder: (BuildContext context){
return alert ;
}
);
}
The hourMinute30Interval() is nothing but a Widget that returns a timePickerSpinner which is a custom Widget. Tap here for that.
The Query that is run after passing the _eventDate and _time is in another class, and it goes as follows :
class AddingEventsUsingRajeshMethod {
getAvailableSlots(String _eventDate , String _time){
return FirebaseFirestore.instance
.collection('events')
.where('event_date', isEqualTo: _eventDate )
.where('selected_time', isEqualTo: _time)
.get();
}
}
You can name it something prettier ;)
I'm trying to do Firebase authentication and Google sign in using Flutter but I'm getting this error message:
'package:flutter/src/painting/_network_image_io.dart': Failed assertion: line 23 pos 14: 'url != null': is not true.
I can't understand what is wrong, can you help me?
login.dart code snippet
-> calls signInWithGoogle() method from sign_in.dart and return FirstScreen
Widget _signInButton() {
return OutlineButton(
splashColor: Colors.grey,
onPressed: () {
signInWithGoogle().whenComplete(() {
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) {
return FirstScreen();
},
),
);
});
},
sign_in.dart code snippet:
-> Here I authenticate the user and try to get logged user name, email and image
final FirebaseAuth _auth = FirebaseAuth.instance;
final GoogleSignIn googleSignIn = GoogleSignIn();
String name;
String email;
String imageUrl;
Future<String> signInWithGoogle() async {
final GoogleSignInAccount googleSignInAccount = await googleSignIn.signIn();
final GoogleSignInAuthentication googleSignInAuthentication =
await googleSignInAccount.authentication;
final AuthCredential credential = GoogleAuthProvider.getCredential(
accessToken: googleSignInAuthentication.accessToken,
idToken: googleSignInAuthentication.idToken,
);
final AuthResult authResult = await _auth.signInWithCredential(credential);
final FirebaseUser user = authResult.user;
// Checking if email and name is null
assert(user.email != null);
assert(user.displayName != null);
assert(user.photoUrl != null);
name = user.displayName;
email = user.email;
imageUrl = user.photoUrl;
// Only taking the first part of the name, i.e., First Name
if (name.contains(" ")) {
name = name.substring(0, name.indexOf(" "));
}
assert(!user.isAnonymous);
assert(await user.getIdToken() != null);
final FirebaseUser currentUser = await _auth.currentUser();
assert(user.uid == currentUser.uid);
log('data: $user');
return 'signInWithGoogle succeeded: $user';
}
void signOutGoogle() async {
await googleSignIn.signOut();
print("User Sign Out");
}
FirstScreen code snippet:
-> Here I try to show user data (name, email and image) based on what I got on previously code
children: <Widget>[
CircleAvatar(
backgroundImage: NetworkImage(
imageUrl,
),
radius: 60,
backgroundColor: Colors.transparent,
),
SizedBox(height: 40),
Text(
'NAME',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black54),
),
Text(
name,
style: TextStyle(
fontSize: 25,
color: Colors.deepPurple,
fontWeight: FontWeight.bold),
),
SizedBox(height: 20),
Text(
'EMAIL',
style: TextStyle(
fontSize: 15,
fontWeight: FontWeight.bold,
color: Colors.black54),
),
Text(
email,
style: TextStyle(
fontSize: 25,
color: Colors.deepPurple,
fontWeight: FontWeight.bold),
),
SizedBox(height: 40)
EDIT: aditional info -> console log shows "Unhandled Exception: PlatformException(sign_in_failed, com.google.android.gms.common.api.ApiException: 10: , null)"
After wasting a day to find my way around the Google SignIn package with various fixes and troubleshooting, I realised there is web package Google has made for web sign in which uses the OAuthClient verification and configuration as per their changing policy with Google Sign in.
Sadly it isn't straight forward and quick as it was before. But following the steps mentioned in the pub.dev.
https://pub.dev/packages/google_sign_in_web
Attaching my main.dart working POC snippets here.
import 'dart:async';
import 'dart:convert' show json;
import 'package:flutter/material.dart';
import 'package:google_sign_in/google_sign_in.dart';
import 'package:http/http.dart' as http;
GoogleSignIn _googleSignIn = GoogleSignIn(
// Optional clientId
// clientId: '479882132969-9i9aqik3jfjd7qhci1nqf0bm2g71rm1u.apps.googleusercontent.com',
scopes: <String>[
'email',
'https://www.googleapis.com/auth/contacts.readonly',
],
);
void main() {
runApp(
const MaterialApp(
title: 'Google Sign In',
home: SignInDemo(),
),
);
}
class SignInDemo extends StatefulWidget {
const SignInDemo({Key? key}) : super(key: key);
#override
State createState() => SignInDemoState();
}
class SignInDemoState extends State<SignInDemo> {
GoogleSignInAccount? _currentUser;
String _contactText = '';
#override
void initState() {
super.initState();
_googleSignIn.onCurrentUserChanged.listen((GoogleSignInAccount? account) {
setState(() {
_currentUser = account;
});
if (_currentUser != null) {
_handleGetContact(_currentUser!);
}
});
_googleSignIn.signInSilently();
}
Future<void> _handleGetContact(GoogleSignInAccount user) async {
setState(() {
_contactText = 'Loading contact info...';
});
final http.Response response = await http.get(
Uri.parse('https://people.googleapis.com/v1/people/me/connections'
'?requestMask.includeField=person.names'),
headers: await user.authHeaders,
);
if (response.statusCode != 200) {
setState(() {
_contactText = 'People API gave a ${response.statusCode} '
'response. Check logs for details.';
});
print('People API ${response.statusCode} response: ${response.body}');
return;
}
final Map<String, dynamic> data =
json.decode(response.body) as Map<String, dynamic>;
final String? namedContact = _pickFirstNamedContact(data);
setState(() {
if (namedContact != null) {
_contactText = 'I see you know $namedContact!';
} else {
_contactText = 'No contacts to display.';
}
});
}
String? _pickFirstNamedContact(Map<String, dynamic> data) {
final List<dynamic>? connections = data['connections'] as List<dynamic>?;
final Map<String, dynamic>? contact = connections?.firstWhere(
(dynamic contact) => (contact as Map<Object?, dynamic>)['names'] != null,
orElse: () => null,
) as Map<String, dynamic>?;
if (contact != null) {
final List<dynamic> names = contact['names'] as List<dynamic>;
final Map<String, dynamic>? name = names.firstWhere(
(dynamic name) =>
(name as Map<Object?, dynamic>)['displayName'] != null,
orElse: () => null,
) as Map<String, dynamic>?;
if (name != null) {
return name['displayName'] as String?;
}
}
return null;
}
Future<void> _handleSignIn() async {
try {
await _googleSignIn.signIn();
} catch (error) {
print(error);
}
}
Future<void> _handleSignOut() => _googleSignIn.disconnect();
Widget _buildBody() {
final GoogleSignInAccount? user = _currentUser;
if (user != null) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
ListTile(
leading: GoogleUserCircleAvatar(
identity: user,
),
title: Text(user.displayName ?? ''),
subtitle: Text(user.email),
),
const Text('Signed in successfully.'),
Text(_contactText),
ElevatedButton(
onPressed: _handleSignOut,
child: const Text('SIGN OUT'),
),
ElevatedButton(
child: const Text('REFRESH'),
onPressed: () => _handleGetContact(user),
),
],
);
} else {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: <Widget>[
const Text('You are not currently signed in.'),
ElevatedButton(
onPressed: _handleSignIn,
child: const Text('SIGN IN'),
),
],
);
}
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Google Sign In'),
),
body: ConstrainedBox(
constraints: const BoxConstraints.expand(),
child: _buildBody(),
));
}
}
I found that the error I was getting was due to the lack of some OAuth Credentials settings. I found the solution here