8. 플러터 : 플랫폼 채널 기본 (1)

일단 처음 생각했던 부분까지 왔다.
지금 작성하는 시리즈까지 정리하고 나면
본업으로 삼아야만 하는 딥러닝에 집중할 수 있을 것이다.

플러터만으로 스마트폰 앱을 완벽하게 구성할 수는 없다.
모바일이라는 공통 분모가 상당 부분 존재하지만
아이폰과 안드로이드라는 이질적인 부분도 존재하기 때문이다.
이와 같은 이질적인 부분에 대해
플러터에서는 아이폰이나 안드로이드에서 코딩하는 것처럼 코딩할 수 있도록 해준다.

아이폰이나 안드로이드와 연동하기 위해서는 플랫폼 채널이 필요한데
이번 글에서는 기본이 되는 이 부분에 대해 집중하도록 하겠다.

플러터 프로젝트에서 생성한 버튼을 누르면
아이폰 시뮬레이터 또는 안드로이드 에뮬레이터로부터 해당 버튼에 맞는 데이터를 수신한 다음
플로터 프로젝트에서 만든 텍스트뷰에 결과를 표시한다.
통신 데이터의 종류로는 문자열 1개, 정수 1개, 문자열 배열 1개를 사용한다.

먼저 플러터 프로젝트를 생성한다.
프로젝트 이름은 tflite_flutter_1로 한다.

이후 글에서도 똑같이 설정해야 하는 부분이 있는데
아래 그림에 있는 것처럼 안드로이드 코딩에서 코틀린을 사용하지 않을 것이다.
한창 앱을 개발할 때는 코틀린이 없었다.
덕분에 배우지 않아도 됐고, 기본은 알고 있는 자바로 코딩을 진행할 것이다.
그러고 보니
이전 글에서도 안드로이드 프로젝트는 모두 자바로 코딩을 했다.

플러터 프로젝트를 만드는 것이 생소하면
플러터 카테고리에 있는 글을 몇 개 보고 오도록 한다.
따로 설명하지 않는다.

안드로이드의 코틀린(kotlin)은 선택하지 않았고
아이폰의 스위프트(swift)는 선택했다.
앱 개발 주력 언어가 스위프트였는데.. 그마저 가물가물 하는 중이다.


플러터 프로젝트에서 라이브러리와 같은 환경 설정은 pubspec.yaml 파일에서 처리한다.
이번엔 플랫폼 채널외에는 별게 없어 pubspec.yaml 파일을 수정하지 않는다.

lib 폴더 아래의 main.dart 파일을 연다.
스마트폰에 표시할 화면을 비롯해서 아이폰/안드로이드와 연동할 코드를 추가한다.
플러터를 비롯해서 다트까지, 코드에 대한 설명은 생략한다.
꼭 플러터 카테고리의 내용을 읽고 오도록 한다.

import 'dart:async';

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

class PlatformChannel extends StatefulWidget {
@override
PlatformChannelState createState() => PlatformChannelState();
}

// State를 상속 받아야 setState 함수를 사용해서 화면 구성을 쉽게 변경할 수 있다.
class PlatformChannelState extends State<PlatformChannel> {
// 채널 생성. 매개변수에 '/'는 중요하지 않다. 'mobile' 영역의 'parameters'라는 항목으로 분류하기 위해 사용.
// 채널을 여러 개 만들어도 되고, 채널 하나에 메소드를 여러 개 얹어도 된다.
// 여기서는 채널 1개에 매개변수와 반환값을 다르게 해서 메소드를 3개 사용한다.
static const MethodChannel mMethodChannel = MethodChannel('mobile/parameters');

// 아이폰/안드로이드에서 수신한 데이터 : 문자열, 정수, 문자열 배열
String mReceivedString = 'Not clicked.';
int mReceivedNumber = -1;
String mReceivedArray = 'Not received.';

// 아이폰/안드로이드로부터 문자열 수신 (비동기)
Future<void> getMobileString() async {
try {
// getMobileText 문자열은 안드로이드와 아이폰에 전달할 식별자.
// if문으로 문자열을 비교해서 코드를 호출하기 때문에 함수 이름과 똑같을 필요는 없다.
// 반환값은 정해진 것이 없고 모든 자료형 사용 가능
final String received = await mMethodChannel.invokeMethod('getMobileString');

// 상태를 변경하면 build 메소드를 자동으로 호출하게 된다.
// 상태를 변경한다는 뜻은 setState 함수를 호출하는 것을 뜻한다. 변수의 값을 바꾸는 것은 없어도 된다.
setState(() {
mReceivedString = received;
});
}
on PlatformException {
mReceivedString = 'Exception. Not implemented.';
}
}

// 아이폰/안드로이드로부터 정수 수신 (비동기)
Future<void> getMobileNumber() async {
try {
// 여러 개의 매개 변수는 맵을 통해 전달
const values = <String, dynamic>{'left': 3, 'rite': 9};
final int received = await mMethodChannel.invokeMethod('getMobileNumber', values);

setState(() {
mReceivedNumber = received;
});
}
on PlatformException {
mReceivedNumber = -999;
}
}

// 아이폰/안드로이드로부터 문자열 배열 수신 (비동기)
Future<void> getMobileArray() async {
try {
// 문자열 배열 수신
final List<dynamic> received = await mMethodChannel.invokeMethod('getMobileArray');

setState(() {
mReceivedArray = '${received[0]}, ${received[1]}, ${received[2]}';
});
}
on PlatformException {
mReceivedArray = 'Exception. Not implemented.';
}
}

// 화면 구성. 유저 인터페이스를 구축한다고 보면 된다.
// 안드로이드에서 사용하는 별도의 xml 파일 없이 직접 코딩으로 화면을 설계한다.
// 아이폰/안드로이드로부터 넘겨받은 데이터를 사용해서 위젯의 내용을 덮어쓴다.
@override
Widget build(BuildContext context) {
return Material(
child: Column(
children: <Widget>[
Column(
children: <Widget>[
Text(mReceivedString, style: TextStyle(fontSize: 23)),
RaisedButton(
child: Text('Get text!', style: TextStyle(fontSize: 23)),
onPressed: getMobileString,
),
],
),
Column(
children: <Widget>[
Text(mReceivedNumber.toString(), style: TextStyle(fontSize: 23)),
RaisedButton(
child: Text('Get number!', style: TextStyle(fontSize: 23)),
onPressed: getMobileNumber,
),
],
),
Column(
children: <Widget>[
Text(mReceivedArray, style: TextStyle(fontSize: 23)),
RaisedButton(
child: Text('Get array!', style: TextStyle(fontSize: 23)),
onPressed: getMobileArray,
),
],
),
],
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
),
);
}
}

// 플러터 프로젝트의 진입점(entry point)
void main() {
runApp(MaterialApp(home: PlatformChannel()));
}


많이 했다.
다음 글에서 아이폰과 안드로이드 코딩을 해보자.