FlutterでTwitterにツイートを投稿する

# こんなのを作る

||||
|:---:|:---:|:---:|
|And|![](https://qiita-image-store.s3.amazonaws.com/0/42132/3f604af9-1815-fc50-19f5-1cf112528d49.png)|![](https://qiita-image-store.s3.amazonaws.com/0/42132/869eb9b5-d24f-0155-c488-4c182d91f9e9.png)|
|iOS|![](https://qiita-image-store.s3.amazonaws.com/0/42132/bc6cb17a-3336-9eb5-ee49-068da48136d8.png)|![](https://qiita-image-store.s3.amazonaws.com/0/42132/61fa64fd-a710-6cf6-88a1-4ec3ea5346d0.png)|


Androidの方はintentでTwitterアプリに飛ばしてツイート、iOSは[TwitterKitのサポート終了の件](https://blog.twitter.com/developer/en_us/topics/tools/2018/discontinuing-support-for-twitter-kit-sdk.html)もあるのでUIActivityViewControllerを使ってツイートするようにしました。

デザインも一応AndroidとiOSで少し変えてみてます。

# どうやってやるのか
タイトルに「Flutterで」と書きましたが、実はツイートの部分はFlutterの**Platform Channels**というものを使っていて、実装自体はそれぞれMainActivity/AppDelegateに書いています。

なので半分Platform Channelsの使い方を説明する記事になります。
Platform Channelsの実装方法については公式にも書いてあるのでそちらを読んでいただくとよりわかりやすいです。
[Writing custom platform-specific code with platform channels](https://flutter.io/platform-channels/)

# 本題
流れとしては、

1. MethodChannelというものを使って各プラットフォームと連携できるチャネルを作り、
2. 各プラットフォーム側でチャネル経由でメソッドが呼ばれた時の準備をしておいて、
3. Flutter側からチャネル経由でメソッドをinvokeする

感じになります。
それぞれ詳しく解説していきます。

## MethodChannelの作成
> MethodChannelというものを使って各プラットフォームと連携できるチャネルを作り、

これは簡単で、MethodChannelのインスタンスを作っておくだけです。

```dart
static const channel = const MethodChannel('com.kitoko552.flutter_post_tweet_example/tweet');
```

`com.kitoko552.flutter_post_tweet_example/tweet`の部分はなんでもよくて、私はiOS出身なのでDispatchQueueのラベルと同じような感じかなと考えて上のような文字列を付与しましたが、これがベストプラクティスかはわかりません🤔

公式にも「we recommend prefixing the channel name with a unique ‘domain prefix’」と書いてあるのでそんなに間違ってはいないはずです。


## 各プラットフォーム側の準備
> 各プラットフォーム側でチャネル経由でメソッドが呼ばれた時の準備をしておいて、

これはAndroidならMainActivity、iOSならAppDelegateにそれぞれ実装することになります。
ただどちらもやることはツイートを行う部分以外は一緒で、特定のMethodChannelを監視して、メソッドがinvokeされた時にツイートする処理を走らせるようにするだけです。

### Android
Androidはこんな感じになりました。
上にも書きましたが、私は元々iOSエンジニアでAndroidは軽く触ったことがある程度なのでほぼ公式のコードをコピペしていて、Twitterを開く処理は[こちら](https://qiita.com/le_skamba/items/c9810a0e6df27097d502)を参考に書きました。

```java:android/app/src/main/java/com/example/flutterposttweetexample/MainActivity.java
public class MainActivity extends FlutterActivity {
  private static final String CHANNEL = "com.kitoko552.flutter_post_tweet_example/tweet";

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    GeneratedPluginRegistrant.registerWith(this);

    new MethodChannel(getFlutterView(), CHANNEL).setMethodCallHandler(
            new MethodCallHandler() {
              @Override
              public void onMethodCall(MethodCall methodCall, Result result) {
                if (methodCall.method.equals("tweet")) {
                  openTwitter();
                } else {
                  result.notImplemented();
                }
              }
            }
    );
  }

  private void openTwitter() {
    Intent intent = new Intent(Intent.ACTION_VIEW);
    String message= Uri.encode("This tweet is posted on Flutter app!");
    intent.setData(Uri.parse("twitter://post?message=" + message));
    startActivity(intent);
  }
}
```

ちなみにプロジェクトディレクトリ配下に`android`というディレクトリがあるので、そこから辿っていけばMainActivity.javaに辿りつけます。

### iOS
iOSはこんな感じになりました。
iOSは得意や任せろと思ってworkspaceを開いたらObjective-Cでびっくりしましたが、私がFlutterのプロジェクトを作る時に設定しなかっただけっぽいのでご安心を。
設定すればSwiftで作られるはずです。
今回は途中でSwiftに変えるのが面倒だったのと、何かよくわからないエラーが起きるのが嫌だったのでObjcのまま書きました（Objc久しぶりに書いたので不備があったらすみません🙇）。

```objc:ios/Runner/AppDelegate.m
@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    [GeneratedPluginRegistrant registerWithRegistry:self];
    
    FlutterViewController* controller = (FlutterViewController*)self.window.rootViewController;
    FlutterMethodChannel* channel = [FlutterMethodChannel
                                            methodChannelWithName:@"com.kitoko552.flutter_post_tweet_example/tweet"
                                            binaryMessenger:controller];
    
    __weak AppDelegate* wself = self;
    [channel setMethodCallHandler:^(FlutterMethodCall* call, FlutterResult result) {
        if ([call.method isEqualToString:@"tweet"]) {
            [wself openActivity];
        } else {
            result(FlutterMethodNotImplemented);
        }
    }];
    
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

- (void)openActivity {
    UIActivityViewController* controller = [[UIActivityViewController alloc] initWithActivityItems:@[@"This tweet is posted on Flutter app!"] applicationActivities:NULL];
    [self.window.rootViewController presentViewController:controller animated:true completion:NULL];
}

@end
```

やってることはAndroidのものと同じです。
違うのはツイートするまでの処理だけです。

## Flutter側でメソッドをinvoke
> Flutter側からチャネル経由でメソッドをinvokeする

こちらも簡単で、任意のタイミングでメソッドを呼ぶだけです。
上の実装で、AndroidもiOSも"tweet"というメソッド名の時にそれぞれの処理を呼ぶようにしているので、今回の場合は以下のようになります。

```dart
Future<void> _onPressedButton() async {
  try {
    await channel.invokeMethod('tweet');
  } on PlatformException catch (e) {
    print(e);
  }
}
```

# おわりに
コードは[GitHub](https://github.com/kitoko552/flutter_post_tweet_example)にあげているのでもし良かったら参考にしてください。

今回はデザインについては言及しませんでしたが、Android/iOSごとにデザインを変えるということも試験的にやっているので、大したことはしていませんが興味がある方はコードを読んでみてください。

# リファレンス
- [Discontinuing Support for Twitter Kit SDK](https://blog.twitter.com/developer/en_us/topics/tools/2018/discontinuing-support-for-twitter-kit-sdk.html)
- [Writing custom platform-specific code with platform channels](https://flutter.io/platform-channels/)
- [SDKを使わないでSNSに投稿する](https://qiita.com/le_skamba/items/c9810a0e6df27097d502)
- [kitoko552/flutter_post_tweet_example](https://github.com/kitoko552/flutter_post_tweet_example)