最初に
最近ReactNativeでexpoを使用して、開発する案件が増えているような気がしています。
ReactNativeアプリで以下のようなことしたいと思ったことはないでしょうか?
- Webブラウザからリンクで直接アプリを開きたい。
- アプリにpush通知を送った際に、push通知をタップされると任意のアプリのページに飛ばしたい。
上記のことはアプリ内にリンクを貼ることで実現できます。
今回はReactNative(expo)でのアプリ開発で重要であるアプリ内でのLinkの貼り方について解説します。
今回の目的
ReactNativeの画面の移動の方法としては、色々とあると思いますが、今回はReactNativeが公式で推奨している画面の移動方法のReactNavigationとexpo-linkingというライブラリを使用してリンク移動できるようにします。
環境構築
$ expo init expo-tutorial
✔ Choose a template: › tabs (TypeScript) several example screens and tabs using react-navigation and TypeScript
✔ Downloaded and extracted project files.
$ cd expo-tutorial
$ npm i
$ npm start
環境構築ですが、expo init
を使用して新しいプロジェクトを作成します。
Choose a template
で色々と選択できるのですが、この際に上から3番目のtabs (TypeScript)
を選択してください。
このtemplateを選択することで、すでにReactNavigationの設定及びLinkingの設定がされています。
npm start
を実行するとブラウザでexpoが立ち上がり、QRコードからアプリを実行できます。
bottomTab
でTabOne
からTabTwo
へページの移動が移動できます。
アプリの解説
公式がすでにLinkまで貼ったアプリを作成してくれているので、
このアプリはどのような構造になっているのか解説することでLinkについて理解していきます。
Navigationの構造
まず、今回のNavigationの構造は以下のようになっています。
RootNavigationの中にBottomNavigationがあり、その中にさらにTabOneStackNavigation及びTabTwoStackNavigationが含まれています。
そしてそのTabOne 及び TabTwo StackNavigationの下に初めてTabOne及びTabTwoScreenがあります。
そのため、ユーザがTabOneScreenにアクセスするには次のフローを辿ります。
RootNavigation → BottomNavigation → TabOneStackNavigaiton → TabOneScreen
実際のコードを確認したいと思います。
必要なコードだけ一部抜粋して記載します。
主にNavigationのcomponentとname引数に注目してください。
nameはnavigationでページ移動する際に指定するものです。
例えば今回の場合は、
navigation.navigate("TabOneScreen")
と記述するとTabOneScreenに移動できたりします。
RootNavigation (navigation/index.ts)
function RootNavigator() {
return (
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="Root" component={BottomTabNavigator} />
<Stack.Screen name="NotFound" component={NotFoundScreen} options={{ title: 'Oops!' }} />
</Stack.Navigator>
);
}
RootNavigationでBottomTabNavigation
とNotFoundScreen
への分岐があります。
BottomTabNavigation
のnameはRoot
NotFoundScreen
のnameはNotFound
BottomNavigation (navigation/BottomTabNavigation.tsx)
export default function BottomTabNavigator() {
const colorScheme = useColorScheme();
return (
<BottomTab.Navigator
initialRouteName="TabOne"
tabBarOptions={{ activeTintColor: Colors[colorScheme].tint }}>
<BottomTab.Screen
name="TabOne"
component={TabOneNavigator}
options={{
tabBarIcon: ({ color }) => <TabBarIcon name="ios-code" color={color} />,
}}
/>
<BottomTab.Screen
name="TabTwo"
component={TabTwoNavigator}
options={{
tabBarIcon: ({ color }) => <TabBarIcon name="ios-code" color={color} />,
}}
/>
</BottomTab.Navigator>
);
}
次にBottomNavigator
からTabOneNavigator
とTabTwoNavigator
へ分岐があります。
TabOneNavigator
のnameはTabOne
TabTwoNavigator
のnameはTabTwo
initialRootName
にTabOne
とあるので、defaultでBottomTabNavigatorまで到達した場合は、
TabOneNavigator
に処理が渡ります。
TabOneStackNavigation (navigation/BottomTabNavigation.tsx)
function TabOneNavigator() {
return (
<TabOneStack.Navigator>
<TabOneStack.Screen
name="TabOneScreen"
component={TabOneScreen}
options={{ headerTitle: 'Tab One Title' }}
/>
</TabOneStack.Navigator>
);
}
最後にTabOneNavigator
からTabOneScreen
に処理が渡り、TabOneScreen
が表示されます。
TabOneScreen
のnameはTabOneScreen
TabTwoStackNavigation (navigation/BottomTabNavigation.tsx)
function TabTwoNavigator() {
return (
<TabTwoStack.Navigator>
<TabTwoStack.Screen
name="TabTwoScreen"
component={TabTwoScreen}
options={{ headerTitle: 'Tab Two Title' }}
/>
</TabTwoStack.Navigator>
);
}
TabOneNavigatorと同じ挙動を行います。
また、RootNavigationの中にはNotFoundScreenというものが存在しますが、
このScreenは普通にアプリを使用する分には、到達できないScreenとなっています。
詳しくはLinkのところで解説します。
リンクの構造
次にリンクの構造について解説します。
リンクについての記載がされているのは次のファイルになります。
navigation/LinkingConfiguration.ts
import * as Linking from 'expo-linking';
export default {
prefixes: [Linking.makeUrl('/')],
config: {
screens: {
Root: {
screens: {
TabOne: {
screens: {
TabOneScreen: 'one',
},
},
TabTwo: {
screens: {
TabTwoScreen: 'two',
},
},
},
},
NotFound: '*',
},
},
};
prefixes
というのはアプリまでのURL(カスタムスキーマなどを含む)を記述するのですが、
Linking.makeUrl
で現在のアプリの/
までのURLを自動で作成してくれます。
※ Expo内で動作しているアプリやスタンドアロンで動いているアプリなど、環境によって、URLが変わってくるので、expoに頼って自動で作成するようにしましょう。
config
にリンクの内容を記載しています。
よく観察すると、リンクの内容はNavigationと類似していることがわかります。
最初の
screens
にRoot
とNotFound
があり、
Root
内のscreens
にTabOne
とTabTwo
があり、
TabOne
内にTabOneScreen
が記載されています。
Navigationにリンクを与える際は、Linkの内容とNavigationの内容が同期している必要があります。
screens
にはNavigatorのcomponent名ではなくname名を記載します。
pathの記載
TabOneScreen:'one'
とありますが、これがpathです。
Linking.makeUrl('/') + "one"
でアプリ内でアクセスできます。
Expoで動作している場合は、次のURLをアプリ内のブラウザに入力してみてください。
exp://127.0.0.1:19001/--/one
もしくは、ターミナルから以下のコマンドを実行でも動作確認できます。(実機では確認不可)
npx uri-scheme open exp://127.0.0.1:19001/--/one --ios #andoridの場合は--androidを実行
pathのネストをする際は次のように記載します。
export default {
prefixes: [Linking.makeUrl('/')],
config: {
screens: {
Root: {
path:"bottomTab",
screens: {
TabOne: {
screens: {
TabOneScreen: 'one',
},
},
TabTwo: {
screens: {
TabTwoScreen: 'two',
},
},
},
},
NotFound: '*',
},
},
};
次の場合はexp://127.0.0.1:19001/--/bottom/one
でアクセスできます。
リンクからデータを渡したいなどは、今回は説明しません。
次の公式ページを参考にしてください。
https://reactnavigation.org/docs/configuring-links/
https://docs.expo.io/guides/linking/#passing-data-to-your-app-through-the
リンク先が見つからない時
リンクで移動する場合は、リンク先が見つからない場合も考慮する必要があります。
そのための記述がNotFound:'*'
です。
この記述を用意することで、リンク先が見つからなかった場合は、このNotFound
が呼びだされ
NotFoundScreen
が表示されます。
Navigationにpathのconfigファイルを設定
上記で作成したPathを設定したファイルをNavigationに設定する必要があります。
それは以下のようにNavigationContainer
のlinking
に先程作成したファイルを指定することで実現できます。
navigation/index.ts
import LinkingConfiguration from './LinkingConfiguration'; //pathの設定ファイル
export default function Navigation({ colorScheme }: { colorScheme: ColorSchemeName }) {
return (
<NavigationContainer
linking={LinkingConfiguration} // ここでNavigationに設定する
theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
<RootNavigator />
</NavigationContainer>
);
}
補足情報
ちょっと便利な情報を記載します。
LinkingConfigurationの型定義
デフォルトで作成されたものでは型定義が聞いていないため、使いにくいので、
pathのconfigファイルにも型定義を与え挙げると次のようなものになります。
navigation/LinkingConfiguration.ts
import * as Linking from 'expo-linking';
import { LinkingOptions } from "@react-navigation/native";
const LinkingConfiguration:LinkingOptions = {
prefixes: [Linking.makeUrl('/')],
config: {
screens: {
Root: {
screens: {
TabOne: {
screens: {
TabOneScreen: 'one',
},
},
TabTwo: {
screens: {
TabTwoScreen: 'two',
},
},
},
},
NotFound: '*',
},
},
};
export default LinkingConfiguration
notificationとの連携
通知をタップしたときに好きなscreenにジャンプさせたいと思うときは
以下のように設定すると実行できます。
import * as Linking from 'expo-linking';
import { LinkingOptions } from "@react-navigation/native";
const LinkingConfiguration:LinkingOptions = {
prefixes: [Linking.makeUrl('/')],
subscribe(listener) {
const onReceiveURL = ({ url }: { url: string }) => {
console.log(url);
listener(url);
};
Linking.addEventListener("url", onReceiveURL);
const notificationClickedListener = Notifications.addNotificationResponseReceivedListener(
(response) => {
/**
* 通知をクリックした際の処理
* dataのurlを取得する。
* 例: data:{"url":"notification"}
*/
const { url } = response.notification.request.content.data;
if (typeof url === "string") {
//redirectUrlを取得
const redirectUrl = Linking.createURL(url);
Linking.openURL(redirectUrl);
return;
}
// rootにリダイレクト
const redirectUrl = Linking.createURL("/");
Linking.openURL(redirectUrl);
}
);
return () => {
/**
* 後処理
*/
Linking.removeEventListener("url", onReceiveURL);
notificationClickedListener.remove();
};
},
config: {
screens: {
Root: {
screens: {
TabOne: {
screens: {
TabOneScreen: 'one',
},
},
TabTwo: {
screens: {
TabTwoScreen: 'two',
},
},
},
},
NotFound: '*',
},
},
};
export default LinkingConfiguration
詳細については説明しませんが、push通知時に指定できるdata
にJSON形式で{"url":"one"}
といった感じで入力すると
通知をタップした際にTabOneScreen
に移動することができます。
詳細は以下をご覧ください。
https://docs.expo.io/versions/latest/sdk/notifications/#handling-push-notifications-with-react-navigation
通知のテストは以下から簡単にできます。tokenの発行が入ります。
https://expo.io/notifications
token発行について
https://docs.expo.io/versions/latest/sdk/notifications/#fetching-tokens-for-push-notifications