【Flutter】テキストファイルを共有で受け取れなかった話

## 概要
flutter_sharing_intentを使用して他のアプリの共有からファイルを受け取ろうとしたところ、テキストファイルを受け取ることができませんでした。
```dart
// アプリ終了中にアプリの外から来た画像を共有する場合
FlutterSharingIntent.instance
    .getInitialSharing()
    .then((List<SharedFile> value) {
  setState(() {
    list = value; //テキストファイルを共有するとなぜかvalue.length=0になる
  });
});
```
試した結果、次のことがわかりました。
* 画像ファイルは受け取ることができる
* ブラウザ等で文字列を選択して共有すると受け取ることができる

## 結論
flutter_sharing_intentの不具合でした。
[Workaround for Text Sharing Bug (#50) #51](https://github.com/bhagat-techind/flutter_sharing_intent/pull/51)

2024年01月08日現在、pub.devは最新版でないため、GitHubから取得するように pubspec.yaml の dependencies を修正しました。
修正後に`flutter clean`コマンドを実行してからアプリを起動すると無事テキストファイルが受信できるようになりました。
```yaml:pubspec.yaml
dependencies:
  flutter:
    sdk: flutter
  flutter_sharing_intent:
    git:
      url: https://github.com/bhagat-techind/flutter_sharing_intent.git
      ref: main
```

## ハマったこと
### GitHubからの取得方法
GitHubからライブラリを取得する方法についてChatGPTに聞いたところ、次の記載方法を示されましたが、`flutter pub get`を行うとエラーが発生しました。
```yaml:pubspec.yaml
dependencies:
  widget_name:
    git:
      url: git://github.com/username/repo_name.git
```

```:エラーメッセージ
[testsharing] flutter pub get --no-example
Resolving dependencies...
Git error. Command: `git clone --mirror git://github.com/bhagat-techind/flutter_sharing_intent.git C:\Users\user\AppData\Local\Pub\Cache\git\cache\flutter_sharing_intent-ca3440aaf546092bc21ebdcbf20cfcce74330aa5`
stdout: 
stderr: Cloning into bare repository 'C:\Users\user\AppData\Local\Pub\Cache\git\cache\flutter_sharing_intent-ca3440aaf546092bc21ebdcbf20cfcce74330aa5'...
fatal: unable to connect to github.com:
github.com[0: 20.27.177.113]: errno=Invalid argument

exit code: 128
exit code 69
```

ChatGPTによるとファイアウォールがGitの通信をブロックしている可能性があるということです。そのため、次の記載に変更しました。
```yaml:pubspec.yaml
dependencies:
  widget_name:
    git:
      url: https://github.com/username/repo_name.git
```
これにより`flutter pub get`が正常に実行できるようになりました。


### ビルドエラーが発生
ところがビルドすると次のエラーが発生しました。
```
Launching lib\main.dart on Android SDK built for x86 in debug mode...
e: C:\Users\user\AppData\Local\Pub\Cache\git\flutter_sharing_intent-5f4f2e66c02580105ba89b3fec5a7be907d5466f\android\src\main\kotlin\com\techind\flutter_sharing_intent\FlutterSharingIntentPlugin.kt: (35, 7): Redeclaration: FlutterSharingIntentPlugin
e: C:\Users\user\AppData\Local\Pub\Cache\git\flutter_sharing_intent-5f4f2e66c02580105ba89b3fec5a7be907d5466f\android\src\main\kotlin\com\techind\flutter_sharing_intent\MyFileDirectory.kt: (24, 8): Redeclaration: MyFileDirectory
e: C:\Users\user\AppData\Local\Pub\Cache\hosted\pub.dev\flutter_sharing_intent-1.1.0\android\src\main\kotlin\com\techind\flutter_sharing_intent\FlutterSharingIntentPlugin.kt: (35, 7): Redeclaration: FlutterSharingIntentPlugin
FAILURE: Build failed with an exception.

* What went wrong:
Execution failed for task ':flutter_sharing_intent:compileDebugKotlin'.
> A failure occurred while executing org.jetbrains.kotlin.compilerRunner.GradleCompilerRunnerWithWorkers$GradleKotlinCompilerWorkAction
   > Compilation error. See log for more details
* Try:
> Run with --stacktrace option to get the stack trace.
> Run with --info or --debug option to get more log output.
> Run with --scan to get full insights.

* Get more help at https://help.gradle.org

BUILD FAILED in 13s
Exception: Gradle task assembleDebug failed with exit code 1
```
ChatGPTによると
>これは、Flutterプロジェクトで重複した定義が見つかったことを示しています。エラーのメッセージにあるように、FlutterSharingIntentPlugin と MyFileDirectory のクラスが複数の場所で再宣言されているようです。

とのことで、その後の指示通りに`flutter clean`を実行し、再度ビルドしたところビルドが正常に行えるようになりました。

## ソース
Androidのみ対応です。
```dart:main.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter_sharing_intent/flutter_sharing_intent.dart';
import 'package:flutter_sharing_intent/model/sharing_file.dart';

void main() {
  runApp(const MyApp());
}

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

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  late StreamSubscription _intentDataStreamSubscription;
  List<SharedFile>? list;

  @override
  void initState() {
    super.initState();
    // アプリがメモリ内にあるときにアプリ外から来た画像を共有する場合
    _intentDataStreamSubscription = FlutterSharingIntent.instance
        .getMediaStream()
        .listen((List<SharedFile> value) {
      setState(() {
        list = value;
      });
    }, onError: (err) {
      print('getIntentDataStream error: $err');
    });

    // アプリ終了中にアプリの外から来た画像を共有する場合
    FlutterSharingIntent.instance
        .getInitialSharing()
        .then((List<SharedFile> value) {
      setState(() {
        list = value;
      });
    });
  }

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

  @override
  Widget build(BuildContext context) {
    final sharingData = list?.firstOrNull?.value;

    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Plugin example app'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: list != null //sharingData != null
              //? Image.file(File(sharingData))
              ? Text(sharingData ?? 'なし')
              : const Text('未選択'),
        ),
      ),
    );
  }
}
```

```yaml:pubspec.yaml
name: testsharing
description: "A new Flutter project."
# The following line prevents the package from being accidentally published to
# pub.dev using `flutter pub publish`. This is preferred for private packages.
publish_to: 'none' # Remove this line if you wish to publish to pub.dev

# The following defines the version and build number for your application.
# A version number is three numbers separated by dots, like 1.2.43
# followed by an optional build number separated by a +.
# Both the version and the builder number may be overridden in flutter
# build by specifying --build-name and --build-number, respectively.
# In Android, build-name is used as versionName while build-number used as versionCode.
# Read more about Android versioning at https://developer.android.com/studio/publish/versioning
# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix.
version: 1.0.0+1

environment:
  sdk: '>=3.2.3 <4.0.0'

# Dependencies specify other packages that your package needs in order to work.
# To automatically upgrade your package dependencies to the latest versions
# consider running `flutter pub upgrade --major-versions`. Alternatively,
# dependencies can be manually updated by changing the version numbers below to
# the latest version available on pub.dev. To see which dependencies have newer
# versions available, run `flutter pub outdated`.
dependencies:
  flutter:
    sdk: flutter
  flutter_sharing_intent:
    git:
      url: https://github.com/bhagat-techind/flutter_sharing_intent.git
      ref: main


  # The following adds the Cupertino Icons font to your application.
  # Use with the CupertinoIcons class for iOS style icons.
  cupertino_icons: ^1.0.2

dev_dependencies:
  flutter_test:
    sdk: flutter

  # The "flutter_lints" package below contains a set of recommended lints to
  # encourage good coding practices. The lint set provided by the package is
  # activated in the `analysis_options.yaml` file located at the root of your
  # package. See that file for information about deactivating specific lint
  # rules and activating additional ones.
  flutter_lints: ^2.0.0

# For information on the generic Dart part of this file, see the
# following page: https://dart.dev/tools/pub/pubspec

# The following section is specific to Flutter packages.
flutter:

  # The following line ensures that the Material Icons font is
  # included with your application, so that you can use the icons in
  # the material Icons class.
  uses-material-design: true

  # To add assets to your application, add an assets section, like this:
  # assets:
  #   - images/a_dot_burr.jpeg
  #   - images/a_dot_ham.jpeg

  # An image asset can refer to one or more resolution-specific "variants", see
  # https://flutter.dev/assets-and-images/#resolution-aware

  # For details regarding adding assets from package dependencies, see
  # https://flutter.dev/assets-and-images/#from-packages

  # To add custom fonts to your application, add a fonts section here,
  # in this "flutter" section. Each entry in this list should have a
  # "family" key with the font family name, and a "fonts" key with a
  # list giving the asset and other descriptors for the font. For
  # example:
  # fonts:
  #   - family: Schyler
  #     fonts:
  #       - asset: fonts/Schyler-Regular.ttf
  #       - asset: fonts/Schyler-Italic.ttf
  #         style: italic
  #   - family: Trajan Pro
  #     fonts:
  #       - asset: fonts/TrajanPro.ttf
  #       - asset: fonts/TrajanPro_Bold.ttf
  #         weight: 700
  #
  # For details regarding fonts from package dependencies,
  # see https://flutter.dev/custom-fonts/#from-packages
```

```xml:android\app\src\main\AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:label="testsharing"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:launchMode="singleInstance"
            android:theme="@style/LaunchTheme"
            android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
            android:hardwareAccelerated="true"
            android:windowSoftInputMode="adjustResize">
            <!-- Specifies an Android theme to apply to this Activity as soon as
                 the Android process has started. This theme is visible to the user
                 while the Flutter UI initializes. After that, this theme continues
                 to determine the Window background behind the Flutter UI. -->
            <meta-data
              android:name="io.flutter.embedding.android.NormalTheme"
              android:resource="@style/NormalTheme"
              />
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
            
            <!--追加-->
            <intent-filter>
                <action android:name="android.intent.action.SEND" />
                <category android:name="android.intent.category.DEFAULT" />
                <data android:mimeType="text/*" />
            </intent-filter>
        </activity>
        <!-- Don't delete the meta-data below.
             This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
        <meta-data
            android:name="flutterEmbedding"
            android:value="2" />
    </application>
</manifest>
```
ちなみに、共有時にすでにアプリが立ち上がっている場合に新しいインスタンスが起動しないようにするため、`android:launchMode="singleInstance"`に変更しています。

## 開発環境
### Flutter
Doctor summary (to see all details, run flutter doctor -v):
[√] Flutter (Channel stable, 3.16.3, on Microsoft Windows [Version 10.0.19045.3803], locale ja-JP)
[√] Windows Version (Installed version of Windows is version 10 or higher)
[√] Android toolchain - develop for Android devices (Android SDK version 34.0.0)
[√] Chrome - develop for the web
[√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.8.3)
[√] Android Studio (version 2022.3)
[√] Connected device (5 available)
[√] Network resources

### デバイス
Pixel 7 API 27 Android8.1 ("Oreo")