見出し画像

React Native での共有ファイルの受信

「React Native」での共有データの受信の手順をまとめました。


前回

1. React Native での共有ファイルの受信

「React Native」の共有ファイルの受信で良いパッケージが見つからなかったので、ネイティブで受信してアプリ内フォルダに保存し、「React Native」のフォアグラウンド復帰で読み込む方法で実装します。

1-1. セットアップ

(1) React Nativeプロジェクトの生成。

npx react-native init my_app
cd my_app

(2) パッケージのインストール。
アプリ内フォルダのファイル読み書き用に「react-native-fs」をインストールします。

npm install react-native-fs

1-2. Androidのセットアップ

(1) Androidプロジェクトの「AndroidManifest.xml」に以下の設定を追加。
今回は共有ファイル種別として「画像」を設定しています。

・AndroidManifest.xml

<manifest xmlns:android="http://schemas.android.com/apk/res/android">

    <uses-permission android:name="android.permission.INTERNET" />

    <application
      android:name=".MainApplication"
      android:label="@string/app_name"
      android:icon="@mipmap/ic_launcher"
      android:roundIcon="@mipmap/ic_launcher_round"
      android:allowBackup="false"
      android:theme="@style/AppTheme"
      android:supportsRtl="true">
      <activity
        android:name=".MainActivity"
        android:label="@string/app_name"
        android:configChanges="keyboard|keyboardHidden|orientation|screenLayout|screenSize|smallestScreenSize|uiMode"
        android:launchMode="singleTask"
        android:windowSoftInputMode="adjustResize"
        android:exported="true">
        <intent-filter>
            <action android:name="android.intent.action.MAIN" />
            <category android:name="android.intent.category.LAUNCHER" />
        </intent-filter>

        <!--TODO: 共有データを受信するためのフィルタを追加-->
        <intent-filter>
            <action android:name="android.intent.action.SEND" />
            <category android:name="android.intent.category.DEFAULT" />
            <data android:mimeType="image/*" /> <!--TODO: 共有ファイル種別を指定-->
        </intent-filter>

      </activity>
    </application>
</manifest>

(2) Androidプロジェクトの 「MainActivity.kt」に以下のコードを追加。

・MainActivity.kt

  // 通常起動時に呼ばれる
  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    handleIncomingIntent(intent)
  }

  // 共有起動時に呼ばれる
  override fun onNewIntent(intent: android.content.Intent) {
    super.onNewIntent(intent)
    handleIncomingIntent(intent)
  }

  // Intentの処理
  private fun handleIncomingIntent(intent: android.content.Intent?) {
    if (intent?.action == android.content.Intent.ACTION_SEND && intent.type != null) {
      val uri: Uri? = intent.getParcelableExtra(android.content.Intent.EXTRA_STREAM)
      uri?.let {
        copyFileContent(it)
      }
    }
  }

  // ファイルのコピー
  private fun copyFileContent(uri: Uri) {
    Log.d("debug", "uri>>>$uri")
    try {
      val inputStream = contentResolver.openInputStream(uri)
      if (inputStream != null) {
        val destFile = File(filesDir, "share.dat")
        val outputStream = FileOutputStream(destFile)
        inputStream.use { input ->
          outputStream.use { output ->
            input.copyTo(output)
          }
        }
        Log.d("debug", "file>>>${destFile.absolutePath}")
      }
    } catch (e: Exception) {
      Log.e("debug", "error>>>", e)
    }
  }

1-3. iOSのセットアップ

(1) podのインストール。

cd ios
pod install
cd ..

(2) Xcodeで生成されたxcworkspaceを開き、署名を行い、iOSにインストールできることを確認。

(3) iOSプロジェクトの「Info.plist」に以下の設定を追加。

・Info.plist

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>

    <!--TODO: 共有データを受信するためのフィルタを追加-->
    <key>CFBundleDocumentTypes</key>
    <array>
        <dict>
            <key>CFBundleTypeName</key>
            <string>Share Data</string>
            <key>LSHandlerRank</key>
            <string>Alternate</string>
            <key>LSItemContentTypes</key>
            <array>
                <string>public.image</string> <!--TODO: 共有ファイル種別を指定-->
            </array>
        </dict>
    </array>
        :

(4) iOSプロジェクトの「AppDelegate.mm」に以下のコードを追加。

// 共有起動時に呼ばれる
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
    NSLog(@"Received file URL: %@", url.absoluteString);

    [self copyFileContent:url];

    return YES;
}

// ファイルのコピー
- (void)copyFileContent:(NSURL *)fileURL {
    NSFileManager *fileManager = [NSFileManager defaultManager];

    // URLの準備
    NSArray<NSURL *> *directories = [fileManager URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask];
    if (directories.count == 0) return;
    NSURL *documentsDirectory = directories.firstObject;
    NSURL *destinationURL = [documentsDirectory URLByAppendingPathComponent:@"share.dat"];

    // 既存ファイルを削除
    NSError *error;
    if ([fileManager fileExistsAtPath:destinationURL.path]) {
        [fileManager removeItemAtURL:destinationURL error:&error];
    }

    // ファイルのコピー
    [fileManager copyItemAtURL:fileURL toURL:destinationURL error:&error];
    if (error) {
        NSLog(@"error>>> %@", error.localizedDescription);
    }
}

1-3. React Native のコードの編集と実行

(1) 「React Native」のコードの編集。

・App.tsx

import React, { useEffect, useRef, useState } from 'react';
import { Image, View, Text } from 'react-native';
import { AppState } from 'react-native';
import RNFS from 'react-native-fs';

// アプリ
const App = () => {
  const activeRef = useRef<boolean>(false);
  const [imageData, setImageData] = useState<string | null>(null);

  // 初期化
  useEffect(() => {
    // アプリのフォアグラウンド・バックグラウンド切替時に呼ばれる
    const onActive = async (active: boolean) => {
      if (active) {
        // 共有ファイルの取得
        try {
          const filePath = `${RNFS.DocumentDirectoryPath}/share.dat`
          const fileExists = await RNFS.exists(filePath);
          if (fileExists) {
            const base64Data = await RNFS.readFile(filePath, 'base64');
            setImageData(base64Data);
          } else {
            console.log('share>>> none');
          }
        } catch (error: any) {
          console.log('error>>>', error);
        }
      }
    }

    // アプリのフォアグラウンド・バックグラウンド切替時のリスナー指定
    const subscription = AppState.addEventListener('change', nextAppState => {
      const nextActive = (nextAppState == 'active');
      if (activeRef.current != nextActive) {
        activeRef.current = nextActive;
        onActive(activeRef.current);
      }
    });
    return () => subscription.remove();
  }, []);

  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      {imageData ? (
        <Image
          source={{ uri: `data:image/png;base64,${imageData}` }}
          style={{ width: 300, height: 300 }}
        />
      ) : (<Text>共有ファイルなし</Text>)}
    </View>
  );
};

export default App;

(2) 実行。

npm start




いいなと思ったら応援しよう!