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 'dart:convert';
6import 'dart:isolate';
7
8import 'package:flutter/material.dart';
9import 'package:flutter/services.dart';
10
11typedef OnProgressListener = void Function(double completed, double total);
12typedef OnResultListener = void Function(String result);
13
14// An encapsulation of a large amount of synchronous processing.
15//
16// The choice of JSON parsing here is meant as an example that might surface
17// in real-world applications.
18class Calculator {
19 Calculator({required this.onProgressListener, required this.onResultListener, String? data})
20 : _data = _replicateJson(data, 10000);
21
22 final OnProgressListener onProgressListener;
23 final OnResultListener onResultListener;
24 final String _data;
25 // This example assumes that the number of objects to parse is known in
26 // advance. In a real-world situation, this might not be true; in that case,
27 // the app might choose to display an indeterminate progress indicator.
28 static const int _NUM_ITEMS = 110000;
29 static const int _NOTIFY_INTERVAL = 1000;
30
31 // Run the computation associated with this Calculator.
32 void run() {
33 int i = 0;
34 final JsonDecoder decoder = JsonDecoder((dynamic key, dynamic value) {
35 if (key is int && i++ % _NOTIFY_INTERVAL == 0) {
36 onProgressListener(i.toDouble(), _NUM_ITEMS.toDouble());
37 }
38 return value;
39 });
40 try {
41 final List<dynamic> result = decoder.convert(_data) as List<dynamic>;
42 final int n = result.length;
43 onResultListener('Decoded $n results');
44 } on FormatException catch (e, stack) {
45 debugPrint('Invalid JSON file: $e');
46 debugPrint('$stack');
47 }
48 }
49
50 static String _replicateJson(String? data, int count) {
51 final StringBuffer buffer = StringBuffer()..write('[');
52 for (int i = 0; i < count; i++) {
53 buffer.write(data);
54 if (i < count - 1) {
55 buffer.write(',');
56 }
57 }
58 buffer.write(']');
59 return buffer.toString();
60 }
61}
62
63// The current state of the calculation.
64enum CalculationState { idle, loading, calculating }
65
66// Structured message to initialize the spawned isolate.
67class CalculationMessage {
68 CalculationMessage(this.data, this.sendPort);
69 String data;
70 SendPort sendPort;
71}
72
73// A manager for the connection to a spawned isolate.
74//
75// Isolates communicate with each other via ReceivePorts and SendPorts.
76// This class manages these ports and maintains state related to the
77// progress of the background computation.
78class CalculationManager {
79 CalculationManager({required this.onProgressListener, required this.onResultListener})
80 : _receivePort = ReceivePort() {
81 _receivePort.listen(_handleMessage);
82 }
83
84 CalculationState _state = CalculationState.idle;
85 CalculationState get state => _state;
86 bool get isRunning => _state != CalculationState.idle;
87
88 double _completed = 0.0;
89 double _total = 1.0;
90
91 final OnProgressListener onProgressListener;
92 final OnResultListener onResultListener;
93
94 // Start the background computation.
95 //
96 // Does nothing if the computation is already running.
97 void start() {
98 if (!isRunning) {
99 _state = CalculationState.loading;
100 _runCalculation();
101 }
102 }
103
104 // Stop the background computation.
105 //
106 // Kills the isolate immediately, if spawned. Does nothing if the
107 // computation is not running.
108 void stop() {
109 if (isRunning) {
110 _state = CalculationState.idle;
111 if (_isolate != null) {
112 _isolate!.kill(priority: Isolate.immediate);
113 _isolate = null;
114 _completed = 0.0;
115 _total = 1.0;
116 }
117 }
118 }
119
120 final ReceivePort _receivePort;
121 Isolate? _isolate;
122
123 void _runCalculation() {
124 // Load the JSON string. This is done in the main isolate because spawned
125 // isolates do not have access to the root bundle. However, the loading
126 // process is asynchronous, so the UI will not block while the file is
127 // loaded.
128 rootBundle.loadString('services/data.json').then<void>((String data) {
129 if (isRunning) {
130 final CalculationMessage message = CalculationMessage(data, _receivePort.sendPort);
131 // Spawn an isolate to JSON-parse the file contents. The JSON parsing
132 // is synchronous, so if done in the main isolate, the UI would block.
133 Isolate.spawn<CalculationMessage>(_calculate, message).then<void>((Isolate isolate) {
134 if (!isRunning) {
135 isolate.kill(priority: Isolate.immediate);
136 } else {
137 _state = CalculationState.calculating;
138 _isolate = isolate;
139 }
140 });
141 }
142 });
143 }
144
145 void _handleMessage(dynamic message) {
146 if (message is List<double>) {
147 _completed = message[0];
148 _total = message[1];
149 onProgressListener(_completed, _total);
150 } else if (message is String) {
151 _completed = 0.0;
152 _total = 1.0;
153 _isolate = null;
154 _state = CalculationState.idle;
155 onResultListener(message);
156 }
157 }
158
159 // Main entry point for the spawned isolate.
160 //
161 // This entry point must be static, and its (single) argument must match
162 // the message passed in Isolate.spawn above. Typically, some part of the
163 // message will contain a SendPort so that the spawned isolate can
164 // communicate back to the main isolate.
165 //
166 // Static and global variables are initialized anew in the spawned isolate,
167 // in a separate memory space.
168 static void _calculate(CalculationMessage message) {
169 final SendPort sender = message.sendPort;
170 final Calculator calculator = Calculator(
171 onProgressListener: (double completed, double total) {
172 sender.send(<double>[completed, total]);
173 },
174 onResultListener: sender.send,
175 data: message.data,
176 );
177 calculator.run();
178 }
179}
180
181// Main app widget.
182//
183// The app shows a simple UI that allows control of the background computation,
184// as well as an animation to illustrate that the UI does not block while this
185// computation is performed.
186//
187// This is a StatefulWidget in order to hold the CalculationManager and
188// the AnimationController for the running animation.
189class IsolateExampleWidget extends StatefulWidget {
190 const IsolateExampleWidget({super.key});
191
192 @override
193 IsolateExampleState createState() => IsolateExampleState();
194}
195
196// Main application state.
197class IsolateExampleState extends State<StatefulWidget> with SingleTickerProviderStateMixin {
198 String _status = 'Idle';
199 String _label = 'Start';
200 String _result = ' ';
201 double _progress = 0.0;
202 late final AnimationController _animation = AnimationController(
203 duration: const Duration(milliseconds: 3600),
204 vsync: this,
205 )..repeat();
206 late final CalculationManager _calculationManager = CalculationManager(
207 onProgressListener: _handleProgressUpdate,
208 onResultListener: _handleResult,
209 );
210
211 @override
212 void dispose() {
213 _animation.dispose();
214 super.dispose();
215 }
216
217 @override
218 Widget build(BuildContext context) {
219 return Material(
220 child: Column(
221 mainAxisAlignment: MainAxisAlignment.spaceAround,
222 children: <Widget>[
223 RotationTransition(
224 turns: _animation,
225 child: Container(width: 120.0, height: 120.0, color: const Color(0xFF882222)),
226 ),
227 Opacity(
228 opacity: _calculationManager.isRunning ? 1.0 : 0.0,
229 child: CircularProgressIndicator(value: _progress),
230 ),
231 Text(_status),
232 Center(child: ElevatedButton(onPressed: _handleButtonPressed, child: Text(_label))),
233 Text(_result),
234 ],
235 ),
236 );
237 }
238
239 void _handleProgressUpdate(double completed, double total) {
240 _updateState(' ', completed / total);
241 }
242
243 void _handleResult(String result) {
244 _updateState(result, 0.0);
245 }
246
247 void _handleButtonPressed() {
248 if (_calculationManager.isRunning) {
249 _calculationManager.stop();
250 } else {
251 _calculationManager.start();
252 }
253 _updateState(' ', 0.0);
254 }
255
256 String _getStatus(CalculationState state) {
257 return switch (state) {
258 CalculationState.loading => 'Loading...',
259 CalculationState.calculating => 'In Progress',
260 CalculationState.idle => 'Idle',
261 };
262 }
263
264 void _updateState(String result, double progress) {
265 setState(() {
266 _result = result;
267 _progress = progress;
268 _label = _calculationManager.isRunning ? 'Stop' : 'Start';
269 _status = _getStatus(_calculationManager.state);
270 });
271 }
272}
273
274void main() {
275 runApp(const MaterialApp(home: IsolateExampleWidget()));
276}
277

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com