Flutter에서 Kotlin Native와 통신하기 셈플

  1. Device 정보조회
  2. 문자열 encoding
  3. 문자열 decoding
  4. Native dialog열기

 

/lib/main.dart

import "package:flutter/material.dart";
import "package:flutter/cupertino.dart";
import "dart:io";
import "package:flutter/services.dart";

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    if (Platform.isIOS) {
      return const CupertinoApp(
        home: CupertinoNativeApp(title: "Cupertino Native App"),
      );
    } else {
      return MaterialApp(
        title: "Native communication",
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ),
        home: const NativeApp(title: "Android Native App"),
      );
    }
  }
}

class CupertinoNativeApp extends StatefulWidget {
  final String title;

  const CupertinoNativeApp({super.key, required this.title});

  @override
  State<StatefulWidget> createState() => _NativeApp();
}

class NativeApp extends StatefulWidget {
  final String title;

  const NativeApp({super.key, required this.title});

  @override
  State<StatefulWidget> createState() => _NativeApp();
}

class _NativeApp extends State<NativeApp> {
  final MethodChannel platform1 = const MethodChannel("com.flutter.dev/info");
  final MethodChannel platform2 = const MethodChannel("com.flutter.dev/encrypt");
  final MethodChannel platform3 = const MethodChannel("com.flutter.dev/dialog");
  String _deviceInfo = "Unknown info.";
  String _encodeText = "";
  String _decodeText = "";

  final TextEditingController _controller = TextEditingController();

  @override
  void initState() {
    super.initState();
  }

  @override
  void dispose() {
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          children: <Widget>[
            Text(_deviceInfo, style: const TextStyle(fontSize: 20)),
            ElevatedButton(
              onPressed: () {
                _getDeviceInfo();
              }, child: const Text("Get Info.")
            ),
            const SizedBox(height: 20),
            TextField(
              controller: _controller,
              keyboardType: TextInputType.text,
            ),
            ElevatedButton(
              onPressed: () {
                _getEncode(_controller.value.text);
              }, child: const Text("Encode")
            ),
            Text(_encodeText, style: const TextStyle(fontSize: 20)),
            ElevatedButton(
              onPressed: () {
                _getDecode(_encodeText);
              }, child: const Text("Decode")
            ),
            Text(_decodeText, style: const TextStyle(fontSize: 20)),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                _showDialog();
              }, child: const Text("Show Native Dialog Window")
            ),
          ],
        ),
      ),
    );
  }

  Future<void> _getDeviceInfo() async {
    String deviceInfo;

    try {
      final String result = await platform1.invokeMethod("getDeviceInfo");
      deviceInfo = "Device Info. :\n$result";
    } on PlatformException catch (e) {
      deviceInfo = "Failed to get Device info ${e.message}.";
    }

    setState(() {
      _deviceInfo = deviceInfo;
    });
  }

  Future<void> _getEncode(String text) async {
    final String result = await platform2.invokeMethod("getEncode", text);

    setState(() {
      _encodeText = result;
    });
  }

  Future<void> _getDecode(String text) async {
    final String result = await platform2.invokeMethod("getDecode", text);

    setState(() {
      _decodeText = result;
    });
  }

  Future<void> _showDialog() async {
    await platform3.invokeMethod("showDialog");
  }
}

 

/android/app/src/main/kotlin/com/example/native_app/MainActivity.kt

package com.example.native_app

import android.app.AlertDialog
import android.os.Build
import android.util.Base64
import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel

class MainActivity: FlutterActivity() {
    private val CHANNEL1 = "com.flutter.dev/info"
    private val CHANNEL2 = "com.flutter.dev/encrypt"
    private val CHANNEL3 = "com.flutter.dev/dialog"

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL1).setMethodCallHandler { call, result ->
            if (call.method == "getDeviceInfo") {
                val deviceInfo = getDeviceInfo()
                result.success(deviceInfo)
            }
        }

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL2).setMethodCallHandler { call, result ->
            if (call.method == "getEncode") {
                val data = call.arguments.toString().toByteArray();
                val encodeText = Base64.encodeToString(data, Base64.DEFAULT)
                result.success(encodeText)
            } else if (call.method == "getDecode") {
                val data = call.arguments.toString()
                val decodeText = Base64.decode(data, Base64.DEFAULT)
                result.success(String(decodeText))
            }
        }

        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL3).setMethodCallHandler { call, _ ->
            if (call.method == "showDialog") {
                AlertDialog.Builder(this).setTitle("Flutter").setMessage("네이티브에서 출력하는 창입니다!").show()
            }
        }
    }

    private fun getDeviceInfo(): String {
        val sb = StringBuffer()

        sb.append(Build.DEVICE + "\n")
        sb.append(Build.BRAND + "\n")
        sb.append(Build.MODEL + "\n")
        sb.append(Build.HARDWARE + "\n")
        sb.append(Build.HOST + "\n")

        return sb.toString()
    }
}