1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:flutter/foundation.dart';
6
7import 'framework.dart';
8
9/// Provides non-leaking access to a [BuildContext].
10///
11/// A [BuildContext] is only valid if it is pointing to an active [Element].
12/// Once the [Element] is unmounted, the [BuildContext] should not be accessed
13/// further. This class makes it possible for a [StatefulWidget] to share its
14/// build context safely with other objects.
15///
16/// Creators of this object must guarantee the following:
17///
18/// 1. They create this object at or after [State.initState] but before
19/// [State.dispose]. In particular, do not attempt to create this from the
20/// constructor of a state.
21/// 2. They call [dispose] from [State.dispose].
22///
23/// This object will not hold on to the [State] after disposal.
24@optionalTypeArgs
25class DisposableBuildContext<T extends State> {
26 /// Creates an object that provides access to a [BuildContext] without leaking
27 /// a [State].
28 ///
29 /// Creators must call [dispose] when the [State] is disposed.
30 ///
31 /// [State.mounted] must be true.
32 DisposableBuildContext(T this._state)
33 : assert(_state.mounted, 'A DisposableBuildContext was given a BuildContext for an Element that is not mounted.') {
34 // TODO(polina-c): stop duplicating code across disposables
35 // https://github.com/flutter/flutter/issues/137435
36 if (kFlutterMemoryAllocationsEnabled) {
37 FlutterMemoryAllocations.instance.dispatchObjectCreated(
38 library: 'package:flutter/widgets.dart',
39 className: '$DisposableBuildContext',
40 object: this,
41 );
42 }
43 }
44
45 T? _state;
46
47 /// Provides safe access to the build context.
48 ///
49 /// If [dispose] has been called, will return null.
50 ///
51 /// Otherwise, asserts the [_state] is still mounted and returns its context.
52 BuildContext? get context {
53 assert(_debugValidate());
54 if (_state == null) {
55 return null;
56 }
57 return _state!.context;
58 }
59
60 /// Called from asserts or tests to determine whether this object is in a
61 /// valid state.
62 ///
63 /// Always returns true, but will assert if [dispose] has not been called
64 /// but the state this is tracking is unmounted.
65 bool _debugValidate() {
66 assert(
67 _state == null || _state!.mounted,
68 'A DisposableBuildContext tried to access the BuildContext of a disposed '
69 'State object. This can happen when the creator of this '
70 'DisposableBuildContext fails to call dispose when it is disposed.',
71 );
72 return true;
73 }
74
75
76 /// Marks the [BuildContext] as disposed.
77 ///
78 /// Creators of this object must call [dispose] when their [Element] is
79 /// unmounted, i.e. when [State.dispose] is called.
80 void dispose() {
81 // TODO(polina-c): stop duplicating code across disposables
82 // https://github.com/flutter/flutter/issues/137435
83 if (kFlutterMemoryAllocationsEnabled) {
84 FlutterMemoryAllocations.instance.dispatchObjectDisposed(object: this);
85 }
86 _state = null;
87 }
88}
89