How to save state with Firebase - firebase

I have switcher function which I created in order to change state of my icon according to information taken from firebase. So, when
"isFavSalon" in switcher is true the icon should be filled else it should be not filled. isFavSalon must be getting from firebase.
The switcher:
initState() {
isFavSalonBlock = widget.isFavSalon;
print(isFavSalonBlock);
super.initState();
}
#override
Widget build(BuildContext context) {
void switcherIsFavSalon() async {
var firebaseUser = FirebaseAuth.instance.currentUser;
var docRef = FirebaseFirestore.instance
.collection('customers')
.doc(firebaseUser.uid)
.collection('favSalons');
if (isFavSalonBlock == false) {
setState(() {
favIconBlock = 'assets/icons/heart_filled.png';
isFavSalonBlock = true;
docRef.add({
"salonName": widget.salonName,
"workTime": widget.workTime,
"location": widget.location,
"rating": widget.rating,
"isFavSalon": isFavSalonBlock,
});
});
} else {
setState(() {
favIconBlock = 'assets/icons/heart_border.png';
isFavSalonBlock = false;
docRef.get().then((value) {
value.docs.forEach((element) async {
if (element.data()['salonName'] == widget.salonName)
await docRef.doc(element.id).delete();
});
});
});
}
}
favIcon in this switcher pass to the icon's image path.
Also, I have function which gets information from firebase and stores it in my Block.
The function:
getFavSalons(AsyncSnapshot<QuerySnapshot> snapshot) {
return snapshot.data.docs
.map((doc) => SalonBlock(
salonName: doc.data()["salonName"],
location: doc.data()["location"],
workTime: doc.data()["workTime"],
rating: doc.data()["rating"],
))
.toList();
}
But when I restart my app and switch between pages state of icons doesn't change according to information stored in firebase.
However when I press on the icon information is sending to firebase.
So how can I save the state in my app?

Related

A document path must be a non-empty string, Flutter - Firebase error?

I have some mistakes with flutter and firebase, if someone can help would be great here is my auth controller
class AuthController extends GetxController {
final FirebaseAuth auth = FirebaseAuth.instance;
final Rxn<User> _firebaseUser = Rxn<User>();
Rx<XFile>? _pickedImage;
XFile? get profilePhoto => _pickedImage?.value;
// final user = FirebaseAuth.instance.currentUser.obs;
Rxn<User> get user => _firebaseUser;
// final user = FirebaseAuth.instance.currentUser;
#override
onInit() {
_firebaseUser.bindStream(auth.authStateChanges());
super.onInit();
}
// void register(
// String name, String email, String password, XFile? image) async {
// try {
// UserCredential _authResult = await auth.createUserWithEmailAndPassword(
// email: email.trim(), password: password);
// //create user in database.dart
// String downloadUrl = await uploadToStorage(image!);
// UserModel _user = UserModel(
// id: _authResult.user?.uid,
// name: name,
// email: _authResult.user?.email,
// profilePic: downloadUrl,
// );
// if (await Database().createNewUser(_user)) {
// Get.find<UserController>().user = _user;
// }
// } catch (e) {
// Get.snackbar(
// "Error creating Account",
// e.toString(),
// snackPosition: SnackPosition.BOTTOM,
// );
// }
// }
void register(
String name, String email, String password, XFile? image) async {
try {
if (name.isNotEmpty &&
email.isNotEmpty &&
password.isNotEmpty &&
image != null) {
// save out user to our ath and firebase firestore
UserCredential _authResult = await auth.createUserWithEmailAndPassword(
email: email,
password: password,
);
String downloadUrl = await uploadToStorage(image);
UserModel _user = UserModel(
id: _authResult.user?.uid,
name: name,
email: _authResult.user?.email,
profilePic: downloadUrl,
);
if (await Database().createNewUser(_user)) {
Get.find<UserController>().user = _user;
} else {
Get.snackbar(
'Error Creating Account',
'Please enter all the fields',
);
}
}
} catch (e) {
Get.snackbar(
'Error Creating Account',
e.toString(),
);
}
}
void login(String email, password) async {
try {
UserCredential _authResult = await auth.signInWithEmailAndPassword(
email: email.trim(), password: password);
Get.find<UserController>().user =
await Database().getUser(_authResult.user?.uid ?? '');
} catch (e) {
Get.snackbar("About User", "User message",
snackPosition: SnackPosition.BOTTOM,
titleText: Text("Acount creation failed"),
messageText:
Text(e.toString(), style: TextStyle(color: Colors.white)));
}
}
Future<void> signOut() async {
await auth.signOut();
Get.find<UserController>().clear();
}
Future pickImage() async {
print("call on click add photo icon");
final ImagePicker _picker = ImagePicker();
final XFile? pickedImage =
await _picker.pickImage(source: ImageSource.gallery);
print('picked image filled with image from gallery'); //This doesnt print at
if (pickedImage != null) {
Get.snackbar('Profile Picture',
'You have successfully selected your profile picture!');
// print(pickedImage.path);
}
_pickedImage = Rx<XFile>(pickedImage!);
// print(_pickedImage);
// print(profilePhoto);
}
// upload to firebase storage
Future<String> uploadToStorage(XFile? image) async {
Reference ref = FirebaseStorage.instance
.ref('')
.child('profilePics')
.child(auth.currentUser!.uid);
// print(ref);
UploadTask uploadTask = ref.putFile(File(image?.path ?? 'idemo'));
print(uploadTask);
// TaskSnapshot snap = await uploadTask;
String downloadUrl = await (await uploadTask).ref.getDownloadURL();
print(downloadUrl);
return downloadUrl;
}
}
Here is my function to createNewUser
class Database {
final FirebaseFirestore _firestore = FirebaseFirestore.instance;
Future<bool> createNewUser(UserModel user) async {
try {
await _firestore.collection("users").doc(user.id).set({
"name": user.name,
"email": user.email,
"profilePhoto": user.profilePic
});
return true;
} catch (e) {
print(e);
return false;
}
}
Here is HomeController
class HomeController extends GetxController {
final Rxn<List<TodoModel>> todoList = Rxn<List<TodoModel>>([]);
var selectedDate = DateTime.now().obs;
List<TodoModel>? get todos => todoList.value;
#override
void onInit() {
super.onInit();
String? uid = Get.find<AuthController>().auth.currentUser?.uid ?? '';
print(uid);
todoList.bindStream(Database().todoStream(uid));
}
chooseDate() async {
DateTime? pickedDate = await showDatePicker(
context: Get.context!,
initialDate: selectedDate.value,
firstDate: DateTime(2000),
lastDate: DateTime(2024),
//initialEntryMode: DatePickerEntryMode.input,
// initialDatePickerMode: DatePickerMode.year,
);
if (pickedDate != null && pickedDate != selectedDate.value) {
selectedDate.value = pickedDate;
}
}
}
and here is View page
GetX<HomeController>(
init: Get.put<HomeController>(HomeController()),
builder: (HomeController todoController) {
if (todoController.todos != null) {
// print(todoController.todos?.done ?? false);
return Expanded(
child: ListView.builder(
itemCount: todoController.todos?.length,
itemBuilder: (_, index) {
return TodoCard(
uid: controller.user.value?.uid ?? '',
todo: todoController.todos![index],
);
},
),
);
} else {
return Text("loading...");
}
},
),
So, I have an error when I register a new user I got this error:
The following assertion was thrown building Builder(dirty):
a document path must be a non-empty string
Failed assertion: line 116 pos 14: ‘path.isNotEmpty’
And here is output from terminal:
The relevant error-causing widget was
GetMaterialApp
lib/main.dart:23
When the exception was thrown, this was the stack
#2 _JsonCollectionReference.doc
#3 Database.todoStream
#4 HomeController.onInit
#5 GetLifeCycleBase._onStart
#6 InternalFinalCallback.call
#7 GetInstance._startController
#8 GetInstance._initDependencies
#9 GetInstance.find
#10 GetInstance.put
#11 Inst.put
So a problem is with this path, and when I reload from the visual studio I god the right user with the right data. So the problem is when I register a user for the first time.
It looks like uid is empty, which you should also be able to see from looking up print(uid); in your output.
When your application or web page loads, Firebase automatically tries to restore the previously signed in user from its local state. This requires that it makes a call to the server however (for example to check if the account has been disabled) and while that call is going on, your main code continues to execute and the currentUser variable is going to be null.
Your code needs to take this into account. The easiest way to do this is to not depend on currentUser, but instead to use an reactively respond to changes in the authentication state as shown in the first example in the documentation on getting the current user:
FirebaseAuth.instance
.authStateChanges()
.listen((User? user) {
if (user != null) {
print(user.uid);
}
});
The authStateChange method here returns a stream that fires an event whenever the authentication state changes, so when the user signs in or signs out. The common way to use this stream is to either set the user to the state of your widget, or to use the stream directly in a StreamBuilder.

Flutter programmatically building a referral system with deep link

I need to implement a deep link or referral system with my flutter application. The theory is
Singup and Signin will be handled by custom backend and not firebase
After a user signs up to my application he will be able to refer the app to others and if others install the app the referrer will gain some points.
Most work in this process will be handled by our custom backend. What I need is when someone uses my referral code I want that code during his/her signup.
So this is the service layer I created:
class DynamicLinkService {
final dynamicLink = FirebaseDynamicLinks.instance;
handleDynamicLink() async {
await dynamicLink.getInitialLink();
// dynamicLink.onLink(onSuccess: (PendingDynamicLinkData data) async {
// // something
// },
// onError: (OnLinkErrorException e) async {
// // something
// },
// );
}
Future<String> createDynamicLink() async {
User user = Store.instance.getUser();
String userId = user.id;
print("User id = $userId");
final DynamicLinkParameters dynamicLinkParameters = DynamicLinkParameters(
uriPrefix: 'https://shoppydev.page.link',
link: Uri.parse(
'https://shoppydev.page.link/?invitedBy=$userId',
),
androidParameters: AndroidParameters(
packageName: 'co.company.app',
minimumVersion: 0,
),
iosParameters: IOSParameters(
bundleId: 'co.company.app',
minimumVersion: '0.0.1',
),
socialMetaTagParameters: SocialMetaTagParameters(
title: 'Refer A friend',
description: 'Refer and earn points',
),
);
final ShortDynamicLink shortDynamicLink = await dynamicLink.buildShortLink(
dynamicLinkParameters,
);
final Uri dynamicUrl = shortDynamicLink.shortUrl;
print(dynamicUrl.toString());
return dynamicUrl.toString();
}
void handleSuccessfulLinking(PendingDynamicLinkData? data) async {
final Uri? deepLink = data!.link;
print(deepLink.toString());
if (deepLink != null) {
var isRefer = deepLink.toString().contains('invitedBy');
if (isRefer) {
var code = deepLink.toString().split('invitedBy=')[1];
print(code);
if (code != null) {
// code contains the referrer's user id
// signup with the referrer's id
}
}
}
}
}
As you can see I tried to create a unique referral link with the user id for now. But most guides I am following as well as some github repos did something like this for handling dynamic link:
dynamicLink.onLink(onSuccess: (PendingDynamicLinkData data) async {
// something
},
onError: (OnLinkErrorException e) async {
// something
},
);
Which throws: The expression doesn't evaluate to a function, so it can't be invoked.
Other notes that might help:
Inside my app.dart I have:
class App extends StatefulWidget {
const App({Key? key}) : super(key: key);
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
#override
void initState() {
super.initState();
initDynamicLinks(context);
}
#override
Widget build(BuildContext context) {
final provider = Provider.of<LocaleProvider>(context);
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'App Name',
theme: ThemeData(
primarySwatch: Colors.blue,
),
onGenerateRoute: buildRouter,
locale: provider.locale,
supportedLocales: L10n.all,
localizationsDelegates: [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
],
);
}
/*
Dynamic Links
*/
void initDynamicLinks(BuildContext context) async {
final PendingDynamicLinkData? data =
await FirebaseDynamicLinks.instance.getInitialLink();
final Uri? link = data?.link;
if (link != null) {
Navigator.pushNamed(context, link.path);
}
}
}
Issues I have faced till now:
I still haven't found a solid documentation on how to get the referral code(which is need for rewarding the referrer).
I have already checked out this two posts on stack:
Implementing referral rewards in Flutter
Flutter - How to pass custom arguments in firebase dynamic links for app invite feature?
In short, I want to create a unique refer link with my user id. Share the user id with someone else and when he/she registers to my app I want to get the referral code attached to the link.
Example: https://app.page.link/?invitedBy=$userId
When someone installs and registers I want the userId so I can pass it to the invitedBy property of SignUpRequest.
Edit: I think I didn't clarify my question enough. So I will set it up with an example:
I want an unique referral link on my phone which I can give to my friend John. And once he downloads and registers the app I want to get some reward points.
So when he sends his SignUpRequest to the Backend I want my referral code to go with that request, so the request will look like:
SignUpRequest()
..name = "John Doe",
..email = "john#gmail.com"
..invitedBy = "...my referral code goes here"
All the other validation and point giving process will be done in the BE
Put all of the below code in the App.dart or Splash screen, basically the first screen
initState
#override
void initState() {
super.initState();
_initDynamicLinks();
}
_initDynamicLinks - this is from where the dynamic link will be launched
Future<void> _initDynamicLinks() async {
final PendingDynamicLinkData data = await instance.getInitialLink();
final Uri deepLink = data?.link;
_handleDynamicLink(deepLink);
FirebaseDynamicLinks.instance.onLink.listen((dynamicLink) {
final uri = dynamicLink.link;
_handleDynamicLink(uri);
}).onError((e) {
print('onLinkError');
print(e.message);
});
}
_handleDynamicLink - this is where you handle the link and parse it
void _handleDynamicLink(Uri deepLink) async {
if (deepLink != null) {
final url = deepLink.toString();
var isRefer = url.contains('invitedBy');
if (isRefer) {
var code = url.split('invitedBy=')[1];
print(code);
if (code != null) {
// code contains the referrer's user id
// signup with the referrer's id
}
}
}
}
I think this way will be more clean
first add this widget
class DynamicLinksWidgetHandler extends StatefulWidget {
const DynamicLinksWidgetHandler({
super.key,
required this.child,
});
final Widget child;
#override
State<DynamicLinksWidgetHandler> createState() =>
_DynamicLinksWidgetHandlerState();
}
class _DynamicLinksWidgetHandlerState extends State<DynamicLinksWidgetHandler> {
#override
void initState() {
super.initState();
_initDynamicLinks();
}
// _initDynamicLinks - this is from where the dynamic link will be launched
Future<void> _initDynamicLinks() async {
final data = await FirebaseDynamicLinks.instance.getInitialLink();
final Uri? deepLink = data?.link;
_handleDynamicLink(deepLink);
FirebaseDynamicLinks.instance.onLink.listen((dynamicLink) {
final uri = dynamicLink.link;
_handleDynamicLink(uri);
}).onError((e) {
print('onLinkError');
print(e.message);
});
}
// _handleDynamicLink - this is where you handle the link and parse it
void _handleDynamicLink(Uri? deepLink) async {
log('_handleDynamicLink:$deepLink');
final code = deepLink?.queryParameters['invitedby'];
if (code == null) return;
// save code to backend
log(code);
}
#override
Widget build(BuildContext context) {
return widget.child;
}
}
and then wrap it on your app widget like this
runApp(
const DynamicLinksWidgetHandler(
child: MyApp(),
),
);

How to get Current User address every time in init State?

I am using GeoLocation and GeoCoding in my app but the address was always return null
Position? _currentPosition; >>>> always return null
String? _currentAddress;
String key = "location_cache";
_getCurrentLocation() {
Geolocator.getCurrentPosition(
desiredAccuracy: LocationAccuracy.high,
forceAndroidLocationManager: true)
.then((Position position) {
setState(() {
_currentPosition = position;
});
_getAddressFromLatLng();
}).catchError((e) {
print(e);
});
}
_getAddressFromLatLng() async {
try {
List<Placemark> p = await placemarkFromCoordinates(
_currentPosition!.latitude, _currentPosition!.longitude);
Placemark place = p[0];
setState(() {
_currentAddress = "${place.locality}";
});
} catch (e) {
print(e);
}
}
#override
void initState() {
_getCurrentLocation();
super.initState();
print(_currentAddress);
}
I WANT TO GET THE USER location every time he go to this screen ??
then I use it in my function to get data from firebase :
StreamBuilder<QuerySnapshot>(
stream: _productServices.getAnnouncesNearUser(
_currentAddress == null ? '' : _currentAddress.toString()),
please try using LocationAccuracy.medium,

Flutter : Dynamic Link not launching the app

I am using firebase dynamic links to open the email verification link in my app, but unfortunetly the link doesn't launch the app when tapped.
What I've done so far
When a new user is created, a link is sent by email to be verified :
if(firebaseUser != null && !firebaseUser.emailVerified){
await createUserInDatabaseIfNew(firebaseUser);
var actionCodeSettings = auth.ActionCodeSettings(
url: 'https://muslimcoloc.page.link/?email=${firebaseUser.email}',
dynamicLinkDomain: "muslimcoloc.page.link",
androidInstallApp: true,
androidMinimumVersion: "12",
androidPackageName: "com.app.muslim_coloc",
iOSBundleId: "com.muslim_coloc.ios",
handleCodeInApp: true,
);
await firebaseUser.sendEmailVerification(actionCodeSettings);
}
I got the dynamicLinkDomain in the firebase console :
Then, I handle the reception of the link in my main.dart file, with the firebase dynamic links package :
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(
MyApp(),
);
}
class MyApp extends StatelessWidget {
MyApp({Key key, }) : super(key: key);
#override
Widget build(BuildContext context) {
return AppView();
}
}
class AppView extends StatefulWidget {
const AppView({
Key key,
}) : super(key: key);
#override
_AppViewState createState() => _AppViewState();
}
class _AppViewState extends State<AppView> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
if (state == AppLifecycleState.resumed) {
this.initDynamicLinks();
}
}
void initDynamicLinks() async {
FirebaseDynamicLinks.instance.onLink(
onSuccess: (PendingDynamicLinkData dynamicLink) async {
final Uri deepLink = dynamicLink?.link;
FirebaseAuth auth = FirebaseAuth.instance;
//Get actionCode from the dynamicLink
var actionCode = deepLink.queryParameters['oobCode'];
try {
await auth.checkActionCode(actionCode);
await auth.applyActionCode(actionCode);
// If successful, reload the user:
auth.currentUser.reload();
} on FirebaseAuthException catch (e) {
if (e.code == 'invalid-action-code') {
print('The code is invalid.');
}
}
if (deepLink != null) {
Navigator.pushNamed(context, deepLink.path);
}
},
onError: (OnLinkErrorException e) async {
print('onLinkError');
print(e.message);
}
);
final PendingDynamicLinkData data =
await FirebaseDynamicLinks.instance.getInitialLink();
final Uri deepLink = data?.link;
if (deepLink != null) {
Navigator.pushNamed(context, deepLink.path);
}
}
#override
Widget build(BuildContext context) {
return MaterialApp(...)
}
When I tap the link of the email, the app doesn't start nor does the browser. Here's what happens :
It tries to launch something on the browser, but then comes back to gmail.
However if I click on the link in a desktop browser, it works fine, the email is validated.
I'm having a hard time understanding what it going on. Is there something wrong about how I did things ?
You should write a function to handle your dynamic links, as per the documentation, and this is working for me in an app being used currently:
void handleDynamicLinks() async {
///To bring INTO FOREGROUND FROM DYNAMIC LINK.
FirebaseDynamicLinks.instance.onLink(
onSuccess: (PendingDynamicLinkData dynamicLinkData) async {
await _handleDeepLink(dynamicLinkData);
},
onError: (OnLinkErrorException e) async {
print('DynamicLink Failed: ${e.message}');
return e.message;
},
);
final PendingDynamicLinkData data =
await FirebaseDynamicLinks.instance.getInitialLink();
_handleDeepLink(data);
}
// bool _deeplink = true;
_handleDeepLink(PendingDynamicLinkData data) async {
final Uri? deeplink = data.link;
if (deeplink != null) {
print('Handling Deep Link | deepLink: $deeplink');
}
}
and in initState:
#override
void initState() {
handleDynamicLinks();
super.initState();
}
write this logic in your home page. Not in void(main..etc)
But in your first widget after that, and it should work.
Also, be sure to double check your package name, i.e com.example.yourAwesomeApp123, it's what lets the whole system know what app is to be opened when the dynamic link is pressed.

How does firebase passwordless authentication work with dynamic link for a flutter app?

When I click on verification link from my email , it opens my app running in background but didChangeAppLifecycleState method returns data as null and deepLink as set in firebase instead of the whole emailLink from my email, resulting in SignInWithEmailAndLink to fail as its supposed to match email address entered in widget from the link recieved.
Here's the code taken from this article https://medium.com/firebase-developers/dive-into-firebase-auth-on-flutter-email-and-link-sign-in-e51603eb08f8 :-
1.didChangeAppLifecycleState method
#override
void didChangeAppLifecycleState(AppLifecycleState state) async {
if (state == AppLifecycleState.resumed) {
final PendingDynamicLinkData data =
await FirebaseDynamicLinks.instance.getInitialLink();
if( data?.link != null ) {
handleLink(data?.link);
}
FirebaseDynamicLinks.instance.onLink(
onSuccess: (PendingDynamicLinkData dynamicLink) async {
final Uri deepLink `enter code here`= dynamicLink?.link;
handleLink(deepLink);
}, onError: (OnLinkErrorException e) async {
print('onLinkError');
print(e.message);
});
}
}
handleLink method
void handleLink(Uri link) async {
if (link != null) {
final User user = (await _auth.signInWithEmailAndLink(
email: _userEmail,
link: link.toString(),
))
.user;
if (user != null) {
setState(() {
_userID = user.uid;
_success = true;
});
} else {
setState(() {
_success = false;
});
}
} else {
setState(() {
_success = false;
});
}
setState(() {});
}
main method (initializing firebase)
void main() async {
WidgetsFlutterBinding.ensureInitialized();
await Firebase.initializeApp();
runApp(MyApp());
}
Note: - Deeplink is a concept still new to me in flutter, so I have set it randomly in firebase. My register page has a WidgetsBindingObserver to help resume app lifecycle state. My signup and sign in code is in an email widget and I'm not using forms to validate my textfields.
it seems that after you signin with email link the firebase instance has been not updated before checking auth.SigninWithEMailLink() you need to update it in firebase instance like this
var user= await auth.currentUser();
await user.reload();
user=await auth.currentUser();
if still you are facing problem please provide some more code to let me understand your issue properly

Resources