1. Overview of Flutter Exceptions#
There are already many articles online about the types of exceptions and their handling in Flutter, so this article will not go into detail. Here, we will only provide a brief summary to ensure the completeness of the article.
Flutter exceptions can be divided into the following categories:
- Dart exceptions
- App exceptions
- Synchronous exceptions
- Asynchronous exceptions
- Framework exceptions
- App exceptions
- Engine exceptions
Dart exceptions can be further divided into App exceptions and Framework exceptions based on their source. App exceptions can be divided into two categories, synchronous exceptions and asynchronous exceptions, based on the execution sequence of the exception code:
- Synchronous exceptions can be caught using the try-catch mechanism.
- Asynchronous exceptions need to be caught using the catchError statement provided by Future.
In Flutter, the Zone.runZoned method is provided. In Dart, Zone represents the scope of code execution, similar to a sandbox. You can use the onError callback function provided by Zone to intercept all uncaught exceptions. Because both synchronous and asynchronous exceptions can be intercepted, we often catch all App exceptions at the runApp layer.
runZoned<Future<Null>>(() async {
runApp(MyApp());
}, onError: (error, stackTrace) async {
//Do sth for error
});
Framework exceptions, on the other hand, are generally thrown by Widgets during build, and the default ErrorWidget is the red screen page displayed during development errors. It can also be overridden.
In our business, we can intercept exceptions thrown by the Flutter framework outside of Flutter by registering the FlutterError.onError
callback:
FlutterError.onError = (FlutterErrorDetails details) {
reportError(details.exception, details.stack);
};
Flutter Engine exceptions, for example, refer to errors that occur in libflutter.so in Android or Flutter.framework in iOS. These errors are directly handled by crash collection SDKs on the platform side, such as Firebase Crashlytics, Bugly, etc. We will explain this in more detail later.
2. Gray Scale Strategy#
Out of respect for online business and certain operational requirements, in order to ensure the stability of operations, we also need to provide a comprehensive gray scale strategy and fallback plan for online Flutter businesses. First, let's talk about the gray scale strategy in this section.
The logic flow of gray scale is relatively simple: configure the gray scale strategy - background configuration delivery & client configuration loading - client processing configuration.
2.1 Gray Scale Strategy Configuration#
We have defined some configuration fields required for Flutter gray scale on our internal configuration platform, including:
key
: Corresponding Flutter page (route)appkey
: Host app corresponding to this configurationminVersion
: Minimum effective versionmaxVersion
: Maximum effective versiontype
: Gray scale strategy, including tail number gray scale, regional gray scale, device disable, system disable, hybrid mode, whitelist mode, etc. Among them, whitelist mode is for testing considerations, and hybrid mode supports configuring various strategies to take effect in union.action
: Effective range, such as full effect, no effect, gray scale effect, etc.url
: Fallback link, supports parameter replacement notation. The client can concatenate the input parameters of the Flutter route into URL query parameters.
2.2 Background Delivery and Client Loading of Configuration#
The configuration is pulled during cold and hot starts. Considering that there will be 3 retries in case of failure, a singleton is maintained locally. Whenever a Flutter page needs to be opened on the business side, the gray scale configuration needs to be checked to determine whether to open the Flutter page. Of course, in order to prevent all 3 retries from failing during configuration pulling, a fallback configuration map of each Flutter page will be stored locally when releasing a new version, and in extreme cases, fallback will be automatically enabled.
2.3 Client Processing of Configuration#
When opening a Flutter page on the business side, the gray scale configuration needs to be checked to determine whether to open the Flutter page. If it is determined to be non-gray scale, that is, fallback is triggered, the fallback link in the configuration will be pulled, and the fallback H5 page will be opened using WebView after the URL parameters are configured.
It should be noted that most of our current businesses are H5 to Flutter migrations, so fallback versions are available by default, and the reliability of fallback versions can be guaranteed. For new businesses that will only use Flutter in the future, we are also researching a Flutter Web isomorphic solution.
3. Fallback Plan#
We need to perform timely fallback to ensure the reliability of Flutter businesses. In essence, both gray scale and fallback are used to distinguish whether the business uses Flutter or H5. The former is manually configured, while the latter takes effect automatically. A fallback configuration based on the app version is maintained locally, and the need for fallback is checked before opening a page. There are several scenarios where timely fallback is needed:
3.1 Non-gray scale fallback#
As mentioned earlier, if the business has configured gray scale strategy, and the corresponding Flutter page is opened in a scenario where gray scale fallback is not triggered, the page needs to be fallbacked and reported.
3.2 Framework exception fallback#
If a Flutter Framework exception is caught, the page will be marked as "needs fallback", and a custom ErrorWidget will be provided to remind the user that the page has encountered an error and needs to be re-entered. After that, when the user enters the page again, fallback will be triggered, and they will be directed to the H5 page.
As for Dart exceptions, since Dart uses an event loop mechanism to run tasks, the running states of various tasks are independent of each other. This means that an exception occurring will only prevent the subsequent code of the current task from being executed, and users can still continue to use other functions in the page. The impact is not significant, so we do not forcefully fallback in this case, but only report the error.
3.3 Engine crash fallback#
However, if an engine error occurs, it will definitely cause the app to crash. In this case, not only do we need to report logs, but we also set a flag. When the user opens the app again, the Flutter Engine will not be started, and all Flutter pages will be fully fallbacked.
3.4 Failure to load assets fallback#
Technically, we use a customized engine and perform Flutter asset trimming. Each time a new version is released, the corresponding reduced package zip's MD5 value will be stored in App.framework. When the app is first launched, the reduced package assets will be downloaded and then the engine will be started. After verifying the integrity of the assets, the customized engine will load the reduced package assets. However, there may be cases where the asset download fails. In addition to periodic retries, in this case, the Flutter Engine cannot be started, and all pages will be fully fallbacked and reported.
3.5 Flutter-related crash fallback#
In addition to the above scenarios, we have also encountered crashes caused by Flutter that are not engine crashes, asset loading issues, or Flutter exceptions. These crashes are only related to Flutter plugins, such as crashes caused by implementation logic issues on the native side of the plugin. This also falls under the category of Flutter-related crashes, but the word "Flutter" cannot be found in the logs reported by Bugly, because the program did not crash inside Flutter or the engine.
For this scenario, we will record the topViewController when the crash or ANR is reported and trace its path. If there is a Flutter Activity or FlutterViewController in the current route stack, as a precaution, fallback will still be triggered in the event of a crash.
4. Operational Daily Report#
The data source for the Flutter operational daily report includes performance reporting and exception reporting. As for crash monitoring and alerts, we rely on the client's Bugly. The daily report records the performance of various Flutter pages in different app versions, and provides the following indicators for readers to refer to:
- Page views (pv)
- Access success rate
- Crash rate, number of users affected by crashes
- Instant start rate (300ms threshold)
- Fallback rate, gray scale rate
- ...
Finally, the startup process diagram combining dynamic asset loading and fallback strategy is as follows: