diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart index 90735ca02e..ec824a4a26 100644 --- a/packages/flutter/lib/src/widgets/app.dart +++ b/packages/flutter/lib/src/widgets/app.dart @@ -12,6 +12,7 @@ import 'package:flutter/rendering.dart'; import 'banner.dart'; import 'basic.dart'; import 'binding.dart'; +import 'focus_manager.dart'; import 'framework.dart'; import 'localizations.dart'; import 'media_query.dart'; @@ -730,7 +731,33 @@ class _WidgetsAppState extends State implements WidgetsBindingObserv } @override - void didChangeAppLifecycleState(AppLifecycleState state) { } + void didChangeAppLifecycleState(AppLifecycleState state) { + switch (defaultTargetPlatform) { + case TargetPlatform.iOS: + return; + case TargetPlatform.android: + case TargetPlatform.fuchsia: + // When the application moves to the background, any focus nodes + // need to lose focus. When the application is restored to the + // foreground state, any focused node should regain focus (including) + // restoring the keyboard. This is only important in cases where + // applications in the background are partially visible. + switch (state) { + case AppLifecycleState.paused: + case AppLifecycleState.suspending: + case AppLifecycleState.inactive: { + final FocusManager focusManager = WidgetsBinding.instance.focusManager; + focusManager.windowDidLoseFocus(); + break; + } + case AppLifecycleState.resumed: { + final FocusManager focusManager = WidgetsBinding.instance.focusManager; + focusManager.windowDidGainFocus(); + break; + } + } + } + } @override void didHaveMemoryPressure() { } diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart index 3453c2641c..fb32f30164 100644 --- a/packages/flutter/lib/src/widgets/focus_manager.dart +++ b/packages/flutter/lib/src/widgets/focus_manager.dart @@ -417,6 +417,45 @@ class FocusManager { final FocusScopeNode rootScope = FocusScopeNode(); FocusNode _currentFocus; + bool _windowHasFocus = true; + FocusNode _cachedFocus; + FocusScopeNode _cachedScope; + + /// Call when the window loses focus. + /// + /// The currently focused node and containg scope is cached, then the current + /// focus is removed. For example, this ensures that cursors do not blink + /// while in the background. + /// + /// This method is safe to call multiple times. + void windowDidLoseFocus() { + if (!_windowHasFocus) { + return; + } + _windowHasFocus = false; + _cachedFocus = _currentFocus; + _cachedScope = _cachedFocus._parent; + _currentFocus?.unfocus(); + } + + /// Call when the window regains focus. + /// + /// If there is a cached focus node from when the window lost focus, this + /// method will restore focus to that node. For example, this ensures that + /// a focused text node will re-request the keyboard. + /// + /// This method is safe to call multiple times. + void windowDidGainFocus() { + if (_windowHasFocus) { + return; + } + _windowHasFocus = true; + if (_cachedFocus != null) { + _cachedScope._setFocus(_cachedFocus); + _cachedFocus = null; + _cachedScope = null; + } + } void _willDisposeFocusNode(FocusNode node) { assert(node != null); @@ -434,16 +473,18 @@ class FocusManager { FocusNode _findNextFocus() { FocusScopeNode scope = rootScope; - while (scope._firstChild != null) + while (scope._firstChild != null) { scope = scope._firstChild; + } return scope._focus; } void _update() { _haveScheduledUpdate = false; final FocusNode nextFocus = _findNextFocus(); - if (_currentFocus == nextFocus) + if (_currentFocus == nextFocus) { return; + } final FocusNode previousFocus = _currentFocus; _currentFocus = nextFocus; previousFocus?._notify(); diff --git a/packages/flutter/test/widgets/focus_test.dart b/packages/flutter/test/widgets/focus_test.dart index c02633bb62..3fd0981f48 100644 --- a/packages/flutter/test/widgets/focus_test.dart +++ b/packages/flutter/test/widgets/focus_test.dart @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:flutter/widgets.dart'; @@ -243,4 +246,29 @@ void main() { parentFocusScope.detach(); }); + + testWidgets('Focus is lost/restored when window focus is lost/restored on Fuchsia', (WidgetTester tester) async { + debugDefaultTargetPlatformOverride = TargetPlatform.fuchsia; + final FocusNode node = FocusNode(); + await tester.pumpWidget(MaterialApp( + home: Scaffold( + body: TextField( + focusNode: node, + autofocus: true, + ) + ), + )); + expect(node.hasFocus, true); + ByteData message = const StringCodec().encodeMessage('AppLifecycleState.inactive'); + await BinaryMessages.handlePlatformMessage('flutter/lifecycle', message, (_) {}); + await tester.pump(); + // Focus is lost when app goes to background. + expect(node.hasFocus, false); + message = const StringCodec().encodeMessage('AppLifecycleState.resumed'); + await BinaryMessages.handlePlatformMessage('flutter/lifecycle', message, (_) {}); + await tester.pump(); + // Focus is restored. + expect(node.hasFocus, true); + debugDefaultTargetPlatformOverride = null; + }); }