🐦

【Flutter】ネイティブ初心者が、パッケージ+SwiftでTwitterへの投稿機能を実装してみた

2020/12/16に公開

FlutterでSNSへのシェア機能を作る場合、shareというパッケージを使うと簡単にできる。
https://pub.dev/packages/share

ただ、一つ問題というか気になる点がある。
それは、iOSだと画像などの添付ができないこと。

iOS Android

AndroidだとTwitterの画面そのものが開く感じで、画像等の添付ができる。
別に必須ではないが、iOSでテキストしかシェアできないのは微妙なので、なんとかしたかった。

どうやったか?

調べてみたところ、ネイティブのコードをFlutterから呼び出すことが可能とのこと。

参考記事↓
https://qiita.com/kitoko552/items/2911577f80ce57fca9aa
アプリ開発はFlutterが初めてで、swiftやkotlinは全く書けないが、さらっと読んだ感じ難しくはなさそうだったので、トライしてみることに。

先述の通り、Androidはパッケージを使うだけでOKなので、iOS(swift)のみ処理を書いてみた。

ただ、いざ実装を進めてみると、参考記事はswiftではなくobjective-cで実装されていることに気づいた。

特に設定した覚えはないが、自分のアプリのios関連のファイルはswiftになっている。
これをobjective-cに変えて問題が起きても嫌だったので、swiftでの実装にトライすることに。

調べたところ、objective-c→swiftに変換してくれる神ツールがあったので、それを使ってみた。
https://swiftify.com/converter/code/

ただこれだけでは不十分で、修正も必要だった。
ネイティブの経験が無いせいだろうが、ちょっと苦労したので、最終的なコードと簡単な解説をまとめておく。

swiftのコード

ios/Runner/AppDelegate.swiftを以下のように変更する。
おそらく丸々コピペで動くはず。

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    GeneratedPluginRegistrant.register(with: self)
     let controller : FlutterViewController = window?.rootViewController as! FlutterViewController;
        let channel = FlutterMethodChannel(
            name: "com.sample.app/share",
            binaryMessenger: controller.binaryMessenger)

        channel.setMethodCallHandler { [weak self] call, result in
            if call.method == "tweet" {
              let text = call.arguments as? String
                self?.openActivity(text)
            } else {
                result(FlutterMethodNotImplemented)
            }
        }
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  func openActivity(_ text: String?) {
    let encodedText = text?.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
    if let encodedText = encodedText,
        let url = URL(string: "https://twitter.com/intent/tweet?text=\(encodedText)") {
        UIApplication.shared.open(url)
    }
  }
}

name: "com.sample.app/share"の部分は、任意の名前でOK
「アプリのID/任意の名前」とするのが一般的ぽい。

 if call.method == "tweet" {
              let text = call.arguments as? String
                self?.openActivity(text)
            } else {

この部分で、Flutter側からtweetが呼ばれたら、openActivity()を実行するようにしている。
tweetは任意の名前でOK、Flutter側の呼び出しは後ほど。

let 変数 = call.arguments as? 変数の型 とすることで、呼び出し側で渡した引数を取得できる。
引数がない場合は、この部分は不要。

openActivity()の中身はというと

func openActivity(_ text: String?) {
    let encodedText = text?.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)
    if let encodedText = encodedText,
        let url = URL(string: "https://twitter.com/intent/tweet?text=\(encodedText)") {
        UIApplication.shared.open(url)
    }
  }

引数でわたされたtextを変換し、それを使ってTwitterのURLを開くイメージ。
この辺の仕様は知らなかったので、以下の記事を参考にした。

https://qiita.com/awesome_hiraoka/items/0ff4a3308a57e7d1b14c

これでswift側の実装は完了。
ただ、このURLを開く機能はiOS10以降でないと対応していないため、ビルドエラーが出る場合は、対応バージョンの引き揚げが必要。

Flutter側の実装

先ほどネイティブ(swift)で書いた処理を、Flutter側で呼び出すための記述。

final channel = MethodChannel("com.sample.app/share");

// 実際の呼び出し
await channel.invokeMethod('tweet', text);

name: "com.sample.app/share"で設定した値を使って、
final channel = MethodChannel("com.sample.app/share");とする。

これにより、swift側のコードを呼び出す準備ができる。

あとは、

await channel.invokeMethod('tweet' ,text);

とすることで、引数textを渡して、swift側の↓この部分が実行されるイメージ。

 if call.method == "tweet" {
              let text = call.arguments as? String
                self?.openActivity(text)
            } else {

今回は試していないが、いくつか処理を実装する場合はここの条件分岐を増やして、
呼び出し側でawait channel.invokeMethod('任意の名前');とすればよさそう。

iOSの時のみswiftを呼び出し

ここまでで実装はほぼ完成。
ただ冒頭でも書いた通り、androidの場合はそのままパッケージの機能を使えばいいので、以下のようにOSによる分岐をはさむ。

スルーしていたが、channel.invokeMethod()はFutureを返すので、async-await.then()を使う必要がある。

import 'package:share/share.dart';


Future<void> share(String text) async{
 final channel = MethodChannel("com.sample.app/share");
 // iosならswiftから呼び出し
   if (Platform.isIOS) {
     await channel.invokeMethod('tweet', text);
   } else {
     await Share.share(text);
   }
}

これはこれで、iOSの場合はTwitter以外にshareできないという問題点があるのだが、今回はTwitterに特化するかわりに、iOSでもテキスト以外も添付できるようにするという選択をした。

以上、ネイティブの経験がなくても情報が豊富なお陰でなんとかなった。
パッケージだけで済むのであれば、非常に簡単なのでそれがベストだが、この程度であればアレンジも難しくないので、参考までに。

その他参考記事

https://qiita.com/mkosuke/items/b384035e507ad0208c10

https://qiita.com/Tazake/items/b3061bf941a413c74fed

Discussion