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 | |
5 | import 'package:flutter/foundation.dart'; |
6 | |
7 | import '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 |
25 | class 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 | |