What's different between FlutterError.onError and runZonedGuarded.onError? - flutter

Please explain to me what is the difference between them. Can they be used together or only separately?

FlutterError.onError is a method that allows you to hook into the Flutter framework's error-handling mechanism, so you can receive callbacks when an error occurs during the build, layout, or painting phases.
runZonedGuarded is a utility function that allows you to run a block of code in a new zone with a custom error handler. The onError parameter is the error handler that will be called if an error occurs within the zone.
So, in summary:
FlutterError.onError is specific to the Flutter framework and is used
to handle errors during the build, layout, and painting phases.
runZonedGuarded.onError is more general and allows you to run a block
of code in a new zone with a custom error handler, which can be used
for a wider range of error-handling needs.
Yes, FlutterError.onError and runZonedGuarded.onError can be used together in a Flutter application.
For example, you can use FlutterError.onError to handle errors that occur during the build, layout, or painting phases, and use runZonedGuarded.onError to handle errors that occur within a specific block of code.
In this way, you can have multiple error handlers in your Flutter application to handle different types of errors in different parts of the code.

Related

WidgetsFlutterBinding.ensureInitialized() equivalent for the platform/ host side

Is there a way to ensure the bindings are initialized between Flutter and Host/ Platform (Android, iOS)? I want to do this because I want to invoke methods from host to dart.
For example, in Android (java), I create a flutter engine, register plugins, and create a method channel, and launch the application:
FlutterEngine flutterEngine = new FlutterEngine(context, null);
// Remember to register plugins
GeneratedPluginRegister.registerGeneratedPlugins(flutterEngine);
DartExecutor executor = flutterEngine.getDartExecutor();
methodChannel = new MethodChannel(executor.getBinaryMessenger(), "my background method channel");
methodChannel.setMethodCallHandler(this);
// Launch app
DartExecutor.DartEntrypoint appEntrypoint = DartExecutor.DartEntrypoint.createDefault();
executor.executeDartEntrypoint(appEntrypoint);
I'd like to use methodChannel.invokeMethod, but the application might not be ready for me yet, even though in appEntrypoint, the same MethodChannel is created on the dart side. If I call methodChannel.invokeMethod too early, I get:
2021-09-14 16:41:01.102 30103-30425/com.example.app E/flutter:
[ERROR:flutter/lib/ui/ui_dart_state.cc(209)] Unhandled Exception:
MissingPluginException
(No implementation found for method MethodName on channel com.example.app.package)
A solution would be to ask the Dart side to call a PlatformMethod when it's ready initializing, which would inform me that it's ready to receive messages. But I would prefer to avoid having to set up 2 way communication if I only need to send one message to the Dart side.
I have a feeling it's not possible. If so, could someone still explain why the binding has to be awaited from the Dart side? Something to do with no listeners available on the Platform side?
The methods were actually being called.
It was working. This was a bad idea, but the actual methods were being called: the 2 Method Channels on dart side and 2 method channels on Platform side were calling each other just fine. They all had the same channel name. Probably not a good idea for complexity reasons (see below), but it worked. 😅
My specific issue:
The reason I was getting No implementation found for method MethodName was exactly because I had two MethodChannels on the dart side, and two on Platform side. I was recreating the same isolate, so I had 2 Flutter applications running simultaneously. When one Isolate called a dart method I did not implement on on Method Call Handler on Platform side, I got this error.
Lesson learnt
If you have separate components on your app (Android components: Activity, Service, Broadcast Receiver or Dart Isolates):
Don't re-create the main application entrypoint. Create a new separate DartEntrypoint.
Use unique channel names for the MethodChannel.

How to monitor errors in audio_service?

How to implement an error monitoring in audio_service?
It runs in isolate, but none of methods available expose Isolate to add error listener. Thus is there any other method for hooking it to error monitoring platform like ex. sentry?
Good news and bad news.
When the UI isolate is present and initiates the method call to the background isolate, any exception generated by the background task will be forwarded to the UI isolate where you can create an error zone or use something like sentry. For example, an error thrown by onPlay in the background isolate will be forwarded and thrown by play in the main UI isolate.
However, when the UI isolate is not around, or the media notification initiates the method call to the background isolate, the exception will be caught and discarded internally.
Making the latter case an uncaught exception would make a good feature request. Although note that the next version of audio_service will run under a single isolate which would also solve the problem.

Passing Isolate/ControlPort through MethodChannel

Within our flutter app we are doing some background processing. For that, we need to create new flutter isolate in our native code so we can run code when activity is not open, based on this guide: https://medium.com/#chetan882777/initiating-calls-to-dart-from-the-native-side-in-the-background-with-flutter-plugin-7d46aed32c47
Now, to not duplicate code and to not cause any concurrency issues, we would also like to have access to that background processing isolate from main flutter UI isolate. That way we can begin processing from both native code and from flutter UI code.
However, there does not seem to be a way for native to pass this Isolate / ControlPort to the main UI side so it can communicate with it. Is there a way I can achieve this (communicate to the same Isolate from both native and UI side)?
From what I see, only way to do this would require native to be the broker between the two sides (send the task to native and then native sends it back to the other side), but it seems like a lot of hassle for one flutter talking to another flutter.
Solution to this is the IsolateNameServer.
One side can call IsolateNameServer.registerPortWithName() and other side can then send messages to that port via IsolateNameServer.lookupPortByName()

Calling top-level async function from different Isolate

Unable to call a top-level async function from another isolate, an example would be trying to access SharedPreferences/Document-path value from a different isolate since getting the instance would require 'awaiting'.
In my case, I am using flutter_downloader, downloads in another isolate, as soon as the download completes an encryption method is called which saves the result in a document path, path_provider's getApplicationDocumentsDirectory() returns a future which requires awaiting. I have made the encrypt function async and it is never getting called using both compute and Isolate.spawn so that it computes the encryption in another isolate.
The problem is probably that there are no plugins available in isolates, thus path_provider and shared_preferences don't work when called from the isolate.
If you need the getApplicationDocumentsDirectory() then you can determine that path before you start the isolate and hand it to the isolate as a message. This way you have the path available in the isolate and can save the file there. Accessing shared_prefs is not really possible, you can only do that after the isolate completes.
There are some projects like https://pub.dev/packages/flutter_isolate that try to make plugins available in isolates. You can explore those and see if it fits your needs.
Specifically for flutter_downloader there is an example in the README that shows how to communicate between the background isolate that handles the download callback and the main isolate of your app.
Basically you need to pass a message to the main isolate like PleaseGiveMeTheApplicationDocumentsDirectory and then answer the message with HereYourGoThisIsTheApplicationDocumentsDirectory.
You can use IsolateNameServer.lookupPortByName to get the SendPort of the main isolate and pass it the SendPort of the background isolate and then answer with the correct path. Once both sides know where to send messages to, you can pass whatever you want, you can also pass a message that triggers some shared_preferences usage in the main isolate.

How should I test functionality accessible only during the lifetime of an async method using #testing-library/react?

I have a React component which allows users to submit potentially long-running queries to a remote service. While the query is running, the component shows a cancel button. I want to test that this button shows up when expected, that its click handler cancels the previous API request, and so on.
Since the button is only present while the async API call is active, the tests I wrote for this purpose make their assertions about the button in the mock implementation of the async API itself. They're not super elegant but I confirmed that they do go red as I expect when I remove parts of the production code.
On upgrading #testing-library/react from 8.0.1 to 9.3.2, although the tests still pass, I now get the following warning several times:
console.error node_modules/#testing-library/react/dist/act-compat.js:52
Warning: You seem to have overlapping act() calls, this is not supported. Be sure to await previous act() calls before making a new one.
I have reproduced the issue in the following CodeSandbox (be sure to selected the "Tests" tab on the right-hand side and then view the "Console" messages at the bottom right).
The final comment on this GitHub issue says that I shouldn't need to worry about act() as long as I'm using the React Testing Library helpers and functions (which I am). What am I missing?
Raising an issue (here) against react-testing-library has helped me reach the conclusion that the best approach seems to be not to worry about it too much. The library's author was kind enough to propose a simpler test pattern of just making assertions directly after the action that causes the component to enter the transient state that you're trying to test. For example:
fireEvent.click(getByText("Submit")); // <-- action which triggers an async API call
expect(getByText("Fetching answers")).toBeInTheDocument();
Previously, I had the same expect statement in my mock implementation of the API call triggered by the click. My reasoning was that this was the only way I could be certain that the assertions would run while the component was in the transient state I was trying to test.
AFAICT, these tests are not strictly correct with respect to asynchronous actions because the promise returned by the mock implementation could resolve before the expectation was checked. In practice though, I have observed that the tests rewritten to the simpler approach:
do not provoke the warning about overlapping act() calls in the OP
can be made to fail as expected if I change the non-test code to break the behaviour under test
have not so far shown any intermittent failures
are far easier to read and understand
This question has never attracted much attention but I hope recording the answer I eventually arrived at myself might help others out in future.