Flutter x Share Extension の覚書

#はじめに
他のアプリから自分のアプリを立ち上げて何かの入力を促すという操作を行いたい場合
share extensionを利用することがあります。
今回はreceive_sharing_intentを利用したので、それの覚書です。
share_extentionというサンプルアプリを作成し、これを様々なアプリから開くようなサンプルを作りたいと思います。

#実装
##1.receive_sharing_intent 1.4.0+2
https://pub.dev/packages/receive_sharing_intent

##2.プロジェクトの作成

```
$ flutter create share_extensin
$ cd share_extension
```

##3.install

```
dependencies:
  flutter:
    sdk: flutter
  receive_sharing_intent: ^1.4.0+2
```

##4.main.dartの修正


```main.dart

import 'package:flutter/material.dart';
import 'dart:async';
import 'package:receive_sharing_intent/receive_sharing_intent.dart';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  StreamSubscription _intentDataStreamSubscription;
  List<SharedMediaFile> _sharedFiles;
  String _sharedText;

  @override
  void initState() {
    super.initState();
     //アプリが起動されている状態で呼ばれた時、ここからURLが入ってくる
    _intentDataStreamSubscription =
        ReceiveSharingIntent.getTextStream().listen((String value) {
      setState(() {
        _sharedText = value;
      });
    }, onError: (err) {
      print("getLinkStream error: $err");
    });

    // アプリが起動されていない状態で呼ばれた時、ここからURLが入ってくる
    ReceiveSharingIntent.getInitialText().then((String value) {
      setState(() {
        _sharedText = value;
      });
    });
  }

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

  @override
  Widget build(BuildContext context) {
    const textStyleBold = const TextStyle(fontWeight: FontWeight.bold);
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Center(
          child: Column(
            children: <Widget>[
              Text("Shared urls/text:", style: textStyleBold),
              Text(_sharedText ?? "")
            ],
          ),
        ),
      ),
    );
  }
}
```


##5.XCodeでプロジェクトを開く

```
open ios/Runner.xcodeproj/
```

##6.Xcodeで、ShareExtensionのディレクトリを作成する

ツールバーからFile > New > Targetを選択し、表示されたダイアログからShare Extensionを選択
![スクリーンショット 2020-08-14 15.36.08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43766/fcc65a5d-3bcf-8b42-618e-0aa3200ef2a6.png)

ProductNameにはMyShareExtensionと入力する。(別に名前はなんでも良い。サービス名+ShareExtensionとかだとわかりやすくて良いかも)

-> サイドバーとTARGETSにMyShareExtensionという項目が出現する
![スクリーンショット 2020-08-14 15.37.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43766/7bae1004-3662-3298-1b78-82c969602a53.png)


#6.5 RunnerとMyShareExtensionのBundleIdentifierを確認する

RunnerとExtensionのBundleIdentifierが同じPrefixになるように設定しておく
Runner:
jp.ne.test.share_xtension
MyShareExtension:
jp.ne.test.share_extension.MyShareExtension

(memo)
これはbuild時に
Embedded binary's bundle identifier is not prefixed with the parent app's bundle identifier.
と言われるため
https://qiita.com/koogawa/items/432b9c65035b6ba17c8b


#7 Runner/Info.plistを編集する

```Runner/Info.plist

<key>CFBundleURLTypes</key>
	<array>
		<dict>
			<key>CFBundleTypeRole</key>
			<string>Editor</string>
			<key>CFBundleURLSchemes</key>
			<array>
				<string>ShareMedia</string>
			</array>
		</dict>
		<dict/>
	</array>

  <key>NSPhotoLibraryUsageDescription</key>
	<string>To upload photos, please allow permission to access your photo library.</string>
```


##8.MyShareExtension/Info.plistを修正する

若干面倒くさいのが、ShareExtensionを利用したいアプリから、自分のアプリが見えるかどうかが
このinfo.plistの書き方によって、変わってくる。
例えば、primevideのアプリから、自分が作ったshare_extensionというアプリを開きたい場合、
下記のように自分のアプリが出てくることが期待されるが、ここに表示されないことがある。
この場合、info.plistのNSExtensionActivationRuleに記載が足りていないケースがある。
(期待されるRuleはアプリによって異なるため、自分が呼び出したアプリによって見直す必要がある)
debug.info.plistの事例のようにTRUEPREDICATEを指定すると、必ず出てくるようになるので、
最初の確認は、TRUEPREDICATEを指定すると良い。


![Image from iOS (1).png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43766/f3c7fb7e-48d3-9915-edbe-0fcd04819326.png)


```debug.info.plist

<key>NSExtension</key>
       <dict>
           <key>NSExtensionAttributes</key>
           <dict>
               <key>NSExtensionActivationRule</key>
               <string>TRUEPREDICATE</string>
           </dict>
           <key>NSExtensionMainStoryboard</key>
           <string>MainInterface</string>
           <key>NSExtensionPointIdentifier</key>
           <string>com.apple.share-services</string>
       </dict>

```

```service.info.plist

    <key>NSExtension</key>
    <dict>
        <key>NSExtensionAttributes</key>
        <dict>
            <key>NSExtensionActivationRule</key>
            <dict>
                <key>NSExtensionActivationSupportsText</key>
                <true/>
                <key>NSExtensionActivationSupportsWebPageWithMaxCount</key>
                <integer>1</integer>
                <key>NSExtensionActivationSupportsTextWithMaxCount</key>
                <integer>1</integer>
                <key>NSExtensionActivationSupportsWebURLWithMaxCount</key>
                <integer>1</integer>
            </dict>
        </dict>
        <key>NSExtensionMainStoryboard</key>
        <string>MainInterface</string>
        <key>NSExtensionPointIdentifier</key>
        <string>com.apple.share-services</string>
    </dict>
    

```

>間違えると..
>
>App Store Connect Operation Error
>ERROR ITMS-90362: "Invalid Info.plist value. The value for the key >'NSExtensionActivationRule' in bundle >Runner.app/PlugIns/OrquestShareExtension.appex is invalid.
>と怒られます..


##9.RunnerとMyShareExtensionを同じApp Groupに登録する

Signing & Capabilities -> +CapabilityボタンApp Groupsを選択。

![スクリーンショット 2020-08-14 15.50.27.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43766/d53173a0-9ab6-e559-d85a-c022551102dd.png)


App Groupsという項が表示されるので、Group名を入力する。(この画面の赤色になっている部分)
グループ名はgroup. + RunnerのBundlerIdentifier。
同じようにTARGETS/MyShareExtensionにもグループ名を設定する。

![スクリーンショット 2020-08-14 15.43.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43766/6ec9078b-81af-9f24-ffb7-9aae560f9599.png)

![スクリーンショット 2020-08-14 15.43.31.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43766/e017577a-6737-57da-939f-4dec94b4ea66.png)


##9.MyShareExtension/ShareViewController.swiftを修正する
今回はURLをシェアしたいだけなので最低限これだけでok。他にも使いたい場合は、
https://pub.dev/packages/receive_sharing_intent　
を参考にdidSelectPost()に条件を増やしていく。


```ShareViewController.swift

import UIKit
import Social
import MobileCoreServices
import Photos

class ShareViewController: SLComposeServiceViewController {
    let hostAppBundleIdentifier = "jp.ne.sakura.oggata.share-extension"
    let sharedKey = "ShareKey2"
    var sharedText: [String] = []
    let urlContentType = kUTTypeURL as String

    override func isContentValid() -> Bool {
        return true
    }

    override func viewDidLoad() {
    }
    
    override func didSelectPost() {
        if let content = extensionContext!.inputItems[0] as? NSExtensionItem {
            if let contents = content.attachments {
                for (index, attachment) in (contents).enumerated() {
                    if attachment.hasItemConformingToTypeIdentifier(urlContentType) {
                        handleUrl(content: content, attachment: attachment, index: index)
                    }
                }
            }
        }
    }

    private func handleUrl (content: NSExtensionItem, attachment: NSItemProvider, index: Int) {
        attachment.loadItem(forTypeIdentifier: urlContentType, options: nil) { [weak self] data, error in
            if error == nil, let item = data as? URL, let this = self {
                this.sharedText.append(item.absoluteString)
                if index == (content.attachments?.count)! - 1 {
                    let userDefaults = UserDefaults(suiteName: "group.\(this.hostAppBundleIdentifier)")
                    userDefaults?.set(this.sharedText, forKey: this.sharedKey)
                    userDefaults?.synchronize()
                    this.redirectToHostApp(type: .text)
                }
            }
        }
    }

    private func redirectToHostApp(type: RedirectType) {
        let url = URL(string: "ShareMedia://dataUrl=\(sharedKey)#\(type)")
        var responder = self as UIResponder?
        let selectorOpenURL = sel_registerName("openURL:")
        while (responder != nil) {
            if (responder?.responds(to: selectorOpenURL))! {
                let _ = responder?.perform(selectorOpenURL, with: url)
            }
            responder = responder!.next
        }
    }
    enum RedirectType {
        case media
        case text
        case file
    }
}
```

##10 pod install

```
cd ./ios
pod install
```


##10.Build Settings > Enable BitcodeをNO
![Slice.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/43766/eb1eda71-b585-bda9-1f08-f45b2aaee363.png)

実機にインストールするときに下記のようなエラーになる..
ld: 'ライブラリ名' does not contain bitcode. You must rebuild it with bitcode enabled (Xcode setting ENABLE_BITCODE)
https://qiita.com/imanishisatoshi/items/8b64af8262d7fe2eb2ba


##11. provisioning profileにAppGroupを付け加える



#Conclution
おしまい