
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
