From 0f3721f6963b761946265de5a30f7fdf050cf754 Mon Sep 17 00:00:00 2001 From: Andrew Brogdon Date: Fri, 13 Sep 2019 10:25:16 -0700 Subject: [PATCH] Quarterly maintenance on isolate_example (#142) --- MAINTENANCE.md | 2 +- isolate_example/.gitignore | 10 +- isolate_example/.metadata | 10 ++ .../flutter/isolate_example/MainActivity.kt | 13 ++ isolate_example/ios/Runner/AppDelegate.swift | 13 ++ .../ios/Runner/Runner-Bridging-Header.h | 1 + isolate_example/lib/main.dart | 25 ++- isolate_example/lib/page_one.dart | 100 +++++------ isolate_example/lib/page_three.dart | 120 ++++++------- isolate_example/lib/page_two.dart | 163 ++++++++---------- isolate_example/pubspec.lock | 9 +- isolate_example/pubspec.yaml | 5 +- 12 files changed, 243 insertions(+), 228 deletions(-) create mode 100644 isolate_example/.metadata create mode 100644 isolate_example/android/app/src/main/kotlin/dev/flutter/isolate_example/MainActivity.kt create mode 100644 isolate_example/ios/Runner/AppDelegate.swift create mode 100644 isolate_example/ios/Runner/Runner-Bridging-Header.h diff --git a/MAINTENANCE.md b/MAINTENANCE.md index 54d5876a1..117be4515 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -10,7 +10,7 @@ match any new language/SDK features, etc.). | chrome-os-best-practices | | | | experimental | | | | flutter_maps_firestore | | | -| isolate_example | | | +| isolate_example | redbrogdon | 9/12/19 | | jsonexample | | | | material_studies/shrine | | | | place_tracker | | | diff --git a/isolate_example/.gitignore b/isolate_example/.gitignore index 65a5e35db..7e44e9bb9 100644 --- a/isolate_example/.gitignore +++ b/isolate_example/.gitignore @@ -15,8 +15,10 @@ *.iws .idea/ -# Visual Studio Code related -.vscode/ +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ # Flutter/Dart/Pub related **/doc/api/ @@ -59,6 +61,7 @@ **/ios/Flutter/app.flx **/ios/Flutter/app.zip **/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh **/ios/ServiceDefinitions.json **/ios/Runner/GeneratedPluginRegistrant.* @@ -67,5 +70,4 @@ !**/ios/**/default.mode2v3 !**/ios/**/default.pbxuser !**/ios/**/default.perspectivev3 -!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages -bash: parse_git_branch: command not found \ No newline at end of file +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages \ No newline at end of file diff --git a/isolate_example/.metadata b/isolate_example/.metadata new file mode 100644 index 000000000..aeb01ee24 --- /dev/null +++ b/isolate_example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 2d2a1ffec95cc70a3218872a2cd3f8de4933c42f + channel: stable + +project_type: app diff --git a/isolate_example/android/app/src/main/kotlin/dev/flutter/isolate_example/MainActivity.kt b/isolate_example/android/app/src/main/kotlin/dev/flutter/isolate_example/MainActivity.kt new file mode 100644 index 000000000..e42a7f888 --- /dev/null +++ b/isolate_example/android/app/src/main/kotlin/dev/flutter/isolate_example/MainActivity.kt @@ -0,0 +1,13 @@ +package dev.flutter.isolate_example + +import android.os.Bundle + +import io.flutter.app.FlutterActivity +import io.flutter.plugins.GeneratedPluginRegistrant + +class MainActivity: FlutterActivity() { + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + GeneratedPluginRegistrant.registerWith(this) + } +} diff --git a/isolate_example/ios/Runner/AppDelegate.swift b/isolate_example/ios/Runner/AppDelegate.swift new file mode 100644 index 000000000..70693e4a8 --- /dev/null +++ b/isolate_example/ios/Runner/AppDelegate.swift @@ -0,0 +1,13 @@ +import UIKit +import Flutter + +@UIApplicationMain +@objc class AppDelegate: FlutterAppDelegate { + override func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + GeneratedPluginRegistrant.register(with: self) + return super.application(application, didFinishLaunchingWithOptions: launchOptions) + } +} diff --git a/isolate_example/ios/Runner/Runner-Bridging-Header.h b/isolate_example/ios/Runner/Runner-Bridging-Header.h new file mode 100644 index 000000000..7335fdf90 --- /dev/null +++ b/isolate_example/ios/Runner/Runner-Bridging-Header.h @@ -0,0 +1 @@ +#import "GeneratedPluginRegistrant.h" \ No newline at end of file diff --git a/isolate_example/lib/main.dart b/isolate_example/lib/main.dart index 67d7e1f6b..dcf6ca594 100644 --- a/isolate_example/lib/main.dart +++ b/isolate_example/lib/main.dart @@ -15,10 +15,16 @@ import 'package:flutter/material.dart'; import 'page_one.dart'; -import 'page_two.dart'; import 'page_three.dart'; +import 'page_two.dart'; -void main() => runApp(MaterialApp(home: StartApp())); +void main() { + runApp( + MaterialApp( + home: StartApp(), + ), + ); +} class StartApp extends StatelessWidget { @override @@ -30,9 +36,18 @@ class StartApp extends StatelessWidget { appBar: AppBar( bottom: TabBar( tabs: [ - Tab(icon: Icon(Icons.flash_on), text: 'Performance'), - Tab(icon: Icon(Icons.sync), text: 'Infinite Process'), - Tab(icon: Icon(Icons.storage), text: 'Data Transfer'), + Tab( + icon: Icon(Icons.flash_on), + text: 'Performance', + ), + Tab( + icon: Icon(Icons.sync), + text: 'Infinite Process', + ), + Tab( + icon: Icon(Icons.storage), + text: 'Data Transfer', + ), ], ), title: Text('Isolate Example'), diff --git a/isolate_example/lib/page_one.dart b/isolate_example/lib/page_one.dart index fa3231deb..ec199e20d 100644 --- a/isolate_example/lib/page_one.dart +++ b/isolate_example/lib/page_one.dart @@ -15,6 +15,20 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +// Computes the nth number in the Fibonacci sequence. +int fib(int n) { + int number1 = n - 1; + int number2 = n - 2; + + if (n == 1) { + return 0; + } else if (n == 0) { + return 1; + } else { + return (fib(number1) + fib(number2)); + } +} + class PerformancePage extends StatefulWidget { @override _PerformancePageState createState() => _PerformancePageState(); @@ -65,7 +79,9 @@ class _PerformancePageState extends State { } VoidCallback createMainIsolateCallBack( - BuildContext context, AsyncSnapshot snapshot) { + BuildContext context, + AsyncSnapshot snapshot, + ) { if (snapshot.connectionState == ConnectionState.done) { return () { setState(() { @@ -101,27 +117,20 @@ class _PerformancePageState extends State { return null; } } -} - -Future computeOnMainIsolate() async { - // The isolate will need a little time to disable the buttons before the performance hit. - await Future.delayed(Duration(milliseconds: 100), () => fib(45)); -} - -Future computeOnSecondaryIsolate() async { - await compute(fib, 45); -} -int fib(int n) { - int number1 = n - 1; - int number2 = n - 2; + Future computeOnMainIsolate() async { + // A delay is added here to give Flutter the chance to redraw the UI at least + // once before the computation (which, since it's run on the main isolate, + // will lock up the app) begins executing. + await Future.delayed( + Duration(milliseconds: 100), + () => fib(45), + ); + } - if (n == 1) { - return 0; - } else if (n == 0) { - return 1; - } else { - return (fib(number1) + fib(number2)); + Future computeOnSecondaryIsolate() async { + // Compute the Fibonacci series on a secondary isolate. + await compute(fib, 45); } } @@ -133,44 +142,32 @@ class SmoothAnimationWidget extends StatefulWidget { class SmoothAnimationWidgetState extends State with TickerProviderStateMixin { AnimationController _controller; - Animation borderRadius; + Animation _borderAnimation; @override void initState() { super.initState(); - _controller = - AnimationController(duration: const Duration(seconds: 1), vsync: this) - ..addStatusListener( - (status) { - if (status == AnimationStatus.completed) { - _controller.reverse(); - } else if (status == AnimationStatus.dismissed) { - _controller.forward(); - } - }, - ); + _controller = AnimationController( + duration: const Duration(seconds: 1), + vsync: this, + ); - borderRadius = BorderRadiusTween( + _borderAnimation = BorderRadiusTween( begin: BorderRadius.circular(100.0), end: BorderRadius.circular(0.0), - ).animate( - CurvedAnimation( - parent: _controller, - curve: Curves.linear, - ), - ); + ).animate(_controller); - _controller.forward(); + _controller.repeat(reverse: true); } @override Widget build(BuildContext context) { - return AnimatedBuilder( - animation: borderRadius, - builder: (context, child) { - return Center( - child: Container( + return Center( + child: AnimatedBuilder( + animation: _borderAnimation, + builder: (context, child) { + return Container( child: FlutterLogo( size: 200, ), @@ -180,13 +177,16 @@ class SmoothAnimationWidgetState extends State decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, - colors: [Colors.blueAccent, Colors.redAccent], + colors: [ + Colors.blueAccent, + Colors.redAccent, + ], ), - borderRadius: borderRadius.value, + borderRadius: _borderAnimation.value, ), - ), - ); - }, + ); + }, + ), ); } diff --git a/isolate_example/lib/page_three.dart b/isolate_example/lib/page_three.dart index 043cb4190..b47b1ca59 100644 --- a/isolate_example/lib/page_three.dart +++ b/isolate_example/lib/page_three.dart @@ -11,9 +11,11 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -import 'dart:typed_data'; + import 'dart:isolate'; import 'dart:math'; +import 'dart:typed_data'; + import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -30,6 +32,8 @@ class DataTransferPageStarter extends StatelessWidget { class DataTransferPage extends StatelessWidget { @override Widget build(context) { + final controller = Provider.of(context); + return SafeArea( child: Column( mainAxisSize: MainAxisSize.min, @@ -42,17 +46,34 @@ class DataTransferPage extends StatelessWidget { padding: EdgeInsets.all(8), ), LinearProgressIndicator( - value: Provider.of(context) - .progressPercent, + value: controller.progressPercent, backgroundColor: Colors.grey[200], ), - Expanded(child: RunningList()), + Expanded( + child: RunningList(), + ), Column( - mainAxisSize: MainAxisSize.min, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [createButtons(context)], + RaisedButton( + child: const Text('Transfer Data to 2nd Isolate'), + color: (controller.runningTest == 1) + ? Colors.blueAccent + : Colors.grey[300], + onPressed: () => controller.generateRandomNumbers(false), + ), + RaisedButton( + child: const Text('Transfer Data with TransferableTypedData'), + color: (controller.runningTest == 2) + ? Colors.blueAccent + : Colors.grey[300], + onPressed: () => controller.generateRandomNumbers(true), + ), + RaisedButton( + child: const Text('Generate on 2nd Isolate'), + color: (controller.runningTest == 3) + ? Colors.blueAccent + : Colors.grey[300], + onPressed: controller.generateOnSecondaryIsolate, ), ], ), @@ -63,33 +84,39 @@ class DataTransferPage extends StatelessWidget { } class DataTransferIsolateController extends ChangeNotifier { - Isolate _newIsolate; - ReceivePort _mIceRP; - SendPort _newIceSP; + Isolate _isolate; + ReceivePort _incomingReceivePort; + SendPort _outgoingSendPort; final currentProgress = []; int runningTest = 0; Stopwatch _timer = Stopwatch(); double progressPercent = 0; + Isolate get newIsolate => _isolate; + + bool get running => runningTest != 0; + DataTransferIsolateController() { createIsolate(); listen(); } Future createIsolate() async { - _mIceRP = ReceivePort(); - _newIsolate = - await Isolate.spawn(_secondIsolateEntryPoint, _mIceRP.sendPort); + _incomingReceivePort = ReceivePort(); + _isolate = await Isolate.spawn( + _secondIsolateEntryPoint, _incomingReceivePort.sendPort); } void listen() { - _mIceRP.listen((dynamic message) { - if (message is SendPort) _newIceSP = message; + _incomingReceivePort.listen((dynamic message) { + if (message is SendPort) { + _outgoingSendPort = message; + } if (message is int) { currentProgress.insert( - 0, '$message% - ${_timer.elapsedMilliseconds / 1000} Sec'); + 0, '$message% - ${_timer.elapsedMilliseconds / 1000} seconds'); progressPercent = message / 100; } @@ -109,7 +136,7 @@ class DataTransferIsolateController extends ChangeNotifier { _timer = Stopwatch(); _timer.start(); - _newIceSP.send('start'); + _outgoingSendPort.send('start'); notifyListeners(); } @@ -150,26 +177,22 @@ class DataTransferIsolateController extends ChangeNotifier { Future sendNumbers(dynamic numList) async { return Future(() { - _newIceSP.send(numList); + _outgoingSendPort.send(numList); }); } - Isolate get newIsolate => _newIsolate; - - bool get running => runningTest != 0; - void dispose() { super.dispose(); - _newIsolate?.kill(priority: Isolate.immediate); - _newIsolate = null; + _isolate?.kill(priority: Isolate.immediate); + _isolate = null; } } class RunningList extends StatelessWidget { @override Widget build(BuildContext context) { - final controller = Provider.of(context); - final progress = controller.currentProgress; + final progress = + Provider.of(context).currentProgress; return DecoratedBox( decoration: BoxDecoration( @@ -234,7 +257,10 @@ Iterable createNums() sync* { } Future generateAndSum( - SendPort callerSP, Iterable iter, int length) async { + SendPort callerSP, + Iterable iter, + int length, +) async { int sum = 0; int count = 1; @@ -248,41 +274,3 @@ Future generateAndSum( return sum; } - -Widget createButtons(BuildContext context) { - final controller = - Provider.of(context, listen: false); - return ButtonBar( - alignment: MainAxisAlignment.center, - children: [ - Column( - children: [ - RaisedButton( - child: const Text('Transfer Data to 2nd Isolate'), - elevation: 8.0, - color: (controller.runningTest == 1) - ? Colors.blueAccent - : Colors.grey[300], - onPressed: () => controller.generateRandomNumbers(false), - ), - RaisedButton( - child: const Text('Transfer Data with TransferableTypedData'), - elevation: 8.0, - color: (controller.runningTest == 2) - ? Colors.blueAccent - : Colors.grey[300], - onPressed: () => controller.generateRandomNumbers(true), - ), - RaisedButton( - child: const Text('Generate on 2nd Isolate'), - elevation: 8.0, - color: (controller.runningTest == 3) - ? Colors.blueAccent - : Colors.grey[300], - onPressed: controller.generateOnSecondaryIsolate, - ), - ], - ), - ], - ); -} diff --git a/isolate_example/lib/page_two.dart b/isolate_example/lib/page_two.dart index 92e2060bb..d5a91ed0e 100644 --- a/isolate_example/lib/page_two.dart +++ b/isolate_example/lib/page_two.dart @@ -12,11 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; import 'dart:isolate'; import 'dart:math'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + class InfiniteProcessPageStarter extends StatelessWidget { @override Widget build(context) { @@ -32,28 +33,40 @@ class InfiniteProcessPage extends StatelessWidget { Widget build(context) { final controller = Provider.of(context); - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Padding( - child: Text( - 'Summation Results', - style: Theme.of(context).textTheme.title, + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( + padding: const EdgeInsets.all(16.0), + child: Text( + 'Summation Results', + style: Theme.of(context).textTheme.title, + ), + ), + Expanded( + child: RunningList(), ), - padding: EdgeInsets.all(8), - ), - Expanded(child: RunningList()), - SafeArea( - child: Column( + Column( mainAxisSize: MainAxisSize.min, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [newButtons(context)], + ButtonBar( + alignment: MainAxisAlignment.center, + children: [ + RaisedButton( + child: const Text('Start'), + elevation: 8.0, + onPressed: () => controller.start(), + ), + RaisedButton( + child: const Text('Terminate'), + elevation: 8.0, + onPressed: () => controller.terminate(), + ), + ], ), Row( mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, children: [ Switch( value: !controller.paused, @@ -66,11 +79,23 @@ class InfiniteProcessPage extends StatelessWidget { Text('Pause/Resume'), ], ), - radioButtonWidget(context), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + for (int i = 1; i <= 3; i++) ...[ + Radio( + value: i, + groupValue: controller.currentMultiplier, + onChanged: (val) => controller.setMultiplier(val), + ), + Text('${i}x') + ], + ], + ), ], ), - ), - ], + ], + ), ); } } @@ -83,9 +108,17 @@ class InfiniteProcessIsolateController extends ChangeNotifier { int _currentMultiplier = 1; List _currentResults = []; - bool _running = false; + bool _created = false; bool _paused = false; + int get currentMultiplier => _currentMultiplier; + + bool get paused => _paused; + + bool get created => _created; + + List get currentResults => _currentResults; + Future createIsolate() async { mIceRP = ReceivePort(); newIsolate = await Isolate.spawn(_secondIsolateEntryPoint, mIceRP.sendPort); @@ -103,23 +136,28 @@ class InfiniteProcessIsolateController extends ChangeNotifier { } Future start() async { - if (_running == false && _paused == false) { + if (_created == false && _paused == false) { await createIsolate(); listen(); - _running = true; + _created = true; notifyListeners(); } } void terminate() { newIsolate.kill(); - _running = false; + _created = false; _currentResults.clear(); notifyListeners(); } void pausedSwitch() { - (_paused) ? newIsolate.resume(capability) : capability = newIsolate.pause(); + if (_paused) { + newIsolate.resume(capability); + } else { + capability = newIsolate.pause(); + } + _paused = !_paused; notifyListeners(); } @@ -135,18 +173,10 @@ class InfiniteProcessIsolateController extends ChangeNotifier { notifyListeners(); } - int get multiplier => _currentMultiplier; - - bool get paused => _paused; - - bool get running => _running; - - List get currentResults => _currentResults; - void dispose() { - super.dispose(); newIsolate?.kill(priority: Isolate.immediate); newIsolate = null; + super.dispose(); } } @@ -171,7 +201,7 @@ class RunningList extends StatelessWidget { leading: Text('${sums.length - index}.'), title: Text('${sums[index]}.'), ), - color: (controller.running && !controller.paused) + color: (controller.created && !controller.paused) ? Colors.lightGreenAccent : Colors.deepOrangeAccent, ), @@ -197,77 +227,28 @@ Future _secondIsolateEntryPoint(SendPort callerSP) async { if (message is int) multiplyValue = message; }); - int forEnd = 10000; + // This runs until the isolate is terminated. while (true) { int sum = 0; - for (int i = 0; i < forEnd; i++) { - sum += await brokenUpComputation(1000); + for (int i = 0; i < 10000; i++) { + sum += await doSomeWork(); } - forEnd += 10; callerSP.send(sum * multiplyValue); } } -Future brokenUpComputation(int num) { +Future doSomeWork() { Random rng = Random(); return Future(() { int sum = 0; - for (int i = 0; i < num; i++) { + for (int i = 0; i < 1000; i++) { sum += rng.nextInt(100); } + return sum; }); } - -Widget newButtons(BuildContext context) { - final controller = - Provider.of(context, listen: false); - - return ButtonBar( - alignment: MainAxisAlignment.center, - children: [ - RaisedButton( - child: const Text('Start'), - elevation: 8.0, - onPressed: () => controller.start(), - ), - RaisedButton( - child: const Text('Terminate'), - elevation: 8.0, - onPressed: () => controller.terminate(), - ), - ], - ); -} - -Widget radioButtonWidget(BuildContext context) { - final controller = Provider.of(context); - - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Radio( - value: 1, - groupValue: controller.multiplier, - onChanged: (dynamic _) => controller.setMultiplier(1), - ), - Text('1x'), - Radio( - value: 2, - groupValue: controller.multiplier, - onChanged: (dynamic _) => controller.setMultiplier(2), - ), - Text('2x'), - Radio( - value: 3, - groupValue: controller.multiplier, - onChanged: (dynamic _) => controller.setMultiplier(3), - ), - Text('3x'), - ], - ); -} diff --git a/isolate_example/pubspec.lock b/isolate_example/pubspec.lock index dd10a80a3..d791389ad 100644 --- a/isolate_example/pubspec.lock +++ b/isolate_example/pubspec.lock @@ -29,13 +29,6 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.14.11" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "0.1.2" flutter: dependency: "direct main" description: flutter @@ -80,7 +73,7 @@ packages: name: provider url: "https://pub.dartlang.org" source: hosted - version: "3.0.0+1" + version: "3.1.0" quiver: dependency: transitive description: diff --git a/isolate_example/pubspec.yaml b/isolate_example/pubspec.yaml index 20d12f5db..7b2f34e5f 100644 --- a/isolate_example/pubspec.yaml +++ b/isolate_example/pubspec.yaml @@ -8,13 +8,12 @@ environment: dependencies: flutter: sdk: flutter - cupertino_icons: ^0.1.2 - provider: ^3.0.0 + provider: ^3.1.0 dev_dependencies: flutter_test: sdk: flutter - pedantic: ^1.7.0 + pedantic: ^1.8.0+1 flutter: