꿈꾸는 시스템 디자이너

Flutter 강좌 - 플랫폼간 메소드 호출 #6 - Android에서 Flutter의 메소드 호출하기 | MethodChannel 사용법 | How to use MethodChannel 본문

Development/Flutter

Flutter 강좌 - 플랫폼간 메소드 호출 #6 - Android에서 Flutter의 메소드 호출하기 | MethodChannel 사용법 | How to use MethodChannel

독행소년 2020. 5. 22. 13:01

 

Flutter Code Examples 강좌를 추천합니다.

  • 제 블로그에서 Flutter Code Examples 프로젝트를 시작합니다.
  • Flutter의 다양한 예제를 소스코드와 실행화면으로 제공합니다.
  • 또한 모든 예제는 Flutter Code Examples 앱을 통해 테스트 가능합니다.

Flutter Code Examples 강좌로 메뉴로 이동

Flutter Code Examples 강좌 목록 페이지로 이동

Flutter Code Examples 앱 설치 | Google Play Store로 이동

 

Flutter Code Examples - Google Play 앱

Are you a beginner at Flutter? Check out the various features of Flutter through the demo. Source code for all demos is also provided.

play.google.com


Flutter 강좌 시즌2 목록 : https://here4you.tistory.com/149

 

이번 강좌에서는 Android에서 Flutter의 메소드를 호출하는 방법에 대해서 알아본다.

이번 강좌는 Flutter Plugin 템플릿 코드를 이용해서 작성되었다.

 

우선 실행화면을 보면서 컨셉을 이해해 보자.

버튼을 클릭할 때마다 하단의 카운트 값이 1씩 증가하고 있다. 실제 동작과정은 다음과 같다.

- 버튼이 클릭되면 Flutter에서 Android로 getCount 메소드 호출을 요청한다.

- Android에서는 getCount 메소드 호출 요청이 수신되면 바로 응답하지 않고, getCount 메소드를 호출한다.

- Android의 getCount 메소드는 Android로 pushCount 메소드 호출을 요청하면서 count 값을 아규먼트로 전달한다.

 

기존의 강좌에서는 메소드 호출 요청이 수신되면 result 객체를 통해 바로 응답을 반환했지만, 이번에는 응답을 Android에서 Flutter로의 메소드 호출 요청으로 반환하고 있다.

 

 

소스 코드를 살펴보자.

 

  • Flutter의 화면 구성
import 'package:flutter/material.dart';
import 'package:methodchanneltest/methodchanneltest.dart';

void main() => runApp(MyApp());

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  // Android로부터 수신한 count값을 저장/출력하기 위한 필드
  int _count = 0;

  @override
  void initState() {
    super.initState();
    // MethodChannel 초기화, 요청 수신을 위한 핸들러 등록용
    Methodchanneltest.initMethodChannel(setCountCallback);
  }

  // Android로부터 수신한 count 값을 필드에 반영하기 위한 콜백
  void setCountCallback(int count) {
    setState(() {
      _count = count;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Column(
            children: <Widget>[
              RaisedButton(
                child: Text("getCount"),
                onPressed: () {
                  Methodchanneltest.getCount();
                },
              ),
              Text("count: $_count")
            ],
          ),
        ),
      ),
    );
  }
}

화면은 단순하게 구성되어 있다. 버튼이 클릭되면 getCount 메소드를 호출한다.

 

 

  • Flutter의 메소드채널플러그인 구현
import 'dart:async';
import 'package:flutter/services.dart';

class Methodchanneltest {
  // 안드로이드와 메소드 호출 통신을 위한 메소드 채널
  static const MethodChannel _channel =
      const MethodChannel('methodchanneltest');

  // 안드로이드로부터 수신한 count 값을 갱신하기 위한 콜백
  static Function(int count) callback;

  // 메소드채널에 안드로이드로부터의 메소드 호출을 수신할 핸들러 등록
  static void initMethodChannel(void Function(int count) setCountCallback) {
    _channel.setMethodCallHandler(handler);
    callback = setCountCallback;
  }

  // 안드로이드로 카운트를 요청하기 위한 메소드
  static void getCount() async {
    await _channel.invokeMethod('getCount');
  }

  // 안드로이드로부터의 메소드 호출을 수신하는 핸들러
  static Future<dynamic> handler(MethodCall call) async {
    switch (call.method) {
      // count 값을 수신하는 메소드 호출인 경우
      case "pushCount":
        var count = call.arguments;
        // 수신한 카운트 값을 콜백을 통해 화면에 갱신
        callback(count);
        break;
      default:
        throw ("method not defined");
    }
  }
}

Android로부터의 메소드 호출 요청을 수신하기 위해 메소드채널에 핸들러를 등록해야 한다. 이를 위해 initMethodChannel 메소드가 초반에 호출되어야 하면, 수신된 카운트값을 갱신하기 위한 콜백도 아규먼트로 받고 있다.

 

getCount 메소드가 호출되면 채널을 통해 getCount 메소드 호출을 요청한다.

 

Android로부터의 메소드 호출 요청을 수신하는 핸들러에서 pushCount 메소드 호출 요청이 수신되면 count값을 받아서 콜백을 통해 화면을 갱신한다.

 

 

  • Android의 메소드 호출 처리
package com.msoc.methodchanneltest;

import android.os.Handler;
import android.os.Looper;

import androidx.annotation.NonNull;

import java.util.Timer;
import java.util.TimerTask;

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

/**
 * MethodchanneltestPlugin
 */
public class MethodchanneltestPlugin implements FlutterPlugin, MethodCallHandler {

    static MethodChannel channel;
    Handler handler = new Handler(Looper.getMainLooper());
    Timer eventTimer = new Timer();
    int count = 0;

    // 메소드 채널 생성 및 핸들러 등록(AndroidX)
    @Override
    public void onAttachedToEngine(@NonNull FlutterPluginBinding flutterPluginBinding) {
        channel = new MethodChannel(flutterPluginBinding.getFlutterEngine().getDartExecutor(), "methodchanneltest");
        channel.setMethodCallHandler(new MethodchanneltestPlugin());
    }

    // 메소드 채널 생성 및 핸들러 등록(non AndroidX)
    public static void registerWith(Registrar registrar) {
        channel = new MethodChannel(registrar.messenger(), "methodchanneltest");
        channel.setMethodCallHandler(new MethodchanneltestPlugin());
    }

    // 플러터발 메소드 호출을 수신하는 핸들러
    @Override
    public void onMethodCall(@NonNull MethodCall call, @NonNull Result result) {
        // getCount를 수신한 경우 fireEvent 메소드를 호출
        if (call.method.equals("getCount")) {
            getCount();
        } else {
            result.notImplemented();
        }
    }


    // pushCount 함수를 요청하면서 count 값을 전달
    void getCount() {

        // 현재 구현 처럼 onMethodCall에서 직접 getCount()를 호출하는 경우 아래와 같이 직접 pushCount를 호출 가능
        // channel.invokeMethod("pushCount", ++count);

        // onAttachedToEngine()나 registerWith()에서 getCount()를 호출하는 경우는
        // 아래와 같이 별도의 스레드로 분리해서 pushCount를 호출 가능
        handler.post(new Runnable() {
            @Override
            public void run() {
                channel.invokeMethod("pushCount", ++count);
            }
        });
    }

    @Override
    public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) {
    }
}

onAttachedToEngine과 registerWith 메소드에서 메소드 채널을 생성하고, 핸들러를 등록한다. 두 함수에서 동일한 작업을 반복하는 이유는 AndroidX 사용 여부와 관련이 있다고 하는데, 이렇게 두번 등록하는 것을 추천하고 있다.

 

onMethodCall의 경우 기존과 동일하게 Flutter로부터의 메소드 호출 요청을 수신하고 처리하는 부분이다. getCount 호출 요청을 받은 경우 getCount 메소드를 호출한다.

 

getCount 메소드의 경우 메소드채널을 이용해서 Flutter의 pushCount 메소드 호출을 요청하면서 값이 1 증가된 count 값을 아규먼트로 전달하는 기능을 한다.

 

이 때 유의해야 할 점이 있다. 이번 구현의 경우 onMethodCall 핸들러 내부에서 getCount 메소드를 호출하고 있기 때문에 channel.invokeMethod 메소드를 이용해서 바로 Flutter의 메소드를 호출할 수 있다.

 

하지만 onAttachedToEngine와 같이 Android의 메인 스레드의 흐름안에서 channel.invokeMethod 메소드를 직접 호출 하면 에러가 발생한다. 이는 Android에서 핸들러를 이용해야하는 이유와 동일하다. 이 경우에는 핸들러를 이용해서 간접적으로 channel.invokeMethod 메소드를 호출해야 한다.

 

 

Comments