Refactor: extract native event to package

This commit is contained in:
Ming Ming 2023-09-01 22:30:09 +08:00
parent 8841673b88
commit e91d6410f5
24 changed files with 304 additions and 85 deletions

View file

@ -3,7 +3,7 @@ import 'dart:convert';
import 'package:logging/logging.dart';
import 'package:nc_photos/stream_extension.dart';
import 'package:nc_photos_plugin/nc_photos_plugin.dart';
import 'package:np_platform_message_relay/np_platform_message_relay.dart';
class NativeEventListener<T> {
NativeEventListener(this.listener);
@ -26,7 +26,7 @@ class NativeEventListener<T> {
}
static final _mappedStream =
NativeEvent.stream.whereType<NativeEventObject>().map((ev) {
MessageRelay.stream.whereType<Message>().map((ev) {
switch (ev.event) {
case FileExifUpdatedEvent._id:
return FileExifUpdatedEvent.fromEvent(ev);
@ -39,20 +39,21 @@ class NativeEventListener<T> {
final void Function(T) listener;
StreamSubscription<T>? _subscription;
final _log = Logger("event.native_event.NativeEventListener<${T.runtimeType}>");
final _log =
Logger("event.native_event.NativeEventListener<${T.runtimeType}>");
}
class FileExifUpdatedEvent {
const FileExifUpdatedEvent(this.fileIds);
factory FileExifUpdatedEvent.fromEvent(NativeEventObject ev) {
factory FileExifUpdatedEvent.fromEvent(Message ev) {
assert(ev.event == _id);
assert(ev.data != null);
final dataJson = jsonDecode(ev.data!) as Map;
return FileExifUpdatedEvent((dataJson["fileIds"] as List).cast<int>());
}
NativeEventObject toEvent() => NativeEventObject(
Message toEvent() => Message(
_id,
jsonEncode({
"fileIds": fileIds,

View file

@ -24,6 +24,7 @@ import 'package:nc_photos_plugin/nc_photos_plugin.dart';
import 'package:np_async/np_async.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_geocoder/np_geocoder.dart';
import 'package:np_platform_message_relay/np_platform_message_relay.dart';
part 'service.g.dart';
@ -237,7 +238,7 @@ class _MetadataTask {
}
if (_processedIds.isNotEmpty) {
unawaited(
NativeEvent.fire(FileExifUpdatedEvent(_processedIds).toEvent()),
MessageRelay.broadcast(FileExifUpdatedEvent(_processedIds).toEvent()),
);
_processedIds = [];
}
@ -319,7 +320,7 @@ class _MetadataTask {
_processedIds.add(file.fileId!);
if (_processedIds.length >= 10) {
NativeEvent.fire(FileExifUpdatedEvent(_processedIds).toEvent());
MessageRelay.broadcast(FileExifUpdatedEvent(_processedIds).toEvent());
_processedIds = [];
}
}

View file

@ -1032,6 +1032,13 @@ packages:
relative: true
source: path
version: "0.0.1"
np_platform_message_relay:
dependency: "direct main"
description:
path: "../np_platform_message_relay"
relative: true
source: path
version: "0.0.1"
np_platform_permission:
dependency: "direct main"
description:

View file

@ -115,6 +115,8 @@ dependencies:
path: ../np_platform_lock
np_platform_log:
path: ../np_platform_log
np_platform_message_relay:
path: ../np_platform_message_relay
np_platform_permission:
path: ../np_platform_permission
np_platform_raw_image:

30
np_platform_message_relay/.gitignore vendored Normal file
View file

@ -0,0 +1,30 @@
# Miscellaneous
*.class
*.log
*.pyc
*.swp
.DS_Store
.atom/
.buildlog/
.history
.svn/
migrate_working_dir/
# IntelliJ related
*.iml
*.ipr
*.iws
.idea/
# 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
# Libraries should not include pubspec.lock, per https://dart.dev/guides/libraries/private-files#pubspeclock.
/pubspec.lock
**/doc/api/
.dart_tool/
.packages
build/

View file

@ -0,0 +1,30 @@
# 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.
version:
revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
channel: stable
project_type: plugin
# Tracks metadata for the flutter migrate command
migration:
platforms:
- platform: root
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
- platform: android
create_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
base_revision: 4d9e56e694b656610ab87fcf2efbcd226e0ed8cf
# User provided section
# List of Local paths (relative to this file) that should be
# ignored by the migrate tool.
#
# Files that are not part of the templates will be ignored by default.
unmanaged_files:
- 'lib/main.dart'
- 'ios/Runner.xcodeproj/project.pbxproj'

View file

@ -0,0 +1 @@
include: package:np_lints/np.yaml

View file

@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.cxx

View file

@ -0,0 +1,52 @@
group 'com.nkming.nc_photos.np_platform_message_relay'
version '1.0-SNAPSHOT'
buildscript {
ext.kotlin_version = '1.8.20'
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.4.2'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
}
}
allprojects {
repositories {
google()
mavenCentral()
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
android {
namespace 'com.nkming.nc_photos.np_platform_message_relay'
compileSdk 31
defaultConfig {
minSdk 21
}
buildTypes {
release {
minifyEnabled false
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
}
dependencies {
implementation "androidx.annotation:annotation:1.6.0"
}

View file

@ -0,0 +1,5 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

View file

@ -0,0 +1 @@
rootProject.name = 'np_platform_message_relay'

View file

@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.nkming.nc_photos.np_platform_message_relay">
</manifest>

View file

@ -0,0 +1,7 @@
package com.nkming.nc_photos.np_platform_message_relay
internal interface K {
companion object {
const val LIB_ID = "com.nkming.nc_photos.np_platform_message_relay"
}
}

View file

@ -1,28 +1,14 @@
package com.nkming.nc_photos.plugin
package com.nkming.nc_photos.np_platform_message_relay
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
internal class NativeEventChannelHandler : MethodChannel.MethodCallHandler,
internal class MessageRelayChannelHandler : MethodChannel.MethodCallHandler,
EventChannel.StreamHandler {
companion object {
const val EVENT_CHANNEL = "${K.LIB_ID}/native_event"
const val METHOD_CHANNEL = "${K.LIB_ID}/native_event_method"
/**
* Fire native events on the native side
*/
fun fire(eventObj: NativeEvent) {
synchronized(eventSinks) {
for (s in eventSinks.values) {
s.success(buildMap {
put("event", eventObj.getId())
eventObj.getData()?.also { put("data", it) }
})
}
}
}
const val EVENT_CHANNEL = "${K.LIB_ID}/message_relay_event"
const val METHOD_CHANNEL = "${K.LIB_ID}/message_relay_method"
private val eventSinks = mutableMapOf<Int, EventChannel.EventSink>()
private var nextId = 0
@ -30,9 +16,9 @@ internal class NativeEventChannelHandler : MethodChannel.MethodCallHandler,
override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) {
when (call.method) {
"fire" -> {
"broadcast" -> {
try {
fire(
broadcast(
call.argument("event")!!, call.argument("data"), result
)
} catch (e: Throwable) {
@ -54,7 +40,7 @@ internal class NativeEventChannelHandler : MethodChannel.MethodCallHandler,
}
}
private fun fire(
private fun broadcast(
event: String, data: String?, result: MethodChannel.Result
) {
synchronized(eventSinks) {

View file

@ -0,0 +1,34 @@
package com.nkming.nc_photos.np_platform_message_relay
import androidx.annotation.NonNull
import io.flutter.embedding.engine.plugins.FlutterPlugin
import io.flutter.plugin.common.EventChannel
import io.flutter.plugin.common.MethodChannel
class NpPlatformMessageRelayPlugin : FlutterPlugin {
override fun onAttachedToEngine(
@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding
) {
val messageRelayHandler = MessageRelayChannelHandler()
eventChannel = EventChannel(
flutterPluginBinding.binaryMessenger,
MessageRelayChannelHandler.EVENT_CHANNEL
)
eventChannel.setStreamHandler(messageRelayHandler)
methodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
MessageRelayChannelHandler.METHOD_CHANNEL
)
methodChannel.setMethodCallHandler(messageRelayHandler)
}
override fun onDetachedFromEngine(
@NonNull binding: FlutterPlugin.FlutterPluginBinding
) {
eventChannel.setStreamHandler(null)
methodChannel.setMethodCallHandler(null)
}
private lateinit var eventChannel: EventChannel
private lateinit var methodChannel: MethodChannel
}

View file

@ -0,0 +1,3 @@
library np_platform_message_relay;
export 'src/message_relay.dart';

View file

@ -0,0 +1 @@
const libId = "com.nkming.nc_photos.np_platform_message_relay";

View file

@ -0,0 +1,59 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:logging/logging.dart';
import 'package:np_codegen/np_codegen.dart';
import 'package:np_platform_message_relay/src/k.dart' as k;
part 'message_relay.g.dart';
class Message {
const Message(this.event, this.data);
static Message fromJson(Map<String, dynamic> json) {
return Message(json["event"], json["data"]);
}
Map<String, dynamic> toJson() => {
"event": event,
if (data != null) "data": data,
};
final String event;
final String? data;
}
/// Relay messages via native side
///
/// Typically used to broadcast messages across different Flutter engines (e.g.,
/// main isolate <-> background isolates)
///
/// Beware that the isolate that broadcasted the message will also receive the
/// message if subscribed
@npLog
class MessageRelay {
static Future<void> broadcast(Message msg) =>
_methodChannel.invokeMethod("broadcast", msg.toJson());
static Stream<Message> get stream =>
_eventChannel.receiveBroadcastStream().map(_toEvent).whereNotNull();
static Message? _toEvent(dynamic ev) {
try {
return Message.fromJson((ev as Map).cast<String, dynamic>());
} catch (e, stackTrace) {
_log.severe("Failed while parsing native events", e, stackTrace);
return null;
}
}
static const _eventChannel = EventChannel("${k.libId}/message_relay_event");
static const _methodChannel =
MethodChannel("${k.libId}/message_relay_method");
static final _log = _$MessageRelayNpLog.log;
}
extension<T> on Stream<T?> {
Stream<T> whereNotNull() => where((e) => e != null).cast<T>();
}

View file

@ -0,0 +1,14 @@
// GENERATED CODE - DO NOT MODIFY BY HAND
part of 'message_relay.dart';
// **************************************************************************
// NpLogGenerator
// **************************************************************************
extension _$MessageRelayNpLog on MessageRelay {
// ignore: unused_element
Logger get _log => log;
static final log = Logger("src.message_relay.MessageRelay");
}

View file

@ -0,0 +1,30 @@
name: np_platform_message_relay
description: A new Flutter plugin project.
version: 0.0.1
homepage:
publish_to: none
environment:
sdk: '>=2.19.6 <3.0.0'
flutter: ">=3.3.0"
dependencies:
flutter:
sdk: flutter
logging: ^1.1.1
np_codegen:
path: ../codegen
dev_dependencies:
build_runner: ^2.2.1
np_codegen_build:
path: ../codegen_build
np_lints:
path: ../np_lints
flutter:
plugin:
platforms:
android:
package: com.nkming.nc_photos.np_platform_message_relay
pluginClass: NpPlatformMessageRelayPlugin

View file

@ -1,6 +0,0 @@
package com.nkming.nc_photos.plugin
internal interface NativeEvent {
fun getId(): String
fun getData(): String? = null
}

View file

@ -60,18 +60,6 @@ class NcPhotosPlugin : FlutterPlugin, ActivityAware,
PreferenceChannelHandler.METHOD_CHANNEL
)
preferenceMethodChannel.setMethodCallHandler(preferenceChannelHandler)
val nativeEventHandler = NativeEventChannelHandler()
nativeEventChannel = EventChannel(
flutterPluginBinding.binaryMessenger,
NativeEventChannelHandler.EVENT_CHANNEL
)
nativeEventChannel.setStreamHandler(nativeEventHandler)
nativeEventMethodChannel = MethodChannel(
flutterPluginBinding.binaryMessenger,
NativeEventChannelHandler.METHOD_CHANNEL
)
nativeEventMethodChannel.setMethodCallHandler(nativeEventHandler)
}
override fun onDetachedFromEngine(
@ -82,8 +70,6 @@ class NcPhotosPlugin : FlutterPlugin, ActivityAware,
mediaStoreMethodChannel.setMethodCallHandler(null)
contentUriMethodChannel.setMethodCallHandler(null)
preferenceMethodChannel.setMethodCallHandler(null)
nativeEventChannel.setStreamHandler(null)
nativeEventMethodChannel.setMethodCallHandler(null)
}
override fun onAttachedToActivity(binding: ActivityPluginBinding) {
@ -136,8 +122,6 @@ class NcPhotosPlugin : FlutterPlugin, ActivityAware,
private lateinit var mediaStoreMethodChannel: MethodChannel
private lateinit var contentUriMethodChannel: MethodChannel
private lateinit var preferenceMethodChannel: MethodChannel
private lateinit var nativeEventChannel: EventChannel
private lateinit var nativeEventMethodChannel: MethodChannel
private lateinit var mediaStoreChannelHandler: MediaStoreChannelHandler
}

View file

@ -3,6 +3,5 @@ library nc_photos_plugin;
export 'src/content_uri.dart';
export 'src/exception.dart';
export 'src/media_store.dart';
export 'src/native_event.dart';
export 'src/notification.dart';
export 'src/preference.dart';

View file

@ -1,34 +0,0 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:nc_photos_plugin/src/k.dart' as k;
class NativeEventObject {
NativeEventObject(this.event, this.data);
final String event;
final String? data;
}
class NativeEvent {
static Future<void> fire(NativeEventObject ev) =>
_methodChannel.invokeMethod("fire", <String, dynamic>{
"event": ev.event,
if (ev.data != null) "data": ev.data,
});
static Stream get stream => _eventStream;
static const _eventChannel = EventChannel("${k.libId}/native_event");
static const _methodChannel = MethodChannel("${k.libId}/native_event_method");
static final _eventStream = _eventChannel
.receiveBroadcastStream()
.map((event) {
if (event is Map) {
return NativeEventObject(event["event"], event["data"]);
} else {
return event;
}
});
}