経緯
React Naviveを使ったアプリで、Deep LinkやPush通知のペイロードに格納されたURLを解析したいのですが、有効な方法が見つからないのでiOS/Androidデバイスの(ネイティブ)ライブラリを使いそれらをパースするコードを実装しました。
方針
iOS, AndroidともにURLを解析するための似たAPIがあるので単純にそれらを呼び出します。
ネイティブコードを呼び出すので、この機能の戻り値はPromise経由で返すようにしています。
これらのAPIではクエリーパラメータについて、&で結合された文字列(式)だけが戻ってくるので、JS側でキーと値を抽出しています。
例外処理やエラー処理は適当です(状況に合わせて書き換えて下さい)。
完全なコード
こちらにあります。
https://github.com/flipfrog/react-native-url-parse
API
こんな感じで使用します。
import URLParse from './URLParse';
:
async componentDidMount(): void {
const url = await URLParse.parse(this.state.urlSpec);
this.setState({
protocol: url.protocol, // httpsとかのプロトコルスキーマ
host: url.host, // ホスト名
port: url.port, // ポート番号
path: url.path, // パス
query: url.query, // クエリーパラメータ
ref: url.ref, // インデックス
queryMap: url.queryMap, // クエリーパラメータのマップオブジェクト
});
}
import {NativeModules} from 'react-native';
export default class URLParse {
static async parse(urlSpec: string) {
const url = await NativeModules.RNURLParseModule.parse(urlSpec);
if (url && url.query) {
const expressions = url.query.split('&');
const queryMap = {};
for (let expression of expressions) {
const values = expression.split('=');
queryMap[values[0]] = (values[1] ? values[1] : null);
}
url['queryMap'] = queryMap;
}
return url;
}
}
iOS
iOSは、.hと.mファイル(Objective-Cを使いました)を作成するだけです。
#if __has_include(<React/RCTBridgeModule.h>)
#import <React/RCTBridgeModule.h>
#else
#import "RCTBridgeModule.h"
#endif
@interface RNURLParseModule : NSObject <RCTBridgeModule>
@end
#import "RNURLParseModule.h"
#if __has_include("RCTUtils.h")
#import "RCTUtils.h"
#else
#import <React/RCTUtils.h>
#endif
#import <Foundation/Foundation.h>
@implementation RNURLParseModule {
}
RCT_EXPORT_MODULE();
RCT_EXPORT_METHOD(parse:(NSString *)urlSpec
parseWithResolver:(RCTPromiseResolveBlock)resolve
rejecter:(RCTPromiseRejectBlock)reject) {
NSURL *url = [NSURL URLWithString:urlSpec];
if (url == NULL) {
NSDictionary *errorDic = @{
NSLocalizedDescriptionKey:@"Parse error",
NSLocalizedRecoverySuggestionErrorKey:@"Confirm parameter urlSpec."
};
NSError *error = [[NSError alloc] initWithDomain:@"org.reactjs.native.example.URLParseSample.parse"
code:-1 userInfo:errorDic];
reject(@"Parse error", @"Parse error", error);
} else {
NSDictionary *info = @{
@"protocol": [url scheme],
@"host": [url host],
@"port": [url port],
@"path": [url path],
@"query": [url query],
@"ref": [url fragment]
};
resolve(info);
}
}
@end
Android
Androidは、モジュールとパッケージ定義を作成して、MainApplication.javaでインスタンス化したパッケージを返すようにします。
package com.urlparsesample.extension;
import java.net.URL;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import java.util.HashMap;
import java.util.Map;
// package private class
class RNURLParseModule extends ReactContextBaseJavaModule {
private static final String E_URL_PARSE_ERROR = "URL Parse error";
RNURLParseModule(ReactApplicationContext context) {
super(context);
}
@Override
public String getName() {
return "RNURLParseModule";
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("IsAndroid", true);
return constants;
}
@ReactMethod
public void parse(final String urlSpec, Promise promise) {
WritableMap info = Arguments.createMap();
try {
URL url = new URL(urlSpec);
info.putString("protocol", url.getProtocol());
info.putString("host", url.getHost());
info.putString("path", url.getPath());
info.putInt("port", url.getPort());
info.putString("query", url.getQuery());
info.putString("ref", url.getRef());
promise.resolve(info);
} catch (Exception e) {
promise.reject(E_URL_PARSE_ERROR, e);
}
}
}
package com.urlparsesample.extension;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
public class RNURLParsePackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules (ReactApplicationContext context) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new RNURLParseModule(context));
return modules;
}
// Deprecated RN 0.47
// @Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext context) {
return Collections.emptyList();
}
}
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage(),
new RNURLParsePackage() // ここに追加します
);
}
実行結果
先に記載したGitHubのコードを実行すると下記のように結果を表示します。