[WIP] Flutter - Writing custom platform-specific code with platform channels (日本語訳)

[Writing custom platform-specific code with platform channels](https://flutter.io/platform-channels/) の日本語訳

# Writing custom platform-specific code with platform channels

このガイドはプラットフォーム固有のコードを書く方法を説明します。いくつかのプラットフォーム固有の機能は既存のパッケージで利用可能です。[using packages](https://flutter.io/using-packages/) を参照してください。

Flutter は柔軟なシステムを採用しています。それは Android では Java か Kotorin で、iOS では ObjectiveC か Swift で書かれたプラットフォーム固有の API を呼び出します。

Flutter の platform-specific API support はコードジェネレーションに頼りません。フレキシブルなメッセージパッシングスタイルを用います:

- あなたの Flutter アプリはプラットフォームチャネルを通して **host** （iOS か Android ）にメッセージを送信します。
- **host** はプラットフォームチャネルをリッスンし、メッセージを受け取ります。そして platform-specific APIs をいくつでも呼び出します（ネイティブプログラミング言語を使って）。そして **client** （Flutter アプリ）にレスポンスを返します。

## Architectural overview: platform channels

以下の図のように、メッセージは client と host の間でプラットフォームチャネルを使ってやりとりされます。

https://flutter.io/images/PlatformChannels.png

Messages and responses are passed asynchronously, to ensure the user interface remains responsive.

メッセージとレスポンスはユーザーインターフェイスを止めないために非同期にやりとりされます。

クライアントサイドでは MethodChannel ([API](https://docs.flutter.io/flutter/services/MethodChannel-class.html)) メッセージを送信し、それはメソッドコールに対応します。プラットフォームサイドでは MethodChannel (Android) ([API](https://docs.flutter.io/javadoc/io/flutter/plugin/common/MethodChannel.html)) と FlutterMethodChannel (iOS) ([API](https://docs.flutter.io/objcdoc/Classes/FlutterMethodChannel.html)) がメソッドコールを受信し、結果を返します。これらのクラスがとても少ない "ボイラープレート" コードでプラットフォームプラグインを開発することを可能にします。

**Note:** 必要であれば、プラットフォームがクライアントとして Dart で実装されたメソッドに対して逆向きにメソッドコールを送信することもできます。具体例: [quick_actions](https://pub.dartlang.org/packages/quick_actions)。

### Platform channel data types support and codecs

標準のプラットフォームチャネルは単純な JSON-like な値（booleans, numbers, Strings, byte buffers, and List and Maps of these (see [StandardMessageCodec](https://docs.flutter.io/flutter/services/StandardMessageCodec-class.html)) for details)）の効率的なバイナリシリアライゼーションをサポートする標準のメッセージコーデックを使用します。これらの値のシリアライゼーションとデシリアライゼーションはメッセージの送信と受信のときに自動で行われます。

以下のテーブルが Dort value がどのようにプラットフォームサイドまたはその逆で受け取られるかを表しています:

| Dart | Android | iOS |
|:-----------------|------------------:|:------------------:|
| null | null | nil (NSNull when nested) |
| bool | java.lang.Boolean | NSNumber numberWithBool: |
| int | java.lang.Integer | NSNumber numberWithInt: |
| int, if 32 bits not enough | java.lang.Long | NSNumber numberWithLong: |
| double | java.lang.Double | NSNumber numberWithDouble: |
| String | java.lang.String | NSString |
| Uint8List | byte[] | FlutterStandardTypedData typedDataWithBytes: |
| Int32List | int[] | FlutterStandardTypedData typedDataWithInt32: |
| Int64List | long[] | FlutterStandardTypedData typedDataWithInt64: |
| Float64List | double[] | FlutterStandardTypedData typedDataWithFloat64: |
| List | java.util.ArrayList | NSArray |
| Map | java.util.HashMap | NSDictionary |

## Example: Calling platform-specific iOS and Android code using platform channels

以下は、現在のバッテリーレベルを取得および表示するためにどのよに platform-specific API を呼び出すかのデモです。それはプラットフォームメッセージ getBatteryLevel を通じて Android BatteryManager API と iOS device.batteryLevel API を利用します。

この例はプラットフォーム固有のコードを main app 自体に追加しています。もしあなたがプラットフォーム固有の処理を他のアプリでも再利用したいならプロジェクト作成のステップは少し違ってきますが（参照 [developing packages](https://flutter.io/developing-packages/#plugin)）、プラットフォームチャネルのコードは変わりません。

**Note:** 完全な実行可能なソースコードはこちらを参照してください。 Android with Java と iOS with Objective-C [/examples/platform_channel/](https://github.com/flutter/flutter/tree/master/examples/platform_channel)、iOS with Swift [/examples/platform_channel_swift/](https://github.com/flutter/flutter/tree/master/examples/platform_channel_swift)。

### Step 1: Create a new app project

新規アプリの作成:

- ターミナルで実行: `flutter create batterylevel`

デフォルトでテンプレートは Android では Java、iOS では Objective-C を使います。Kotlin や Swift を使うためには `-i` や `-a` フラグを使います。

- ターミナルで実行: `flutter create -i swift -a kotlin batterylevel`

### Step 2: Create the Flutter platform client

アプリの `State` クラスが現在のアプリのステートを保持します。これを現在のバッテリーの状態を保持できるように拡張する必要があります。

まず、チャネルを作ります。バッテリーレベルを返す一つのプラットフォームメソッドをともなう `MethodChannel` を使います。

チャネルのクライアントとホストはチャネルのコンストラクタで指定したチャネル名によって接続されます。全てのチャネル名は一つのアプリの中でユニークでなければなりません。ユニークドメインプレフィックスをチャネル名の先頭に付けることを推奨します。例: `samples.flutter.io/battery`。

```dart
import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
...
class _MyHomePageState extends State<MyHomePage> {
  static const platform = const MethodChannel('samples.flutter.io/battery');

  // Get battery level.
}
```

次に、具体的なメソッドを String identifier `getBatteryLevel` を通じて指定することでメソッドチャネルのメソッドを実行します。もし例えばプラットフォームがそのプラットフォーム API をサポートしていなければメソッドコールは失敗します（シミュレーター上で動かしている時など）。なので `invokeMethod` を try-catch で囲みます。

setState の中で戻り値を使って UIステート _batteryLevel を更新します。

```dart
  // Get battery level.
  String _batteryLevel = 'Unknown battery level.';

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

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }
```

最終的に、テンプレートがバッテリーの状態を文字列で表示する小さなユーザーインターフェイスを含むように、また値を更新するボタンを含むように `build` メソッドを以下のように置き換えます。

```dart
@override
Widget build(BuildContext context) {
  return Material(
    child: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.spaceEvenly,
        children: [
          RaisedButton(
            child: Text('Get Battery Level'),
            onPressed: _getBatteryLevel,
          ),
          Text(_batteryLevel),
        ],
      ),
    ),
  );
}
```

### Step 3a: Add an Android platform-specific implementation using Java

**Note:** 以下のステップは Java を使います。Kotlin が良ければ 3b までスキップしてください。

Android Studio であなたの Flutter アプリの Android ホストを開きます:

1. Android Studio を開く
2. ‘File > Open…’ メニューを選択
3. Flutter アプリのあるディレクトリに行き、その中の `android` フォルダーを選択し、OK をクリック
4. Project ビューで java フォルダーの中の `MainActivity.java` を開く

次に、 `MethodChannel` を作り `onCreate` メソッドの中で `MethodCallHandler` をセットする。Flutter のクライアント側で使ったのと同じチャネル名を使うようにしてください。

```dart
import io.flutter.app.FlutterActivity;
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;

public class MainActivity extends FlutterActivity {
    private static final String CHANNEL = "samples.flutter.io/battery";

    @Override
    public void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        GeneratedPluginRegistrant.registerWith(this);

        new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
                new MethodCallHandler() {
                    @Override
                    public void onMethodCall(MethodCall call, Result result) {
                        // TODO
                    }
                });
    }
}
```

次に、実際に バッテリーレベルを取得するために Android battery APIs を呼び出す Java コードを追加します。このコードはあなたかネイティブ Android アプリのために書くであろうコードとまったく同じです。

まず、ファイルの先頭に必要な import を追加します:

```java
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 android.os.Bundle;
```

そしてアクティビティクラスの `onCreate` メソッドの下に以下のメソッドを追加します:

```java
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;
}
```

最後に、先に追加した `onMethodCall` メソッドを完成させます。シングルプラットフォームメソッド `getBatteryLevel` を処理する必要があるため、call 引数でそれをテストします。実装は前のステップで書いた Android コードを単に呼び出します。そして `response` 引数を使って成功とエラーのためにレスポンスを返します。もし不明なメソッドが呼ばれた場合、それを伝えます。以下を

```java
public void onMethodCall(MethodCall call, Result result) {
    // TODO
}
```

以下のように書き換えてください:

```java
@Override
public void onMethodCall(MethodCall call, Result result) {
    if (call.method.equals("getBatteryLevel")) {
        int batteryLevel = getBatteryLevel();

        if (batteryLevel != -1) {
            result.success(batteryLevel);
        } else {
            result.error("UNAVAILABLE", "Battery level not available.", null);
        }
    } else {
        result.notImplemented();
    }
}     
```

これでアプリを Android で動かすことができるはずです。もし Android エミュレータを使っているなら、ツールバーの `...` からアクセスできる Extended Controls panel からバッテリーレベルをセットできます。

### Step 3b: Add an Android platform-specific implementation using Kotlin

書きかけ