В предыдущем посте я продемонстрировал, как загружать модули React Native с кодом, реализованным на Objective C и Java. Давайте расширим это, добавим внешнюю библиотеку и скомпилируем ее. Давайте воспользуемся библиотекой AWS для iOS и Android. Amazon выполнила частичный переход на React Native, поэтому некоторые библиотеки все еще необходимо перенести. Один из них - AWS Cognito Identity. Цель этого упражнения - иметь возможность зарегистрироваться с новой парой учетных данных на платформе iOS и Android, при этом часть кода javascript будет идентична!
Предполагается, что вы выполнили шаги из предыдущего поста до того, как попали сюда. Также предполагается, что вы уже создали пул пользователей в регионе EU-WEST.
iOS
Сначала мы добавляем фреймворки iOS. Скачайте SDK для iOS. Нам нужны только файлы .framework, относящиеся к Cognito, включая библиотеку Core. Найдите их и скопируйте в новую папку под названием Frameworks в папке ios компонента react-native-kickass.
Файлы Framework содержат заголовки и двоичные файлы. Нам нужно сделать их известными в проекте. Нам нужно добавить два элемента:
- Файлы .h должны быть найдены нашим модулем
- Бинарные файлы .framework должны быть встроены в скомпилированный двоичный файл.
Начнем с 1. Адаптируйте путь поиска Framework к модулю как $ (SRCROOT) / Frameworks.
Также добавьте файлы фреймворка в приложение как $ (PROJECT_DIR) /../ node_modules / response-native-kickass-component / ios / Frameworks.
Шаг 2: Добавьте встроенные двоичные файлы:
response-native-kickass-component / ios / RNKickassComponent.h (жирный шрифт - новый)
#import "RCTBridgeModule.h" #import <AWSCore/AWSCore.h> #import <AWSCognitoIdentityProvider/AWSCognitoIdentityProvider.h> #import <AWSCore/AWSTask.h> #import <AWSCore/AWSService.h> @interface RNKickassComponent : NSObject <RCTBridgeModule> @end
response-native-kickass-component / ios / RNKickassComponent.m (жирный шрифт - новый)
#import "RNKickassComponent.h" @implementation RNKickassComponent @synthesize methodQueue = _methodQueue; typedef void (^ Block)(id, int); - (dispatch_queue_t)methodQueue { return dispatch_get_main_queue(); } RCT_EXPORT_MODULE(RNKickassComponent) -(instancetype)init{ self = [super init]; if (self) { [AWSServiceConfiguration addGlobalUserAgentProductToken:[NSString stringWithFormat:@"react-native-kickass-component/0.0.1"]]; } return self; } RCT_EXPORT_METHOD(concatStr:(NSString *)string1 secondString:(NSString *)string2 resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { resolve([NSString stringWithFormat:@"%@ %@", string1, string2]); } RCT_EXPORT_METHOD(signUp:(NSString *)region userPoolId:(NSString *)userPoolId clientId:(NSString *)clientId email:(NSString *)email password:(NSString *)password resolver:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject) { // Normally, you would do this only once, but for sake of example: AWSServiceConfiguration *configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionEUWest1 credentialsProvider:nil]; [configuration addUserAgentProductToken:@"AWSCognitoIdentity"]; [AWSServiceManager defaultServiceManager].defaultServiceConfiguration = configuration; AWSCognitoIdentityUserPoolConfiguration *userPoolConfiguration = [[AWSCognitoIdentityUserPoolConfiguration alloc] initWithClientId:clientId clientSecret:nil poolId:userPoolId]; [AWSCognitoIdentityUserPool registerCognitoIdentityUserPoolWithConfiguration:configuration userPoolConfiguration:userPoolConfiguration forKey:@"UserPool"]; AWSCognitoIdentityUserPool *userPool = [AWSCognitoIdentityUserPool CognitoIdentityUserPoolForKey:@"UserPool"]; // Now signup: AWSCognitoIdentityUserAttributeType * emailAttribute = [AWSCognitoIdentityUserAttributeType new]; emailAttribute.name = @"email"; emailAttribute.value = email; //start a separate thread for this to avoid blocking the component queue, since //it will have to comunicate with the javascript in the mean time while trying to signup NSString* queueName = [NSString stringWithFormat:@"%@.signUpAsyncQueue", [NSString stringWithUTF8String:dispatch_queue_get_label(self.methodQueue)] ]; dispatch_queue_t concurrentQueue = dispatch_queue_create([queueName UTF8String], DISPATCH_QUEUE_CONCURRENT); dispatch_async(concurrentQueue, ^{ [[userPool signUp:email password:password userAttributes:@[emailAttribute] validationData:nil] continueWithBlock:^id _Nullable(AWSTask<AWSCognitoIdentityUserPoolSignUpResponse *> * _Nonnull task) { if (task.exception){ reject([NSString stringWithFormat:@"Exception "],task.exception.reason, [[NSError alloc] init]); } if (task.error) { reject([NSString stringWithFormat:@"%ld",task.error.code],task.error.description,task.error); } else { // Return the username as registered with Cognito. resolve(task.result.user.username); } return nil; }]; }); } @end
ПРИМЕЧАНИЕ: если вы хотите опубликовать не только response-native-kickass-component, но и сам ReactNativeKickassApp, вам потребуются небольшие изменения (поскольку XCode всегда отменяет ссылки на ссылки, созданные с помощью npm link) :
- Добавьте компонент response-native-kickass в package.json. Очевидно, вам сначала нужно будет развернуть компонент response-native-kickass в npm.
- Закройте графический интерфейс XCode.
- Запустите редактор и откройте ios / ReactNativeKickassApp.xcodeproj / project.pbxproj. Обратите внимание, что файл проекта находится внутри файла xcodeproj, и вам может потребоваться перейти через командную строку, чтобы отредактировать его, поскольку он не отображается в Finder.
- Найдите раздел под названием / * Begin PBXFileReference section * / и отредактируйте каждую строку, которая содержит ../ .. на своем пути, и измените ее на ../node_modules, например:
path = «../../react-native-kickass-component/ios/build/Debug-iphoneos/libRNKickassComponent.a»
в
path = «../node_modules/react-native-kickass-component/ios/build/Debug-iphoneos/libRNKickassComponent.a»
и сохраните файл.
Когда вы снова откроете XCode, вы должны увидеть путь относительно ../node_modules вместо ../ .., как это будет в развернутой версии.
Android
Нам нужно добавить некоторые зависимости компиляции в response-native-kickass-component / android / build.gradle (выделено новым жирным шрифтом):
dependencies { compile 'com.facebook.react:react-native:0.20.+' compile 'com.amazonaws:aws-android-sdk-core:2.2.+' compile 'com.amazonaws:aws-android-sdk-cognitoidentityprovider:2.2.+' }
response-native-kickass-component / src / main / java / com / reactlibrary / RNKickassComponentModule / java (жирный шрифт - новый):
package com.reactlibrary; import com.amazonaws.ClientConfiguration; import com.amazonaws.auth.AnonymousAWSCredentials; import com.amazonaws.regions.Region; import com.amazonaws.regions.Regions; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.Promise; import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserPool; import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserAttributes; import com.amazonaws.mobileconnectors.cognitoidentityprovider.handlers.SignUpHandler; import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUser; import com.amazonaws.mobileconnectors.cognitoidentityprovider.CognitoUserCodeDeliveryDetails; import com.amazonaws.services.cognitoidentityprovider.AmazonCognitoIdentityProviderClient; public class RNKickassComponentModule extends ReactContextBaseJavaModule { private final ReactApplicationContext reactContext; public RNKickassComponentModule(ReactApplicationContext reactContext) { super(reactContext); this.reactContext = reactContext; } @Override public String getName() { return "RNKickassComponent"; } @ReactMethod public void concatStr( String string1, String string2, Promise promise) { promise.resolve(string1 + " " + string2); } @ReactMethod public void signUp( String region, String userPoolId, String clientId, String email, String password, final Promise promise) { AmazonCognitoIdentityProviderClient client = new AmazonCognitoIdentityProviderClient(new AnonymousAWSCredentials(), new ClientConfiguration()); // Sorry, hard coded as well. client.setRegion(Region.getRegion(Regions.EU_WEST_1)); CognitoUserPool userPool = new CognitoUserPool(this.reactContext, userPoolId, clientId, null, client); CognitoUserAttributes userAttributes = new CognitoUserAttributes(); userAttributes.addAttribute("email", email); SignUpHandler signupCallback = new SignUpHandler() { @Override public void onSuccess(CognitoUser cognitoUser, boolean userConfirmed, CognitoUserCodeDeliveryDetails cognitoUserCodeDeliveryDetails) { // Sign-up was successful promise.resolve(cognitoUser.getUserId()); } @Override public void onFailure(Exception exception) { // Sign-up failed, check exception for the cause promise.reject("signup failed", exception.getMessage()); } }; userPool.signUpInBackground(email, password, userAttributes, null, signupCallback); }; }
Общий javascript
Самое замечательное здесь то, что часть javascript идентична как для Android, так и для iOS.
react-native-kickass-component / index.js (выделено новым жирным шрифтом):
import { NativeModules } from 'react-native'; const component = NativeModules.RNKickassComponent; class KickassComponent { constructor(region_id, user_pool_id, client_id) { this.region_id = region_id; this.user_pool_id = user_pool_id; this.client_id = client_id; } async concatStr(string1, string2) { return await component.concatStr(string1, string2); } async signUp(email, password) { return await component.signUp(this.region_id, this.user_pool_id, this.client_id, email, password); } } export default KickassComponent;
Поместим конфигурацию AWS в отдельную конфигурацию ReactNativeKickassApp / awsconfig.js:
export default awsConfig = { "region": "eu-west-1", "user_pool_id": "YOUR_USER_POOL_ID", "client_id": "YOUR_CLIENT_ID" };
Обратите внимание, что регион жестко запрограммирован в примерах модулей (упражнение оставлено читателю).
ReactNativeKickassApp / index.ios.js и index.android.js могут быть одинаковыми (выделено новым жирным шрифтом):
import React, { Component } from 'react'; import { AppRegistry, StyleSheet, Text, View, TextInput, Button } from 'react-native'; import awsConfig from './awsconfig'; import KickassComponent from 'react-native-kickass-component' export default class ReactNativeKickassApp extends Component { constructor() { super(); this.state = { message: '', email: '', password: '', signupUsername: '', errorMessage:'' }; this.component = new KickassComponent(awsConfig.region, awsConfig.user_pool_id, awsConfig.client_id); this.component.concatStr('Hello', 'world') .then(message => this.setState({message: message})); } signup() { this.component.signUp(this.state.email, this.state.password) .then(signupUsername => this.setState({signupUsername: signupUsername, errorMessage: ''})) .catch(errorMessage => this.setState({signupUsername: '', errorMessage: errorMessage.message})); } message() { if (this.state.signupUsername === '' && this.state.errorMessage === '') { return (<Text style={styles.label}>Enter email and password and press the button</Text>) } else if (this.state.signupUsername !== '') { return (<Text style={styles.label}>Signed up as {this.state.signupUsername} was succesfull</Text>) } else { return (<Text style={styles.label}>Error signing up {this.state.errorMessage}</Text>) } } render() { return ( <View style={styles.container}> <Text /> {this.message()} <Text style={styles.label}>E-mail address</Text> <TextInput style={styles.input} onChangeText={(text) => this.setState({ email: text })} value={this.state.email} /> <Text style={styles.label}>Password</Text> <TextInput style={styles.input} secureTextEntry={true} onChangeText={(password) => this.setState({ password: password })} value={this.state.password} /> <Button style={styles.button} title="Signup" onPress={this.signup.bind(this)} /> </View> ); } } const styles = StyleSheet.create({ container: { flex: 1, }, label: { flexDirection: 'row', paddingHorizontal: 10, paddingVertical: 5, borderBottomWidth: 1, borderBottomColor: '#EEE', }, input: { borderColor: 'gray', paddingHorizontal: 10, paddingVertical: 5, borderWidth: 1, marginLeft: 10, marginRight: 10, height: 40, }, button: { height: 40, } }) AppRegistry.registerComponent('ReactNativeKickassApp', () => ReactNativeKickassApp);
Теперь запустите либо через react-native run-ios, либо через react-native run-adroid.. Должен появиться Пользовательский интерфейс для ввода учетных данных и регистрации.
Использование библиотеки в новом проекте
Обратите внимание, поскольку мы используем фреймворки, которые не включены в репозиторий NPM, необходимо проделать дополнительную работу:
- Добавьте «react-native-kickass-library» в package.json (очевидно)
- Упомянутые выше файлы .framework следует скопировать в папку node_modules / response-native-kickass-component / ios / Frameworks /.
- Выполните «реакцию на нативную ссылку», чтобы пакет был включен в iOS и Android.
- Необходимо указать «Путь к платформе» в разделе «Библиотеки» нового проекта. [Это необходимо выяснить, почему это так. Он уже должен быть в порядке, потому что он включен в «react-native-kickass-component».]
- Файлы .framework необходимо добавить в проект «Встроенные двоичные файлы». [Это также требует некоторого расследования, поскольку оно не требуется по той же причине, что и пункт 4].
[Примечание автора: я буду рад узнать от кого-нибудь, почему возникают упомянутые выше проблемы.]