Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save enzoz/9fe57c458ca211b7a7426b7f6acbb826 to your computer and use it in GitHub Desktop.

Select an option

Save enzoz/9fe57c458ca211b7a7426b7f6acbb826 to your computer and use it in GitHub Desktop.

Criando uma biblioteca Android com seu projeto React Native

Nas últimas semanas comecei a trabalhar em um projeto em meu time é responsável por um microfrontend dentro de uma aplicação mobile. A aplicação que meu time cuida é uma aplicação bastante simples e não exige acesso a muito recursos dos SOs. Dado este cenário tomou-se por decisão o uso de React Native para a construção de aplicações desta natureza.

Com isto resolvi compartilhar em passo-a-passo simples como gerar um Android Archive (AAR) do código ReactNative para ser importado em uma aplicação Android já existente. Tenha em mente que diferentes versões de alguma das bibliotecas podem acarretar no não funcionamento correto, caso isso ocorra StackOverflow está cheio de pequenas dicas, além disso recomendo verificar as issues reportadas no repositório do React Native. Ao final vou listar algumas issues e perguntas que me ajudaram a chegar no resultado.

Dependências:

  • Java entre 1.8 e 11
  • Android Studio com um SDK e Simulador instalados
  • Yarn
  • Node

Não entrarei nos por menores da instalação das dependências. Se precisares de mais detalhes pode consultar a página de instalação de ambiente do ReactNative.

Construindo um contador simples

Para esse exemplo iremos construir um contador simples. A biblioteca vai expor uma interface onde o usuário poderá incrementar um contador.

Inicializando uma aplicação React Native

Para criar a aplicação ReactNative iremos executar o seguinte comando no terminal:

npx react-native init SimpleReactNativeComponent --version=0.61.5

É possível executar a aplicação gerada no simulador usando o seguinte comando:

yarn android

Com código gerado agora podemos construir nossa biblioteca. Para esse projeto iremos assumir que nossa aplicação será executada em uma Activity completa.

Adicionando o contador

Para que nossa aplicação possua ao menos uma funciondalidade que vá além de um "Olá mundo!" criaremos um contador simples. Guardamos no estado o valor do contador e incrementamos ele com um botão na tela. Para isso será necessário alterar o arquivo App.js e adicionar o conteúdo que está no arquivo correspondente deste gist.

Após realizar as alterações necessárias tu poderá executar a aplicação com o mesmo comando citado anteriormente, yarn android.

Com estas moficações vamos ao que realmente nos interessa.

Gerando um AAR a partir da aplicação RN

Quando criamos uma nova aplicação a partir da CLI do ReactNative ele irá configurar a aplicação recém criada para ser construída como uma aplicação Android regular, gerando um APK ao final do processo de build. Como queremos gerar um AAR ao invés um APK.

Construindo uma biblioteca

Para fazer isso precisamos alterar a forma o código que é responsável por construir e empacotar a biblioteca. E também precisamos criar o código responsável para por comunicar como a nossa biblioteca será executada a partir do código na app nativa.

Primeiro, precisamos criar o diretório assets dentro do diretório android/app/main/src. Excutando o comando abaixo você irá realizar isso:

mkdir android/app/src/main/assets

Com este diretório criado, poderemos gerar o bundle da nossa biblioteca ReactNative.

Também é preciso editar o arquivo ./android/app/build.gradle.

Na primeira linha do arquivo build.gradle:

apply plugin: "com.android.application"

Por:

apply plugin: "com.android.library"

Remover

apply from: "../../node_modules/react-native/react.gradle" // Está próximo a linha 84 do arquivo

E por fim remover o applicationId de dentro do bloco defaultConfig.

TODO: colocar as infos do build.gradle

Com tudo isto configurado podemos gerar AAR da nossa biblioteca. Para facilicar a execução desta etapa adicionei ao final do Gist um arquivo package.json com um script para gerar o arquivo AAR. Ele irá criar o bundle do código javascript com o script android-generate-bundle e empacotar em um AAR com o script android-bundle-lib. Para facilitar a execução, podemos executar somente android-build-lib.

Estemos comandos irão gerar o bundle JavaScript da aplicação, gerar o arquivo AAR com base nas configurações do build.gradle que foram feitas e copiar o aquivo AAR para a raiz do projeto.

Precisamos também construir o código que será responsável por criar camada que permitirá que nosso código JavaScript seja executado a partir da aplicação nativa. Para isso iremos extender a classe ReactContextBaseJavaModule e implementar a classe ReactPackage. Implementando a classe ReactContextBaseJavaModule iremos informar qual o nome do nosso módulo. E a interface ReactPackage para que o nosso módulo seja registrado. O código que faz isto está ao final do Gist, copie as classes para o seu projeto e pronto.

Adicionando a biblioteca a um projeto Android

Precisamos de uma app Android para que nosso AAR seja utilizado, para isso vamos usar o Android Studio e criaremos um projeto do tipo Empty Activity. A única coisa que alterei foi o SDK min para 24 ou Nougat.

Depois de criado um projeto precisamos ir até o diretório do projeto e criar uma novo diretório chamado android e mover todo o conteúdo que existia anteriormente para dentro dele. Depois de concluir isto, precisamos abrir o projeto novamente no Android Studio, agora abrindo o projeto a partir do diretório android. Podemos inclusive executar o projeto criado no emulador.

Ao final você deve ter uma estrutura similar a esta:

UsingRNLibrary/
└── android
Importando a biblioteca no projeto

Para poder usar nossa biblioteca precisamos fazer executar algumas etapas:

  • Instalar o React Native como depedência da aplicação Android
  • Instalar a nossa biblioteca como dependência
  • Criar uma Activity onde iremos "executar" nossa biblioteca

Agora com a estrutura de diretórios criada podemos realizar as configurações necessárias para instalar a nossa biblioteca no projeto. Primeiro iremos iniciar um projeto, para fazer isso, no diretório UsingRNLibrary iremos executar o comando yarn init. Siga as instruções do CLI e pronto.

Precisamos ainda instalar o React Native, precisamos dele para executar a nossa biblioteca no app de Android. Podemos fazer isso executando yarn install react-native@<versão-da-lib>, é imprescindível que a versão do React Native seja a mesma que a nossa biblioteca foi construída, se não teremos problemas depois.

Certo, com o ReactNative instalado precisamos dizer para o nosso projeto Android que ele é uma dependência. Para fazer isto iremos alterar dois arquivos do nosso projeto: a. android/build.gradle: aqui iremos dizer onde o React Native está; b. android/app/build.gradlw: aqui iremos declara-lo como dependência;

Para isso precisamos adicionar ao android/build.gradle o seguinte código dentro do bloco allprojects/repositoties:

maven {
  url "$rootDir/../node_modules/react-native/android"
}
maven {
    url "$rootDir/../node_modules/jsc-android/dist"
}
flatDir {
  dirs "$rootDir/app/aars"
}

Dentro do diretório UsingRNLibrary/android/app criaremos um diretório chamado aars que usaremos para armazenar a biblioteca que criamos. Podemos fazer isso com o seguinte comando

mkdir android/app/aars

Com o diretório criado precisamos também informar que esta é um diretório

Agora precisamos indicar ao nosso arquivo de build que ele precisa olhar para esta pasta na hora de fazer o download das dependências. Para isso iremos alterar o arquivo

E no arquivo android/app/build.gradle iremos adicionar o seguinte código dentro do bloco dependencies:

implementation 'com.facebook.react:react-native:0.62.2'
implementation(name: 'simplereactnativecomponent', ext: 'aar')

E também no mesmo arquivo precisamos adicionar após a última linha que contém apply plugin: //... o seguinte código:

// Este código diz como ReactNative será
project.ext.react = [
    enableHermes: false
]
apply from: "${rootDir}/../node_modules/react-native/react.gradle"

Também precisamos dizer quais as dependências preciam ser instaladas

Pontos para melhorar

  • Melhorar o suporte para executar a biblioteca
  • Publicar a biblioteca criada em um repositório de artefatos como Artifactory ou Nexus

Referências:

/**
* Sample React Native App
* https://github.com/facebook/react-native
*
* @format
* @flow strict-local
*/
import React, {
useState
} from 'react';
import {
SafeAreaView,
StyleSheet,
ScrollView,
View,
Text,
Button,
} from 'react-native';
import {
Colors,
} from 'react-native/Libraries/NewAppScreen';
const App: () => React$Node = () => {
const [count, setCount] = useState(0)
const increaseByOne = () => setCount(count+1)
return (
<>
<SafeAreaView>
<ScrollView
contentInsetAdjustmentBehavior="automatic">
<View style={styles.container}>
<Text style={styles.counter}>Counting: {count}</Text>
</View>
<View style={styles.container}>
<Button onPress={increaseByOne} style={styles.sectionButton} title="Increase one" />
</View>
</ScrollView>
</SafeAreaView>
</>
);
};
const styles = StyleSheet.create({
scrollView: {
backgroundColor: Colors.lighter,
},
counter: {
fontSize: 24,
fontWeight: '600',
color: Colors.black,
},
container: {
marginTop: 30,
marginLeft: 20,
paddingHorizontal: 24,
},
});
export default App;
package com.example.usingrnlibrary
import android.annotation.TargetApi
import android.content.Intent
import android.os.Build
import android.os.Bundle
import android.view.KeyEvent
import androidx.appcompat.app.AppCompatActivity
import com.facebook.react.ReactInstanceManager
import com.facebook.react.ReactPackage
import com.facebook.react.ReactRootView
import com.facebook.react.modules.core.DefaultHardwareBackBtnHandler
import com.facebook.react.modules.core.PermissionAwareActivity
import com.facebook.react.modules.core.PermissionListener
abstract class BaseReactNativeActivity : AppCompatActivity(), DefaultHardwareBackBtnHandler,
PermissionAwareActivity {
@JvmField
protected var mReactRootView: ReactRootView? = null
@JvmField
protected var mReactInstanceManager: ReactInstanceManager? = null
@JvmField
protected var reactPackage: ReactPackage? = null
@JvmField
protected var permissionListener: PermissionListener? = null
@JvmField
protected var initialProperties = Bundle()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
try {
initialProperties.putString("env", BuildConfig.FLAVOR)
mReactRootView = ReactRootView(this)
} catch (e: Exception) {
println("An error occurred while starting React Native")
}
}
override fun invokeDefaultOnBackPressed() {
super.onBackPressed()
}
override fun onPause() {
super.onPause()
mReactInstanceManager?.onHostPause(this)
}
override fun onResume() {
super.onResume()
mReactInstanceManager?.onHostResume(this, this)
}
override fun onDestroy() {
super.onDestroy()
mReactInstanceManager?.onHostDestroy(this)
}
override fun onBackPressed() {
if (mReactInstanceManager != null) {
mReactInstanceManager?.onBackPressed()
} else {
super.onBackPressed()
}
}
override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {
if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) {
mReactInstanceManager!!.showDevOptionsDialog()
return true
}
return super.onKeyUp(keyCode, event)
}
@TargetApi(Build.VERSION_CODES.M)
override fun requestPermissions(permissions: Array<String>, requestCode: Int, listener: PermissionListener) {
permissionListener = listener
requestPermissions(permissions, requestCode)
}
override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<String>, grantResults: IntArray) {
if (permissionListener != null) {
permissionListener!!.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
}
}
apply plugin: "com.android.library"
/**
* Set this to true to create two separate APKs instead of one:
* - An APK that only works on ARM devices
* - An APK that only works on x86 devices
* The advantage is the size of the APK is reduced by about 4MB.
* Upload all the APKs to the Play Store and people will download
* the correct one based on the CPU architecture of their device.
*/
def enableSeparateBuildPerCPUArchitecture = false
/**
* Run Proguard to shrink the Java bytecode in release builds.
*/
def enableProguardInReleaseBuilds = false
android {
compileSdkVersion rootProject.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.0"
}
splits {
abi {
reset()
enable enableSeparateBuildPerCPUArchitecture
universalApk false // If true, also generate a universal APK
include "armeabi-v7a", "x86", "arm64-v8a", "x86_64"
}
}
signingConfigs {
debug {
storeFile file('debug.keystore')
storePassword 'android'
keyAlias 'androiddebugkey'
keyPassword 'android'
}
}
buildTypes {
debug {
signingConfig signingConfigs.debug
}
release {
// Caution! In production, you need to generate your own keystore file.
// see https://facebook.github.io/react-native/docs/signed-apk-android.
signingConfig signingConfigs.debug
minifyEnabled enableProguardInReleaseBuilds
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
}
}
}
dependencies {
implementation fileTree(dir: "libs", include: ["*.jar"])
implementation "com.facebook.react:react-native:0.61.5" // From node_modules
implementation "androidx.core:core:1.2.0"
}
// Run this once to be able to run the application with BUCK
// puts all compile dependencies into folder libs for BUCK to use
task copyDownloadableDepsToLibs(type: Copy) {
from configurations.compile
into 'libs'
}
repositories {
mavenCentral()
}
{
"name": "SimpleReactNativeComponent",
"version": "0.0.1",
"private": true,
"scripts": {
"android": "react-native run-android",
"android-generate-bundle": "react-native bundle —-platform android —-dev false --entry-file index.js --bundle-output android/app/src/main/assets/simplereactnativecomponent.android.bundle --assets-dest android/app/src/main/res",
"android-bundle-lib": "cd android && ./gradlew assembleRelease && cp app/build/outputs/aar/app-release.aar ../simplereactnativecomponent.aar",
"android-build-lib": "yarn android-bundle-lib && yarn android-bundle-lib",
"ios": "react-native run-ios",
"start": "react-native start",
"test": "jest",
"lint": "eslint ."
},
"dependencies": {
"react": "16.11.0",
"react-native": "0.62.2"
},
"devDependencies": {
"@babel/core": "^7.6.2",
"@babel/runtime": "^7.6.2",
"@react-native-community/eslint-config": "^0.0.5",
"babel-jest": "^24.9.0",
"eslint": "^6.5.1",
"jest": "^24.9.0",
"metro-react-native-babel-preset": "^0.58.0",
"react-test-renderer": "16.11.0"
},
"jest": {
"preset": "react-native"
}
}
package com.simplereactnativecomponent;
public class SimpleReactNativeComponentConfiguration {
public static String BUNDLE_NAME = "simplereactnativecomponent.android.bundle";
public static String MODULE_NAME = "SimpleReactNativeComponent";
}
package com.simplereactnativecomponent;
import android.app.Activity;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class SimpleReactNativeComponentModule extends ReactContextBaseJavaModule {
public SimpleReactNativeComponentModule(@NonNull ReactApplicationContext reactContext) {
super(reactContext);
}
@NonNull
@Override
public String getName() {
return "SimpleReactNativeComponent";
}
@ReactMethod
public void finishReact() {
Activity currentActivity = this.getCurrentActivity();
if (currentActivity != null) {
currentActivity.finish();
}
}
}
package com.simplereactnativecomponent;
import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.List;
import static java.util.Arrays.asList;
public class SimpleReactNativeComponentPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
return asList(new SimpleReactNativeComponentModule(reactContext));
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return asList();
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment