Flutter Android SDK MethodChannel 연동¶
이 문서는 Flutter 앱에서 Dart와 Android 모듈 사이를 MethodChannel로 연결하는 방법을 정리한 문서입니다. 현재 권장 SDK 버전은 Android SDK 변경 로그에서 확인하세요.
목차¶
- 왜 MethodChannel이 필요한가
- 사전 조건
- Dart에서 Push Token 생성 후 Android로 전달
- Android에서 Push Token 처리
- SET 계열 메소드 패턴
- GET 계열 메소드 패턴
- Activity 인자가 필요한 메소드
- 브리지 확장 순서 권장 사항
- 함께 보면 좋은 문서
왜 MethodChannel이 필요한가¶
Flutter 앱에서 Groobee Android SDK는 Android 네이티브 모듈 안에서 동작합니다.
즉, Dart 코드에서 직접 Groobee.getInstance()를 호출할 수 없기 때문에 아래 흐름이 필요합니다.
- Dart에서 필요한 값을 준비합니다.
MethodChannel로 Android에 메소드 이름과 파라미터를 전달합니다.- Android
MainActivity또는 별도 브리지 클래스에서 Groobee SDK 메소드를 호출합니다. - 조회성 API는 Android에서 JSON 문자열로 직렬화해 Dart로 되돌려줍니다.
사전 조건¶
- Android Flutter SDK 설치 가이드의 설치가 먼저 완료되어 있어야 합니다.
android/app/google-services.json이 포함되어 있어야 합니다.- Dart 측에서 FCM 토큰을 만들기 전에
Firebase.initializeApp()이 먼저 실행되어야 합니다.
Dart에서 Push Token 생성 후 Android로 전달¶
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/services.dart';
const _channel = MethodChannel('Groobee-Android-Channel');
Future<void> initializeGroobeePush() async {
await Firebase.initializeApp();
final token = await FirebaseMessaging.instance.getToken();
if (token != null) {
await _channel.invokeMethod('setPushToken', {'token': token});
}
}
Android에서 Push Token 처리¶
Kotlin:
class MainActivity : FlutterActivity() {
companion object {
private const val CHANNEL = "Groobee-Android-Channel"
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL)
.setMethodCallHandler { call, result ->
when (call.method) {
"setPushToken" -> {
val token = call.argument<String>("token")
if (token.isNullOrBlank()) {
result.error("INVALID_TOKEN", "token is required", null)
} else {
Groobee.getInstance().setPushToken(token)
result.success(null)
}
}
else -> result.notImplemented()
}
}
}
}
Java:
public class MainActivity extends FlutterActivity {
private static final String CHANNEL = "Groobee-Android-Channel";
@Override
public void configureFlutterEngine(@NonNull FlutterEngine flutterEngine) {
super.configureFlutterEngine(flutterEngine);
new MethodChannel(flutterEngine.getDartExecutor().getBinaryMessenger(), CHANNEL)
.setMethodCallHandler((call, result) -> {
if ("setPushToken".equals(call.method)) {
String token = call.argument("token");
if (token == null || token.trim().isEmpty()) {
result.error("INVALID_TOKEN", "token is required", null);
} else {
Groobee.getInstance().setPushToken(token);
result.success(null);
}
} else {
result.notImplemented();
}
});
}
}
SET 계열 메소드 패턴¶
Dart에서 값을 보내고 Android에서 Groobee 메소드를 호출하는 가장 단순한 패턴입니다.
예시:
Future<void> setServiceLogin(String memberId) async {
await _channel.invokeMethod('setServiceLogin', {'memberId': memberId});
}
Future<void> setAgreedPush(bool isAgreed) async {
await _channel.invokeMethod('setAgreedPush', {'isAgreed': isAgreed});
}
Android:
Kotlin:
"setServiceLogin" -> {
val memberId = call.argument<String>("memberId")
if (memberId.isNullOrBlank()) {
result.error("INVALID_MEMBER_ID", "memberId is required", null)
} else {
Groobee.getInstance().setServiceLogin(memberId)
result.success(null)
}
}
"setAgreedPush" -> {
val isAgreed = call.argument<Boolean>("isAgreed")
if (isAgreed == null) {
result.error("INVALID_ARG", "isAgreed is required", null)
} else {
Groobee.getInstance().setAgreedPush(isAgreed)
result.success(null)
}
}
Java:
case "setServiceLogin": {
String memberId = call.argument("memberId");
if (memberId == null || memberId.trim().isEmpty()) {
result.error("INVALID_MEMBER_ID", "memberId is required", null);
} else {
Groobee.getInstance().setServiceLogin(memberId);
result.success(null);
}
break;
}
case "setAgreedPush": {
Boolean isAgreed = call.argument("isAgreed");
if (isAgreed == null) {
result.error("INVALID_ARG", "isAgreed is required", null);
} else {
Groobee.getInstance().setAgreedPush(isAgreed);
result.success(null);
}
break;
}
GET 계열 메소드 패턴¶
조회성 메소드는 Android에서 받은 결과를 Dart로 다시 넘겨야 합니다.
result.success()에는 문자열이 들어가도록 Gson().toJson()을 사용해 JSON 문자열로 직렬화한 뒤 전달하는 방식을 권장합니다.
비동기식 예시: getPushAgreed¶
Dart:
import 'dart:convert';
Future<Map<String, dynamic>?> getPushAgreed(String memberId) async {
final jsonString = await _channel.invokeMethod<String>(
'getPushAgreed',
{'memberId': memberId},
);
if (jsonString == null) {
return null;
}
return jsonDecode(jsonString) as Map<String, dynamic>;
}
Android:
Kotlin:
private var pendingResult: MethodChannel.Result? = null
"getPushAgreed" -> {
val memberId = call.argument<String>("memberId")
if (memberId.isNullOrBlank()) {
result.error("INVALID_MEMBER_ID", "memberId is required", null)
} else {
pendingResult = result
Groobee.getInstance().getPushAgreed(memberId, resultAgreeds)
}
}
private val resultAgreeds = object : ResultAgreeds {
override fun onSuccess(agreeds: Agreeds) {
pendingResult?.success(Gson().toJson(agreeds))
pendingResult = null
}
override fun onFailed(exceptMsg: String) {
pendingResult?.error("GROOBEE_ERROR", exceptMsg, null)
pendingResult = null
}
}
Java:
private MethodChannel.Result pendingResult;
case "getPushAgreed": {
String memberId = call.argument("memberId");
if (memberId == null || memberId.trim().isEmpty()) {
result.error("INVALID_MEMBER_ID", "memberId is required", null);
} else {
pendingResult = result;
Groobee.getInstance().getPushAgreed(memberId, resultAgreeds);
}
break;
}
private final ResultAgreeds resultAgreeds = new ResultAgreeds() {
@Override
public void onSuccess(Agreeds agreeds) {
if (pendingResult != null) {
pendingResult.success(new Gson().toJson(agreeds));
pendingResult = null;
}
}
@Override
public void onFailed(String exceptMsg) {
if (pendingResult != null) {
pendingResult.error("GROOBEE_ERROR", exceptMsg, null);
pendingResult = null;
}
}
};
동기식 예시: getPushAgreedSync¶
Kotlin:
"getPushAgreedSync" -> {
val memberId = call.argument<String>("memberId")
if (memberId.isNullOrBlank()) {
result.error("INVALID_MEMBER_ID", "memberId is required", null)
} else {
result.success(
Gson().toJson(Groobee.getInstance().getPushAgreed(memberId))
)
}
}
Java:
case "getPushAgreedSync": {
String memberId = call.argument("memberId");
if (memberId == null || memberId.trim().isEmpty()) {
result.error("INVALID_MEMBER_ID", "memberId is required", null);
} else {
result.success(new Gson().toJson(Groobee.getInstance().getPushAgreed(memberId)));
}
break;
}
Activity 인자가 필요한 메소드¶
아래 메소드는 Activity 인자가 필요하므로 Android Activity 컨텍스트에서 호출해야 합니다.
setMemberJoin()setSearchKeyword()setViewGoods()setShoppingCart()setGoodsOrder()setGoodsOrderComplete()setCategory()setScreenData()setCustomEvent()
즉, Flutter에서 화면 진입을 감지하더라도 실제 Groobee 호출은 MainActivity 같은 Android 쪽 브리지에서 처리하는 구성이 안전합니다.
브리지 확장 순서 권장 사항¶
setPushToken(),setServiceLogin(),setAgreedPush()처럼 단순한 SET 메소드부터 연결합니다.- 이후
setScreenData()와setSearchKeyword()같은 행동 이력 수집 메소드를 연결합니다. - 다음으로
Goods모델 변환이 필요한 상품 이벤트 메소드를 추가합니다. - 마지막으로
getPushAgreed()나 추천 상품 API처럼 JSON 응답이 필요한 GET 계열을 확장합니다.