Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add BuildContext.mounted #111619

Merged
merged 3 commits into from Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
38 changes: 38 additions & 0 deletions packages/flutter/lib/src/widgets/framework.dart
Expand Up @@ -2081,6 +2081,29 @@ typedef ElementVisitor = void Function(Element element);
///
/// {@youtube 560 315 https://www.youtube.com/watch?v=rIaaH87z1-g}
///
/// Avoid storing instances of [BuildContext]s because they may become invalid
/// if the widget they are associated with is unmounted from the widget tree.
/// {@template flutter.widgets.BuildContext.asynchronous_gap}
/// If a [BuildContext] is used across an asynchronous gap (i.e. after performing
/// an asynchronous operation), consider checking [mounted] to determine whether
/// the context is still valid before interacting with it:
///
/// ```dart
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is almost a functional example app. Would it make sense to make it into a full sample?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, it doesn't demonstrate anything visually though. In this case I think a full sample would actually distract from the point this is trying to make...

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, makes sense. I mean it could show a dialog going away after a second, but I agree it's not all that visual.

/// @override
/// Widget build(BuildContext context) {
/// return OutlinedButton(
/// onPressed: () async {
/// await Future<void>.delayed(const Duration(seconds: 1));
/// if (context.mounted) {
/// Navigator.of(context).pop();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(here)

/// }
/// },
/// child: const Text('Delayed pop'),
/// );
/// }
/// ```
/// {@endtemplate}
///
/// [BuildContext] objects are actually [Element] objects. The [BuildContext]
/// interface is used to discourage direct manipulation of [Element] objects.
abstract class BuildContext {
Expand All @@ -2091,6 +2114,18 @@ abstract class BuildContext {
/// managing the rendering pipeline for this context.
BuildOwner? get owner;

/// Whether the [Widget] this context is associated with is currently
/// mounted in the widget tree.
///
/// Accessing the properties of the [BuildContext] or calling any methods on
/// it is only valid while mounted is true. If mounted is false, assertions
/// will trigger.
///
/// Once unmounted, a given [BuildContext] will never become mounted again.
///
/// {@macro flutter.widgets.BuildContext.asynchronous_gap}
bool get mounted;

/// Whether the [widget] is currently updating the widget or render tree.
///
/// For [StatefulWidget]s and [StatelessWidget]s this flag is true while
Expand Down Expand Up @@ -3271,6 +3306,9 @@ abstract class Element extends DiagnosticableTree implements BuildContext {
Widget get widget => _widget!;
Widget? _widget;

@override
bool get mounted => _widget != null;

/// Returns true if the Element is defunct.
///
/// This getter always returns false in profile and release builds.
Expand Down
63 changes: 63 additions & 0 deletions packages/flutter/test/widgets/build_context_test.dart
@@ -0,0 +1,63 @@
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
testWidgets('StatefulWidget BuildContext.mounted', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(TestStatefulWidget(
onBuild: (BuildContext context) {
capturedContext = context;
}
));
expect(capturedContext.mounted, isTrue);
await tester.pumpWidget(Container());
expect(capturedContext.mounted, isFalse);
});

testWidgets('StatelessWidget BuildContext.mounted', (WidgetTester tester) async {
late BuildContext capturedContext;
await tester.pumpWidget(TestStatelessWidget(
onBuild: (BuildContext context) {
capturedContext = context;
}
));
expect(capturedContext.mounted, isTrue);
await tester.pumpWidget(Container());
expect(capturedContext.mounted, isFalse);
});
}

typedef BuildCallback = void Function(BuildContext);

class TestStatelessWidget extends StatelessWidget {
const TestStatelessWidget({super.key, required this.onBuild});

final BuildCallback onBuild;

@override
Widget build(BuildContext context) {
onBuild(context);
return Container();
}
}

class TestStatefulWidget extends StatefulWidget {
const TestStatefulWidget({super.key, required this.onBuild});

final BuildCallback onBuild;

@override
State<TestStatefulWidget> createState() => _TestStatefulWidgetState();
}

class _TestStatefulWidgetState extends State<TestStatefulWidget> {
@override
Widget build(BuildContext context) {
widget.onBuild(context);
return Container();
}
}