StreamSubscription not cancel properly - flutter

I am following this guide to build my App. Then, I add a document listener to the "users" collection. Now the problem is even if I log out of the first account and log in to the second account, the stream is still reading the previous document if I manually change the document using the console.
Account _userInfo = Account(email: '', name: '', typeName: '', fcmToken: '', isDisable: false);
Account get userInfo => _userInfo;
StreamSubscription<DocumentSnapshot>? userListener;
Future<void> init() async {
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform,
);
FirebaseAuth.instance.userChanges().listen((user) async {
if (user != null) {
print('User != null');
_loginState = ApplicationLoginState.loggedIn;
await setFcmToken();
listenToMyself();
} else {
print('User null');
userListener?.cancel();
_loginState = ApplicationLoginState.loggedOut;
}
notifyListeners();
});
void listenToMyself() {
print('Listening to: ${FirebaseAuth.instance.currentUser!.uid}');
userListener = usersCollection.doc(FirebaseAuth.instance.currentUser!.uid).snapshots().listen(
(event) {
print("current data: ${FirebaseAuth.instance.currentUser!.uid} ${event.data()}");
_userInfo = Account.fromJson(event.data() as Map<String, dynamic>);
notifyListeners();
},
onError: (error) => print("Listen failed: $error"),
);
}
Future<void> setFcmToken() async {
String? token = await FirebaseMessaging.instance.getToken();
_token = token;
await FirebaseFirestore.instance.collection('users').doc(FirebaseAuth.instance.currentUser!.uid).set({'fcmToken': token}, SetOptions(merge: true));
}

Related

Flutter Stripe paymentsheet is opening webpage (hooks.stripe.com) while processing payments

I am developing a flutter application which uses stripe for payments. I am using https://pub.dev/packages/flutter_stripe for this.
Everything is working fine but whenever I initiate payments I always get a webpage middleware (Screenshots attached). What am I doing wrong?
Here is my implementation in Flutter
Future<void> makePayment(String planName, String type) async {
Fluttertoast.showToast(msg: "initiating Payments, Please wait.");
ApiProvider provider = ApiProvider();
final tokenResponse = await provider
.getPaymentToken(PlanPayment(planName: planName, type: type));
if (tokenResponse != null) {`
var _service = locator<NavigationService>();
String secret = tokenResponse.clientSecret;
// make a get call from this url
Map<String, dynamic> paymentIntentData = Map();
await payment.Stripe.instance.initPaymentSheet(
paymentSheetParameters: payment.SetupPaymentSheetParameters(
merchantCountryCode: 'IN',
testEnv: true,
paymentIntentClientSecret: secret,
googlePay: true,
));
try {
// await Stripe.instance.handleCardAction(secret);
await payment.Stripe.instance.presentPaymentSheet().then((value) {});
await payment.Stripe.instance
.confirmPaymentSheetPayment()
.then((value) async {
// await _service.pushNamed(paymentStatus, args: {'isSuccess': true});
});
} catch (e) {
// await _service.pushNamed(paymentStatus, args: {'isSuccess': false});
print("Stripe error" + e.toString());
}
await provider
.confirmPayment(tokenResponse.transactionId)
.then((value) async {
await _service
.pushReplacementNamed(paymentStatus, args: {"isSuccess": value});
});
}
}
`
Maybe you have a webhook put in your account?
1)Provide valid Secret key
2)Provide valid Publisable key
3)Update flutterstripe pacakge
4)provide valid currency code to create stripe account country
ex :- stripe account create india to inr etc..
5)Right Way to implemet
- Main.dart to main method run app to implemet
ex :--
Stripe.publishableKey = "your publishable key ";
- create controller / method
code:-
Map<String, dynamic>? paymentIntentData;
Future<void> makePayment({amount}) async {
try {
paymentIntentData =
await createPaymentIntent(amount: amount, currency: 'INR');
if (paymentIntentData != null) {
await Stripe.instance.initPaymentSheet(
paymentSheetParameters: SetupPaymentSheetParameters(
// applePay: true,
googlePay: const PaymentSheetGooglePay(merchantCountryCode: 'INR'),
merchantDisplayName: "PGA",
customerId: paymentIntentData!['customer'],
paymentIntentClientSecret: paymentIntentData!['client_secret'],
customerEphemeralKeySecret: paymentIntentData!['ephemeralkey'],
));
}
displayPaymentSheet();
} catch (err) {
logger.e(err);
}
}
void displayPaymentSheet() async {
try {
await Stripe.instance.presentPaymentSheet();
Get.snackbar("PaymentInfo", "Payment Successfully");
} on Exception catch (e) {
if (e is StripeException) {
logger.e(e, "Error From Stripe");
} else {
logger.e(e, "Unforeseen error");
}
} catch (e) {
logger.e("exeption === $e");
}
}
var id = "";
createPaymentIntent({amount, currency}) async {
try {
Map<String, dynamic> body = {
'amount': calculateAmount(amount: amount),
'currency': currency,
'payment_method_types[]': 'card'
};
var response = await http.post(
Uri.parse('https://api.stripe.com/v1/payment_intents'),
headers: {
'Authorization':
'Bearer YourSecretKey',
'Content-Type': 'application/x-www-form-urlencoded'
},
body: body,
);
if (response.statusCode == 200) {
var decode = jsonDecode(response.body);
logger.e(decode);
id = decode['id'];
return decode;
}
} catch (e) {
logger.e(e, "error charging user");
}
}
calculateAmount({amount}) {
logger.e(amount.round());
final a = (int.parse(amount.toString())) * 100;
logger.e(a.toString());
update();
return a.toString();
}
6) How to Access Stripe payment :-
ex :- any button click
ontap : (){
makePayment(
amount: "200")
}

Flutter - Refresh page if data isn't loaded on screen

I have a Future.delayed in my initState function that gets jwt cache and uses it to get the logged in user's details. The initState function is:
#override
void initState() {
super.initState();
Future.delayed(Duration.zero, () async {
final token = await CacheService().readCache(key: "jwt");
if (token != null) {
await Provider.of<ProfileNotifier>(context, listen: false)
.decodeUserData(
context: context,
token: token,
option: 'home',
);
}
});
}
Now it does work and I do get the data but not on the first run. I have to either hot reload the emulator or navigate to another page and come back for the page to rebuild itself and show the data on screen. I don't understand why it doesn't show the data on the first run itself.
I tried to add conditional to build method to run setState and initState again if data is not there.
#override
Widget build(BuildContext context) {
ProfileModel profile =
Provider.of<ProfileNotifier>(context, listen: false).profile;
if (profile.profileName.isEmpty) {
print('reloading to get data');
initState();
setState(() {});
} ....
And it doesn't run coz the data is there but somehow it doesn't show up on the screen till the page is refreshed. I can't seem to figure out the problem here. Please help.
EDIT: ProfileNotifier class:
class ProfileNotifier extends ChangeNotifier {
final ProfileAPI _profileAPI = ProfileAPI();
final CacheService _cacheService = CacheService();
ProfileModel _profile = ProfileModel(
profileImage: "",
profileName: "",
profileBio: "",
);
AccountModel _account = AccountModel(
userId: "",
userEmail: "",
userPassword: "",
);
ProfileModel get profile => _profile;
AccountModel get account => _account;
Future decodeUserData({
required BuildContext context,
required String token,
required String option,
}) async {
try {
_profileAPI.decodeUserData(token: token).then((value) async {
final Map<String, dynamic> parsedData = await jsonDecode(value);
var userData = parsedData['data'];
if (userData != null) {
List<String>? userProfileData = await _cacheService.readProfileCache(
key: userData['userData']['id'],
);
if (userProfileData == null) {
final isProfileAvailable =
await Provider.of<ProfileNotifier>(context, listen: false)
.getProfile(
context: context,
userEmail: userData['userData']['userEmail'],
);
if (isProfileAvailable is ProfileModel) {
_profile = isProfileAvailable;
} else {
_account = AccountModel(
userId: userData['userData']['id'],
userEmail: userData['userData']['userEmail'],
userPassword: userData['userData']['userPassword'],
);
_profile = ProfileModel(
profileImage: '',
profileName: '',
);
}
if (option != 'profileCreation' && isProfileAvailable == false) {
Navigator.of(context).pushReplacementNamed(ProfileCreationRoute);
}
} else {
_account = AccountModel(
userId: userData['userData']['id'],
userEmail: userData['userData']['userEmail'],
userPassword: userData['userData']['userPassword'],
);
_profile = ProfileModel(
profileName: userProfileData[3],
profileImage: userProfileData[4],
profileBio: userProfileData[5],
);
}
} else {
Navigator.of(context).pushReplacementNamed(AuthRoute);
}
notifyListeners();
});
} catch (e) {
debugPrint('account/profileNotifier decode error: ' + e.toString());
}
}
Future getProfile({
required BuildContext context,
required String userEmail,
}) async {
try {
var getProfileData = await _profileAPI.getProfile(
userEmail: userEmail,
);
final Map<String, dynamic> parsedProfileData =
await jsonDecode(getProfileData);
bool isReceived = parsedProfileData["received"];
dynamic profileData = parsedProfileData["data"];
if (isReceived && profileData != 'Fill some info') {
Map<String, dynamic> data = {
'id': (profileData['account']['id']).toString(),
'userEmail': profileData['account']['userEmail'],
'userPassword': profileData['account']['userPassword'],
'profile': {
'profileName': profileData['profileName'],
'profileImage': profileData['profileImage'],
'profileBio': profileData['profileBio'],
}
};
AccountModel accountModel = AccountModel.fromJson(
map: data,
);
return accountModel;
} else {
return false;
}
} catch (e) {
debugPrint('profileNotifier getProfile error: ' + e.toString());
}
}
Future setProfile({
required String profileName,
required String profileImage,
required String profileBio,
}) async {
_profile.profileName = profileName;
_profile.profileImage = profileImage;
_profile.profileBio = profileBio;
await _cacheService.writeProfileCache(
key: _account.userId,
value: [
_account.userId,
_account.userEmail,
_account.userPassword as String,
profileName,
profileImage,
profileBio,
],
);
notifyListeners();
}
}
I've removed the create profile, update profile and profile image upload methods in the notifier as they are not involved here.
The CacheService class using shared_preferences package is:
class CacheService {
Future<String?> readCache({
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
String? cache = await sharedPreferences.getString(key);
return cache;
}
Future<List<String>?> readProfileCache({
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
List<String>? cachedData = await sharedPreferences.getStringList(key);
return cachedData;
}
Future writeCache({required String key, required String value}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.setString(key, value);
}
Future writeProfileCache(
{required String key, required List<String> value}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.setStringList(key, value);
}
Future deleteCache({
required BuildContext context,
required String key,
}) async {
final SharedPreferences sharedPreferences =
await SharedPreferences.getInstance();
await sharedPreferences.remove(key).whenComplete(() {
Navigator.of(context).pushReplacementNamed(AuthRoute);
});
}
}
I'm not sure if the code is completely optimized. Any improvement is welcome. Thanks.

Getting NoSuchMethodError: The getter 'uid' was called on null flutter

I'm pretty new to flutter firebase and I am getting this error NoSuchMethodError: The getter 'uid' was called on null flutter. I have searched and looked very carefully but left scratching my head only where is the error. Over on my condition I'm checking if user is not equal to null then save it but its not happening.
Here's my phone authentication file code which should save the user as well but its not doing it right now because of the null issue
class PhoneAuthService {
FirebaseAuth auth = FirebaseAuth.instance;
User user = FirebaseAuth.instance.currentUser;
CollectionReference users = FirebaseFirestore.instance.collection('users');
Future<void> addUser(context, uid) async {
final QuerySnapshot result = await users.where('uid', isEqualTo: uid).get();
List<DocumentSnapshot> document = result.docs;
if (document.length > 0) {
Navigator.pushReplacementNamed(context, LocationScreen.id);
} else {
return users.doc(user.uid).set({
'uid': user.uid,
'mobile': user.phoneNumber,
'email': user.email,
}).then((value) {
Navigator.pushReplacementNamed(context, LocationScreen.id);
}).catchError((error) => print("Failed to add user: $error"));
}
}
Future<void> verifyPhonenumber(BuildContext context, number) async {
final PhoneVerificationCompleted verificationCompleted =
(PhoneAuthCredential credential) async {
await auth.signInWithCredential(credential);
};
final PhoneVerificationFailed verificationFailed =
(FirebaseAuthException e) {
if (e.code == 'invalid-phone-number') {
print('The phone number is not valid');
}
print("The error is ${e.code}");
};
final PhoneCodeSent codeSent = (String verId, int resendToken) async {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => OTPScreen(
number: number,
verId: verId,
)));
};
try {
auth.verifyPhoneNumber(
phoneNumber: number,
verificationCompleted: verificationCompleted,
verificationFailed: verificationFailed,
codeSent: codeSent,
timeout: const Duration(seconds: 60),
codeAutoRetrievalTimeout: (String verificationId) {
print(verificationId);
});
} catch (e) {
print("Error ${e.toString()}");
}
}
}
This is where I'm getting the user otp and proceeding it further
Future<void> phoneCredential(BuildContext context, String otp) async {
FirebaseAuth _auth = FirebaseAuth.instance;
try {
PhoneAuthCredential credential = PhoneAuthProvider.credential(
verificationId: widget.verId, smsCode: otp);
final User user = (await _auth.signInWithCredential(credential)).user;
if (user != null) {
_services.addUser(context, user.uid);
Navigator.pushReplacementNamed(context, LocationScreen.id);
} else {
print("Login Process Failed ");
if (mounted) {
setState(() {
error = 'Login Failed';
});
}
}
} catch (e) {
print(e.toString());
if (mounted) {
setState(() {
error = 'Invalid OTP';
});
}
}
}
If anyone understood where I'm doing wrong please help I'm stuck for 3 days on it now.

Flutter bloc registration trouble

I have bloc, which listen to changes of currentUser state from firebase.
AuthBloc({#required AuthService authService})
: assert(authService != null),
_authService = authService,
super(AuthInitial()) {
_userSubscription = _authService.currentUser
.listen((user) => add(AuthenticationUserChanged(user)));
}
Then add event and the event call this function
Stream<AuthState> _mapAuthenticationUserChangedToState(
AuthenticationUserChanged event) async* {
if (event.user != null) {
// TO GET CUSTOM USER FROM FIRESTORE because of expiration, etc.
var user = await _authService.getUser(event.user.uid);
if (user.expiration == "") {
yield NotAuthorized(user);
} else {
var isAfter =
DateTime.now().toUtc().isAfter(DateTime.fromMillisecondsSinceEpoch(
user.expiration.millisecondsSinceEpoch,
isUtc: false,
).toUtc());
if (isAfter) {
yield NotAuthorized(user);
} else {
yield Authenticated(event.user);
}
}
} else {
yield Unautheticated();
}
But in my RegisterCubit I have signUpSubmitted method, which creates Firebase user and immediately after this is called my AuthBloc, which make sense.
But in my Bloc i need my custom Firestore user, which i have to create after FirebaseUser creation(because I need UID and email).
And this is the problem.
Future<void> signUpFormSubmitted() async {
if (!state.status.isValidated) return;
emit(state.copyWith(status: FormzStatus.submissionInProgress));
try {
await _authService
.registerUser(state.email.value, state.password.value)
.then((value) async {
// THIS IS CALLED AFTER BLOC
// I NEED TO CALL IT AFTER _registerUser() but in front of BLOC
var user =
ApplicationUser(uid: value.user.uid, email: value.user.email);
await _authService.addUserToDocument(user);
});
emit(state.copyWith(status: FormzStatus.submissionSuccess));
} on Exception {
print(Exception);
emit(state.copyWith(status: FormzStatus.submissionFailure));
}
}
My AuthService methods
Future<UserCredential> registerUser(email, password) =>
_auth.createUserWithEmailAndPassword(email: email, password: password);
Future<void> addUserToDocument(ApplicationUser user) {
return _db.collection('users').doc(user.uid).set({
'uid': user.uid,
'firstname': '',
'lastname': '',
'age': '',
'expiration': '',
'email': user.email
});
}

Flutter | Getting Firebase Email Link Login Data

I have difficulty implementing the Email Link login with Firebase.
I send the email link using:
_firebaseAuth.sendSignInLinkToEmail(
email: email,
actionCodeSettings: ActionCodeSettings(
url: 'https://subdomain.example.com/user-auth', //<subdomain.example.com> = my real domain
handleCodeInApp: true,
androidInstallApp: true,
androidPackageName: 'com.example.app',
),
);
Email is sent and when clicking I open the link using the DynamicLink package:
void _handleDynamicLinks() {
FirebaseDynamicLinks.instance.onLink(onSuccess: _onSuccess);
}
Future<dynamic> _onSuccess(PendingDynamicLinkData data) async {
print('---onLink---');
// How to pass signIn link to `isSignInWithEmailLink` and `signInWithEmailLink` ???
// data.link returns `https://subdomain.example.com/user-auth` which is not the complete link
}
Every method I call on PendingDynamicLinkData data doesn't return the full dynamic link and isSignInWithEmailLink returns false!
Try this in your _handleDynamicLink function.
try {
FirebaseDynamicLinks.instance.onLink.listen((dynamicLink) {
final Uri? deepLink = dynamicLink.link;
if (deepLink != null) {
emailLinkService.handleLink(deepLink, _emailController.text);
FirebaseDynamicLinks.instance.onLink.listen((dynamicLink) {
final Uri? deepLink = dynamicLink.link;
emailLinkService.handleLink(deepLink!, _emailController.text);
}, onError: (e) async {
print(e.message);
});
}
}, onError: (e) async {
print(e.message);
});
final PendingDynamicLinkData? data =
await FirebaseDynamicLinks.instance.getInitialLink();
final Uri? deepLink = data?.link;
print('deepLink :: $deepLink');
} catch (e) {
// you can print this error as well
}
And check if your url is the same as here:
And also add the Dynamic link as your custom Authorised domain like this:
Here is the handleLink method:
class EmailLinkService {
final FirebaseAuth _auth = FirebaseAuth.instance;
Future<void> signInWithEmailAndLink(
{required String userEmail}) async {
var _userEmail = userEmail;
var acs = ActionCodeSettings(
url: Constants.firebaseProjectURL,
handleCodeInApp: true,
iOSBundleId: 'com.example....',
androidPackageName: 'com.example....',
try {
return await _auth
.sendSignInLinkToEmail(email: _userEmail, actionCodeSettings: acs);
} on FirebaseAuthException catch (e) {
}
void handleLink(Uri link, userEmail) async {
if (link != null) {
final UserCredential user =
await FirebaseAuth.instance.signInWithEmailLink(
email: userEmail,
emailLink: link.toString(),
);
} else {
print(" link is null");
}
}
}