Flutter Plugin 만들기 - part1 Flutter Plugin 프로젝트 생성하기

Flutter Plugin 만들기 - part1 Flutter Plugin 프로젝트 생성하기

이야기를 하기에 앞서, 이 글은 네가지 파트로 나뉘어져 있습니다. 각 글을 나눈 기준은 제가 항상 Flutter로 개발하면서 작업을 나누는 단위입니다. 각 단위 내에서도 더 작게 쪼개지지만 플랫폼 관련 기능을 구현할 때는 Flutter -> iOS -> Android 순서로 작업합니다. (Android보다 iOS쪽이 제약이 많기 때문에 기능 확인차 이렇게 하고있습니다.)

Plugin?

우리는 여러 플랫폼에서 어떤 프로그램을 개발할 때 수 많은 라이브러리들을 가져다가 쓴다. iOS는 CocoaPods.org, Web에서 Javascript로 개발할 때는 npm | build amazing things 이 대표적이다.

이와 마찬가지로 Flutter에는 pub.dev라는 곳이 존재하며 여기엔 이런 저런 플러그인들이 올라와있으며, 손쉽게 다운로드 해서 사용할 수 있다.

개발할 때 우리는 간단한 기능조차 라이브러리를 사용하는 등의 남용을 하지는 말아야 하지만, 필요한 기능을 제공하는 적당한 규모의 라이브러리를 사용하는 것은 개발속도를 높여주며 효율성을 높여준다.

이렇게 라이브러리를 만들어 두면 개발할때 바퀴를 재발명 하지 않아 효율적이고 모듈화를 통해 코드간 디펜던시를 줄여주며 안정적인 소프트웨어를 만들 수 있으며, 다른 사람들도 내가 만들어둔 라이브러리와 다른 라이브러리를 조합하여 또 다른 소프트웨어를 만들 수 있어 선순환이 된다.

우리는 Plugin을 Flutter에서 어떻게 생성하고, 개발하며 어떻게 배포 하는지를 알아볼 것이다.

Plugin 생성하기

  • 플러그인 생성
    1
    flutter create --template=package YOUR_PACKAGE_NAME

보통 flutter create YOUR_PROJECT_NAME 이런식으로 작성하면 보통의 Flutter 프로젝트가 생성되지만, —template=package 옵션을 주면 Plugin을 생성할 수 있다.

  • 플랫폼 레벨의 기능 사용을 위해 Android, iOS쪽의 코드를 작성해두어야 하는 라이브러리
    기본적으로 iOS는 swift, Android는 kotlin으로 생성된다.
1
flutter create --org YOUR_ORGANIZATION_NAME --template=plugin YOUR_PACKAGE_NAME

만약 YOUR_ORGANIZATION_NAME = com.example, YOUR_PACKAGE_NAME = hello 라면 hello 라는 이름의 Plugin project가 생성하며, android, ios의 패키지, 번들 이름이 com.example.hello 라고 생성된다.

  • 만약 ios는 object - c, Android는 java로 개발하고 싶다면 아래의 명령어를 통해 생성하면 된다.
    1
    flutter create —template=plugin -i objc -a java YOUR_PACKAGE_NAME

Plugin 구성

1
2
3
4
5
6
7
8
9
. # 패키지 루트
+— ios # iOS part
+— android # Android part
+— lib # Flutter part
+— example # 패키지의 샘플 어플리케이션
| +— ios # example application의 iOS 플랫폼 part
| +— android # example application의 Android 플랫폼 part
| +— lib # example application의 Flutter part
+— test
  • iOS, Android part에서 각 플랫폼의 네이티브 코드를 통해 네이티브 기능을 사용해 볼 것이며 각 플랫폼의 다른 라이브러리도 추가해서 사용해 볼 것 이다.

  • 중요한 부분이 example이다. example은 그냥 샘플 어플리케이션이라고 생각하겠지만 각 iOS, Android 코드를 짤때 example 내부의 iOS, Android 프로젝트를 열어서 개발해야한다! 그리고 개발하면서 자연스럽게 샘플 어플리케이션이 만들어지며 이 플러그인을 사용하는 개발자들은 사용 예시를 쉽게 볼 수 있다. (Flutter 측의 의도일수도 있겠다)

  • lib는 네이티브와 연결되는 부분이다. method channel, event channel을 call 하여 각 플랫폼과 통신한다.

Android, iOS 개발을 위해 build를 먼저 해줘야함

  1. cd example
  2. flutter build apk // for android
  3. flutter build ios —no-codesign // for ios

생성되는 코드 설명

이 글을 보고있는 Plugin을 생성하고자 하는 분들은 이미 Flutter가 네이티브와 통신하는 과정들을 다 이해하고 계시겠지만 그래도 약간의 설명을 더하고자 한다.

  • lib/YOUR_PLUGIN_NAME.dart
1
2
3
4
5
6
7
8
9
10
11
12
13
14
import 'dart:async';

import 'package:flutter/services.dart';

class YOUR_PLUGIN_NAME {
// Method channel을 생성한다.
static const MethodChannel _channel = const MethodChannel('YOUR_PLUGIN_NAME');

static Future<String> get platformVersion async {
// Method channel에 등록되어있는 getPlatformVersion 이라는 메소드를 call 해서 platform version을 받아온다.
final String version = await _channel.invokeMethod('getPlatformVersion');
return version;
}
}
  • ios/Classes/SwiftYourPlugin.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import Flutter
import UIKit

public class SwiftYourPlugin: NSObject, FlutterPlugin {
// register 메소드가 내부적으로 불리며 해당 Plugin class를 Flutter와 연결한다.
public static func register(with registrar: FlutterPluginRegistrar) {
// 메소드 채널 생성
let channel = FlutterMethodChannel(name: "YOUR_PLUGIN_NAME", binaryMessenger: registrar.messenger())
let instance = SwiftYourPlugin()
// 메소드 채널에서 call되어 오는 메세지를 받기 위해 현재 Plugin을 등록
registrar.addMethodCallDelegate(instance, channel: channel)
}

public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) {
// 원래는 call.method에서 getPlatformVersion이 오면 result로 systemVersion을 넘겨줘야하는데 생성되는 예제에서는 handle 메소드가 불리면 바로 넘겨주고 있다.
result("iOS " + UIDevice.current.systemVersion)
}
}
  • android/src/main/kotlin/com/yourorgname/yourpackagename/YourPlugin.kt
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.yourorgname.yourpackagename

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
import io.flutter.plugin.common.PluginRegistry.Registrar

class YourPlugin: MethodCallHandler {
// 위의 iOS의 설명과 같이 이 Handler를 Flutter에 등록한다.
companion object {
@JvmStatic
fun registerWith(registrar: Registrar) {
val channel = MethodChannel(registrar.messenger(), "YOUR_PLUGIN_NAME")
channel.setMethodCallHandler(YourPlugin())
}
}

override fun onMethodCall(call: MethodCall, result: Result) {
// 위에서 설명했던 바와 같이 getPlatformVersion method가 call되면 결과값으로 system version을 넘겨준다.
if (call.method == "getPlatformVersion") {
result.success("Android ${android.os.Build.VERSION.RELEASE}")
} else {
result.notImplemented()
}
}
}
  1. Flutter 쪽에서 method channel에 getPlatformVersion 메소드를 call 한다.
  2. 플랫폼 별로 대응 해서 Flutter 쪽에 응답을 준다.
    • ios는 handle 메소드에서 result(~~)를 통해 응답한다.
    • androidn 또한 handle 메소드에서 result.success(~~)를 통해 응답한다.

어떤 앱을 만들면서 이야기를 진행할까?

회사에서 하는 작업 중 사용자가 입력한 Wi-Fi ssid, password를 가지고 해당 ap에 연결하는 작업이 있었고, 이 부분을 플러그인으로 만들면 좋겠다고 생각했었다.

Android에서는 wifi를 scan, 연결, 연결 해제가 가능하지만 ios는 scan이 불가능하며 오직 ssid와 password를 가지고 연결하는 것이 가능하다(iOS 11 이상).

iOS 10 이하의 사용자들은 지원 안하는건가? 라는 생각을 할 수 있지만 • Apple devices iOS version share worldwide 2016-2019 | Statista 여길 보면 10 이하 사용자가 5%정도 되는걸 알 수 있으며 최소 지원 버전을 11로 잡는게 문제 없을 것으로 판단하였다.

따라서 우리는 wifi를 연결하는 공통적인 기능을 플러그인으로 구현 해 볼 것이다! 순서는 역시 Flutter -> iOS -> Android 이다.

Flutter part 코드

  • lib/wifi_connector.dart
1
2
3
4
5
6
7
8
9
10
11
12
import 'dart:async';

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

class WifiConnector {
static const MethodChannel _channel = const MethodChannel('wifi_connector');

static Future<bool> connectToWifi({@required ssid, password}) async {
return await _channel.invokeMethod('connectToWifi');
}
}

각 플랫폼의 handle 메소드 내부에서 connectToWifi 라는 메소드를 handle 하도록 구현 할 것이며, 현재는 그 부분이 각 플랫폼에 구현되어있다고 생각하며, connect 성공시 true, 실패시 false가 온다고 가정하고 메소드를 구현해 두었다.

  1. Flutter 쪽에서 connectToWifi() 메소드 실행
  2. iOS, Android 쪽에서 해당 기능 handle
  3. Flutter 쪽에서 성공 여부 return

테스트를 위한 UI는 example 앱에 간단히 가운데에 text field 두개, 확인 버튼 한개가 있는 뷰를 만들어 두었다.

이로써 Flutter 쪽의 구현은 완료되었으며, 다음글에서는 iOS쪽부터 기능 구현을 진행해보자.

댓글

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×