Flutter exception
A Flutter exception refers to an unexpected error event that occurs when Dart code in the Flutter program is running. We can use try catch similar to Swift
Mechanism to capture it. But unlike Swift, the Dart program does not force us to handle exceptions.
This is because Dart uses the event loop mechanism to run tasks, so the running states of each task are independent of each other. That is, even if a task has an exception, we don't catch it, Dart
The program will not exit, but the subsequent code of the current task will not be executed, and the user can still continue to use other functions.
Dart exceptions can be subdivided into App exceptions and Framework exceptions according to the source. Flutter provides different capture methods for these two exceptions.
Capture method of App exception
App exceptions are exceptions of application code, which are usually caused by exceptions thrown by other modules of the application layer that are not handled. According to the execution timing of the exception code, app
Exceptions can be divided into two types: synchronous exceptions and asynchronous exceptions: synchronous exceptions can be captured through the try catch mechanism, and asynchronous exceptions need to adopt the catch error provided by Future
Statement capture.
The two exception capture methods are as follows:
// Catch synchronization exceptions using try catch
try {
throw SYReportException('One occurred dart Synchronization exception');
}
catch(e) {
print(e);
}
// Catch asynchronous exceptions using catchrror
Future.delayed(Duration(seconds: 1)).then((e) {
if (sendFlag) {
print('Before asynchronous exception occurs >>>>>>>>>>>');
throw SYReportException('One occurred dart Asynchronous exception');
}
print('Code executed after asynchronous exception <<<<<<<<<<<');
});
// Note that the following code cannot catch asynchronous exceptions
try {
Future.delayed(Duration(seconds: 1)).then((e) {
if (sendFlag) {
print('Before asynchronous exception occurs >>>>>>>>>>>');
throw SYReportException('One occurred dart Asynchronous exception');
}
print('Code executed after asynchronous exception <<<<<<<<<<<');
});
} catch (e) {
print("This will not be implemented. ");
}
It should be noted that these two methods cannot be mixed. As you can see, in the above code, we can't use try catch to catch the exception thrown by an asynchronous call.
Synchronous try catch and asynchronous catch error provide us with the ability to directly catch specific exceptions. If we want to centrally manage all exceptions in the code, fluent
Zone is also provided Runzoned method.
We can assign a Zone to the code execution object. In Dart, Zone
It represents the environment range of code execution. Its concept is similar to sandbox, and different sandboxes are isolated from each other. If we want to observe the exceptions in code execution in the sandbox, the sandbox provides onError
Callback function to intercept those uncapped exceptions in the code execution object.
In the following code, we put the statements that may throw exceptions in the Zone. As you can see, try catch and catch error are not used
In this case, both synchronous and asynchronous exceptions can be directly captured through the Zone:
runZoned(() {
// Synchronous throw exception
throw SYReportException('One occurred dart Synchronization exception');
}, onError: (dynamic e, StackTrace stack) {
print('zone Synchronization exception caught');
});
runZoned(() {
// Throw an exception asynchronously
Future.delayed(Duration(seconds: 1))
.then((e) => throw SYReportException('One occurred dart Asynchronous exception'));
}, onError: (dynamic e, StackTrace stack) {
print('zone An asynchronous exception was caught');
});
Therefore, if we want to focus on capturing unhandled exceptions in the fluent application, we can also place the runApp statement in the main function in the Zone
Yes. In this way, when an exception is detected in the code, we can handle it uniformly according to the obtained exception context information:
runZonedGuarded(() {
runApp(MyApp());
}, (error, stackTrace) {
// The Exception in this closure cannot be captured by @ mangosteen
SYExceptionReportChannel.reportException(error, stackTrace);
}, zoneSpecification: ZoneSpecification(
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
// Record all print logs
parent.print(zone, "line What is it $line");
},
));
Next, let's look at how Framework exceptions should be caught.
How to catch Framework exceptions
Framework exception is the exception thrown by the fluent framework, which is usually triggered by the application code
Caused by abnormal judgment at the bottom of the framework. For example, when the layout is out of specification, fluent will automatically pop up a shocking red error interface, as shown below:
framework_error.png
This is actually because the Flutter framework performs try catch processing when calling the build method to build a page, and provides a
ErrorWidget, which is used to prompt information when an exception occurs:
@override
void performRebuild() {
Widget built;
try {
// Create page
built = build();
} catch (e, stack) {
// Create page using ErrorWidget
built = ErrorWidget.builder(_debugReportException(ErrorDescription("building $this"), e, stack));
...
}
...
}
This page has rich feedback information, which is suitable for positioning problems in the development period. But it's bad to let users see such a page. Therefore, we usually rewrite errorwidget builder
Method to replace such an error prompt page with a more friendly page.
The following code demonstrates how to customize the error page. In this example, we customized the error page to display the navigation bar and scrollable error messages:
// Rewrite the builder of ErrorWidget and show it gracefully
ErrorWidget.builder = (FlutterErrorDetails details) {
print('error widget The detailed error information is:' + details.toString());
return MaterialApp(
title: 'Error Widget',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: Scaffold(
appBar: AppBar(
title: Text('Widget Rendering exception!!!'),
),
body: _createBody(details),
),
);
};
The operation effect is as follows:
custom_error_widget.png
Compared with the shocking red error page before, the customized one looks more elegant. Of course, you can also ask the UI to help design a more friendly interface. Note that errorwidget builder
Method provides a parameter details
It is used to represent the current error context. In order to avoid users directly seeing the error message, we do not show it on the interface here. However, we cannot discard such exception information. We need to provide a unified exception handling mechanism for subsequent analysis of the cause of the exception.
In order to centrally handle framework exceptions, Flutter provides the flutterror class, which is called onError
Property will execute the corresponding callback when the framework exception is received. Therefore, to implement custom capture logic, we only need to provide it with a custom error handling callback.
In the following code, we use the handleUncaughtError statement provided by Zone to uniformly forward the exceptions of the fluent framework to the current Zone
In this way, we can uniformly use Zone to handle all exceptions in the application:
// framework exception capture and forward to the current Zone
FlutterError.onError = (FlutterErrorDetails details) async {
Zone.current.handleUncaughtError(details.exception, details.stack);
};
Abnormal reporting
So far, we have caught all unhandled exceptions in the application. However, if we just print these exceptions on the console, we still can't solve the problem. We also need to report them to the place where developers can see them for subsequent analysis, positioning and problem solving.
Third, we usually use bugly. It would be better if the company had a bug system developed by itself.
For these exception reports, we will use MethodChannel to push them to Native, and Native will report them to bugly or self-developed exception systems.
Only Dart's code implementation is shown here. As for how Native implements Channel, you can Google by yourself
Dart implementation
The code is as follows:
/// flutter exception channel
class SYExceptionReportChannel {
static const MethodChannel _channel =
const MethodChannel('sy_exception_channel');
// Report exception
static reportException(dynamic error, dynamic stack) {
print('Exception type caught >>> : ${error.runtimeType}');
print('Captured exception information >>> : $error');
print('Caught exception stack >>> : $stack');
Map reportMap = {
'type': "${error.runtimeType}",
'title': error.toString(),
'description': stack.toString()
};
// You have to use this
print('This is through convert Turning json');
print(jsonEncode(reportMap));
_channel.invokeListMethod('reportException', reportMap);
}
}
After we catch the exception, the channel pushes it to Native, which contains three information:
- Exception type information
- Brief description of the exception (i.e. the toString value of error)
- Abnormal stack information
Optimization, packaging and problem points
Based on the above description, we will package and optimize the code.
- Optimization: after exception capture, it is handled differently in the debug and release modes. In the debug mode, printing directly to the console is the most intuitive. In the release mode, we can't perceive what's wrong, so we need to report and analyze the problem.
There is a clever way to distinguish between debug and release. The code and comments are as follows:
// A clever way to determine whether it is the debug mode
static bool get isInDebugMode {
bool inDebugMode = false;
// If the assignment will be triggered in the debug mode, the assert will be executed only in the debug mode
assert(inDebugMode = true);
return inDebugMode;
}
Based on the above ideas, we forward uncapped exceptions to zone for judgment:
// framework exception capture and forward to the current Zone
FlutterError.onError = (FlutterErrorDetails details) async {
// debug mode
if (ExceptionReportUtil.isInDebugMode) {
// Print to console
FlutterError.dumpErrorToConsole(details);
// release mode
} else {
// Forward to zone
Zone.current.handleUncaughtError(details.exception, details.stack);
}
};
- Encapsulation: naturally, the more concise the code in the main function, the better. However, forwarding uncapped exceptions to the zone and error Widget rewriting must be placed in main, so a tool class ExceptionReportUtil is extracted:
///Tool class
class ExceptionReportUtil {
// A clever way to determine whether it is the debug mode
static bool get isInDebugMode {
bool inDebugMode = false;
// If assignment is triggered in debug mode, assert will be executed only in debug mode
assert(inDebugMode = true);
return inDebugMode;
}
// Initialize exception capture configuration
static void initExceptionCatchConfig() {
// framework exception capture and forward to the current Zone
FlutterError.onError = (FlutterErrorDetails details) async {
// debug mode
if (ExceptionReportUtil.isInDebugMode) {
// Print to console
FlutterError.dumpErrorToConsole(details);
// release mode
} else {
// Forward to zone
Zone.current.handleUncaughtError(details.exception, details.stack);
}
};
// Rewrite the builder of ErrorWidget and show it gracefully
ErrorWidget.builder = (FlutterErrorDetails details) {
print('error widget The detailed error information is:' + details.toString());
return MaterialApp(
title: 'Error Widget',
theme: ThemeData(
primarySwatch: Colors.red,
),
home: Scaffold(
appBar: AppBar(
title: Text('Widget Rendering exception!!!'),
),
body: _createBody(details),
),
);
};
}
// Create error widget body
static Widget _createBody(dynamic details) {
// Correct code
return Container(
color: Colors.white,
child: SingleChildScrollView(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Text(
details.toString(),
style: TextStyle(color: Colors.red),
),
),
),
);
}
}
- Problem point: receive uncapped exceptions in the closure of runZonedGuarded function and report them. If an exception occurs in the code executing the closure, it cannot be captured:
The codes and notes are as follows:
main(List<String> args) {
// Initialize Exception capture configuration
ExceptionReportUtil.initExceptionCatchConfig();
runZonedGuarded(() {
runApp(MyApp());
}, (error, stackTrace) {
// The Exception in this closure cannot be captured by @ mangosteen
SYExceptionReportChannel.reportException(error, stackTrace);
}, zoneSpecification: ZoneSpecification(
print: (Zone self, ZoneDelegate parent, Zone zone, String line) {
// Record all print logs
parent.print(zone, "line What is it $line");
},
));
}
We use syexceptionreportchannel reportException(error,
stackTrace) reports the error to Native, but if Native does not implement the channel link, it will inevitably report MissingPluginException. This exception is not in the current zone, so it cannot be caught.
missingPluginException.png
An example is given to verify our exception capture
An example is written to demonstrate the implementation of this function and the specific effects:
demo_page.png
Before clicking the third button, the first two buttons work normally without exceptions. After clicking, exceptions will occur.
By printing information, let's see what information is captured for each exception:
- Dart synchronization exception:
dart synchronization exception png
- Dart asynchronous exception:
dart asynchronous exception png
- Fluent framework exception:
flutter_framework exception png
Through the exception type, exception information and the specific stack of exceptions, it will be very helpful to locate exceptions.
summary
The exception capture of fluent application can be divided into single exception capture and multi exception unified interception.
Among them, single exception capture uses the synchronous exception try catch provided by Dart and the asynchronous Exception Catch error
Mechanism can be realized. The unified interception of multiple exceptions can be divided into the following two cases: first, App exceptions. We can place the code execution block in the Zone through onError
Callback for unified processing; The second is Framework exception. We can use flutterror The onerror callback is intercepted.
After the exception is captured, we need to report the exception information for subsequent analysis and positioning.
It should be noted that the exception interception provided by fluent can only intercept the exceptions of Dart layer, but cannot intercept the exceptions of Engine layer. This is because most of the implementation of the Engine layer is
Once an exception occurs in C + + code, the whole program will Crash directly. However, generally speaking, the probability of such abnormalities is very low, and they are generally at the bottom of Flutter
Bug has nothing to do with our implementation in the application layer, so we don't need to worry too much.