Repository navigation
Description
Steps to reproduce
Tested on a Pixel 7 running Android 17 (API 37), 3 button navigation, debug build, Impeller (Vulkan).
The code sample below is a flutter create bug project with only main.dart replaced.
- Run the app. The banner at the top reads
viewInsets.bottom = 0.00. - Tap the
TextField. The keyboard opens and the banner follows the animation up to398.10. - Switch to another app (recents or home), then return to this app.
- Tap the
unfocusbutton. - Tap
second page (no text field), a route that contains no text input at all.
This is timing-dependent and does not fire on every attempt, varying the moment of backgrounding after step 2 changes whether it happens. It took a handful of attempts here.
A hit is distinguishable from a miss in the log: when the inset stays wrong, the resume contains
ImeTracker: system_server:…: onCancelled at PHASE_CLIENT_ANIMATION_CANCEL
followed by further Sending viewport metrics to the engine lines. When the inset ends up correct, that line is absent. See the Logs section.
Expected results
- After step 3 the keyboard is gone, so
MediaQuery.viewInsets.bottomshould be0.0and stay there. - After step 5 the second page's
Scaffoldbody should fill the available height.
Actual results
After returning from the background, viewInsets.bottom is briefly reported as 0.0 and is then set back to the keyboard height, where it stays. No keyboard is visible and no TextField is focused: the banner reads viewInsets.bottom = 398.10, text field focused: false.
Any Scaffold with the default resizeToAvoidBottomInset: true then lays out its body short by that amount. In the sample, the second page's body is 516.19 instead of 914.29.
The value survives a hot restart and is cleared only by relaunching the app.
Related:
- [iOS] Keyboard remains visible after quick app switch while TextField no longer has focus #189051 : same symptom on iOS.
- [Android] viewInsets.bottom jumps by the navigation bar height at the end of the IME open animation (edgeToEdge, API < 35) #190974 : same area, different behaviour: a single frame at the end of the IME open animation, of the navigation bar's size, reported for API 34. This device is API 37.
Code sample
main.dart
import 'package:flutter/material.dart';
void main() => runApp(const MyApp());
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) => MaterialApp(
home: const FirstPage(),
builder: (context, child) => Stack(
children: [
child!,
const Positioned(top: 0, left: 0, right: 0, child: _InsetBanner()),
],
),
);
}
class _InsetBanner extends StatefulWidget {
const _InsetBanner();
@override
State<_InsetBanner> createState() => _InsetBannerState();
}
class _InsetBannerState extends State<_InsetBanner> {
@override
void initState() {
super.initState();
FocusManager.instance.addListener(_onFocusChanged);
}
@override
void dispose() {
FocusManager.instance.removeListener(_onFocusChanged);
super.dispose();
}
void _onFocusChanged() {
if (mounted) setState(() {});
}
@override
Widget build(BuildContext context) {
final inset = MediaQuery.viewInsetsOf(context).bottom;
final focus = FocusManager.instance.primaryFocus;
final focused =
focus?.hasPrimaryFocus == true &&
focus?.context?.findAncestorStateOfType<EditableTextState>() != null;
final bad = inset > 0 && !focused;
return IgnorePointer(
child: Material(
color: bad ? Colors.red : Colors.black87,
child: SafeArea(
bottom: false,
child: Padding(
padding: const EdgeInsets.all(8),
child: Text(
'viewInsets.bottom = ${inset.toStringAsFixed(2)}\n'
'text field focused: $focused'
'${bad ? '\nINCONSISTENT: inset without focus' : ''}',
style: const TextStyle(color: Colors.white, fontSize: 16),
),
),
),
),
);
}
}
class FirstPage extends StatelessWidget {
const FirstPage({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('1 - has a text field')),
body: Padding(
padding: const EdgeInsets.fromLTRB(16, 96, 16, 16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const TextField(
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'tap me, then background the app',
),
),
const SizedBox(height: 24),
OutlinedButton(
onPressed: () => FocusManager.instance.primaryFocus?.unfocus(),
child: const Text('unfocus'),
),
const SizedBox(height: 12),
FilledButton(
onPressed: () => Navigator.push(
context,
MaterialPageRoute(builder: (_) => const SecondPage()),
),
child: const Text('second page (no text field)'),
),
],
),
),
);
}
class SecondPage extends StatelessWidget {
const SecondPage({super.key});
@override
Widget build(BuildContext context) => Scaffold(
appBar: AppBar(title: const Text('2 - no text field')),
body: Container(
color: Colors.amber,
child: Center(
child: Text(
'This amber area is the Scaffold body.\n'
'It should reach the bottom of the screen.\n\n'
'body height: '
'${MediaQuery.sizeOf(context).height - MediaQuery.viewInsetsOf(context).bottom}',
textAlign: TextAlign.center,
),
),
),
);
}Screenshots or Video
Screenshots / Video demonstration
After returning from the background and tapping `unfocus`: no keyboard on screen, no focused text field, but `viewInsets.bottom = 398.10`.
The second page, which contains no text input: the amber `Scaffold` body stops ~40% above the bottom edge. `body height: 516.19` instead of `914.29`.
Logs
Logs
Android reports that the IME is gone, and the inset that reaches MediaQuery disagrees.
Run where the inset stays wrong, the same resume, from ime:null onwards:
D/VRI[MainActivity]: WindowInsets changed: ime:null
D/FlutterJNI: Sending viewport metrics to the engine.
W/ImeBackCallbackProxy: Ime callback not found. Ignoring unregisterReceivedCallback.
D/InsetsController: hide(ime())
D/InsetsController: Setting requestedVisibleTypes to 503 (was 511)
D/WindowOnBackDispatcher: setTopOnBackInvokedCallback (unwrapped): android.view.ImeBackAnimationController@b9ec638
D/WindowOnBackDispatcher: setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@5eaf65
D/WindowOnBackDispatcher: setTopOnBackInvokedCallback (unwrapped): android.view.ImeBackAnimationController@b9ec638
I/ImeTracker: system_server:884a5fae: onCancelled at PHASE_CLIENT_ANIMATION_CANCEL
D/FlutterJNI: Sending viewport metrics to the engine.
D/WindowOnBackDispatcher: setTopOnBackInvokedCallback (unwrapped): android.app.Activity$$ExternalSyntheticLambda0@5eaf65
D/FlutterJNI: Sending viewport metrics to the engine.
Run where the inset ends up correct, same point:
D/VRI[MainActivity]: WindowInsets changed: ime:null
D/FlutterJNI: Sending viewport metrics to the engine.
W/ImeBackCallbackProxy: Ime callback not found. Ignoring unregisterReceivedCallback.
D/InsetsController: hide(ime())
D/InsetsController: Setting requestedVisibleTypes to 503 (was 511)
I/ImeTracker: 80c8e479: onRequestHide at ORIGIN_CLIENT reason HIDE_SOFT_INPUT fromUser false userId 0 displayId 0
D/InsetsController: hide(ime())
I/ImeTracker: 80c8e479: onCancelled at PHASE_CLIENT_ALREADY_HIDDEN
The difference is PHASE_CLIENT_ANIMATION_CANCEL and the two additional Sending viewport metrics to the engine lines that follow it. Those are the frames in which the value goes back to the keyboard height.
Earlier in both runs, opening the keyboard produced WindowInsets changed: ime:[0,0,0,1045]. 1045 physical pixels / 2.625 device pixel ratio = 398.0952 logical pixels, i.e. exactly the value the framework then reports. For scale, this device's navigation bar is navigationBars:[0,0,0,126], i.e. 48 logical pixels.
Flutter Doctor output
flutter doctor -v
[√] Flutter (Channel stable, 3.41.7, on Microsoft Windows [Version 10.0.26200.9168], locale de-DE) [461ms]
• Flutter version 3.41.7 on channel stable at D:\dev\flutter
• Upstream repository https://github.com/flutter/flutter.git
• Framework revision cc0734ac71 (4 months ago), 2026-04-15 21:21:08 -0700
• Engine revision 59aa584fdf
• Dart version 3.11.5
• DevTools version 2.54.2
• Feature flags: enable-web, enable-linux-desktop, enable-macos-desktop, enable-windows-desktop, enable-android, enable-ios, cli-animations, enable-native-assets, omit-legacy-version-file, enable-lldb-debugging, enable-uiscene-migration
[√] Windows Version (11 Home 64-bit, 25H2, 2009) [1.744ms]
[√] Android toolchain - develop for Android devices (Android SDK version 36.1.0) [9,7s]
• Android SDK at D:\Users\rawtp\AppData\Local\Android\Sdk
• Emulator version 36.5.10.0 (build_id 15081367) (CL:N/A)
• Platform android-36.1, build-tools 36.1.0
• ANDROID_HOME = D:\Users\rawtp\AppData\Local\Android\Sdk
• Java binary at: D:\Program Files\Android\Android Studio\jbr\bin\java
This is the JDK bundled with the latest Android Studio installation on this machine.
To manually set the JDK path, use: `flutter config --jdk-dir="path/to/jdk"`.
• Java version OpenJDK Runtime Environment (build 21.0.10+-14961533-b1163.108)
• All Android licenses accepted.
[√] Chrome - develop for the web [128ms]
• Chrome at C:\Program Files\Google\Chrome\Application\chrome.exe
[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.2.0) [126ms]
• Visual Studio at C:\Program Files\Microsoft Visual Studio\2022\Community
• Visual Studio Community 2022 version 17.2.32505.173
• Windows 10 SDK version 10.0.19041.0
[√] Connected device (4 available) [675ms]
• Pixel 7 (mobile) • 2A231********* • android-arm64 • Android 17 (API 37)
• Windows (desktop) • windows • windows-x64 • Microsoft Windows [Version 10.0.26200.9168]
• Chrome (web) • chrome • web-javascript • Google Chrome 151.0.7922.138
• Edge (web) • edge • web-javascript • Microsoft Edge 149.0.4022.69
[√] Network resources [511ms]
• All expected network resources are available.
• No issues found!
Device: Pixel 7, device pixel ratio 2.625, 3 button navigation, debug build, Impeller (Vulkan).
Activity
boiOne commentedon Aug 15, 2026
Correction to the reproduction notes above:
PHASE_CLIENT_ANIMATION_CANCELis not a reliable marker. I have since seen a run where the inset ends up wrong without that line appearing at all, so it should not be used to tell a hit from a miss.What has held across every failing run so far is narrower: after
WindowInsets changed: ime:null, the framework briefly reportsviewInsets.bottom == 0.0, and furtherSending viewport metrics to the enginelines then restore it to the keyboard height. That transient0.0followed by the restore is the signature to look for.suryaKommana2662 commentedon Aug 17, 2026
@boiOne Thanks for the detailed reproduction steps, sample code, logs, and clarification. I noticed that the issue was verified on Flutter 3.41.7. Could you please verify whether the issue is still reproducible on the latest stable Flutter release?
boiOne commentedon Aug 17, 2026
@suryaKommana2662
Still reproducible on the latest stable, Flutter 3.47.0.
I rebuilt the minimal reproduction from the original report against 3.47.0 and ran through the steps repeatedly.
It took about six attempts before it triggered, which matches the timing-dependent nature described earlier:
the app has to go to the background while the keyboard animation is still running. If the animation completes first, the bug does not occur.
When it triggers, the reproduction's banner turns red and reports INCONSISTENT: inset without focus, with viewInsets.bottom still holding the keyboard height while no EditableText has focus.
suryaKommana2662 commentedon Aug 18, 2026
@boiOne Thanks for confirming that the issue is still reproducible on the latest stable Flutter 3.47.0 and for providing the updated reproduction details. The issue is sufficiently reproducible with the provided minimal example and appears to be specific to Android IME/window-inset handling after returning from the background while the IME animation is in progress.
This appears to be related to #190974, which also involves incorrect viewInsets.bottom values during IME transitions on Android. However, the reproduction conditions and symptoms differ #190974 is specific to a navigation-bar-height jump at the end of the IME opening animation on API < 35 with edge-to-edge, whereas this issue involves the keyboard height being restored after returning from the background even though the keyboard is no longer visible and no text field is focused. The issue will remain open and is being routing to the t
eam-androidfor further investigation.7 remaining items
flutter-triage-bot commentedon Aug 18, 2026
The
triaged-androidlabel is irrelevant if there is noteam-androidlabel orfyi-androidlabel.justinmc commentedon Aug 18, 2026
Seems likely not Android specific, though I couldn't reproduce it on an iOS simulator with a quick couple of tries.
elvrusu commentedon Oct 19, 2026
Are there any workarounds to this issue? Since fixing it on Flutter side will take some time.
Henry-Hiles commentedon Oct 20, 2026
That linked issue has been fixed by an iOS-specific PR, so this is indeed Android-specific. Personally, I cannot reproduce it on Android 15, but users of my app can reproduce it on Android 17. Can this be re-routed back to the Android team?
add keyboard inset workaround to MainActivity
Henry-Hiles commentedon Oct 20, 2026
This seems to work as a temporary workaround: https://git.federated.nexus/Nexus/nexus/commit/070a9ede8f517253afcfefebe7d102fc027a5e6c
fix(#400): the keyboard's height no longer outlives the keyboard on A…
Clear the keyboard gap Flutter leaves after an app switch
fix: screens no longer stop short after leaving the app while the key…
Fix ime insets animation jump (flutter#191453)