點燈坊

失くすものさえない今が強くなるチャンスよ

從 Dart 將 Callback 傳給 Java

Sam Xiao's Avatar 2024-08-14

可將 Dark 的 function 傳入 Java,以 Callback 的方式讓 Java 執行之。

Version

Flutter 3.24
Dart 3.5

Create Project

$ flutter create --template=plugin -a java --platforms=android callback_plugin
  • 使用 flutter create 建立 Flutter 專案
  • --template:指定以 plugin 樣板建立專案
  • -a java:以 Java 為 Android 的語言
  • --platforms=android:只建立 Android 的 plugin
  • callback_plugin:專案名稱

Java Class

android/src/main/java/com.example.callback_plugin/TaskProcessor.java

package com.example.callback_plugin;

import android.os.Handler;
import android.os.Looper;

public class TaskProcessor {

  public interface TaskCallback {
    void onComplete(String result);
    void onError(String error);
  }

  public void startTask(TaskCallback callback) {
    new Thread(() -> {
      try {
        // 模擬非同步任務
        Thread.sleep(2000); // 假設任務耗時2秒

        // 模擬任務成功
        String result = "Task completed successfully!";

        // 在主線程回調結果
        new Handler(Looper.getMainLooper()).post(() -> callback.onComplete(result));
      } catch (InterruptedException e) {
        // 模擬任務失敗
        new Handler(Looper.getMainLooper()).post(() -> callback.onError("Task was interrupted"));
      }
    }).start();
  }
}
  • 一般 Java class 處理相關邏輯

Line 13

public void startTask(TaskCallback callback) {
  Thread.sleep(2000);
  String result = "Task completed successfully!";
 }
  • startTask() 為 Java 普通的 method,但其參數為 callback
  • result:Java 要傳給 Dart 的資料

Line 8

public interface TaskCallback {
  void onComplete(String result);
  void onError(String error);
}
  • 定義 callback 的 interface
  • 當 Java 執行 完成 時呼叫 onComplete(),並將 成功資料 傳給 Dart
  • 當 Java 執行 失敗 時,呼叫 onError(),並將 錯誤資料 傳給 Dart

Line 23

new Handler(Looper.getMainLooper()).post(() -> callback.onComplete(result));
  • 在 main thread 呼叫 onComplete(),並將 成功資料 傳給 Dart

Line 27

new Handler(Looper.getMainLooper()).post(() -> callback.onError("Task was interrupted"));
  • 在 main thread 呼叫 onError(),並將 錯誤資料 傳給 Dart

Java Plugin Wrapper

android/src/main/java/com.example.callback_plugin/CallbackPlugin.java

package com.example.callback_plugin;

import androidx.annotation.NonNull;
import io.flutter.embedding.engine.plugins.FlutterPlugin;
import io.flutter.plugin.common.MethodCall;
import io.flutter.plugin.common.MethodChannel;
import io.flutter.plugin.common.MethodChannel.MethodCallHandler;
import io.flutter.plugin.common.MethodChannel.Result;

public class CallbackPlugin implements FlutterPlugin, MethodCallHandler {
  private MethodChannel channel;
  private TaskProcessor taskProcessor;

  @Override
  public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
    channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "callback_plugin");
    channel.setMethodCallHandler(this);
    taskProcessor = new TaskProcessor();
  }

  @Override
  public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
    channel.setMethodCallHandler(null);
    channel = null;
  }

  @Override
  public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
    if (call.method.equals("startTask")) {
      taskProcessor.startTask(new TaskProcessor.TaskCallback() {
        @Override
        public void onComplete(String result) {
          channel.invokeMethod("onTaskComplete", result);
        }

        @Override
        public void onError(String error) {
          channel.invokeMethod("onTaskError", error);
        }
      });
      result.success(null);
    } else {
      result.notImplemented();
    }
  }
}
  • Java 的 plugin wrapper

Line 10

public class CallbackPlugin implements FlutterPlugin, MethodCallHandler {
}
  • 定義 CallbackPlugin class,實現以下兩個 interface
  • FlutterPlugin:實作 plugin,
    • onAttachedToEngine():加入 FlutterEngine 時被觸發
    • onDetachedFromEngine():離開 FlutterEngine 時被觸發
  • MethodCallHandler:處理來自於 Dart 的呼叫
    • onMethodCall(),當 plugin 的 method 被呼叫時,會觸發 onMethodCall()

Line 11

private MethodChannel channel;
private TaskProcessor taskProcessor;
  • channel:定義 MethodChannel
  • taskProcessor:定義原本的 Java Class

Line 14

@Override
public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
  channel = new MethodChannel(flutterPluginBinding.getBinaryMessenger(), "callback_plugin");
  channel.setMethodCallHandler(this);
  taskProcessor = new TaskProcessor();
}
  • 實現 FlutterPlugin interface
  • 加入 FlutterEngine 時被觸發
  • 建立 MethodChannel 與自己的 Java class

Line 21

@Override
public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
  channel.setMethodCallHandler(null);
  channel = null;
}
  • 實現 FlutterPlugin interface
  • 離開 FlutterEngine 時被觸發
  • 釋放 MethodChannel

Line 27

@Override
public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
}
  • 實現 MethodCallHandler interface
  • 所有 Dart 對 Java 的呼叫都會進入 onMethodCall(),由 call.method 判斷是哪一個 method 被呼叫

Line 30

taskProcessor.startTask(new TaskProcessor.TaskCallback() {
  @Override
  public void onComplete(String result) {
    channel.invokeMethod("onTaskComplete", result);
  }

  @Override
  public void onError(String error) {
    channel.invokeMethod("onTaskError", error);
  }
});
  • 呼叫自己 Java class 的 startTask()
  • 建立 TaskProcessor.TaskCallback 的 anonymous object
    • onComplete():當 Java 執行成功,透過 MethodChannel 呼叫 plugin 的 onTaskComplete()
    • onError():當 Java 執行失敗,透過 MethodChannel 呼叫 plugin 的 onTaskError()

Dart Plugin Wrapper

lib/callback_plugin.dart

import 'package:flutter/services.dart';

class CallbackPlugin {
  static const MethodChannel _channel = MethodChannel('callback_plugin');

  static Future<void> startTask({
    required Function(String result) onComplete,
    required Function(String error) onError,
  }) async {
    _channel.setMethodCallHandler((MethodCall call) async {
      if (call.method == 'onTaskComplete') {
        onComplete(call.arguments);
      } else if (call.method == 'onTaskError') {
        onError(call.arguments);
      }
    });

    // 呼叫 Android 方法
    await _channel.invokeMethod('startTask');
  }
}
  • Dart 的 plugin wrapper

Line 4

static const MethodChannel _channel = MethodChannel('callback_plugin');
  • 定義 MethodChannel

Line 6

static Future<void> startTask({
  required Function(String result) onComplete,
  required Function(String error) onError,
}) async {

}
  • startTask():定義 plugin 的 method,建議與原 Java class 的 method 相同
  • 參數為 callback,與 Java 的 TaskCallback interface 相同,只是改成 Dart 語法
  • ChannelMethod 會以非同步形式呈現,因此在 plugin 的 method 都會回傳 Future 與 async

Line 10

_channel.setMethodCallHandler((MethodCall call) async {
  if (call.method == 'onTaskComplete') {
    onComplete(call.arguments);
  } else if (call.method == 'onTaskError') {
    onError(call.arguments);
  }
});
  • 設定呼叫 plugin 時的處理方式
    • onTaskComplete:當呼叫 plugin 的 onTaskComplete() 時,呼叫 Dart 傳進的 onComplete(),並傳入 Java 傳來的 成功資料
    • onTaskError:當呼叫 plugin 的 onTaskError() 時,呼叫 Dart 傳進的 onError(),並傳入 Java 傳來的 錯誤資料

Flutter Dart

example/lib/main.dart

import 'package:callback_plugin/callback_plugin.dart';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(title: const Text('Dart to Java Callback Example')),
        body: Center(
          child: ElevatedButton(
            onPressed: () {
              CallbackPlugin.startTask(
                onComplete: (result) {
                  print("Received from Java: $result");
                },
                onError: (error) {
                  print("Error from Java: $error");
                },
              );
            },
            child: const Text('Start Task'),
          ),
        ),
      ),
    );
  }
}
  • Flutter 使用 plugin 呼叫 Java,並傳入 callback

Line 1

import 'package:callback_plugin/callback_plugin.dart';
  • 引用 Dart 的 plugin

Line 17

ElevatedButton(
  onPressed: () {
    CallbackPlugin.startTask(
      onComplete: (result) {
        print("Received from Java: $result");
      },
      onError: (error) {
        print("Error from Java: $error");
      },
    );
  },
  child: const Text('Start Task'), 
)
  • 在按下 button 後呼叫 CallbackPlugin.startTask(),並傳入 onComplete()onError() 兩個 callback
  • resulterror 為 Java 所回傳的資料

callback01

  • Start Task 置中顯示,按下可在 Dart 呼叫 Java

callback02

  • 收到 Java 所傳回的 成功資料

Conclusion

  • 若要將原本 Java class 包成 plugin 給 Dart 使用,有以下 SOP
    • 新增 Java 的 plugin wrapper class
    • 新增 Dart 的 plugin wrapper class
    • Dart 使用 plugin 呼叫 Java