본문 바로가기
📱Mobile/🔥Flutter

[Flutter] Flutter 플랫폼 채널을 이용해서 Android 코드 호출하기 (MethodChannel)

by 후누스 토르발즈 2020. 10. 25.
반응형

 

생성시 Paltform channel language 체크 모두 해제하기

* 플랫폼 채널을 사용하는데 있어서 중요한것은 Platform channel language를 어떤것을 사용할것인지이다.

Include Swift support for iOS code 를 선택한 적이 있는데 Swift 코드를 작성할 사람이 없어서 낭패를 본 적이 있다.

 

플랫폼 채널을 사용하려면 java, kotlin, Objective-c, swift 모두 숙지해 두어야 한다.

 

project/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.';
  String _batteryLevel = 'Unknown battery level.';

  // 아이폰/안드로이드로부터 문자열 수신 (비동기)
  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.';
    }
  }

  Future<void> _getBatteryLevel() async {
    String batteryLevel;
    try {
      final int result = await mMethodChannel.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }
  // 화면 구성. 유저 인터페이스를 구축한다고 보면 된다.
  // 안드로이드에서 사용하는 별도의 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,
              ),
            ],
          ),
          Column(
            children: <Widget>[
              Text(_batteryLevel, style: TextStyle(fontSize: 23)),
              RaisedButton(
                child: Text('Get array!', style: TextStyle(fontSize: 23)),
                onPressed: _getBatteryLevel,
              ),
            ],
          ),
        ],
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
      ),
    );
  }
}

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

 

 

MainActiviry의 경로이다.

project/android/app/main/java/package/MainActivity

 

MainActivity.java

package net.e4net.platformchannel;

import android.os.Bundle;
import java.util.ArrayList;                 // 배열 사용 필수
import android.content.ContextWrapper;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.BatteryManager;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;

// 기본적으로 포함되어 있는 모듈
import io.flutter.app.FlutterActivity;
import io.flutter.embedding.engine.FlutterEngine;
import io.flutter.plugins.GeneratedPluginRegistrant;

// MethodChannel 관련 모듈 (추가해야 함)
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.MethodCall;

public class MainActivity extends FlutterActivity {
    // 채널 문자열. 플러터에 정의한 것과 동일해야 한다.
    private static final String mChannel = "mobile/parameters";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        // 빌트인 코드
        super.onCreate(savedInstanceState);
        FlutterEngine flutterEngine = new FlutterEngine(this);
        GeneratedPluginRegistrant.registerWith(flutterEngine);
        // 추가한 함수.
        new MethodChannel(getFlutterView(), mChannel).setMethodCallHandler(
                new MethodCallHandler() {
                    @Override
                    public void onMethodCall(MethodCall call, Result result) {
                        // 각각의 메소드를 switch로 분류. 아이폰에서는 else if 사용.
                        switch (call.method) {
                            case "getMobileString":
                                result.success("ANDROID");
                                break;
                            case "getMobileNumber":
                                // 매개 변수 추출
                                final int left = call.argument("left");
                                final int rite = call.argument("rite");
                                result.success(left * rite);
                                break;
                            case "getMobileArray":
                                // 배열 생성 후 전달. success 함수에 넣으면 json으로 인코딩된다. 플러터에서는 json 문자열 디코딩.
                                ArrayList<String> words = new ArrayList<>();
                                words.add("one");
                                words.add("two");
                                words.add("three");
                                result.success(words);
                                break;
                            case "getBatteryLevel":
                                int batteryLevel = getBatteryLevel();
                                result.success(batteryLevel);
                                break;
                            default:
                                result.notImplemented();
                        }
                    }
                }
        );
    }
    private int getBatteryLevel() {
        int batteryLevel = -1;
        if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
            BatteryManager batteryManager = (BatteryManager) getSystemService(BATTERY_SERVICE);
            batteryLevel = batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY);
        } else {
            Intent intent = new ContextWrapper(getApplicationContext()).
                    registerReceiver(null, new IntentFilter(Intent.ACTION_BATTERY_CHANGED));
            batteryLevel = (intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100) /
                    intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
        }

        return batteryLevel;
    }
}

 

MainActivity에서 오류가 나지만 우측 상단의 Open for Editing in Android Studio을 클릭하여 new Window로 열어서 씽크를 맞추게 되는데 그곳에서 오류를 없애주면 된다. MainActivity 에서 오류가 나는것은 그다지 상관이 없어보인다.

flutter upgrade로 처리가 가능하다고 하지만 안된다. 

반응형