feat: Implement a wrapper around the service APIs
This commit is contained in:
		
							parent
							
								
									3b5e331aca
								
							
						
					
					
						commit
						2299b766cc
					
				@ -1,29 +1,23 @@
 | 
			
		||||
import 'dart:io';
 | 
			
		||||
import 'dart:typed_data';
 | 
			
		||||
import 'dart:ui';
 | 
			
		||||
import 'package:flutter/material.dart';
 | 
			
		||||
import 'package:flutter/services.dart';
 | 
			
		||||
import 'package:get_it/get_it.dart';
 | 
			
		||||
import 'package:moxxy_native/moxxy_native.dart';
 | 
			
		||||
import 'package:permission_handler/permission_handler.dart';
 | 
			
		||||
 | 
			
		||||
@pragma('vm:entrypoint')
 | 
			
		||||
Future<void> serviceHandleData(Map<String, dynamic>? data) async {
 | 
			
		||||
  print('[BG] Received data $data');
 | 
			
		||||
  GetIt.I.get<BackgroundService>().send(
 | 
			
		||||
    TestEvent(),
 | 
			
		||||
    id: data!['id']! as String,
 | 
			
		||||
  );
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@pragma('vm:entry-point')
 | 
			
		||||
Future<void> entrypoint() async {
 | 
			
		||||
  WidgetsFlutterBinding.ensureInitialized();
 | 
			
		||||
 | 
			
		||||
  print('CALLED FROM NEW FLUTTERENGINE');
 | 
			
		||||
  final api = MoxxyBackgroundServiceApi();
 | 
			
		||||
  final extra = await api.getExtraData();
 | 
			
		||||
  print('EXTRA DATA: $extra');
 | 
			
		||||
 | 
			
		||||
  MethodChannel('org.moxxy.moxxy_native/background').setMethodCallHandler((call) async {
 | 
			
		||||
    print('[BG] Received ${call.method} with ${call.arguments}');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  print('Waiting...');
 | 
			
		||||
  await Future<void>.delayed(const Duration(seconds: 5));
 | 
			
		||||
 | 
			
		||||
  await api.sendData('Hello from the foreground service');
 | 
			
		||||
  print('Data sent');
 | 
			
		||||
Future<void> serviceEntrypoint(String initialLocale) async {
 | 
			
		||||
  print('Initial locale: $initialLocale');
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void main() {
 | 
			
		||||
@ -37,6 +31,20 @@ class MyApp extends StatefulWidget {
 | 
			
		||||
  MyAppState createState() => MyAppState();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class TestCommand extends BackgroundCommand {
 | 
			
		||||
  @override
 | 
			
		||||
  Map<String, dynamic> toJson() => {
 | 
			
		||||
    'request': 'return_name',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class TestEvent extends BackgroundEvent {
 | 
			
		||||
  @override
 | 
			
		||||
  Map<String, dynamic> toJson() => {
 | 
			
		||||
    'name': 'Moxxy',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
class MyAppState extends State<MyApp> {
 | 
			
		||||
  String? imagePath;
 | 
			
		||||
 | 
			
		||||
@ -138,15 +146,23 @@ class MyAppState extends State<MyApp> {
 | 
			
		||||
 | 
			
		||||
                  await Permission.notification.request();
 | 
			
		||||
 | 
			
		||||
                  final handle = PluginUtilities.getCallbackHandle(entrypoint)!
 | 
			
		||||
                      .toRawHandle();
 | 
			
		||||
                  final api = MoxxyServiceApi();
 | 
			
		||||
                  await api.configure(handle, 'lol');
 | 
			
		||||
                  MethodChannel("org.moxxy.moxxy_native/foreground").setMethodCallHandler((call) async {
 | 
			
		||||
                    print('[FG] Received ${call.method} with ${call.arguments}');
 | 
			
		||||
                    await api.sendData('Hello from the foreground');
 | 
			
		||||
                  });
 | 
			
		||||
                  await api.start();
 | 
			
		||||
                  final srv = ForegroundService();
 | 
			
		||||
                  await srv.start(
 | 
			
		||||
                    const ServiceConfig(
 | 
			
		||||
                      serviceEntrypoint,
 | 
			
		||||
                      serviceHandleData,
 | 
			
		||||
                      'en',
 | 
			
		||||
                    ),
 | 
			
		||||
                    (data) async {
 | 
			
		||||
                      print('[FG] Received data $data');
 | 
			
		||||
                    },
 | 
			
		||||
                  );
 | 
			
		||||
 | 
			
		||||
                  await Future<void>.delayed(const Duration(milliseconds: 600));
 | 
			
		||||
                  await srv.dataSender.sendData(
 | 
			
		||||
                    TestCommand(),
 | 
			
		||||
                    awaitable: false,
 | 
			
		||||
                  );
 | 
			
		||||
                },
 | 
			
		||||
                child: const Text('Start foreground service')),
 | 
			
		||||
          ],
 | 
			
		||||
 | 
			
		||||
@ -41,6 +41,14 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.17.0"
 | 
			
		||||
  crypto:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: crypto
 | 
			
		||||
      sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.0.3"
 | 
			
		||||
  cupertino_icons:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@ -75,6 +83,14 @@ packages:
 | 
			
		||||
    description: flutter
 | 
			
		||||
    source: sdk
 | 
			
		||||
    version: "0.0.0"
 | 
			
		||||
  get_it:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
      name: get_it
 | 
			
		||||
      sha256: "529de303c739fca98cd7ece5fca500d8ff89649f1bb4b4e94fb20954abcd7468"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "7.6.0"
 | 
			
		||||
  js:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -91,6 +107,14 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "2.0.1"
 | 
			
		||||
  logging:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: logging
 | 
			
		||||
      sha256: "623a88c9594aa774443aa3eb2d41807a48486b5613e67599fb4c41c0ad47c340"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.2.0"
 | 
			
		||||
  matcher:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -115,6 +139,14 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.8.0"
 | 
			
		||||
  moxlib:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: moxlib
 | 
			
		||||
      sha256: "2a76a632d23ea73906964cee4463352995e40199036162217ea323a6c3846e73"
 | 
			
		||||
      url: "https://git.polynom.me/api/packages/Moxxy/pub/"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.2.0"
 | 
			
		||||
  moxxy_native:
 | 
			
		||||
    dependency: "direct main"
 | 
			
		||||
    description:
 | 
			
		||||
@ -215,6 +247,14 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.2.0"
 | 
			
		||||
  synchronized:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: synchronized
 | 
			
		||||
      sha256: "5fcbd27688af6082f5abd611af56ee575342c30e87541d0245f7ff99faa02c60"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.1.0"
 | 
			
		||||
  term_glyph:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
@ -231,6 +271,22 @@ packages:
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "0.4.16"
 | 
			
		||||
  typed_data:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: typed_data
 | 
			
		||||
      sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "1.3.2"
 | 
			
		||||
  uuid:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
      name: uuid
 | 
			
		||||
      sha256: "648e103079f7c64a36dc7d39369cabb358d377078a051d6ae2ad3aa539519313"
 | 
			
		||||
      url: "https://pub.dev"
 | 
			
		||||
    source: hosted
 | 
			
		||||
    version: "3.0.7"
 | 
			
		||||
  vector_math:
 | 
			
		||||
    dependency: transitive
 | 
			
		||||
    description:
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,7 @@ dependencies:
 | 
			
		||||
  # Use with the CupertinoIcons class for iOS style icons.
 | 
			
		||||
  cupertino_icons: ^1.0.2
 | 
			
		||||
  permission_handler: ^10.4.5
 | 
			
		||||
  get_it: ^7.6.0
 | 
			
		||||
 | 
			
		||||
dev_dependencies:
 | 
			
		||||
  flutter_test:
 | 
			
		||||
 | 
			
		||||
@ -6,3 +6,4 @@ export 'pigeon/notifications.g.dart';
 | 
			
		||||
export 'pigeon/picker.g.dart';
 | 
			
		||||
export 'pigeon/platform.g.dart';
 | 
			
		||||
export 'pigeon/service.g.dart';
 | 
			
		||||
export 'src/service.dart';
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										168
									
								
								lib/src/service.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								lib/src/service.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,168 @@
 | 
			
		||||
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();
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										21
									
								
								lib/src/service_android.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								lib/src/service_android.dart
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,21 @@
 | 
			
		||||
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';
 | 
			
		||||
 | 
			
		||||
@pragma('vm:entry-point')
 | 
			
		||||
Future<void> androidEntrypoint() async {
 | 
			
		||||
  // ignore: avoid_print
 | 
			
		||||
  print('androidEntrypoint: Called on new FlutterEngine');
 | 
			
		||||
 | 
			
		||||
  // Pull and deserialize the extra data passed on.
 | 
			
		||||
  WidgetsFlutterBinding.ensureInitialized();
 | 
			
		||||
  final config = ServiceConfig.fromString(
 | 
			
		||||
    await MoxxyBackgroundServiceApi().getExtraData(),
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  // Setup the background service
 | 
			
		||||
  final srv = BackgroundService();
 | 
			
		||||
  GetIt.I.registerSingleton(srv);
 | 
			
		||||
  srv.init(config);
 | 
			
		||||
}
 | 
			
		||||
@ -11,6 +11,12 @@ environment:
 | 
			
		||||
dependencies:
 | 
			
		||||
  flutter:
 | 
			
		||||
    sdk: flutter
 | 
			
		||||
  get_it: ^7.6.0
 | 
			
		||||
  logging: ^1.2.0
 | 
			
		||||
  moxlib:
 | 
			
		||||
    hosted: https://git.polynom.me/api/packages/Moxxy/pub
 | 
			
		||||
    version: ^0.2.0
 | 
			
		||||
  uuid: ^3.0.5
 | 
			
		||||
 | 
			
		||||
dev_dependencies:
 | 
			
		||||
  flutter_lints: ^2.0.0
 | 
			
		||||
 | 
			
		||||
		Loading…
	
		Reference in New Issue
	
	Block a user