Skip to content

Commit db30797

Browse files
committed
Overhaul dispose/controllers API (switch to stateful widget to dispose automatically, expose controllers to users in a proper API)
1 parent 434edae commit db30797

File tree

6 files changed

+125
-46
lines changed

6 files changed

+125
-46
lines changed

lib/flutter_html.dart

Lines changed: 108 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
library flutter_html;
22

3+
import 'package:chewie/chewie.dart';
4+
import 'package:chewie_audio/chewie_audio.dart';
35
import 'package:flutter/material.dart';
46
import 'package:flutter/rendering.dart';
57
import 'package:flutter_html/html_parser.dart';
@@ -8,6 +10,7 @@ import 'package:flutter_html/src/html_elements.dart';
810
import 'package:flutter_html/src/utils.dart';
911
import 'package:flutter_html/style.dart';
1012
import 'package:html/dom.dart' as dom;
13+
import 'package:video_player/video_player.dart';
1114
import 'package:webview_flutter/webview_flutter.dart';
1215

1316
//export render context api
@@ -22,7 +25,7 @@ export 'package:flutter_html/src/styled_element.dart';
2225
//export style api
2326
export 'package:flutter_html/style.dart';
2427

25-
class Html extends StatelessWidget {
28+
class Html extends StatefulWidget {
2629
/// The `Html` widget takes HTML as input and displays a RichText
2730
/// tree of the parsed HTML content.
2831
///
@@ -64,7 +67,8 @@ class Html extends StatelessWidget {
6467
this.tagsList = const [],
6568
this.style = const {},
6669
this.navigationDelegateForIframe,
67-
}) : document = null,
70+
})
71+
: document = null,
6872
assert(data != null),
6973
_anchorKey = anchorKey ?? GlobalKey(),
7074
super(key: key);
@@ -85,7 +89,8 @@ class Html extends StatelessWidget {
8589
this.tagsList = const [],
8690
this.style = const {},
8791
this.navigationDelegateForIframe,
88-
}) : data = null,
92+
})
93+
: data = null,
8994
assert(document != null),
9095
_anchorKey = anchorKey ?? GlobalKey(),
9196
super(key: key);
@@ -142,54 +147,124 @@ class Html extends StatelessWidget {
142147
/// to use NavigationDelegate.
143148
final NavigationDelegate? navigationDelegateForIframe;
144149

145-
static List<String> get tags => new List<String>.from(STYLED_ELEMENTS)
146-
..addAll(INTERACTABLE_ELEMENTS)
147-
..addAll(REPLACED_ELEMENTS)
148-
..addAll(LAYOUT_ELEMENTS)
149-
..addAll(TABLE_CELL_ELEMENTS)
150-
..addAll(TABLE_DEFINITION_ELEMENTS);
150+
/// Get the list of supported tags for the [Html] widget
151+
static List<String> get tags =>
152+
new List<String>.from(STYLED_ELEMENTS)
153+
..addAll(INTERACTABLE_ELEMENTS)..addAll(REPLACED_ELEMENTS)..addAll(
154+
LAYOUT_ELEMENTS)..addAll(TABLE_CELL_ELEMENTS)..addAll(
155+
TABLE_DEFINITION_ELEMENTS);
156+
157+
/// Protected member to track controllers used in all [Html] widgets. Please
158+
/// refrain from using this member, and rather use the [chewieAudioControllers],
159+
/// [chewieControllers], [videoPlayerControllers], and [audioPlayerControllers]
160+
/// getters to access the controllers in your own code.
161+
@protected
162+
static final InternalControllers controllers = InternalControllers();
163+
164+
/// Internal member to track controllers used in the specific [Html] widget.
165+
/// This is only used so controllers can be automatically disposed when the
166+
/// widget disposes.
167+
final InternalControllers _controllers = InternalControllers();
168+
169+
/// Getter for all [ChewieAudioController]s initialized by [Html] widgets.
170+
static List<ChewieAudioController> get chewieAudioControllers => controllers.chewieAudioControllers.values.toList();
171+
/// Getter for all [ChewieController]s initialized by [Html] widgets.
172+
static List<ChewieController> get chewieControllers => controllers.chewieControllers.values.toList();
173+
/// Getter for all [VideoPlayerController]s for video widgets initialized by [Html] widgets.
174+
static List<VideoPlayerController> get videoPlayerControllers => controllers.videoPlayerControllers.values.toList();
175+
/// Getter for all [VideoPlayerController]s for audio widgets initialized by [Html] widgets.
176+
static List<VideoPlayerController> get audioPlayerControllers => controllers.audioPlayerControllers.values.toList();
177+
178+
/// Convenience method to dispose all controllers used by all [Html] widgets
179+
/// at this time. This is not necessary to be called, as each [Html] widget
180+
/// will automatically handle disposing.
181+
static void disposeAll() {
182+
controllers.chewieAudioControllers.values.forEach((element) {
183+
element.dispose();
184+
});
185+
controllers.chewieControllers.values.forEach((ChewieController element) {
186+
element.dispose();
187+
});
188+
controllers.videoPlayerControllers.values.forEach((element) {
189+
element.dispose();
190+
});
191+
controllers.audioPlayerControllers.values.forEach((element) {
192+
element.dispose();
193+
});
194+
}
151195

152-
final InternalControllers controllers = InternalControllers();
196+
/// Internal method to add controllers to the global list and widget-specific
197+
/// list. This should not be used in your app code.
198+
void addController(int hashCode, dynamic controller, {bool isAudioController = false}) {
199+
if (controller is ChewieAudioController) {
200+
controllers.chewieAudioControllers[hashCode] = controller;
201+
_controllers.chewieAudioControllers[hashCode] = controller;
202+
} else if (controller is ChewieController) {
203+
controllers.chewieControllers[hashCode] = controller;
204+
_controllers.chewieControllers[hashCode] = controller;
205+
} else if (controller is VideoPlayerController && !isAudioController) {
206+
controllers.videoPlayerControllers[hashCode] = controller;
207+
_controllers.videoPlayerControllers[hashCode] = controller;
208+
} else if (controller is VideoPlayerController) {
209+
controllers.audioPlayerControllers[hashCode] = controller;
210+
_controllers.audioPlayerControllers[hashCode] = controller;
211+
}
212+
}
153213

214+
@override
215+
State<StatefulWidget> createState() => _HtmlState();
216+
}
217+
218+
class _HtmlState extends State<Html> {
219+
late final dom.Document doc;
220+
221+
@override
222+
void initState() {
223+
super.initState();
224+
doc =
225+
widget.data != null ? HtmlParser.parseHTML(widget.data!) : widget.document!;
226+
}
227+
228+
@override
154229
void dispose() {
155-
controllers.chewieAudioControllers.forEach((element) {
230+
widget._controllers.chewieAudioControllers.values.forEach((element) {
156231
element.dispose();
157232
});
158-
controllers.chewieControllers.forEach((element) {
233+
widget._controllers.chewieControllers.values.forEach((ChewieController element) {
159234
element.dispose();
160235
});
161-
controllers.videoPlayerControllers.forEach((element) {
236+
widget._controllers.videoPlayerControllers.values.forEach((element) {
162237
element.dispose();
163238
});
239+
widget._controllers.audioPlayerControllers.values.forEach((element) {
240+
element.dispose();
241+
});
242+
super.dispose();
164243
}
165244

166245
@override
167246
Widget build(BuildContext context) {
168-
final dom.Document doc =
169-
data != null ? HtmlParser.parseHTML(data!) : document!;
170-
final double? width = shrinkWrap ? null : MediaQuery.of(context).size.width;
171-
172247
return Container(
173-
width: width,
248+
width: widget.shrinkWrap ? null : MediaQuery.of(context).size.width,
174249
child: HtmlParser(
175-
key: _anchorKey,
250+
key: widget._anchorKey,
176251
htmlData: doc,
177-
onLinkTap: onLinkTap,
178-
onAnchorTap: onAnchorTap,
179-
onImageTap: onImageTap,
180-
onCssParseError: onCssParseError,
181-
onImageError: onImageError,
182-
onMathError: onMathError,
183-
shrinkWrap: shrinkWrap,
252+
onLinkTap: widget.onLinkTap,
253+
onAnchorTap: widget.onAnchorTap,
254+
onImageTap: widget.onImageTap,
255+
onCssParseError: widget.onCssParseError,
256+
onImageError: widget.onImageError,
257+
onMathError: widget.onMathError,
258+
shrinkWrap: widget.shrinkWrap,
184259
selectable: false,
185-
style: style,
186-
customRender: customRender,
260+
style: widget.style,
261+
customRender: widget.customRender,
187262
imageRenders: {}
188-
..addAll(customImageRenders)
263+
..addAll(widget.customImageRenders)
189264
..addAll(defaultImageRenders),
190-
tagsList: tagsList.isEmpty ? Html.tags : tagsList,
191-
navigationDelegateForIframe: navigationDelegateForIframe,
192-
root: this,
265+
tagsList: widget.tagsList.isEmpty ? Html.tags : widget.tagsList,
266+
navigationDelegateForIframe: widget.navigationDelegateForIframe,
267+
root: widget,
193268
),
194269
);
195270
}
@@ -297,6 +372,7 @@ class SelectableHtml extends StatelessWidget {
297372
/// Allows you to override the default scrollPhysics for [SelectableText.rich]
298373
final ScrollPhysics? scrollPhysics;
299374

375+
/// Get the list of supported tags for the [SelectableHtml] widget
300376
static List<String> get tags => new List<String>.from(SELECTABLE_ELEMENTS);
301377

302378
@override

lib/html_parser.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ class HtmlParser extends StatelessWidget {
5858
final List<String> tagsList;
5959
final NavigationDelegate? navigationDelegateForIframe;
6060
final OnTap? _onAnchorTap;
61-
final Html root;
61+
final Html? root;
6262
final TextSelectionControls? selectionControls;
6363
final ScrollPhysics? scrollPhysics;
6464

@@ -78,7 +78,7 @@ class HtmlParser extends StatelessWidget {
7878
required this.imageRenders,
7979
required this.tagsList,
8080
required this.navigationDelegateForIframe,
81-
required this.root,
81+
this.root,
8282
this.selectionControls,
8383
this.scrollPhysics,
8484
}) : this._onAnchorTap = onAnchorTap != null

lib/image_render.dart

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import 'dart:convert';
44
import 'package:flutter/material.dart';
55
import 'package:flutter_html/html_parser.dart';
66
import 'package:flutter_svg/flutter_svg.dart';
7-
import 'package:flutter_svg/parser.dart';
87
import 'package:html/dom.dart' as dom;
98

109
typedef ImageSourceMatcher = bool Function(

lib/src/replaced_element.dart

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import 'package:chewie/chewie.dart';
44
import 'package:chewie_audio/chewie_audio.dart';
55
import 'package:flutter/material.dart';
66
import 'package:flutter/widgets.dart';
7+
import 'package:flutter_html/flutter_html.dart';
78
import 'package:flutter_html/html_parser.dart';
89
import 'package:flutter_html/src/anchor.dart';
910
import 'package:flutter_html/src/html_elements.dart';
@@ -133,8 +134,8 @@ class AudioContentElement extends ReplacedElement {
133134
showControls: showControls,
134135
autoInitialize: true,
135136
);
136-
context.parser.root.controllers.videoPlayerControllers.add(audioController);
137-
context.parser.root.controllers.chewieAudioControllers.add(chewieAudioController);
137+
context.parser.root?.addController(element.hashCode, audioController, isAudioController: true);
138+
context.parser.root?.addController(element.hashCode, chewieAudioController);
138139
return Container(
139140
key: AnchorKey.of(context.parser.key, this),
140141
width: context.style.width ?? 300,
@@ -189,8 +190,8 @@ class VideoContentElement extends ReplacedElement {
189190
autoInitialize: true,
190191
aspectRatio: _width / _height,
191192
);
192-
context.parser.root.controllers.videoPlayerControllers.add(videoController);
193-
context.parser.root.controllers.chewieControllers.add(chewieController);
193+
context.parser.root?.addController(element.hashCode, videoController);
194+
context.parser.root?.addController(element.hashCode, chewieController);
194195
return AspectRatio(
195196
aspectRatio: _width / _height,
196197
child: Container(

lib/src/styled_element.dart

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import 'package:flutter_html/src/css_parser.dart';
33
import 'package:flutter_html/style.dart';
44
import 'package:html/dom.dart' as dom;
55
//TODO(Sub6Resources): don't use the internal code of the html package as it may change unexpectedly.
6+
//ignore: implementation_imports
67
import 'package:html/src/query_selector.dart';
78

89
/// A [StyledElement] applies a style to all of its children.
@@ -12,7 +13,7 @@ class StyledElement {
1213
final List<String> elementClasses;
1314
List<StyledElement> children;
1415
Style style;
15-
final dom.Node? _node;
16+
final dom.Element? _node;
1617

1718
StyledElement({
1819
this.name = "[[No name]]",
@@ -24,15 +25,15 @@ class StyledElement {
2425
}) : this._node = node;
2526

2627
bool matchesSelector(String selector) =>
27-
_node != null && matches(_node as dom.Element, selector);
28+
_node != null && matches(_node!, selector);
2829

2930
Map<String, String> get attributes =>
3031
_node?.attributes.map((key, value) {
3132
return MapEntry(key.toString(), value);
3233
}) ??
3334
Map<String, String>();
3435

35-
dom.Element? get element => _node as dom.Element?;
36+
dom.Element? get element => _node;
3637

3738
@override
3839
String toString() {

lib/src/utils.dart

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import 'dart:math';
33

44
import 'package:chewie/chewie.dart';
55
import 'package:chewie_audio/chewie_audio.dart';
6-
import 'package:flutter/gestures.dart';
76
import 'package:flutter/material.dart';
87
import 'package:video_player/video_player.dart';
98
import 'package:flutter_html/style.dart';
@@ -81,10 +80,13 @@ class CustomBorderSide {
8180
BorderStyle style;
8281
}
8382

83+
/// Helps keep track of controllers used by [Html] widgets.
84+
/// A map is used so that controllers are not duplicated on widget rebuild
8485
class InternalControllers {
85-
List<ChewieAudioController> chewieAudioControllers = [];
86-
List<ChewieController> chewieControllers = [];
87-
List<VideoPlayerController> videoPlayerControllers = [];
86+
Map<int, ChewieAudioController> chewieAudioControllers = {};
87+
Map<int, ChewieController> chewieControllers = {};
88+
Map<int, VideoPlayerController> audioPlayerControllers = {};
89+
Map<int, VideoPlayerController> videoPlayerControllers = {};
8890
}
8991

9092
String getRandString(int len) {

0 commit comments

Comments
 (0)