В предыдущем посте я продемонстрировал, как загружать модули 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 содержат заголовки и двоичные файлы. Нам нужно сделать их известными в проекте. Нам нужно добавить два элемента:

  1. Файлы .h должны быть найдены нашим модулем
  2. Бинарные файлы .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) :

  1. Добавьте компонент response-native-kickass в package.json. Очевидно, вам сначала нужно будет развернуть компонент response-native-kickass в npm.
  2. Закройте графический интерфейс XCode.
  3. Запустите редактор и откройте ios / ReactNativeKickassApp.xcodeproj / project.pbxproj. Обратите внимание, что файл проекта находится внутри файла xcodeproj, и вам может потребоваться перейти через командную строку, чтобы отредактировать его, поскольку он не отображается в Finder.
  4. Найдите раздел под названием / * 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, необходимо проделать дополнительную работу:

  1. Добавьте «react-native-kickass-library» в package.json (очевидно)
  2. Упомянутые выше файлы .framework следует скопировать в папку node_modules / response-native-kickass-component / ios / Frameworks /.
  3. Выполните «реакцию на нативную ссылку», чтобы пакет был включен в iOS и Android.
  4. Необходимо указать «Путь к платформе» в разделе «Библиотеки» нового проекта. [Это необходимо выяснить, почему это так. Он уже должен быть в порядке, потому что он включен в «react-native-kickass-component».]
  5. Файлы .framework необходимо добавить в проект «Встроенные двоичные файлы». [Это также требует некоторого расследования, поскольку оно не требуется по той же причине, что и пункт 4].

[Примечание автора: я буду рад узнать от кого-нибудь, почему возникают упомянутые выше проблемы.]