Flutter code in widgetsBinding.instance.addPostFrameCallback getting called multiple times - firebase

I am building a sign in functionality using bloc pattern, if the entered credentials are invalid, bloc will return a authErrorState, so I will display a invalid credentials popup as soon as the bloc return a authError State
please check the code :
if (state is IsAuthLoadingState) {
return const LoadingSpinnerWidget();
} else if (state is IsAuthenticatedState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
stopTimer();
BlocProvider.of<AuthBloc>(context).add(LoadAuthStatus());
Navigator.pop(context, true);
});
} else if (state is AuthErrorState) {
WidgetsBinding.instance.addPostFrameCallback((_) {
stopTimer();
showCustomPopUp(state.message);
});
}
Bloc code :
void _onLoginUser(LoginUser event, Emitter<AuthState> emit) async {
emit(IsAuthLoadingState());
final UserLoggedInResponse userDetails =
await authRepository.handleLoginUser(event.phoneNumber, event.otp);
if (userDetails.status == "success") {
for (var item in userDetails.wishlist) {
await _localRepo.addWishlistItem(item);
}
for (var item in userDetails.cart) {
await _localRepo.addCartItem(item);
}
for (var item in userDetails.recentSearches) {
await _localRepo.addRecentSearchTerm(item);
}
await _localRepo.addPurchasedItems(userDetails.purchasedItemIds);
await _localRepo.setIsAuthenticated(
userDetails.accessToken, userDetails.userId);
emit(IsAuthenticatedState());
} else {
emit(AuthErrorState(
message: userDetails.message, content: userDetails.content));
}
}
But, the invalid credentials popup written in authErrorState is getting called multiple times.
Any help is really appreciated. Thank you.

As I didn't found any alternative options, I someone tried to manage this for now like this,
I used a bool variable called isErrorShown, and it was set to false by default,
once the code in widgetsBinding is executed, it will set the isErrorShown to true, function is widgetsBinding checks the value of isErrorShown and executes only if it is false :
else if (state is AuthErrorState) {
print("error state");
WidgetsBinding.instance.addPostFrameCallback((_) {
if (!isErrorShown) {
stopTimer();
if (state.message ==
"user does not exits, please create user") {
Navigator.pushReplacementNamed(context, '/create-user',
arguments: CreateUserPage(
showProfile: widget.showProfile,
phoneNumber: phoneNumberController.text,
otp: otpController.text,
));
// BlocProvider.of<AuthBloc>(context).add(LoadAuthStatus());
// Navigator.pushNamed(context, '/create-user');
} else {
showCustomPopUp(state.message);
}
isErrorShown = true;
}
});

Related

finding a nil value in session when accessing value in view

So i have a sessionStore:
class SessionStore: ObservableObject {
var handle: AuthStateDidChangeListenerHandle?
#Published var isLoggedIn = false
#Published var userInSession: User?
func listenAuthenticationState() {
handle = Auth.auth().addStateDidChangeListener({(auth, user) in
if let user = user {
let firestoreGetUser = Firestore.firestore().collection("users").document(user.uid)
firestoreGetUser.getDocument{(document, error) in
if let dict = document?.data() {
guard let decodedUser = try? User.init(fromDictionary: dict) else { return }
self.userInSession = decodedUser
print("decoded user = \(decodedUser)")
}
}
self.isLoggedIn = true
print("user logged in")
} else {
self.isLoggedIn = false
self.userInSession = nil
print("no one logged in")
}
})
}
func logout() {
do {
try Auth.auth().signOut()
} catch {
}
}
func unbind() {
if let handle = handle {
Auth.auth().removeStateDidChangeListener(handle)
}
}
deinit {
unbind()
}
}
Its working as expected, I am able to sign in etc.
I have the following to pull the current user data:
import Foundation
import Firebase
import FirebaseAuth
import FirebaseFirestore
class ProfileViewModel: ObservableObject {
var uid: String = ""
var email: String = ""
var username: String = ""
var profileURL: String = ""
var bio: String = ""
var occupation: String = ""
var city: String = ""
func LoadAUser(userId: String) {
Firestore.firestore().collection("users").document(userId).getDocument{(snapshot, error) in
guard let snap = snapshot else {
print("error fetching data")
return
}
let dict = snap.data()
guard let decodedUser = try? User.init(fromDictionary: dict!) else { return }
print("decoded user - load user - \(decodedUser)")
}
}
}
In my view im trying to call it like:
import SwiftUI
struct ProfileView: View {
#EnvironmentObject var session: SessionStore
#ObservedObject var profileViewModel = ProfileViewModel()
func loadUserData() {
profileViewModel.LoadAUser(userId: session.userInSession!.uid)
}
var body: some View {
VStack {
Text("Edit Profile")
.fontWeight(.semibold)
.font(.system(.title, design: .rounded))
.foregroundColor(Color("startColor"))
Spacer()
VStack(alignment: .leading) {
Text("view")
}.padding()
.onAppear(perform: loadUserData)
}
}
struct ProfileView_Previews: PreviewProvider {
static var previews: some View {
ProfileView()
}
}
Im using .onAppear(perform: loadUserData) which is causing an issue - Thread1: Fatal error: Unexpectedly found nil while unwrapping
I also tried:
init() {
profileViewModel.LoadAUser(userId: session.userInSession!.uid)
}
But this also causes the same error.
The thing is I should only be able to get to this view if I'm logged in as this already works:
struct InitialView: View {
#EnvironmentObject var session: SessionStore
func listen() {
session.listenAuthenticationState()
}
var body: some View {
Group {
if session.isLoggedIn {
MainView()
} else {
NavigationView {
SignUpView()
}
}
}.onAppear(perform: listen)
}
}
I have an initialView()
struct InitialView: View {
#EnvironmentObject var session: SessionStore
func listen() {
session.listenAuthenticationState()
}
var body: some View {
Group {
if session.isLoggedIn {
MainView()
} else {
NavigationView {
SignUpView()
}
}
}.onAppear(perform: listen)
}
}
which takes you to the MainView() which has tabs to control which screen you can navigate to, then from here i can go to ProfileView()
Anyway by the logic of provided code it is more correct to activate isLoggedIn in
let firestoreGetUser = Firestore.firestore().collection("users").document(user.uid)
firestoreGetUser.getDocument{(document, error) in
if let dict = document?.data() {
guard let decodedUser = try? User.init(fromDictionary: dict) else { return }
self.userInSession = decodedUser
print("decoded user = \(decodedUser)")
self.isLoggedIn = true // << here !!
print("user logged in")
}
}
So whats worked for me is passing in Auth instead of session data:
func loadUserData() {
profileViewModel.LoadAUser(userId: Auth.auth().currentUser!.uid)
}

Firebase: is it good enough to check if reference exists before start a transaction

I wonder if it is good enough to test if the reference exists
BEFORE I start a transaction on this reference?
e.g: by using .once('value') and snapshot.exists()
I mean if the check is outside the transaction isn't there a risk another user to delete the reference just after the check and before the transacton executor function?
==== edited to include the minimal complete code =====
here is my data in realtime database:
activeOffers
-LKohyZ58cnzn0vCnt9p
details
direction: "city"
seatsCount: 2
timeToGo: 5
uid: "-ABSIFJ0vCnt9p8387a" ---- offering user
And here is my code flow:
===== index.js =====
entries = require('./entries');
/// cloud function
exports.TEST_askOfferSeats = functions.https.onCall((data, context) => {
console.log('data: ' + JSON.stringify(data));
return entries.askSeats(data);
});
here is my test data sent by Postman:
{
"data":
{
"uid": "-FGKKSDFGK12387sddd", ---- the requesting/asking user
"id": "-LKpCACQlL25XTWJ0OV_",
"details":
{
"direction": "city",
"seatsCount": 1,
"timeToGo": 5
}
}
}
===== entries.js =======
exports.askSeats = function(data) {
const TAG = '[askSeats]: ';
var entryRef = db.ref('activeOffers/' + data.id);
return globals.exists(entryRef)
.then((found)=>{
if (found) {
return dealSeats(entryRef, data);
} else {
return 'Offer not found [' + data.id + ']';
}
});
}
===== globals.js ======
exports.exists = (ref)=>{
return ref.once('value')
.then((snapshot)=>{
return (snapshot.exists());
});
}
===== entries.js =====
dealSeats = function(entryRef, data) {
const TAG = '[dealSeats]: ';
return entryRef.transaction((entry)=>{
if (entry) {
if ((entry.deals) && (entry.deals[data.uid])) {
throw new Error('You've already made a deal.');
} else if (entry.details.seatsCount >= data.details.seatsCount) {
entry.details.seatsCount -= data.details.seatsCount;
var deal = [];
deal.status = 'asked';
deal.details = data.details;
if (!entry.deals) {
entry.deals = {};
}
entry.deals[data.uid] = deal;
} else {
throw new Error('Not enought seats.');
}
}
return entry;
})
.then((success)=>{
return success.snapshot.val();
})
.catch((error)=>{
return Promise.reject(error);
});
}
Btw: is this 'throw new Error(......)' is the correct way to break the transaction ?
========= updated with final source ===
Thanks to Doug Stevenson.
So here is my final source that is working fine. If someone sees a potential problem please let me know. Thanks.
dealSeats = function(entryRef, data) {
const TAG = '[dealSeats]: ';
var abortReason;
return entryRef.transaction((entry)=>{
if (entry) {
if ((entry.deals) && (entry.deals[data.uid])) {
abortReason = 'You already made a reservation';
return; // abort transaction
} else if (entry.details.seatsCount >= data.details.seatsCount) {
entry.details.seatsCount -= data.details.seatsCount;
var deal = [];
deal.status = 'asked';
deal.details = data.details;
if (!entry.deals) {
entry.deals = {};
}
entry.deals[data.uid] = deal;
// Reservation is made
} else {
abortReason = 'Not enought seats';
return; // abort transaction
}
}
return entry;
})
.then((result)=>{ // resolved
if (!result.committed) { // aborted
return abortReason;
} else {
let value = result.snapshot.val();
if (value) {
return value;
} else {
return 'Offer does not exists';
}
}
})
.catch((reason)=>{ // rejected
return Promise.reject(reason);
});
}
If you read a value before a transaction, then read it again inside the transaction, you have absolutely no guarantee that the second read inside the transaction will yield the same result as the initial read outside before the transaction. It could be modified by the time the transaction is performed.
If you want a truly atomic update, only check value that participate in the transaction within the transaction itself, and make a decision about what to do in the transaction handler.

Angular Http Request .subscribe() cause an infinite loop

I want to write a function loggedIn() in file auth.service.ts to check the token from local storage, and then verify it with firebase/php-jwt in server side. But the code in Typescript gives an infinite loop. Here is my code:
auth.service.ts
loggedIn(){
const token: string = localStorage.getItem('id_token');
if (token == null) {
return false;
}
else {
const subs = this.http.post('http://localhost/url/to/myPHP.php', {"token":token})
.map(res=>res.json()).subscribe(data=>{
if(data.valid){
this.valid = true;
} else {
this.valid = false;
}
},
err=>console.log(err));
if (this.valid){
console.log("Valid");
return true;
} else {
console.log("Invalid");
return false;
}
}
}
Given token: valid token.
Result: give no error but infinite console.log of 'Valid' as well as return true, until the Apache down.
Given token: invalid token
Result: give no error but infinite console.log of 'Invalid' as well as return false, until the Apache down.
What I have tried:
loggedIn(){
const token: string = localStorage.getItem('id_token');
if (token == null) {
return false;
}
else {
const subs = this.http.post('http://localhost/url/to/myPHP.php', {"token":token})
.map(res=>res.json()).subscribe(data=>{
if(data.valid){
this.valid = true;
} else {
this.valid = false;
}
},
err=>console.log(err));
if (this.valid){
console.log("Valid");
console.log(this.valid);
return true;
} else {
console.log("Invalid");
console.log(this.valid);
return false;
}
subs.unsubscribe();
return true;
}
}
The line subs.unsubscribe(); did stop the loop, yet it will literally unsubscribe the Observable<Response> and the code inside .subscribe() will not run. Please help.
Edit: Usage of loggedIn()
*ngIf="authService.loggedIn()
for 4 times in navbar component.
Inside auth.guard.ts
canActivate(){
if (this.authService.validToken){
return true;
} else {
this.router.navigate(['/login']);
return false;
}
}
In app.module.ts
{path:'profile', component:ProfileComponent, canActivate:[AuthGuard]}
I finally solve the problem. The problem with my code: .subscribe() is an async call (which actually scheduled for last/later execution). This is the situation:ngOnInit() : which located in component < ts > file
ngOnInit(){
this.authService.loggedIn();
console.log("10");
}
loggedIn() : which located in auth.service.ts file
loggedIn(){
const token: string = localStorage.getItem('id_token');
if (token == null) {
return false;
}
else {
const subs = this.http.post('http://localhost/url/to/myPHP.php', {"token":token})
.map(res=>res.json()).subscribe(data=>{
if(data.valid){
console.log("1");
} else {
console.log("2");
}
console.log("3")
},
err=>console.log(err));
}
}
and then the result will be :
1013 which mean, anything you do inside the subscribe will change only after you need it (in many cases). So we need to do whatever we need to, inside the subscribe(), and fire it only when needed. In my case, I want to fire it if any changes apply to token inside local storage.
This is my solution
As I want it always check with the token. I used DoCheck()
Inside auth.service.ts
verifyToken(authToken) {
const body = {token:authToken};
return this.http.post('http://localhost/url/to/myPHP.php',body)
.map(res => res.json());
}
tokenExist(): boolean {
const token: string = localStorage.getItem('id_token');
if (token == null) {
return false;
} else {
return true;
}
}
Inside navbar.component.ts
ngOnInit() {
this.curToken = localStorage.getItem('id_token');
this.loggedIn(this.curToken);
}
ngDoCheck(){
if (this.curToken != localStorage.getItem('id_token')){
this.curToken = localStorage.getItem('id_token');
this.loggedIn(this.curToken);
}
}
loggedIn(token){
if(this.authService.tokenExist()){
this.authService.verifyToken(token).subscribe(data=>{
if(data.valid){
this.validLogin = true;
} else {
console.log("Invalid Token");
this.validLogin = false;
}
});
} else {
console.log("Token Missing");
this.validLogin = false;
}
}
Of course, don't forget to implement DoCheck in the line
export class NavbarComponent implements OnInit, DoCheck

how to call a function after press "on action selected" in react native Toolbar

I implemented a React native Toolbar its showing me all actions which i have given but after press on that any action it gives me error. Its entering in the function that onActionSelected But after that my Logout() , Move() any function is not working.
where i am going wrong plz help
code:
<ToolbarAndroid
title="Shopcon"
style={styles.toolbar}
actions={toolbarActions}
onActionSelected={this.onActionSelected}
/>
const toolbarActions = [
{title: 'Logout', show: 'never'},
{title: 'Got to Login', show: 'never'},
];
onActionSelected(position) {
if (position === 0) {
console.log("I am in 0");
this.Logout();
}
if (position === 1) {
console.log("I am in 1");
this.Move();
}
}
async Logout() {
console.log("fun1");
const { navigate } = this.props.navigation;
try {
await AsyncStorage.removeItem(STORAGE_KEY);
Alert.alert("Logout Success! Token:" + DEMO_TOKEN)
} catch (error) {
console.log('AsyncStorage error: ' + error.message);
}
navigate("Login");
}
Move(){
console.log("fun2")
const { navigate } = this.props.navigation;
navigate("Login");
}
both are entering in onActionSelected(position) function but not entering in those other functions.
where i am going wrong please help.

How do I dynamically publish collections via a Meteor method?

I dynamically create collections with this method:
createPlist: function(jid) {
try {
Plist[jid] = new Meteor.Collection(pid);
} catch(e) {
console.log("oops, I did it again");
}
Plist[jid].insert({
...,
...,
public:true,
uid:this.userId
});
}
Then I am wanting to publish these selectively, and I am attempting to do it via a method:
getPlist: function(jid,pid) {
// var future = new Future();
try {
Plist[jid] = new Meteor.Collection(pid);
} catch(e) {
console.log("oops, I did it again");
}
Meteor.publish(pid, function() {
console.log(Plist[jid].find({}));
// future["return"](Plist[jid].find({}));
return Plist[jid].find();
});
// return future.wait();
},
This returns 'undefined' to my Template helper, and returns nothing (i.e. waits forever) using Future.
Any user can log in and create a Plist collection, which can be either public or not. A user can also subscribe to any collection where public is true. The variable jid is passed to the method 'getPlist' from the template. It is stored in the user's Session.
Thanks! I hope I have explained it well enough!
And of course the template:
Template.plist.helpers({
getPlist: function() {
Pl = []
jid = Session.get('jid');
//alert(jid);
pid = "pl_"+jid;
// console.log(pid);
Meteor.call('getPlist', jid, pid, function(err,res) {
console.log(res); //returns undefined
try {
Pl[jid] = new Meteor.Collection(pid);
} catch(e) {
console.log(e);
}
Meteor.subscribe(pid);
// return Pl[jid].find({}).fetch();
});
}

Resources