feat: Provide a much cleaner Dart API
This commit is contained in:
		
							parent
							
								
									2299b766cc
								
							
						
					
					
						commit
						d6ce224956
					
				| @ -1,3 +1,4 @@ | ||||
| // ignore_for_file: avoid_print | ||||
| import 'dart:io'; | ||||
| import 'dart:typed_data'; | ||||
| import 'package:flutter/material.dart'; | ||||
| @ -10,13 +11,14 @@ import 'package:permission_handler/permission_handler.dart'; | ||||
| Future<void> serviceHandleData(Map<String, dynamic>? data) async { | ||||
|   print('[BG] Received data $data'); | ||||
|   GetIt.I.get<BackgroundService>().send( | ||||
|     TestEvent(), | ||||
|     id: data!['id']! as String, | ||||
|   ); | ||||
|         TestEvent(), | ||||
|         id: data!['id']! as String, | ||||
|       ); | ||||
| } | ||||
| 
 | ||||
| @pragma('vm:entry-point') | ||||
| Future<void> serviceEntrypoint(String initialLocale) async { | ||||
|   // avoid_print | ||||
|   print('Initial locale: $initialLocale'); | ||||
| } | ||||
| 
 | ||||
| @ -34,15 +36,15 @@ class MyApp extends StatefulWidget { | ||||
| class TestCommand extends BackgroundCommand { | ||||
|   @override | ||||
|   Map<String, dynamic> toJson() => { | ||||
|     'request': 'return_name', | ||||
|   }; | ||||
|         'request': 'return_name', | ||||
|       }; | ||||
| } | ||||
| 
 | ||||
| class TestEvent extends BackgroundEvent { | ||||
|   @override | ||||
|   Map<String, dynamic> toJson() => { | ||||
|     'name': 'Moxxy', | ||||
|   }; | ||||
|         'name': 'Moxxy', | ||||
|       }; | ||||
| } | ||||
| 
 | ||||
| class MyAppState extends State<MyApp> { | ||||
| @ -61,7 +63,6 @@ class MyAppState extends State<MyApp> { | ||||
|               onPressed: () async { | ||||
|                 final result = await MoxxyPickerApi() | ||||
|                     .pickFiles(FilePickerType.image, false); | ||||
|                 // ignore: avoid_print | ||||
|                 print('User picked: $result'); | ||||
|               }, | ||||
|               child: const Text('Photo picker'), | ||||
| @ -70,7 +71,6 @@ class MyAppState extends State<MyApp> { | ||||
|               onPressed: () async { | ||||
|                 final result = await MoxxyPickerApi() | ||||
|                     .pickFiles(FilePickerType.imageAndVideo, true); | ||||
|                 // ignore: avoid_print | ||||
|                 print('User picked: $result'); | ||||
|               }, | ||||
|               child: const Text('Photo/Video multi-picker'), | ||||
| @ -79,7 +79,6 @@ class MyAppState extends State<MyApp> { | ||||
|               onPressed: () async { | ||||
|                 final result = await MoxxyPickerApi() | ||||
|                     .pickFiles(FilePickerType.generic, true); | ||||
|                 // ignore: avoid_print | ||||
|                 print('User picked: $result'); | ||||
|               }, | ||||
|               child: const Text('Generic multi-picker'), | ||||
| @ -101,7 +100,6 @@ class MyAppState extends State<MyApp> { | ||||
|                   'SHA-256', | ||||
|                 ); | ||||
|                 if (encResult == null) { | ||||
|                   // ignore: avoid_print | ||||
|                   print('Failed to encrypt file'); | ||||
|                   return; | ||||
|                 } | ||||
| @ -115,7 +113,6 @@ class MyAppState extends State<MyApp> { | ||||
|                   'SHA-256', | ||||
|                 ); | ||||
|                 if (decResult == null) { | ||||
|                   // ignore: avoid_print | ||||
|                   print('Failed to decrypt file'); | ||||
|                   return; | ||||
|                 } | ||||
| @ -146,7 +143,7 @@ class MyAppState extends State<MyApp> { | ||||
| 
 | ||||
|                   await Permission.notification.request(); | ||||
| 
 | ||||
|                   final srv = ForegroundService(); | ||||
|                   final srv = getForegroundService(); | ||||
|                   await srv.start( | ||||
|                     const ServiceConfig( | ||||
|                       serviceEntrypoint, | ||||
| @ -159,7 +156,7 @@ class MyAppState extends State<MyApp> { | ||||
|                   ); | ||||
| 
 | ||||
|                   await Future<void>.delayed(const Duration(milliseconds: 600)); | ||||
|                   await srv.dataSender.sendData( | ||||
|                   await getForegroundService().send( | ||||
|                     TestCommand(), | ||||
|                     awaitable: false, | ||||
|                   ); | ||||
|  | ||||
| @ -108,7 +108,7 @@ packages: | ||||
|     source: hosted | ||||
|     version: "2.0.1" | ||||
|   logging: | ||||
|     dependency: transitive | ||||
|     dependency: "direct main" | ||||
|     description: | ||||
|       name: logging | ||||
|       sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340" | ||||
|  | ||||
| @ -30,6 +30,7 @@ dependencies: | ||||
|   cupertino_icons: ^1.0.2 | ||||
|   permission_handler: ^10.4.5 | ||||
|   get_it: ^7.6.0 | ||||
|   logging: ^1.2.0 | ||||
| 
 | ||||
| dev_dependencies: | ||||
|   flutter_test: | ||||
|  | ||||
| @ -6,4 +6,7 @@ export 'pigeon/notifications.g.dart'; | ||||
| export 'pigeon/picker.g.dart'; | ||||
| export 'pigeon/platform.g.dart'; | ||||
| export 'pigeon/service.g.dart'; | ||||
| export 'src/service.dart'; | ||||
| export 'src/service/background/base.dart'; | ||||
| export 'src/service/config.dart'; | ||||
| export 'src/service/datasender/types.dart'; | ||||
| export 'src/service/foreground/base.dart'; | ||||
|  | ||||
| @ -20,10 +20,10 @@ class MoxxyBackgroundServiceApi { | ||||
| 
 | ||||
|   Future<int> getHandler() async { | ||||
|     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.getHandler', codec, | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.getHandler', | ||||
|         codec, | ||||
|         binaryMessenger: _binaryMessenger); | ||||
|     final List<Object?>? replyList = | ||||
|         await channel.send(null) as List<Object?>?; | ||||
|     final List<Object?>? replyList = await channel.send(null) as List<Object?>?; | ||||
|     if (replyList == null) { | ||||
|       throw PlatformException( | ||||
|         code: 'channel-error', | ||||
| @ -47,10 +47,10 @@ class MoxxyBackgroundServiceApi { | ||||
| 
 | ||||
|   Future<String> getExtraData() async { | ||||
|     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.getExtraData', codec, | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.getExtraData', | ||||
|         codec, | ||||
|         binaryMessenger: _binaryMessenger); | ||||
|     final List<Object?>? replyList = | ||||
|         await channel.send(null) as List<Object?>?; | ||||
|     final List<Object?>? replyList = await channel.send(null) as List<Object?>?; | ||||
|     if (replyList == null) { | ||||
|       throw PlatformException( | ||||
|         code: 'channel-error', | ||||
| @ -74,7 +74,8 @@ class MoxxyBackgroundServiceApi { | ||||
| 
 | ||||
|   Future<void> setNotificationBody(String arg_body) async { | ||||
|     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.setNotificationBody', codec, | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.setNotificationBody', | ||||
|         codec, | ||||
|         binaryMessenger: _binaryMessenger); | ||||
|     final List<Object?>? replyList = | ||||
|         await channel.send(<Object?>[arg_body]) as List<Object?>?; | ||||
| @ -96,7 +97,8 @@ class MoxxyBackgroundServiceApi { | ||||
| 
 | ||||
|   Future<void> sendData(String arg_data) async { | ||||
|     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.sendData', codec, | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.sendData', | ||||
|         codec, | ||||
|         binaryMessenger: _binaryMessenger); | ||||
|     final List<Object?>? replyList = | ||||
|         await channel.send(<Object?>[arg_data]) as List<Object?>?; | ||||
| @ -120,8 +122,7 @@ class MoxxyBackgroundServiceApi { | ||||
|     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyBackgroundServiceApi.stop', codec, | ||||
|         binaryMessenger: _binaryMessenger); | ||||
|     final List<Object?>? replyList = | ||||
|         await channel.send(null) as List<Object?>?; | ||||
|     final List<Object?>? replyList = await channel.send(null) as List<Object?>?; | ||||
|     if (replyList == null) { | ||||
|       throw PlatformException( | ||||
|         code: 'channel-error', | ||||
|  | ||||
| @ -22,8 +22,8 @@ class MoxxyServiceApi { | ||||
|     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyServiceApi.configure', codec, | ||||
|         binaryMessenger: _binaryMessenger); | ||||
|     final List<Object?>? replyList = | ||||
|         await channel.send(<Object?>[arg_handle, arg_extraData]) as List<Object?>?; | ||||
|     final List<Object?>? replyList = await channel | ||||
|         .send(<Object?>[arg_handle, arg_extraData]) as List<Object?>?; | ||||
|     if (replyList == null) { | ||||
|       throw PlatformException( | ||||
|         code: 'channel-error', | ||||
| @ -44,8 +44,7 @@ class MoxxyServiceApi { | ||||
|     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyServiceApi.isRunning', codec, | ||||
|         binaryMessenger: _binaryMessenger); | ||||
|     final List<Object?>? replyList = | ||||
|         await channel.send(null) as List<Object?>?; | ||||
|     final List<Object?>? replyList = await channel.send(null) as List<Object?>?; | ||||
|     if (replyList == null) { | ||||
|       throw PlatformException( | ||||
|         code: 'channel-error', | ||||
| @ -71,8 +70,7 @@ class MoxxyServiceApi { | ||||
|     final BasicMessageChannel<Object?> channel = BasicMessageChannel<Object?>( | ||||
|         'dev.flutter.pigeon.moxxy_native.MoxxyServiceApi.start', codec, | ||||
|         binaryMessenger: _binaryMessenger); | ||||
|     final List<Object?>? replyList = | ||||
|         await channel.send(null) as List<Object?>?; | ||||
|     final List<Object?>? replyList = await channel.send(null) as List<Object?>?; | ||||
|     if (replyList == null) { | ||||
|       throw PlatformException( | ||||
|         code: 'channel-error', | ||||
|  | ||||
| @ -1,168 +0,0 @@ | ||||
| import 'dart:async'; | ||||
| import 'dart:convert'; | ||||
| import 'dart:io'; | ||||
| import 'dart:ui'; | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:logging/logging.dart'; | ||||
| import 'package:moxlib/moxlib.dart'; | ||||
| import 'package:moxxy_native/moxxy_native.dart'; | ||||
| import 'package:moxxy_native/src/service_android.dart'; | ||||
| import 'package:uuid/uuid.dart'; | ||||
| 
 | ||||
| typedef EntrypointCallback = Future<void> Function(String initialLocale); | ||||
| typedef HandleEventCallback = Future<void> Function(Map<String, dynamic>? data); | ||||
| 
 | ||||
| abstract class BackgroundCommand implements JsonImplementation {} | ||||
| 
 | ||||
| abstract class BackgroundEvent implements JsonImplementation {} | ||||
| 
 | ||||
| class ServiceConfig { | ||||
|   const ServiceConfig( | ||||
|     this.entrypoint, | ||||
|     this.handleData, | ||||
|     this.initialLocale, | ||||
|   ); | ||||
| 
 | ||||
|   factory ServiceConfig.fromString(String rawData) { | ||||
|     final data = jsonDecode(rawData) as Map<String, dynamic>; | ||||
|     return ServiceConfig( | ||||
|       PluginUtilities.getCallbackFromHandle( | ||||
|         CallbackHandle.fromRawHandle( | ||||
|           data['entrypoint']! as int, | ||||
|         ), | ||||
|       )! as EntrypointCallback, | ||||
|       PluginUtilities.getCallbackFromHandle( | ||||
|         CallbackHandle.fromRawHandle( | ||||
|           data['handleData']! as int, | ||||
|         ), | ||||
|       )! as HandleEventCallback, | ||||
|       data['initialLocale']! as String, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   final String initialLocale; | ||||
|   final EntrypointCallback entrypoint; | ||||
|   final HandleEventCallback handleData; | ||||
| 
 | ||||
|   @override | ||||
|   String toString() { | ||||
|     return jsonEncode({ | ||||
|       'entrypoint': PluginUtilities.getCallbackHandle(entrypoint)!.toRawHandle(), | ||||
|       'handleData': PluginUtilities.getCallbackHandle(handleData)!.toRawHandle(), | ||||
|       'initialLocale': initialLocale, | ||||
|     }); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// Wrapper API that is only available to the background service. | ||||
| class BackgroundService { | ||||
|   final MoxxyBackgroundServiceApi _api = MoxxyBackgroundServiceApi(); | ||||
| 
 | ||||
|   /// A method channel for Foreground -> Service communication | ||||
|   // TODO(Unknown): Move this into a constant for reuse | ||||
|   final MethodChannel _channel = MethodChannel('org.moxxy.moxxy_native/background'); | ||||
| 
 | ||||
|   /// A logger. | ||||
|   final Logger _log = Logger('BackgroundService'); | ||||
| 
 | ||||
|   Future<void> send(BackgroundEvent event, {String? id}) async { | ||||
|     final data = DataWrapper( | ||||
|       id ?? const Uuid().v4(), | ||||
|       event, | ||||
|     ); | ||||
| 
 | ||||
|     await _api.sendData(jsonEncode(data.toJson())); | ||||
|   } | ||||
| 
 | ||||
|   void init( | ||||
|     ServiceConfig config, | ||||
|   ) { | ||||
|     // Ensure that the Dart executor is ready to use plugins | ||||
|     WidgetsFlutterBinding.ensureInitialized(); | ||||
|     DartPluginRegistrant.ensureInitialized(); | ||||
| 
 | ||||
|     // Register the channel for Foreground -> Service communication | ||||
|     _channel.setMethodCallHandler((call) async { | ||||
|       // TODO(Unknown): Maybe do something smarter like pigeon and use Lists instead of Maps | ||||
|       final args = call.arguments! as String; | ||||
|       await config.handleData(jsonDecode(args) as Map<String, dynamic>); | ||||
|     }); | ||||
| 
 | ||||
|     // Start execution | ||||
|     _log.finest('Setup complete. Calling main entrypoint...'); | ||||
|     config.entrypoint(config.initialLocale); | ||||
|   } | ||||
| 
 | ||||
|   void setNotificationBody(String body) { | ||||
|     _api.setNotificationBody(body); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| class ForegroundServiceDataSender extends AwaitableDataSender<BackgroundCommand, BackgroundEvent> { | ||||
|   ForegroundServiceDataSender(this._api);  | ||||
|   final MoxxyServiceApi _api; | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> sendDataImpl(DataWrapper<JsonImplementation> data) { | ||||
|     return _api.sendData(jsonEncode(data.toJson())); | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| /// Wrapper API that is only available to the UI isolate. | ||||
| // TODO(Unknown): Dumb naming. Name it something better | ||||
| class ForegroundService { | ||||
|   ForegroundService() { | ||||
|     dataSender = ForegroundServiceDataSender(_api); | ||||
|   } | ||||
| 
 | ||||
|   final MoxxyServiceApi _api = MoxxyServiceApi(); | ||||
| 
 | ||||
|   /// A method channel for background service -> UI isolate communication. | ||||
|   final MethodChannel _channel = MethodChannel('org.moxxy.moxxy_native/foreground'); | ||||
| 
 | ||||
|   late final ForegroundServiceDataSender dataSender; | ||||
| 
 | ||||
|   /// A logger. | ||||
|   final Logger _log = Logger('ForegroundService'); | ||||
| 
 | ||||
|   Future<void> attach( | ||||
|     HandleEventCallback handleData,     | ||||
|   ) async { | ||||
|     _channel.setMethodCallHandler((call) async { | ||||
|       await handleData( | ||||
|         jsonDecode(call.arguments! as String) as Map<String, dynamic>, | ||||
|       ); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   Future<void> start( | ||||
|     ServiceConfig config, HandleEventCallback uiHandleData, | ||||
|   ) async { | ||||
|     int platformEntrypointHandle; | ||||
|     if (Platform.isAndroid) { | ||||
|       platformEntrypointHandle = PluginUtilities.getCallbackHandle( | ||||
|         androidEntrypoint, | ||||
|       )!.toRawHandle(); | ||||
|     } else { | ||||
|       // TODO: Custom exception | ||||
|       throw Exception('Unsupported platform'); | ||||
|     } | ||||
| 
 | ||||
|     // Configure the service on the native side | ||||
|     await _api.configure(platformEntrypointHandle, config.toString()); | ||||
| 
 | ||||
|     // Prepare the method channel | ||||
|     await attach(uiHandleData); | ||||
| 
 | ||||
|     // Start the service | ||||
|     await _api.start(); | ||||
|     _log.finest('Background service started...'); | ||||
|   } | ||||
| 
 | ||||
|   /// Returns true if the background service is already running. False, if not. | ||||
|   Future<bool> isRunning() async { | ||||
|     WidgetsFlutterBinding.ensureInitialized(); | ||||
|     return _api.isRunning(); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										16
									
								
								lib/src/service/background/base.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								lib/src/service/background/base.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | ||||
| import 'package:moxxy_native/src/service/config.dart'; | ||||
| import 'package:moxxy_native/src/service/datasender/types.dart'; | ||||
| 
 | ||||
| /// Wrapper API that is only available to the background service. | ||||
| abstract class BackgroundService { | ||||
|   /// Send [event] with optional id [id] to the foreground. | ||||
|   Future<void> send(BackgroundEvent event, {String? id}); | ||||
| 
 | ||||
|   /// Platform specific initialization routine that is called after | ||||
|   /// the entrypoint has been called. | ||||
|   Future<void> init(ServiceConfig config); | ||||
| 
 | ||||
|   /// Update the notification body, if the platform shows a persistent | ||||
|   /// notification. | ||||
|   void setNotificationBody(String body); | ||||
| } | ||||
							
								
								
									
										58
									
								
								lib/src/service/background/pigeon.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										58
									
								
								lib/src/service/background/pigeon.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,58 @@ | ||||
| import 'dart:convert'; | ||||
| import 'dart:ui'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:flutter/widgets.dart'; | ||||
| import 'package:logging/logging.dart'; | ||||
| import 'package:moxlib/moxlib.dart'; | ||||
| import 'package:moxxy_native/pigeon/background_service.g.dart'; | ||||
| import 'package:moxxy_native/src/service/background/base.dart'; | ||||
| import 'package:moxxy_native/src/service/config.dart'; | ||||
| import 'package:moxxy_native/src/service/datasender/types.dart'; | ||||
| import 'package:uuid/uuid.dart'; | ||||
| 
 | ||||
| class PigeonBackgroundService extends BackgroundService { | ||||
|   final MoxxyBackgroundServiceApi _api = MoxxyBackgroundServiceApi(); | ||||
| 
 | ||||
|   /// A method channel for Foreground -> Service communication | ||||
|   // TODO(Unknown): Move this into a constant for reuse | ||||
|   final MethodChannel _channel = | ||||
|       const MethodChannel('org.moxxy.moxxy_native/background'); | ||||
| 
 | ||||
|   /// A logger. | ||||
|   final Logger _log = Logger('PigeonBackgroundService'); | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> send(BackgroundEvent event, {String? id}) async { | ||||
|     final data = DataWrapper( | ||||
|       id ?? const Uuid().v4(), | ||||
|       event, | ||||
|     ); | ||||
| 
 | ||||
|     await _api.sendData(jsonEncode(data.toJson())); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> init( | ||||
|     ServiceConfig config, | ||||
|   ) async { | ||||
|     // Ensure that the Dart executor is ready to use plugins | ||||
|     WidgetsFlutterBinding.ensureInitialized(); | ||||
|     DartPluginRegistrant.ensureInitialized(); | ||||
| 
 | ||||
|     // Register the channel for Foreground -> Service communication | ||||
|     _channel.setMethodCallHandler((call) async { | ||||
|       // TODO(Unknown): Maybe do something smarter like pigeon and use Lists instead of Maps | ||||
|       final args = call.arguments! as String; | ||||
|       await config.handleData(jsonDecode(args) as Map<String, dynamic>); | ||||
|     }); | ||||
| 
 | ||||
|     // Start execution | ||||
|     _log.finest('Setup complete. Calling main entrypoint...'); | ||||
|     await config.entrypoint(config.initialLocale); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   void setNotificationBody(String body) { | ||||
|     _api.setNotificationBody(body); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										55
									
								
								lib/src/service/config.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								lib/src/service/config.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | ||||
| import 'dart:convert'; | ||||
| import 'dart:ui'; | ||||
| 
 | ||||
| /// A function that can act as a service entrypoint. | ||||
| typedef EntrypointCallback = Future<void> Function(String initialLocale); | ||||
| 
 | ||||
| /// A function that can be called when data is received. | ||||
| typedef HandleEventCallback = Future<void> Function(Map<String, dynamic>? data); | ||||
| 
 | ||||
| /// Configuration that will be passed to the service's entrypoint | ||||
| class ServiceConfig { | ||||
|   const ServiceConfig( | ||||
|     this.entrypoint, | ||||
|     this.handleData, | ||||
|     this.initialLocale, | ||||
|   ); | ||||
| 
 | ||||
|   /// Reconstruct the configuration from a JSON string. | ||||
|   factory ServiceConfig.fromString(String rawData) { | ||||
|     final data = jsonDecode(rawData) as Map<String, dynamic>; | ||||
|     return ServiceConfig( | ||||
|       PluginUtilities.getCallbackFromHandle( | ||||
|         CallbackHandle.fromRawHandle( | ||||
|           data['entrypoint']! as int, | ||||
|         ), | ||||
|       )! as EntrypointCallback, | ||||
|       PluginUtilities.getCallbackFromHandle( | ||||
|         CallbackHandle.fromRawHandle( | ||||
|           data['handleData']! as int, | ||||
|         ), | ||||
|       )! as HandleEventCallback, | ||||
|       data['initialLocale']! as String, | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   /// The initial locale to use. | ||||
|   final String initialLocale; | ||||
| 
 | ||||
|   /// The entrypoint to call into. | ||||
|   final EntrypointCallback entrypoint; | ||||
| 
 | ||||
|   /// Entry function to call when the service receives data. | ||||
|   final HandleEventCallback handleData; | ||||
| 
 | ||||
|   @override | ||||
|   String toString() { | ||||
|     return jsonEncode({ | ||||
|       'entrypoint': | ||||
|           PluginUtilities.getCallbackHandle(entrypoint)!.toRawHandle(), | ||||
|       'handleData': | ||||
|           PluginUtilities.getCallbackHandle(handleData)!.toRawHandle(), | ||||
|       'initialLocale': initialLocale, | ||||
|     }); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										15
									
								
								lib/src/service/datasender/pigeon.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								lib/src/service/datasender/pigeon.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,15 @@ | ||||
| import 'dart:convert'; | ||||
| import 'package:moxlib/moxlib.dart'; | ||||
| import 'package:moxxy_native/pigeon/service.g.dart'; | ||||
| import 'package:moxxy_native/src/service/datasender/types.dart'; | ||||
| 
 | ||||
| class PigeonForegroundServiceDataSender | ||||
|     extends AwaitableDataSender<BackgroundCommand, BackgroundEvent> { | ||||
|   PigeonForegroundServiceDataSender(this._api); | ||||
|   final MoxxyServiceApi _api; | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> sendDataImpl(DataWrapper<JsonImplementation> data) { | ||||
|     return _api.sendData(jsonEncode(data.toJson())); | ||||
|   } | ||||
| } | ||||
							
								
								
									
										20
									
								
								lib/src/service/datasender/types.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								lib/src/service/datasender/types.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,20 @@ | ||||
| import 'dart:io'; | ||||
| import 'package:moxlib/moxlib.dart'; | ||||
| import 'package:moxxy_native/pigeon/service.g.dart'; | ||||
| import 'package:moxxy_native/src/service/datasender/pigeon.dart'; | ||||
| import 'package:moxxy_native/src/service/exceptions.dart'; | ||||
| 
 | ||||
| typedef ForegroundServiceDataSender | ||||
|     = AwaitableDataSender<BackgroundCommand, BackgroundEvent>; | ||||
| 
 | ||||
| abstract class BackgroundCommand implements JsonImplementation {} | ||||
| 
 | ||||
| abstract class BackgroundEvent implements JsonImplementation {} | ||||
| 
 | ||||
| ForegroundServiceDataSender getForegroundDataSender(MoxxyServiceApi api) { | ||||
|   if (Platform.isAndroid) { | ||||
|     return PigeonForegroundServiceDataSender(api); | ||||
|   } else { | ||||
|     throw UnsupportedPlatformException(); | ||||
|   } | ||||
| } | ||||
| @ -1,10 +1,14 @@ | ||||
| import 'package:flutter/cupertino.dart'; | ||||
| import 'package:get_it/get_it.dart'; | ||||
| import 'package:moxxy_native/pigeon/background_service.g.dart'; | ||||
| import 'package:moxxy_native/src/service.dart'; | ||||
| import 'package:moxxy_native/src/service/background/base.dart'; | ||||
| import 'package:moxxy_native/src/service/background/pigeon.dart'; | ||||
| import 'package:moxxy_native/src/service/config.dart'; | ||||
| 
 | ||||
| /// An entrypoint that should be used when the service runs | ||||
| /// in a new Flutter Engine. | ||||
| @pragma('vm:entry-point') | ||||
| Future<void> androidEntrypoint() async { | ||||
| Future<void> pigeonEntrypoint() async { | ||||
|   // ignore: avoid_print | ||||
|   print('androidEntrypoint: Called on new FlutterEngine'); | ||||
| 
 | ||||
| @ -15,7 +19,7 @@ Future<void> androidEntrypoint() async { | ||||
|   ); | ||||
| 
 | ||||
|   // Setup the background service | ||||
|   final srv = BackgroundService(); | ||||
|   GetIt.I.registerSingleton(srv); | ||||
|   srv.init(config); | ||||
|   final srv = PigeonBackgroundService(); | ||||
|   GetIt.I.registerSingleton<BackgroundService>(srv); | ||||
|   await srv.init(config); | ||||
| } | ||||
							
								
								
									
										8
									
								
								lib/src/service/exceptions.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								lib/src/service/exceptions.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,8 @@ | ||||
| import 'dart:io'; | ||||
| 
 | ||||
| /// An exception representing that moxxy_native does not support the given platform. | ||||
| class UnsupportedPlatformException implements Exception { | ||||
|   UnsupportedPlatformException(); | ||||
| 
 | ||||
|   String get message => 'Unsupported platform "${Platform.operatingSystem}"'; | ||||
| } | ||||
							
								
								
									
										49
									
								
								lib/src/service/foreground/base.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								lib/src/service/foreground/base.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,49 @@ | ||||
| import 'dart:io'; | ||||
| import 'package:moxlib/moxlib.dart'; | ||||
| import 'package:moxxy_native/src/service/config.dart'; | ||||
| import 'package:moxxy_native/src/service/datasender/types.dart'; | ||||
| import 'package:moxxy_native/src/service/exceptions.dart'; | ||||
| import 'package:moxxy_native/src/service/foreground/pigeon.dart'; | ||||
| 
 | ||||
| /// Wrapper API that is only available to the UI isolate. | ||||
| // TODO(Unknown): Dumb naming. Name it something better | ||||
| abstract class ForegroundService { | ||||
|   /// Perform setup such that we [handleData] is called whenever the background service | ||||
|   /// sends data to the foreground. | ||||
|   Future<void> attach(HandleEventCallback handleData); | ||||
| 
 | ||||
|   /// Start the background service with the config [config]. Additionally, perform | ||||
|   /// setup such that [uiHandleData] is called whenever the background service sends | ||||
|   /// data to the foreground. | ||||
|   Future<void> start(ServiceConfig config, HandleEventCallback uiHandleData); | ||||
| 
 | ||||
|   /// Return true if the background service is running. False, if not. | ||||
|   Future<bool> isRunning(); | ||||
| 
 | ||||
|   /// Return the [AwaitableDataSender] that is used to send data to the background service. | ||||
|   ForegroundServiceDataSender getDataSender(); | ||||
| 
 | ||||
|   /// Convenience wrapper around getDataSender().sendData. The arguments are the same | ||||
|   /// as for [AwaitableDataSender]. | ||||
|   Future<BackgroundEvent?> send( | ||||
|     BackgroundCommand command, { | ||||
|     bool awaitable = true, | ||||
|   }); | ||||
| } | ||||
| 
 | ||||
| /// "Singleton" ForegroundService instance to prevent having to type "GetIt.I.get<ForegroundService>()" | ||||
| ForegroundService? _service; | ||||
| 
 | ||||
| /// Either returns or creates a [ForegroundService] object of the correct type for the | ||||
| /// current platform. | ||||
| ForegroundService getForegroundService() { | ||||
|   if (_service == null) { | ||||
|     if (Platform.isAndroid) { | ||||
|       _service = PigeonForegroundService(); | ||||
|     } else { | ||||
|       throw UnsupportedPlatformException(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   return _service!; | ||||
| } | ||||
							
								
								
									
										82
									
								
								lib/src/service/foreground/pigeon.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								lib/src/service/foreground/pigeon.dart
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,82 @@ | ||||
| import 'dart:convert'; | ||||
| import 'dart:ui'; | ||||
| import 'package:flutter/services.dart'; | ||||
| import 'package:flutter/widgets.dart'; | ||||
| import 'package:logging/logging.dart'; | ||||
| import 'package:moxxy_native/pigeon/service.g.dart'; | ||||
| import 'package:moxxy_native/src/service/config.dart'; | ||||
| import 'package:moxxy_native/src/service/datasender/pigeon.dart'; | ||||
| import 'package:moxxy_native/src/service/datasender/types.dart'; | ||||
| import 'package:moxxy_native/src/service/entrypoints/pigeon.dart'; | ||||
| import 'package:moxxy_native/src/service/foreground/base.dart'; | ||||
| 
 | ||||
| class PigeonForegroundService extends ForegroundService { | ||||
|   PigeonForegroundService() { | ||||
|     _dataSender = PigeonForegroundServiceDataSender(_api); | ||||
|   } | ||||
| 
 | ||||
|   /// Pigeon channel to the native side. | ||||
|   final MoxxyServiceApi _api = MoxxyServiceApi(); | ||||
| 
 | ||||
|   /// A method channel for background service -> UI isolate communication. | ||||
|   final MethodChannel _channel = | ||||
|       const MethodChannel('org.moxxy.moxxy_native/foreground'); | ||||
| 
 | ||||
|   /// The data sender backing this class. | ||||
|   late final PigeonForegroundServiceDataSender _dataSender; | ||||
| 
 | ||||
|   /// A logger. | ||||
|   final Logger _log = Logger('PigeonForegroundService'); | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> attach( | ||||
|     HandleEventCallback handleData, | ||||
|   ) async { | ||||
|     _channel.setMethodCallHandler((call) async { | ||||
|       await handleData( | ||||
|         jsonDecode(call.arguments! as String) as Map<String, dynamic>, | ||||
|       ); | ||||
|     }); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<void> start( | ||||
|     ServiceConfig config, | ||||
|     HandleEventCallback uiHandleData, | ||||
|   ) async { | ||||
|     await _api.configure( | ||||
|       PluginUtilities.getCallbackHandle( | ||||
|         pigeonEntrypoint, | ||||
|       )! | ||||
|           .toRawHandle(), | ||||
|       config.toString(), | ||||
|     ); | ||||
| 
 | ||||
|     // Prepare the method channel | ||||
|     await attach(uiHandleData); | ||||
| 
 | ||||
|     // Start the service | ||||
|     await _api.start(); | ||||
|     _log.finest('Background service started...'); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   Future<bool> isRunning() async { | ||||
|     WidgetsFlutterBinding.ensureInitialized(); | ||||
|     return _api.isRunning(); | ||||
|   } | ||||
| 
 | ||||
|   @override | ||||
|   ForegroundServiceDataSender getDataSender() => _dataSender; | ||||
| 
 | ||||
|   @override | ||||
|   Future<BackgroundEvent?> send( | ||||
|     BackgroundCommand command, { | ||||
|     bool awaitable = true, | ||||
|   }) { | ||||
|     return _dataSender.sendData( | ||||
|       command, | ||||
|       awaitable: awaitable, | ||||
|     ); | ||||
|   } | ||||
| } | ||||
| @ -10,7 +10,6 @@ import 'package:pigeon/pigeon.dart'; | ||||
|     ), | ||||
|   ), | ||||
| ) | ||||
| 
 | ||||
| @HostApi() | ||||
| abstract class MoxxyBackgroundServiceApi { | ||||
|   int getHandler(); | ||||
|  | ||||
| @ -10,7 +10,6 @@ import 'package:pigeon/pigeon.dart'; | ||||
|     ), | ||||
|   ), | ||||
| ) | ||||
| 
 | ||||
| @HostApi() | ||||
| abstract class MoxxyServiceApi { | ||||
|   void configure(int handle, String extraData); | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user