How to cancel firebase async requests in flutter instead of checking mounted - firebase

I have a flutter app talking to the Firebase Realtime Database. I get the data asynchronously, obviously, but my UI allows the user to move to a different part of the app, which means by the time the request completes, the Widget may be unmounted. Best practices say to cancel the async work instead of checking the mounted property but I cannot seem to figure out how to do this for some reason.
#override void initState() {
super.initState();
firebaseRealtimeReference.child('myData').once().then((results) {
if (mounted) {
setState(() {
_myLocalData = results;
}
}
}
/* Alternately with async/await: */
_myLocalData = firebaseRealtimeDatabaseReference.child('myData').once();
}
#override Widget build(BuildContext context) {
return new MyWidget(_myLocalData);
}
#override dispose() {
// Instead of checking mounted in the future, I should instead
// cancel the work in progress here.
super.dispose();
}

Related

Instance member 'setCurrentScreen' can't be accessed using static access

I am trying to add Firebase Analytics to my flutter app to display different screen names on the dashboard. I used a method that I found here on stackoverflow in this link:
How do I track Flutter screens in Firebase analytics?
I added this code in a separate file:
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:flutter/widgets.dart';
// A Navigator observer that notifies RouteAwares of changes to state of their Route
final routeObserver = RouteObserver<PageRoute>();
mixin RouteAwareAnalytics<T extends StatefulWidget> on State<T>
implements RouteAware {
AnalyticsRoute get route;
#override
void didChangeDependencies() {
routeObserver.subscribe(this, ModalRoute.of(context) as PageRoute<dynamic>);
super.didChangeDependencies();
}
#override
void dispose() {
routeObserver.unsubscribe(this);
super.dispose();
}
#override
void didPop() {}
#override
void didPopNext() {
// Called when the top route has been popped off,
// and the current route shows up.
_setCurrentScreen(route);
}
#override
void didPush() {
// Called when the current route has been pushed.
_setCurrentScreen(route);
}
#override
void didPushNext() {}
Future<void> _setCurrentScreen(AnalyticsRoute analyticsRoute) {
print('Setting current screen to $analyticsRoute');
return FirebaseAnalytics.**setCurrentScreen**(
screenName: screenName(analyticsRoute),
screenClassOverride: screenClass(analyticsRoute),
);
}
}
enum AnalyticsRoute { screenName }
String screenClass(AnalyticsRoute route) {
switch (route) {
case AnalyticsRoute.screenName:
return 'screenName';
}
throw ArgumentError.notNull('route');
}
String screenName(AnalyticsRoute route) {
switch (route) {
case AnalyticsRoute.screenName:
return '/screenName';
}
throw ArgumentError.notNull('route');
}
I then added the route observer in the screen that I want to track.
The setCurrentScreen method is not working well and the app is not building. Any suggestions on what I could do to build it.
From the FlutterFire documentation on screen tracking:
await FirebaseAnalytics.instance
.setCurrentScreen(
screenName: 'Products'
);

Flutter + Firebase - How to dispose Listen events

I have a main widget in which based on the boolean value I show two different widgets
drawScreen ? DrawScreenWidget() : GuessScreenWidget(),
On the GuessScreenWidget() I have Listen events
sketchStroke = databaseReference
.child('ref')
.onValue
.listen((data) {
});
So when the boolean changes, the screen moves between the two widgets, but the listen events on GuessScreenWidget don't get cancelled. I tried to use dispose but it doesn't work.
#override
dispose() {
super.dispose();
sketchStroke.cancel();
}
I would appreciate if someone could guide how to cancel the listen event when the GuessScreenWidget is switched to DrawScreenWidget. Thank you in advance.
Stream<QuerySnapshot<Map<String, dynamic>>> myStream = FirebaseFirestore.instance.collection("handleCountM").limit(1).snapshots();
late StreamSubscription<QuerySnapshot<Map<String, dynamic>>> streamSubscription;
void handleDelete() {
streamSubscription = myStream.listen((value) {
value.docs.forEach((element) {
element.reference.delete();
});
});
}
#override
void dispose() {
streamSubscription.cancel(); //Cancel your subscription here.
super.dispose();
}
Your other alternative, would be to use a streambuilder, and it'll handle the subscription and termination for you.
or you can use this
late CollectionReference reference;
late StreamSubscription streamSubscription;
#override
void initState() {
// TODO: implement initState
reference = _firestore.collection(singleAuction.id.toString());
streamSubscription = reference.snapshots().listen((querySnapshot) {
for (var element in querySnapshot.docChanges) {}
});
super.initState();
}
#override
void dispose() {
// TODO: implement dispose
super.dispose();
streamSubscription.cancel();
}

Flutter - Is it possible to show the number of users online?

I want to show how many people are using my mobile application instantly in my application. For example: "342 people are currently using the application." or "342 people are online right now." I could not find a solution for this.
I store users data with Firebase. So what I want to do is possible by extracting data from the firebase?
You're simplest and most cost effective way, is to create a document, put in a collection for example called .collection(general), when a user logsIn, add 1 to that value, when they logout, subtract 1, and put this in a stream builder.
After success login, run the following function
await FirebaseFirestore.instance
.collection('general')
.doc('onlineCount)
.update({'membersOnline': FieldValue.increment(1)})//this will increase the number by 1.
);
On logout, substract 1.
this is very easy to handle this logic just save the status when users open your app for eg: on homepage and when they kill your app just update that collection to that particular is offline and at the and do query
where(user:online)
and check the number of users you got and simply show that number.
I hope you got this logic.
A little late to the party. But I would personally recommend making use of the App Lifecycle. Meaning:
detached: The application is still hosted on a flutter engine but is detached from any host views.
inactive: The application is in an inactive state and is not receiving user input. For example during a phone call.
paused: The application is not currently visible to the user and running in the background. This is when you press the Home button.
resumed: The application is visible and responding to user input. In this state, the application is in the foreground.
So you will have to create a StatefulWidget and WidgetsBindingObserver:
import 'package:flutter/material.dart';
class LifeCycleManager extends StatefulWidget {
LifeCycleManager({Key key, #required this.child}) : super(key: key);
final Widget child;
#override
_LifeCycleManagerState createState() => _LifeCycleManagerState();
}
class _LifeCycleManagerState extends State<LifeCycleManager> with WidgetsBindingObserver {
#override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
}
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
print('AppLifecycleState: $state');
}
#override
Widget build(BuildContext context) {
return widget.child;
}
#override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
}
And then just check the states as follows:
AppLifecycleState _appLifecycleState;
#override
void didChangeAppLifecycleState(AppLifecycleState state) {
setState(() {
_appLifecycleState = state;
});
if(state == AppLifecycleState.paused) {
print('AppLifecycleState state: Paused audio playback');
//update user file eg. online_status: offline
}
if(state == AppLifecycleState.resumed) {
print('AppLifecycleState state: Resumed audio playback');
//update user file eg. online_status: online
}
print('AppLifecycleState state: $state');
}

how to use singleton firebase service on Flutter?

I have 4 pages. I called getRide() method in every 4 pages. it's means 4 times database call. Am I right? Is it possible to create a singleton for this scenario?
Firebase Service:
class FirebaseService {
final Firestore _db = Firestore.instance;
Stream<List<RideModel>> getRide() {
return _db.collection('ride')
.snapshots()
.map((list) => list.documents.map((doc) => RideModel.fromFirestore(doc))
.toList());
}
}
Calling Method:
#override
void initState() {
super.initState();
db.getRide().listen(getRide);
}
void getRide(List<RideModel> model) {
if (!mounted) return;
setState(() {
rideModel = model;
});
}
I can't pass rideModel through Navigator. because when change data in ride collection need to change 4 pages UI.
Someone tells me this answer is correct for the above problem.
I found this way to solve this problem.
I used get_it package and create service locator,
GetIt locator = GetIt.instance;
void setupSingletons() async {
locator.registerLazySingleton<FirebaseService>(() => FirebaseService());
}
And then added to the main class
void main() {
setupSingletons();
runApp(MultiProvider(
providers: globalProviders,
child: MyApp(),
));
}
And every screen I added,
class _Screen1 extends State<Screen1> {
// final db = FirebaseService();
FirebaseService db = GetIt.I.get<FirebaseService>();

Flutter StreamSubscription not stopping or pausing

In my Flutter app StreamSubscription is not pausing or cancelling. When I call cancel() if it started before, it will stop. If I call cancel() after starting, it will not stop. I am using Firestore snapshot listener. Below is my code.
I have tried different methods but it's still not working. The problem is that the Firestore listener is not stopping after loading data.
StreamSubscription<QuerySnapshot> streamSubscription;
#override
void initState() {
super.initState();
print("Creating a streamSubscription...");
streamSubscription =Firestore.collection("name").document("d1").collection("d1")
.snapshots().listen((data){
//It will display items
}, onDone: () { // Not excecuting
print("Task Done");
}, onError: (error) {
print("Some Error");
});
streamSubscription.cancel(); //It will work but cancel stream before loading
}
#override
void dispose() {
streamSubscription.cancel(); //Not working
super.dispose();
}
When you push a new page, the previous page is still rendered and therefore dispose() is not called.
How to get current route path in Flutter?
Also sometimes it can happen that the widget is not rendered anymore but dispose was not yet called, which can lead to weird error messages. So adding such a check is probably a good idea as well if you use dispose.
https://docs.flutter.io/flutter/widgets/State/mounted.html
Change
//It will display items
to
if(myIsCurrentRoute && mounted) {
//It will display items
}
You are not assigning the subscription into the right variable.
StreamSubscription<QuerySnapshot> subscription;
#override
void initState() {
super.initState();
print("Creating a streamSubscription...");
subscription=Firestore.collection("name").document("d1").collection("d1")
.snapshots().listen((data){
//It will display items
}, onDone: () { // Not excecuting
print("Task Done");
}, onError: (error) {
print("Some Error");
});
subscription.cancel(); //It will work but cancel stream before loading
}
#override
void dispose() {
subscription.cancel(); //Not working
super.dispose();
}
I was experiencing the same problem and it turns out that the stream seems to keep listening for events for a while before canceling, but if you debug you will see that after dispose is called it will stop listening at some point.
Therefore, Gunter's solution works fine, since you can prevent your callback function from being called if mount is false, which means your page is no longer there.

Resources