본문 바로가기

EXPERIENCE/Flutter

[Flutter/Dart] Flutter Plugin 만들어 보기 - 2 (with. Plugin 프로젝트 생성하는/만드는 법)

728x90
728x90

 

 

 

 

Package와 Plugin의 차이점
 

[Flutter/Dart] Flutter Plugin/Package 만들어 보기 - 1 (with. Plugin과 Package 차이점)

Package란? 일반적으로 프로그래밍에서 패키지란 다음과 같은 뜻을 가진다. "일반적으로 많이 사용되는 기법이나 특수한 목적만을 위해 미리 프로그램으로 작성하여 다른 사람이 실제 프로그램을

s-o-h-a.tistory.com

 

 

 

 

 

해당 글은 Flutter에서 기본적으로 제공하는 플랫폼 버전을 가져오는 예제를 이용해 작성하였다.

실제 제작한 Plugin 프로젝트는 Flutter와 iOS/Android의 통신을 위한 Plugin만 사용된 것이 아니라,

부가적인 추가과정이 많아 Flutter Plugin 프로젝트로만 예제로 쓰기에 복잡했다 ㅜㅜ

 

예를 들어 iOS는 .a 확장자인 라이브러리를 이용해 추가적으로 C언어로 구현된 함수 사용이 필요했으며,

Android는 C++언어로 작성된 코드와의 통신을 위해 JNI를 구현하는 추가과정이 필요했다...

 

그래서 기본예제로 글을 작성할 수 밖에 없었으나 이후 추가 함수라도 구현하여 글을 수정해볼까 한다!

 

 

 

 

 

 

 

 

 

Plugin 프로젝트 생성

 

새 프로젝트 생성 - Flutter

 

Project Type을 Plugin으로 설정

 

나머지 항목은 기존 프로젝트와 같이 채우기
Platform은 제작할 Plugin이 사용될 플랫폼들만 선택하기

 

 

 

 

 

 

 

728x90

 

 

 

 

 

 

 

MethodChannel을 이용한 통신

 

Method Channel을 이용한 통신 구조 도식화

 

 

프로젝트 생성 후 lib파일을 확인하면 3개의 .dart 파일이 생성되어있다.

 

 

  • test_plugin_platform_interface.dart
import 'package:plugin_platform_interface/plugin_platform_interface.dart';
import 'test_plugin_method_channel.dart';


/// 인터페이스 작성
abstract class TestPluginPlatform extends PlatformInterface {
  /// TestPluginPlatform 생성
  TestPluginPlatform() : super(token: _token);

  static final Object _token = Object();

  /// TestPluginPlatform의 인스턴스
  static TestPluginPlatform _instance = MethodChannelTestPlugin();
  static TestPluginPlatform get instance => _instance;

  /// 플랫폼별 구현체는 자신들을 등록할 때 TestPluginPlatform를 확장한 클래스로 값을 설정해야함
  static set instance(TestPluginPlatform instance) {
    PlatformInterface.verifyToken(instance, _token);
    _instance = instance;
  }

  /// 함수가 구현되지 않았을 경우 처리
  Future<String?> getPlatformVersion() {
    throw UnimplementedError('platformVersion() has not been implemented.');
  }
}
PlatformInterface라는 추상 클래스를 확장하여 작성된 커스텀 추상 클래스(TestPluginPlatform)를 작성한다

 

  • test_plugin_method_channel.dart
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'test_plugin_platform_interface.dart';


/// TestPluginPlatform 구현
///  Method Channel을 사용
class MethodChannelTestPlugin extends TestPluginPlatform {
  /// 네이티브와 상호 작용하는 데 사용되는 메소드 채널
  @visibleForTesting
  final methodChannel = const MethodChannel('test_plugin');

  /// 특정 네이티브 함수를 호출하여 결과를 얻어온다
  @override
  Future<String?> getPlatformVersion() async {
    final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
    return version;
  }
}
커스텀 추상 클래스(TestPluginPlatform)를 구현하는 클래스(MethodChannelTestPlugin)를 작성한다
해당 클래스 내부에 MethodChannel를 생성하여 네이티브 코드들과 직접적인 통신을 할 수 있다 

 

  • test_plugin.dart
import 'test_plugin_platform_interface.dart';


/// Flutter에서 실제 호출되는 함수
/// 함수 내부에서 TestPluginPlatform의 인스턴스를 이용해 Method Channel로 접근한다
class TestPlugin {
  Future<String?> getPlatformVersion() {
    return TestPluginPlatform.instance.getPlatformVersion();
  }
}
위에서 작성된 커스텀 추상 클래스(TestPluginPlatform) 내부의 정적 인스턴스를 이용한다
인스턴스의 특정 함수를 호출하면 TestPluginPlatform를 구현한 MethodChannelTestPlugin 클래스의 메소드가 호출되며,
해당 메소드 내부에서 Method Channel을 사용하여 네이티브 코드와 통신한다

 

 

 

 

 

 

플랫폼(Android, iOS)별 코드 작성

 

 

 

  • Android
package com.bailey.testplugin.test_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


// Flutter와 네이티브 안드로이드 간 통신을 담당하는 MethodChannel
class TestPlugin: FlutterPlugin, MethodCallHandler {
  private lateinit var channel : MethodChannel

  override fun onAttachedToEngine(@NonNull flutterPluginBinding: FlutterPlugin.FlutterPluginBinding) {
    channel = MethodChannel(flutterPluginBinding.binaryMessenger, "test_plugin")
    channel.setMethodCallHandler(this)
  }

  // methodChannel.invokeMethod<String>('getPlatformVersion');
  // 호출 시 작동하며 call.method = 'getPlatformVersion'로 전달된다
  // 해당 변수값을 통해 구분 및 작동 시킬 수 있다
  override fun onMethodCall(@NonNull call: MethodCall, @NonNull result: Result) {
    if (call.method == "getPlatformVersion") {
      result.success("Android ${android.os.Build.VERSION.RELEASE}")
    } else {
      result.notImplemented()
    }
  }

  override fun onDetachedFromEngine(@NonNull binding: FlutterPlugin.FlutterPluginBinding) {
    channel.setMethodCallHandler(null)
  }
}

 

  • iOS
import Flutter
import UIKit

// Flutter와 네이티브 iOS 간 통신을 담당하는 MethodChannel (FlutterMethodChannel)
public class TestPlugin: NSObject, FlutterPlugin {
  public static func register(with registrar: FlutterPluginRegistrar) {
    let channel = FlutterMethodChannel(name: "test_plugin", binaryMessenger: registrar.messenger())
    let instance = TestPlugin()
    registrar.addMethodCallDelegate(instance, channel: channel)
  }

// methodChannel.invokeMethod<String>('getPlatformVersion');
// 호출 시 작동하며 call.method = 'getPlatformVersion'로 전달된다
// 해당 변수값을 통해 구분 및 작동 시킬 수 있다
  public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
    result("iOS " + UIDevice.current.systemVersion)
  }
}

 

 

Flutter에서 "함수이름"을 인자로 사용해 메소드를 호출하면 onMethodCall 메소드가 실행되며
call.method 변수에 "함수이름"을 받아온다.

해당 변수를 이용하여 네이티브 코드(Android/iOS)에서 실행시켜야하는 코드를 실행하거나,
결과 값을 Flutter로 리턴하여 통신을 완료한다.

 

 

 

 

 

 

 

 

플랫폼에 따른 데이터 유형

 

 

네이티브 코드 내에서 실행만 진행하는 함수 호출은 상관없지만

네이티브에서 특정 정보를 받아 Flutter로 전달 해야한다면 데이터 타입에 대한 부분이 중요하다.

아래 URL에는 플랫폼 별로 지원하는 데이터 유형 및 코덱이 작성되어 있으며 아래 간단하게 Kotlin과 Swift만 첨부하였다. 

 

 

Writing custom platform-specific code

Learn how to write custom platform-specific code in your app.

docs.flutter.dev

 

[ Dart / Kotlin ]

 

[ Dart / Swift ]

 

 

 

 

 

 

 

 

 

 

 

 

 

 

참고
 

Writing custom platform-specific code

Learn how to write custom platform-specific code in your app.

docs.flutter.dev

 

728x90
728x90