I using https://pub.dev/packages/flutter_uploader in my project.
repository
try {
final result = await InternetAddress.lookup('google.com');
if (result.isNotEmpty && result[0].rawAddress.isNotEmpty) {
final taskId = await uploader.enqueue(
url:
'https://xxxx',
files: files,
data: {
....
},
method: UploadMethod.POST,
showNotification: true,
tag: title);
final subscription = uploader.result.listen((result) async {
print("response " + result.response);
}, onError: (ex, stacktrace) {
print(ex.toString());
});
}
} catch (e) {
...
}
}
When first time I call this, uploader.result.listen will only print once. But if I call this method again, uploader.result.listen will call twice. Why?
Edit
I have changed my code to this
PageA
StreamSubscription<UploadTaskResponse> _subscription;
FlutterUploader uploader = FlutterUploader();
#override
void initState() {
super.initState();
_subscription = uploader.result.listen(
(result) {
// insert result to database
.....
},
onError: (ex, stacktrace) {
// ... code to handle error
},
);
}
void dispose() {
super.dispose();
_subscription.cancel();
_defectBloc.dispose();
}
In page A, it has floatingActionButton. When the floating action button is clicked, it will open page B. I will pass the uploader param to PageB and bloc, so it can listen to the uploader. Data able to insert into local database if I on the init page. How can I let the insert work too if I exit the app?
When you call uploader.result.listen it'll add a subscription each time, if you call that n times, n subscription will be added.
To fix the issue, either you need to cancel previous subscription using cancel() method or you have to add the subscription only once (In your initState and cancel in your dispose method).
Related
I am using Stripe's prebuilt checkout with react and firebase. The checkout process works fine and directs the user to the succes_url, but I would like to update a field under the user in the database as well. I don't understand how I can include a function that updates the DB that runs upon a successful checkout.
export const checkoutWithStripe = async(user) => {
const checkoutSessionsRef = collection(db, 'users', user.uid, 'checkout_sessions');
const singleCheckoutSessionRef = await addDoc(checkoutSessionsRef, {
price: 'price_xyz',
allow_promotion_codes: true,
success_url: `${window.location.origin}/dashboard/app?success=true`,
cancel_url: `${window.location.origin}/dashboard/app?canceled=true`
});
onSnapshot(singleCheckoutSessionRef, (snap) => {
const { error, url: checkoutUrl } = snap.data();
if (error) {
console.error(`An checkout error occured: ${error.message}`);
}
if (checkoutUrl) {
window.location.assign(checkoutUrl);
}
});
// TODO: Update user type in firebase from free to starter on successful checkout
};
Thankful for any help.
Update:
I solved it, in 2 parts.
In Stripe I created a new webhook that points to a exported firebase function (2) that fires when "checkout.session.completed" is fired.
In Firebase i created a function that listens for the "checkout.session.completed" event and then calls a function that updates the DB based on the user email that I get from the Stripe event.
This is the Firebase function that listens to the event:
/**
* A webhook handler function for the relevant Stripe events.
*/
exports.updateCustomer = functions.https.onRequest((req, resp) => {
functions.logger.log("updateCustomer body", req);
const relevantEvents = new Set([
'checkout.session.completed'
]);
let event;
// Instead of getting the `Stripe.Event`
// object directly from `req.body`,
// use the Stripe webhooks API to make sure
// this webhook call came from a trusted source
try {
event = stripe.webhooks.constructEvent(
req.rawBody,
req.headers['stripe-signature'],
endpointSecret
);
} catch (error) {
functions.logger.log(`Webhook Error: Invalid Secret`);
resp.status(401).send('Webhook Error: Invalid Secret');
return;
}
functions.logger.log("updateCustomer", event.type);
if (relevantEvents.has(event.type)) {
// logs.startWebhookEventProcessing(event.id, event.type);
try {
switch (event.type) {
case 'checkout.session.completed':
const session = event.data.object;
functions.logger.log("checkout.session.completed:", session);
updatePlan(session.customer_details.email);
break;
default:
functions.logger.log(`Unhandled event type ${event.type}`);
}
} catch (error) {
functions.logger.log(`Unhandled event error ${event.type}`);
resp.json({
error: 'Webhook handler failed. View function logs in Firebase.',
});
return;
}
}
// Return a response to Stripe to acknowledge receipt of the event.
resp.json({ received: true });
});
If you need to run some code when the Checkout Session is successful, then you should use Stripe webhooks and listen to the checkout.session.completed event. This is covered in the Stripe documentation.
I have created a PWA template using CRA v4 and enabled the service worker that comes with it by registering it, because I needed to create a pop up notification about installing the PWA.
The lighthouse test has to pass for the app to be PWA compatible so that the browser would fire the beforeinstallprompt event listener needed to detect if the user has already installed the PWA or not.
The problem now is that this service worker is using cache-first strategy. As a result refreshing the page does not trigger an update and I am left with an older version of the app appearing after I have deployed an update.
How can I change the caching strategy of CRA v4's service worker such that the user would get a new version of the app by simply refreshing the page?
I am also interested in knowing why this cache-first strategy is used by default. To me it seems bad that the user has to close every tab to get a new version. Why haven't more people brought this up? This is clearly not user friendly...
To change the strategy you need to implement your own code changing service-worker.js and potentially
serviceWorkerRegistration.js. (https://developers.google.com/web/tools/workbox/modules/workbox-strategies)
I implement my own strategy:
check for updates at the very beginning check for updates each 3 min if there is a update in the very beginning update
cache and refresh the website. if it is after show a popup asking to fresh
serviceWorkerRegistration.ts
/**
* ...... previous code
**/
const CHECK_INTERVAL_TIME = 1000 * 60 * 3 // 3 min
function registerValidSW(swUrl: string, config?: Config) {
navigator.serviceWorker
.register(swUrl)
.then((registration) => {
registration.onupdatefound = () => {
const installingWorker = registration.installing;
if (installingWorker == null) {
return;
}
installingWorker.onstatechange = () => {
if (installingWorker.state === 'installed') {
if (navigator.serviceWorker.controller) {
// At this point, the updated precached content has been fetched,
// but the previous service worker will still serve the older
// content until all client tabs are closed.
console.log(
'New content is available and will be used when all ' +
'tabs for this page are closed. See https://cra.link/PWA.'
);
// Execute callback
if (config && config.onUpdate) {
config.onUpdate(registration);
}
} else {
// At this point, everything has been precached.
// It's the perfect time to display a
// "Content is cached for offline use." message.
console.info('Content is cached for offline use.');
// Execute callback
if (config && config.onSuccess) {
config.onSuccess(registration);
}
}
}
};
};
/****************************
start new code
*****************************/
registration.update().then(() => console.debug("Checked for update...")).catch(console.error)
setInterval(() => {
console.debug("Checked for update...");
registration.update().catch(console.error);
}, CHECK_INTERVAL_TIME);
/****************************
end new code
*****************************/
})
.catch((error) => {
console.error('Error during service worker registration:', error);
});
}
App.tsx
/**
* ...... previous code
**/
function App() {
const time = useRef(Date.now()); //can be let, depending of your logic
useEffect(() => {
serviceWorkerRegistration.register({
onSuccess(registration) {
console.debug('serviceWorkerRegistration success')
},
onUpdate(registration) {
console.debug('serviceWorkerRegistration updated',Date.now()-time.current)
const refresh=async ()=>{
await registration?.waiting.postMessage({type: 'SKIP_WAITING'}); //send message to update the code (stop waiting)
if ('caches' in window) { //delete cache, i think is no necessary but you lose nothing
const names = await caches.keys()
for (const name of names) {
await caches.delete(name)
}
}
window.location.reload();
}
if (Date.now()-time.current<=2000){
return refresh()
}
logicToShowPopup({
onClick: refresh
})
}
})
}, [])
return (<div>My App</div>)
}
I hope this suits for your needs
So i am curious when does onDataChange method occur?
It seems like it is activated when user add new information or change already existed data.
However, what I am trying to do is that, before adding new data, I want to check if the item is existing in database....if there is an identical item, adding new data won't be done, or if there is no such item, then it should be added to database.
so, my actual question is that, this process "Checking all the database items", can it be done without using onDataChange method?
You basically set up a subscription to the "onDataChange" so its actually watching firebase for changes.
But for checking you could literate through the results or do one time query to the exact path your data it held at.
It also may be a better choice to record everything and then remove the data when not needed.
import { AngularFirestore } from 'angularfire2/firestore';
import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';
import { map } from 'rxjs/operators';
import { Observable, Subscription } from 'rxjs';
import firebase as firebase from 'firebase/app';
private mysubscription: Subscription;
public this.items:any = [];
constructor(
public _DB: AngularFireDatabase
) {
try {
//subscription using AngulaFire
this.mysubscription = this._DB.list("myFireBaseDataPath").snapshotChanges().pipe(map(actions => {
return actions.map(action => ({ key: action.key, val: action.payload.val() }));
}))
.subscribe(items => {
this.items = [];
this.items = items.map(item => item);
console.log("db results",this.items);
var icount=0;
for (let i in this.items) {
console.log("key",this.items[i].key);
console.log("val",this.items[i].val);
console.log("----------------------------------);
//checking if something exists
if (this.items[i].key == 'SomeNodePath') {
var log = this.items[i].val;
}
}
} catch (e) {
console.error(e);
}
});
}
ngOnDestroy() {
this.mysubscription.unsubscribe();
}
//or we can do a one time query using just the firebase module
try {
return firebase.database().ref("myFireBaseDataPath").once('value').then(function(snapshot) { return snapshot.val(); })
.then(res => {
for (let myNode in res) {
console.log(res[myNode]);
console.warn(res[myNode].myChildPath);
console.log("----------------------------------);
}
})
.catch(error => console.log(error));
} catch (e) {
console.error(e);
}
//however it may be better practice to log all data and then firebase.database().ref(/logs").remove(); the entire log when not needed
var desc ="abc";
let newPostKey = firebase.database().ref("/logs").push();
newPostKey.set({
'info': desc,
'datetime': new Date().toISOString()
});
When does onDataChange method occur?
The onDataChange method is called for every change in the database reference it is attached to. It is also called for every visit to the database reference it is attached to.
For example,
final FirebaseDatabase database = FirebaseDatabase.getInstance();
DatabaseReference ref = database.getReference("some/database/refrence");
ref.addValueEventListener(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
// This method will be fired for any change in the
database.getReference("some/database/refrence") part of the database.
// It will also be fired anytime you request for data in the
database.getReference("some/database/refrence") part of the database
}
#Override
public void onCancelled(DatabaseError databaseError) {
System.out.println("The read failed: " + databaseError.getCode());
// This method will be fired anytime you request for data in the
database.getReference("some/database/refrence") part of the database
and an error occurred
}
});
Before adding new data, I want to check if the item is existing in database....if there is an identical item, adding new data won't be done, or if there is no such item, then it should be added to database.
This can be done by calling the exists() method on the snapshot retrieved from your database query.
Check this stackoverflow question Checking if a particular value exists in the firebase database for an answer to that
So, my actual question is that, this process "Checking all the database items", can it be done without using onDataChange method?
No. The onDataChange method is the callback used to retrieve data from the database. Even if you use the equalTo() method on a query, you'll still have to use the onDataChange method.
I am not a Firebaser Specialist tho. There are folks who work at Firebase on here. They could give you more information
PS: Please make your own research on your questions first before asking. Some questions are already answered in the documentation and on stackoverflow.
I'm learning NativeScript/Angular 2 and would need to get help with this issue.
In order to implement a multi-role login system within the Firebase platform I thought about this solution
Login the user through Firebase authentication
Query the /stores/ path for a store which has a merchantEmail field same as the e-mail that has just logged in
If I find it, I set the store ID inside a BackendService service which uses getString/setString to store tokens, then route to a MerchantDashboardComponent
If I don't find it, just route to a BuyerDashboardComponent
This is part of my code in the login.service:
login (email: string, password: string) {
return firebase.login({
type: firebase.LoginType.PASSWORD,
email: email,
password: password
}).then(
(result: any) => {
firebase.query(
(_result) => { // Here I set BackendService.storeID
Inside the .query() callback I am assigning the tokens I need in the application.
This is the method I'm using in my login.component:
doLogin () {
this.isAuthenticating = true;
if (!this.validateEmail()) {
alert("Please insert a valid email");
return false;
}
this.loginService.login(this.email, this.password).then(
() => {
this.isAuthenticating = false;
if (BackendService.loginError)
alert(BackendService.loginError)
else if (BackendService.storeID != '') {
this.router.navigate(['/merchant-dashboard'], {clearHistory: true});
}
else {
this.router.navigate(['/home/categories'], {clearHistory: true});
}
}
);
}
Everything works except for the fact that the Merchant gets routed to the Buyer dashboard. I've managed to discover that the execution order is not what I expected to be, in fact:
firebase.login() gets executed and returns a Promise
.then() handler is executed inside the doLogin() method
Only after this, the firebase.query() method completes the callback and my tokens are available, but doLogin() has already navigated the user because storeID is still empty when I need it
I hope I've been clear as much as possible.
Thanks for your attention.
Greetings,
Davide
So, the problem was in the login service method.
I now return the Promise generated by firebase.query(), which causes then() calls to chain in the correct order.
Yep that was exactly was I was going to propose to wrap it in a promise and create a chain.
example code
return new Promise<any>((resolve, reject) => {
firebase.login({ loginArguments })
.then((result: any) => {
var onQueryEvent = function (result) {
};
return firebase.query(
onQueryEvent,
"/owner",
{
// query arguments follows here
}
).then(res => {
return res;
})
})
.then(finalResult => {
console.log(finalResult);
try {
resolve(finalResult);
} catch (e) {
reject(e);
}
})
});
Basically what i try to do is to hit my API once and save the result inside global variable in my Service, and then share and modify this value in my parent and child component with two helpers functions.
repairs.service.ts
public myItems:any[];
public GetRepairs = ():Observable<any> => {
this.headers = new Headers();
this.headers.set('Authorization', 'Bearer' + ' ' + JSON.parse(window.localStorage.getItem('token')));
return this._http.get(this.actionUrl +'repairs'{headers:this.headers})
.map((res) => {return res.json();
}).map((item) => {
let result:Array<any> = [];
if (item.items) {
item.items.forEach((item) => {
result.push(item);
});
}
this.myItems = result;
return this.myItems;
});
};
public GetItems() {
return this.myItems;
};
public UpdateItems(data:any[]) {
this.myItems = data;
};
And then in my main component i do
repairs.component.ts
export class RepairsComponent implements OnInit {
public myItems:any[];
constructor(private _userService:UserService,
private _RepairsService:RepairsService,
public _GlobalService:GlobalService) {
}
ngOnInit() {
this._userService.userAuthenticate();
this.getAllItems();
}
private getAllItems():void {
this._RepairsService
.GetRepairs()
.subscribe((data) => {
this._RepairsService.UpdateItems(data);
},
error => console.log(error),
() => {
this.myItems = this._RepairsService.GetItems();
});
}
}
This work just fine but when i try to invoke GetItems() in child component i get undefinded. I try to do it inside constructor and ngOnInit with the same result.
child.component.ts
export class ChildComponent {
private items:any[] = [];
constructor(private _RepairsService:RepairsService,
private _Configuration:Configuration) {
this.items = this._RepairsService.GetItems();
// undefinded
}
ngOnInit() {
this.items = this._RepairsService.GetItems();
// undefinded
}
}
From what i can see in the limited amount of code you shared, it would seem you are trying to get the items before the http get call finishes and saves the data. I think a better design pattern would be to make the GetItems() function also an observable or promise, and check if the data is there, if not call the http get call, and once that completes send the data back to the different components that need it.
As #MSwehli mentioned with async code execution you can't rely on the order of code lines. In this code:
ngOnInit() {
this.items = this._RepairsService.GetItems();
// undefinded
}
the async code in GetItems(); is scheduled for later execution into the event queue and then continued with the sync code. The scheduled code will be executed eventually but it's not determined when. It depends on the response of the server in this example.
If you return a Promise you can use .then(...) the chain the execution so that your code is only executed when the async execution is completed.
There are two errors/inconsistencies in your code:
userAuthenticate() call followed with getAllItems() call. These calls are async, user is not yet authenticated by the time getAllItems() is called, getAllItems will fail.
Solution here is to chain calls using rxjs flatMap:
//assuming userAuthenticate returns Observable
userService.userAuthenticate().flatMap(()=>{
return repairsService.GetRepairs();
}).subscribe(..process repairs..);
getAllItems() is called nearly at the same time as GetItems(). In most cases it fails also, because previous http request is not completed when GetItems() is called.
In my opinion early initialization is not necessary here, use service directly:
//ChildComponent
ngOnInit() {
this._RepairsService.GetRepairs().subscribe(..do anything with list of repairs i.e. assign to bindable property..);
}
You could add console.log statements in each part of the code to see the order of events in your app.