Flutter StreamSubscription not stopping or pausing - firebase

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.

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();
}

Blazor InvokeAsync vs await InvokeAsync

Edit: for clarification, this is in a Blazor Server application
I'm confused about the correct usage of InvokeAsync and updating the UI. The documentation has several usages without really explaining the reason for awaiting or not awaiting. I have also seen some contradictory threads without much to backup their reasoning.
It seems wrong to make all methods async to await InvokeAsync(StateHasChanged) and I read somewhere that the reason InvokeAsync was introduced was to prevent the need for async code everywhere. But then what situations might I want to await it?
awaiting:
https://learn.microsoft.com/en-us/aspnet/core/blazor/components/?view=aspnetcore-5.0&viewFallbackFrom=aspnetcore-3.0#invoke-component-methods-externally-to-update-state
non-await discard:
https://learn.microsoft.com/en-us/aspnet/core/blazor/components/rendering?view=aspnetcore-5.0
Here are some examples of different usages that I have seen, if anyone could explain or share any links with information on some of the differences between them that'd be great (thanks!)
public void IncrementCounter()
{
_counter++;
InvokeAsync(StateHasChanged);
}
public void IncrementCounter()
{
InvokeAsync(() =>
{
_counter++;
StateHasChanged);
}
}
public async Task IncrementCounter()
{
_counter++;
await InvokeAsync(StateHasChanged);
}
public async Task IncrementCounter()
{
await InvokeAsync(() =>
{
_counter++;
StateHasChanged();
});
}
IncrementCounter (a ButtonClick handler) is the wrong thing to look at - it always runs on the SyncContext thread and can always use a plain StateHasChanged() without Invoke.
So lets look at a Timer event instead. The Threading.Timer class does not support async handlers so you run in a void Tick() { ... } on an unspecified thread.
You do need InvokeAsync(StateHasChanged) here. You could make the Tick method an async void just to await the InvokeAsync but that gives of the wrong signals. Using InvokeAsync without await is the lesser evil.
void Tick() // possibly threaded event handler
{
_counter++;
InvokeAsync(StateHasChanged); // fire-and-forget mode
}
But when you are in an async method and still need InvokeAsync, it is neater to await it, just because you can.
async Task SomeService()
{
_counter++;
await InvokeAsync(StateHasChanged);
}

Flutter and external JAR Library: android.os.NetworkOnMainThreadException

Im trying to use olingo with Flutter on Android. I set up my channel and I can call the library but I keep getting this message:
E/AndroidRuntime(28391): FATAL EXCEPTION: main
E/AndroidRuntime(28391): Process: com.example.odata, PID: 28391
E/AndroidRuntime(28391): org.apache.olingo.client.api.http.HttpClientException: android.os.NetworkOnMainThreadException
E/AndroidRuntime(28391): at org.apache.olingo.client.core.communication.request.AbstractODataRequest.doExecute(AbstractODataRequest.java:312)
So it looks like it is running on the main thread - which is a no go as this would block. I tried the looper to ask Java to run on the UI Thread:
public void onMethodCall(MethodCall call, Result result) {
// Note: this method is invoked on the main thread.
Log.i("test", "using " + call.method);
String serviceUrl = "http://services.odata.org/OData/OData.svc/";
new Handler(Looper.getMainLooper()).post(new Runnable() {
#Override
public void run() {
if (call.method.equals("getMetaData")) {
String metadata;
final Edm edm = ODataClientFactory.getClient().getRetrieveRequestFactory().getMetadataRequest(serviceUrl).execute().getBody();
metadata = edm.toString();
if (metadata != "") {
result.success(metadata);
} else {
result.error("UNAVAILABLE", "Metadata cannot read.", null);
}
} else {
result.notImplemented();
}
}
});
But Im still getting the same error.
So how exactly can I deal with external JAR Library which are doing blocking operations ? To my understanding an external call is a Future anyway so it will not block my Flutter thread anyway - but Android Java does not think so ...
This is my method call in flutter
Future<void> _getMetaData() async {
String metadata;
try {
final String result = await platform.invokeMethod('getMetaData');
metadata = result;
} on PlatformException catch (e) {
metadata = e.message;
}
setState(() {
_metadata = metadata;
});
}
Thanks for the answer, this is the solution for anyone that may be interested:
public void onMethodCall(MethodCall call, Result result) {
if (call.method.equals("getMetaData")) {
class MetadataLoader extends AsyncTask<String , Integer, String> {
#Override
protected String doInBackground(String... urls) {
// call your Java library method here, including blocking methods
return your_return_value;
}
protected void onPostExecute(String _result) {
// your_return_value is now passed in _result
result.success(_result);
}
}
new MetadataLoader().execute(); // Start the Async
}
On the flutter side,
Future<void> _getMetaData() async {
String metadata;
try {
final String result = await platform.invokeMethod('getMetaData');
// do something with the result
// the Flutter thread will stop at the await and resume when the Java
// will call result.success
}
}
You will need to create a new Java thread or Worker. (Note that the "main" thread and the "UI" thread are the same thing - so by posting to the main looper you've ended up in the same place - trying to do network i/o on the main thread.)
Yes, the Flutter engine is running in different threads, but you still need to leave the main native thread unblocked as it is responsible for detecting user input, etc.
Also note that when your blocking activity completes - on its non-main thread - it will likely want to deliver the response to Dart. To do this it will need to use part of your code above - to post the results back to the main thread, which can then invoke method channel operations.
You'll probably want to use your method channel bi-directionally. From flutter to native to request an operation (returning, say, a sequence number), and from native to flutter to deliver the results (quoting the sequence number so that the result can be tied back to the request).

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

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();
}

Resources