Issue 1:
If I use a Navigator as a child of a TabBarView, and that Navigator uses a GlobalKey, then there is a situation where Element.updateChild is called, and this causes a spurious assert that

Multiple widgets used the same GlobalKey".

Here is some code that demonstrates the problem. Run the following app, and after it starts up, hit tab "C" to see the error.

import 'package:flutter/material.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {

// This widget is the root of your application.
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}

class MyHomePage extends StatefulWidget {
MyHomePage({Key key, this.title}) : super(key: key);
final String title;

@override
_MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
GlobalKey globalKeyA = GlobalKey(debugLabel: 'A');

@override
Widget build(BuildContext context) {
return SafeArea(
child: DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: TabBarView(
children: [
Navigator(
key: globalKeyA,
onGenerateRoute: (_) =>
MaterialPageRoute(
settings: RouteSettings(
isInitialRoute: true
),
builder: (context) => Text('Page A'),
)
),
Text('Page B'),
Text('Page C'),

]
),
bottomNavigationBar: TabBar(
labelColor: Colors.black,
tabs: [
Tab(text: 'A'),
Tab(text: 'B'),
Tab(text: 'C')
]
),
)
)
);
}
}

The error is:

flutter: #2 Element.updateChild.<anonymous closure> (package:flutter/src/widgets/framework.dart:2724:13)
flutter: #3 Element.updateChild (package:flutter/src/widgets/framework.dart:2727:6)
flutter: #4 SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:4838:14)
flutter: #5 Element.inflateWidget (package:flutter/src/widgets/framework.dart:2950:14)
flutter: #6 Element.updateChild (package:flutter/src/widgets/framework.dart:2753:12)
flutter: #7 SingleChildRenderObjectElement.mount (package:flutter/src/widgets/framework.dart:4838:14)
(etc...)

The problem is that in Element.updateChild, the code checks that a GlobalKey is not being reused by a different widget. But this check occurs before the old child widget has had a chance to be deactivated, so it is unsurprising that the GlobalKey appears to be used twice.

This will always happen when an Element that uses a GlobalKey has updateChild called.

The fix would be to move the assertion that checks that the GlobalKey is not reused to be later in the updateChild method, after the original has been deactivated.

--- a/packages/flutter/lib/src/widgets/framework.dart
+++ b/packages/flutter/lib/src/widgets/framework.dart
@@ -2705,18 +2705,12 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
/// | **child != null** | Old child is removed, returns null. | Old child updated if possible, returns child or new [Element]. |
@protected
Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
- assert(() {
- if (newWidget != null && newWidget.key is GlobalKey) {
- final GlobalKey key = newWidget.key;
- key._debugReserveFor(this);
- }
- return true;
- }());
if (newWidget == null) {
if (child != null)
deactivateChild(child);
return null;
}
+
if (child != null) {
if (child.widget == newWidget) {
if (child.slot != newSlot)
@@ -2737,6 +2731,13 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
deactivateChild(child);
assert(child._parent == null);
}
+ assert(() {
+ if (newWidget.key is GlobalKey) {
+ final GlobalKey key = newWidget.key;
+ key._debugReserveFor(this);
+ }
+ return true;
+ }());
return inflateWidget(newWidget, newSlot);
}

It's okay to move the assert to the end, because it cannot possibly trigger on the other code paths.

Either newWidget == null, so it can't have a GlobalKey anyway. Or child.widget == newWidget so the GlobalKey is already being used by the child. Or (see code below), Widget.canUpdate(child.widget, newWidget) is true, which means that child.widget.key == newWidget.key, according to the rules of being allowed to replace a widget.

if (Widget.canUpdate(child.widget, newWidget)) {
if (child.slot != newSlot)
updateSlotForChild(child, newSlot);
child.update(newWidget);
assert(child.widget == newWidget);
assert(() {
child.owner._debugElementWasRebuilt(child);
return true;
}());
return child;
}

Therefore, the only place that needs to have the assert to check we're not using a GlobalKey more than once, is near the end of this method.

See also:

#16630

#21630

https://stackoverflow.com/questions/49862572/multiple-widgets-used-the-same-globalkey?rq=1

$ flutter doctor -v
[✓] Flutter (Channel master, v0.11.10-pre.2, on Mac OS X 10.13.6 17G3025, locale en-GB)
• Flutter version 0.11.10-pre.2 at /Users/steve/code/flutter
• Framework revision b55e52c6f7 (18 hours ago), 2018-11-21 15:48:23 -0500
• Engine revision 1e37b99ac0
• Dart version 2.1.0 (build 2.1.0-dev.9.4 f9ebf21297)

[✓] Android toolchain - develop for Android devices (Android SDK 28.0.1)
• Android SDK at /Users/steve/Library/Android/sdk
• Android NDK location not configured (optional; useful for native profiling support)
• Platform android-28, build-tools 28.0.1
• Java binary at: /Applications/Android Studio.app/Contents/jre/jdk/Contents/Home/bin/java
• Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1024-b01)
• All Android licenses accepted.

[!] iOS toolchain - develop for iOS devices (Xcode 10.1)
• Xcode at /Applications/Xcode.app/Contents/Developer
• Xcode 10.1, Build version 10B61
✗ Verify that all connected devices have been paired with this computer in Xcode.
If all devices have been paired, libimobiledevice and ideviceinstaller may require updating.
To update with Brew, run:
brew update
brew uninstall --ignore-dependencies libimobiledevice
brew uninstall --ignore-dependencies usbmuxd
brew install --HEAD usbmuxd
brew unlink usbmuxd
brew link usbmuxd
brew install --HEAD libimobiledevice
brew install ideviceinstaller
• ios-deploy 2.0.0
• CocoaPods version 1.5.3

[✓] Android Studio (version 3.1)
• Android Studio at /Applications/Android Studio.app/Contents
• Flutter plugin version 26.0.1
• Dart plugin version 173.4700
• Java version OpenJDK Runtime Environment (build 1.8.0_152-release-1024-b01)

[✓] VS Code (version 1.29.1)
• VS Code at /Applications/Visual Studio Code.app/Contents
• Flutter extension version 2.20.0

[✓] Connected device (1 available)
• iPhone XR • 1EE1625B-61BB-421E-B104-CB3C8A5B6076 • ios • iOS 12.1 (simulator)

! Doctor found issues in 1 category.

Issue 2:
Steps to Reproduce

cd /examples/hello_world/

connect a physical Android device

flutter run

Output:

mit-macbookpro2:hello_world mit$ flutter run -d 00ca05b380789730
Launching lib/main.dart on Nexus 5X in debug mode...
Exception from flutter run: FormatException: Bad UTF-8 encoding 0xff
dart:isolate _RawReceivePortImpl._handleMessage

Building APK in debug mode (android-arm)... 5081ms
Installing build/app.apk... 2435ms
Syncing files to device... 3968ms

Running on an Android or iOS simulator does not throw the exception!

Flutter Doctor

Paste the output of running flutter doctor here.

$ flutter doctor
[✓] Flutter (on Mac OS, channel master)
• Flutter at /Users/mit/dev/github/flutter
• Framework revision 3a43fc88b6 (5 hours ago), 2017-01-31 23:32:10
• Engine revision 2d54edf0f9
• Tools Dart version 1.22.0-dev.9.1

[✓] Android toolchain - develop for Android devices (Android SDK 25.0.0)
• Android SDK at /Users/mit/Library/Android/sdk
• Platform android-25, build-tools 25.0.0
• ANDROID_HOME = /Users/mit/Library/Android/sdk
• Java(TM) SE Runtime Environment (build 1.8.0_112-b16)

[✓] iOS toolchain - develop for iOS devices (Xcode 8.2.1)
• XCode at /Applications/Xcode.app/Contents/Developer
• Xcode 8.2.1, Build version 8C1002

[✓] IntelliJ IDEA Ultimate Edition (version 2016.3.4)
• Dart plugin version 163.12753
• Flutter plugin version 0.1.8.1

[✓] Connected devices
• Nexus 5X • 00ca05b380789730 • android-arm • Android 7.0 (API 24)
• Android SDK built for x86 • emulator-5554 • android-x86 • Android 6.0 (API 23) (emulator)
$