Default flutter Navigator.pop()
will close the app when there is no stacked pages in the navigation.
The sudden close app may make users feel strange as they may not aware this is the last page of the app. However, different platforms may have a slightly different behaviour on the confirmation, here is the action I usually implemented and how I do it in the flutter project.
Android
Most of the time, Toast message or Dialog will be implemented. Personally, I prefer toast because users don’t have to move their finger to proceed the exit. This avoid users leave app unexpectedly. By wrapping a WillPopScope widget to the home page widget, this allows me checking whether existing is the last page of the navigation stack. The reason why I have escaped IOS will explained in the IOS session.
@override
Widget build(BuildContext context) {
if (Platform.isIOS) {
return _buildPage(context);
}
return WillPopScope(
onWillPop: () {
if (Navigator.canPop(context)) {
return Future.value(true);
}
if (Platform.isAndroid && !_isGoingExit) {
_isGoingExit = true;
Fluttertoast.showToast(
msg: 'Click again to exit', toastLength: Toast.LENGTH_LONG);
Future.delayed(const Duration(seconds: 2), () {
_isGoingExit = false;
});
return Future.value(false);
}
return Future.value(true);
},
child: _buildPage(context),
);
}
This is the result:
IOS
For IOS, I have escaped the WillPopScoped because of 2 reasons. One is according the apple human interface guideline, it is not recommended to exit the app by in-app action including clicking a button, swipe back and other gesture. The manually call of exiting the app will causing the apple store to reject your app. Details can check the guideline. Another reason is that I will avoid using WillPopScope in IOS platform because it has issue on the IOS’s swipe back action. Implementing this will causing the swipe action not function. Thus, I will try to separate IOS platform when I need to use WillPopScope.
Desktop (Windows + Linux + MacOS)
In-App Exit
In app exit mostly happens when it is a full screen app. An alert dialog to confirm the exit will be useful.
Exit with Method Channel
await showDialog(
context: context,
builder: (dialogContext) {
return AlertDialog(
title: const Text('Are you sure to leave?'),
actions: <Widget>[
ElevatedButton(
child: const Text('Confirm'),
onPressed: () {
Navigator.pop(dialogContext);
if (!Navigator.canPop(context)) {
_methodChannel.invokeMethod('exit');
}
},
),
ElevatedButton(
child: const Text('Cancel'),
onPressed: () {
Navigator.pop(dialogContext);
},
),
],
);
},
);
Using method channel instead of Navigator.pop(context)
to exit app because on desktop app it will become blank page when there is no stack in the navigation.
Thus, I have implemented the exit call with the method channel. More information about implementing method channel in different platform can reference to this article.
Windows
flutter::MethodChannel<> channel(
flutter_controller_->engine()->messenger(), "cbl.tool.flutter_exit",
&flutter::StandardMethodCodec::GetInstance());
channel.SetMethodCallHandler(
[](const flutter::MethodCall<>& call,
std::unique_ptr<flutter::MethodResult<>> result) {
if (call.method_name() == "exit") {
PostQuitMessage(0);
result->Success(flutter::EncodableValue(true));
}
else {
result->NotImplemented();
}
});
MacOS
let flutterChannel = FlutterMethodChannel(name: "cbl.tool.flutter_exit",
binaryMessenger: flutterViewController.engine.binaryMessenger)
flutterChannel.setMethodCallHandler({
(call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
if (call.method == "exit") {
self.close()
result(true)
} else {
result(FlutterMethodNotImplemented)
}
})
Linux
// method channel callback
static void method_call_cb(FlMethodChannel* channel,
FlMethodCall* method_call,
gpointer user_data)
{
g_autoptr(FlMethodResponse) response = nullptr;
const gchar* method = fl_method_call_get_name(method_call);
if (strcmp(method, "exit") == 0) {
gtk_window_close(window);
response = FL_METHOD_RESPONSE(fl_method_success_response_new(FlValue(true)));
} else {
response = FL_METHOD_RESPONSE(fl_method_not_implemented_response_new());
}
fl_method_call_respond(method_call, response, nullptr);
}
static void my_application_activate(GApplication* application) {
...
FlEngine* engine = fl_view_get_engine(view);
g_autoptr(FlBinaryMessenger) messenger = fl_engine_get_binary_messenger(engine);
g_autoptr(FlMethodChannel) channel =
fl_method_channel_new(messenger,
"cbl.tool.flutter_exit",
FL_METHOD_CODEC(fl_standard_method_codec_new()));
fl_method_channel_set_method_call_handler(channel,
method_call_cb, g_object_ref(view), g_object_unref);
}
Result
Using flutter_window_close Plugin
flutter_window_close plugin provided the close method enable to close window on Windows, MacOS and Linux. This plugin can handle the close action from closeWindow()
trigger or user close window but window’s exit button.
@override
void initState(){
super.initState();
/// add listener for close window action
FlutterWindowClose.setWindowShouldCloseHandler(() async {
return await showDialog(
context: context,
builder: (dialogContext) {
return AlertDialog(
title: const Text('Are you sure to leave?'),
actions: [
ElevatedButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: const Text('Confirm')),
ElevatedButton(
onPressed: () => Navigator.pop(dialogContext, false),
child: const Text('Cancel')),
]);
});
});
}
/// call inside exit button
FlutterWindowClose.closeWindow();
Window Exit
Exit with Method Channel
Referencing the flutter_window_close plugin, the close button action can get via the listener of different platform. After receiving the signal, method channel can be used to let dart handle UI asking confirmation.
@override
void initState(){
super.initState();
channel.setMethodCallHandler((call) async {
if (call.method == 'onWindowClose') {
/// handle UI and later call exit app like previous session
}
});
}
Windows
For Windows, it is listening the WM_CLOSE signal to determent the window is starting to close. By returning the 0
to intercept the close action and use method channel to ask flutter part for confirmation action.
WindowCloseWndProc(HWND hWnd, UINT iMessage, WPARAM wparam, LPARAM lparam)
{
if (iMessage == WM_CLOSE) {
auto args = std::make_unique<flutter::EncodableValue>(nullptr);
channel_->InvokeMethod("onWindowClose", std::move(args));
return 0;
}
return oldProc(hWnd, iMessage, wparam, lparam);
}
MacOS
For MacOS, overriding the windowShouldClose method allow us to know the window is starting to close. Similarly, returning false to intercept the window to hold and using method channel to let flutter part for confirmation action.
public func windowShouldClose(_ sender: NSWindow) -> Bool {
notificationChannel?.invokeMethod("onWindowClose", arguments: nil)
return false
}
Linux
Similarly, by listening to the delete_event
signal and method channel to trigger the flutter part UI handling.
main_window_close(GtkWidget* window, gpointer data)
{
FlValue* value = fl_value_new_null();
fl_method_channel_invoke_method(notificationChannel,
"onWindowClose",
value, NULL, NULL, NULL);
return TRUE;
}
...
g_signal_connect(G_OBJECT(window), "delete_event",
G_CALLBACK(main_window_close),
NULL);
Using flutter_window_close Plugin
Using flutter_window_close is easy. The setWindowShouldCloseHandler
will receive the close signal from window’s exit button. This can share the confirmation logic with the user trigger one mentioned above session.
@override
void initState(){
super.initState();
/// add listener for close window action
FlutterWindowClose.setWindowShouldCloseHandler(() async {
return await showDialog(
context: context,
builder: (dialogContext) {
return AlertDialog(
title: const Text('Are you sure to leave?'),
actions: [
ElevatedButton(
onPressed: () => Navigator.pop(dialogContext, true),
child: const Text('Confirm')),
ElevatedButton(
onPressed: () => Navigator.pop(dialogContext, false),
child: const Text('Cancel')),
]);
});
});
}
Aware MacOS Multi-window
From flutter_window_close readme, MacOS allows multi window for the same app and have difficulties on identifying which window is closing. Default flutter desktop app is single window, this should have no problem. However, developing multi-window may aware on the close window handling.