본문 바로가기

프로그래밍/Flutter

[flutter] 플러그인 프로젝트 자동 생성 요소

1. 플러그인 프로젝트 생성

flutter create --org com.example --template=plugin --platforms=android,ios -i swift hello_world

 

생성된 플러그인 클래스 확인
/pubspec.yaml 파일의 fultter - plugin - platforms 에서 각 플랫폼별로 패키지명이나 클래스를 확인할 수 있다.

flutter:
   plugin:
      ios:
         pluginClass: HelloWorldPlugin

 

./lib 하위에 dart 파일들이 생성된다.
hello_world.dart
hello_world_platform_interface.dart
hello_world_method_channel.dart

이 파일은 dart에서 각 플랫폼의 함수를 호출하는 코드로 플랫폼 인터페이스와 메쏘드 채널을 구현하고 있다.

./example 하위에는 dart에서 이 plugin을 호출하는 예제로 구성되어 있다.

 

2. API 구현

1) PlatformInterface

플랫폼 api 정의

lib/hello_world_platform_interface.dart

기본적인 인스턴스 할당과 구현이 필요한 인터페이스 메쏘드들을 정의

import 'package:plugin_platform_interface/plugin_platform_interface.dart';

abstract class HelloWorldPlatform extends PlatformInterface {
   HelloWorldPlatform() : super(token: _token);
   
   static final Object _token = Object();
   
   static HelloWorldPlatform _instance = MethodChannelHelloWorld();
   
   static HelloWorldPlatform get instance => _instance;
   static set instance(HelloWorldPlatform instance) {
      PlatformInterface.verityToken(instance, _token);
      _instance = instance;
   }
   
   Future<String>? getPlatformVersion() {
      throw UnimplementedError('platformVersion() has not been implemented.');
   }
}

 

 

2) MethodChannel

lib/hello_world_method_channel.dart

플랫폼 인터페이스의 구현부로 실제 플랫폼의 메쏘드 이름을 매핑해 호출하고 결과를 반환

채널명이 "hello_world" 인 메쏘드 채널 생성
해당 메쏘드 채널의 특정 메쏘드 호출

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';

import 'hello_world_platform_interface.dart';

// api 구현부
class MethodChannelHelloWorld extends HelloWorldPlatform {
   @visibleForTesting
   final methodChannel = const MethodChannel('hello_world');
   
   @override
   Future<String?> getPlatformVersion() async {
      final version = await methodChannel.invokeMethod<String>('getPlatformVersion');
      return version;
   }
}

 

 

3) API 메인 클래스

/lib/hello_world.dart

import 'hello_world_platform_interface.dart'

class HelloWorld {
   Future<String?> getPlatformVersion() {
      return HelloWorldPlatform.instance.getPlatformVersion();
   }
}

 

 

3. iOS 플러그인 구현

dart에서 호출할 메쏘드는 정의되었으니 각 플랫폼별로 해당 메쏘드를 구현해 주어야한다.
플랫폼별로 구현이 차이가 있는데, AOS, iOS의 경우 FlutterPlugin, Windows의 경우 flutter::Plugin 을 상속받은 클래스에서 메소드 호출 여부를 위한 메쏘드를 제공한다.

FlutterPlugin 클래스가 해당 메쏘드와 바인드되도록 하기위해 FlutterPluginRegistrar or flutter::PluginRegistrarManager 등을 사용해 해당 클래서를 등록해 주어야 한다.
생성된 샘플에서는 static 메쏘드로 채널명이 "hello_world" 인 메쏘드 채널을 등록한다.

이후, FlutterPlugin 클래스의 handle(_ call: result:) 를 작성한다.(AOS는 onMethodCall)

swift 로 프로젝트 생성 시

/ios/Classes/HelloWorldPlugin.swift

import Flutter

public class HelloWorldPlugin: NSObject, FlutterPlugin {
   public static func register(with registrar: FlutterPluginRegistrar) {
      let channel = FlutterMethodChannel(name: "hello_world", binaryMessenger: registrar.messenger())
      let instance = HelloWorldPlugin()
      registrar.addMethodCallDelegate(instance, channel: channel)
   }
   
   public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
      return IODevice.current.systemVersion
   }
}

 

objc로 프로젝트 생성 시

/ios/Classes/HelloWorldPlugin.h, .m

@interface HelloWorldPlugin : NSObject<FlutterPlugin>
@end


@implementation HelloPlugin
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar {
   FlutterMethodChannel* channel = [FlutterMethodChannel methodChannelWithName:@"hello_world" binaryMessenger:[registrar messenger]];
   HelloWorldPlugin* instance = [[HelloWorldPlugin alloc] init];
   [registrar addMethodCallDelegate:instance channel:channel];
}

- (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result {
   if (@"getPlatformVersion" isEqualToString:call.method]) {
      result([@"" stringByAppendingString:[[UIDevice currentDevice] systemVersion]]);
   } else {
      result(FlutterMethodNotImplemented);
   }
}
@end

 

4. 샘플 앱에서 플러그인 사용

example/pubspec.yaml

1) dependency 설정

dependencies:
   flutter:
      sdk: flutter
   
   hello_world:
      path: ../

 

2) 자동생성 등록 루틴

example/ios/Runner/GeneratedPluginRegistrant.h, .m

프로젝트 생성 후에는 이 메쏘드가 존재하지 않으며, 앱 build 시에 자동 생성(flutter build ios --no-codesign)
플러그인 구현부의 static 메쏘드를 호출해 메쏘드채널과 핸들러를 등록한다.

@interface GeneratedPluginRegistrant: NSObject
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry;
@end


@impleementation GeneratedPluginRegistrant
+ (void)registerWithRegistry:(NSObject<FlutterPluginRegistry>*)registry {
   [HelloWorldPlugin registerWithRegistrar: [registry registrarForPlugin:@"HelloWorldPlugin"]];
}

 

3) AppDelegate 플러그인 등록

실제 AppDelegate는 Flutter 내부에 있으며, FlutterAppDelegate 를 상속받아 생성된 등록 루틴을 호출한다.

import Flutter

@UIApplicationMain
@objc class Adddelegate: FlutterAppDelegate {
   override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
      GeneratedPluginRegistrant.register(with: self)
      return super.application(application, didFinishLaunchingWithOptions: launchOptions)
   }
}

 

4) 플러그인 API 사용

example/lib/main.dart

// entry point
void main() {
   runApp(const MyApp());
}

class MyApp extends StatefulWidget {
   const MyApp({super.key});
   
   @override
   State<MyApp> createState() => _MyAppState();
}


// api 객체 생성 및 사용
class _MyAppState extends State<MyApp> {
   String _platformVersion = 'Unknown';
   final _helloWorldPlugin = HelloWorld();
   
   
   Future<void> initPlaotformState() async {
      String platformVersion;
      
      try {
         platformVersion = await _helloWorldPlugin.getPlatformVersion() ?? 'unknown';
      } on PlatformException {
         platformVersion = 'faliled';
      }
      
      if (!mounted) return;
      
      setState(() {
         _platformVersion = platformVersion;
      });
   }
   
   
   @Override
   Widget build(BuildContext context) {
      return MaterialApp(home: 
         Scaffold(
            appBar: AppBar(title: const Text('Plugin example app'),
            body: Center(child: Text('version: $_platformVersion')
         )
      );
   }
}