DEV Community

Cover image for React Native: Turbo Modules
Emerson Vieira
Emerson Vieira

Posted on

React Native: Turbo Modules

React Native: Nova Arquitetura

Ao longo do post, você irá se deparar com termos/conceitos com o qual pode não estar familiarizado, então faz-se necessário algum entendimento. Abaixo um link para leitura:

React Native — Ultimate Guide on New Architecture in depth

O que são Turbo Modules

"TurboModules são a próxima iteração de módulos nativos que oferecem alguns benefícios extras, em particular esses módulos usam JSI, uma interface JavaScript para código nativo, que permite uma comunicação mais eficiente entre código nativo e JavaScript do que a ponte".

Fonte: Turbo Modules

Turbo Modules são módulos nativos otimizados que facilitam o acesso à API nativa do dispositivo e outros recursos. Eles permitem a integração entre a camada JavaScript e a nativa, usando Java/Kotlin (Android) ou Objective-C/Swift (iOS).

Implementando um módulo nativo com Turbo Modules

Umas das primeiras impressões que tive ao ter contato com Turbo Modules na prática é que é um pouco mais "complicado" de implementar se comparado com a maneira antiga e se quiser ter uma noção de como é, basta acessar o post.

Prática

Iremos criar um módulo nativo que retorne a informação da versão do Android no dispositivo.

1 - Você deve criar uma pasta chamada TurboModulesPractice
2 - Dentro da pasta criada anteriomente, crie uma pasta chamada RTNDeviceInfo ( RTN significa "React Native" e é um prefixo recomendado para módulos React Native)
3 - Ainda na pasta criada no passo 1, crie um novo projeto React Native com o comando abaixo:

npx react-native@latest init DeviceInfoPractice
Enter fullscreen mode Exit fullscreen mode

4 - Dentro da pasta RTNDeviceInfo, crie duas subpastas js e android .

Sua estrutura de pastas deve ficar assim:

TurboModulesPractice
├── DeviceInfoPractice
└── RTNDeviceInfo
    ├── android
    └── js
Enter fullscreen mode Exit fullscreen mode

5 - Dentro da subpasta js que fica dentro da pasta RTNDeviceInfo, crie um arquivo chamado NativeDeviceInfo.ts

NativeDeviceInfo.ts

import type { TurboModule } from "react-native/Libraries/TurboModule/RCTExport";
import { TurboModuleRegistry } from "react-native";

export interface Spec extends TurboModule {
  getAndroidVersion(): Promise<string>;
}

export default TurboModuleRegistry.get<Spec>("RTNDeviceInfo") as Spec | null;
Enter fullscreen mode Exit fullscreen mode

6 - Na raiz da pasta RTNDeviceInfo, crie um arquivo chamado package.json

package.json

{
    "name": "rtn-device-info",
    "version": "0.0.1",
    "description": "Get device info(s) with Turbo Modules",
    "react-native": "js/index",
    "source": "js/index",
    "files": [
      "js",
      "android",
      "!android/build"
    ],
    "keywords": [
      "react-native",
      "android"
    ],
    "license": "MIT",
    "devDependencies": {},
    "peerDependencies": {
      "react": "*",
      "react-native": "*"
    },
    "codegenConfig": {
      "name": "RTNDeviceInfoSpec",
      "type": "modules",
      "jsSrcsDir": "js",
      "android": {
        "javaPackageName": "com.rtndeviceinfo"
      }
    }
  }
Enter fullscreen mode Exit fullscreen mode

Esse arquivo será necessário para a instação do módulo

7 - Crie um arquivo chamado build.gradle na subpasta android que fica dentro da pasta RTNDeviceInfo

build.gradle

buildscript {
  ext.safeExtGet = {
    prop,
    fallback ->
    rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
  }
  repositories {
    google()
    gradlePluginPortal()
  }
  dependencies {
    classpath("com.android.tools.build:gradle:7.3.1")
    classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.22")
  }
}

apply plugin: 'com.android.library'
apply plugin: 'com.facebook.react'
apply plugin: 'org.jetbrains.kotlin.android'

android {
  compileSdkVersion safeExtGet('compileSdkVersion', 33)
  namespace "com.rtndeviceinfo"
}

repositories {
  mavenCentral()
  google()
}

dependencies {
  implementation 'com.facebook.react:react-native'
}
Enter fullscreen mode Exit fullscreen mode

8- Crie uma pasta chamada rtndeviceinfo em: android/src/main/java/com

Sua estrutura de pastas deve ficar assim:

├── android
│ ├── build.gradle
│ └── src
│ └── main
│ └── java
│ └── com
│ └── rtndeviceinfo


├── js
│ └── NativeDeviceInfo.ts
└── package.json

9 - Dentro da pasta rtndeviceinfo crie um arquivo chamado DeviceInfoModule.kt

DeviceInfoModule.kt

package com.rtndeviceinfo;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.rtndeviceinfo.NativeDeviceInfoSpec;
import android.os.Build;

class DeviceInfoModule(reactContext: ReactApplicationContext) : NativeDeviceInfoSpec(reactContext) {

  override fun getName() = NAME

  override fun getAndroidVersion(promise: Promise) {
    promise.resolve(Build.VERSION.RELEASE)
  }

  companion object {
    const val NAME = "RTNDeviceInfo"
  }
}
Enter fullscreen mode Exit fullscreen mode

10 - Ainda na pasta rtndeviceinfo crie um arquivo chamado DeviceInfoPackage.kt

DeviceInfoPackage.kt

package com.rtndeviceinfo;

import com.facebook.react.TurboReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.module.model.ReactModuleInfo;
import com.facebook.react.module.model.ReactModuleInfoProvider;
import java.util.HashMap;

class DeviceInfoPackage : TurboReactPackage() {
  override fun getModule(name: String, reactContext: ReactApplicationContext): NativeModule? {
    return if (name == DeviceInfoModule.NAME) {
        DeviceInfoModule(reactContext)
    } else {
      null
    }
  }

  override fun getReactModuleInfoProvider(): ReactModuleInfoProvider {
    return ReactModuleInfoProvider {
      val moduleInfos: MutableMap<String, ReactModuleInfo> = HashMap()
      moduleInfos[DeviceInfoModule.NAME] = ReactModuleInfo(
        DeviceInfoModule.NAME,
        DeviceInfoModule.NAME,
        false,  // canOverrideExistingModule
        false,  // needsEagerInit
        true,  // hasConstants
        false,  // isCxxModule
        true // isTurboModule
      )
      moduleInfos
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Com o que já foi feito até aqui, temos nosso módulo pronto para ser integrado ao projeto React Native, mas ainda falta uma etapa para a integração de fato.

11 - Acesse a pasta DeviceInfoPractice que é a pasta do projeto react native gerado no passo 3
12 - Execute os comandos a seguir:

yarn add ../RTNDeviceInfo
cd android
./gradlew generateCodegenArtifactsFromSchema
Enter fullscreen mode Exit fullscreen mode

Você poderá verificar o resultado desses comandos em:

DeviceInfoPractice/node_modules/rtn-device-info/android/build/generated/source/codegen

Caso exista esse caminho, deu tudo certo na execução dos comandos.

O primeiro comando adiciona o pacote/módulo ao aplicativo. Então, em seguida acessa a pasta android, após isso ele invoca uma tarefa Gradle para criar o código gerado.

Para mais informações: https://github.com/reactwg/react-native-new-architecture

13 - Acesse android/gradle.properties no projeto DeviceInfoPractice e certifique-se de que a propriedade newArchEnabled seja true.

Agora podemos utilizar o Módulo Turbo para usar getAndroidVersion função no aplicativo!

14 - Acesse a pasta do projeto DeviceInfoPractice e abra o arquivo App.tsx e substitua o código com o conteúdo a seguir:

App.tsx

import React, { useEffect, useState } from 'react';
import { SafeAreaView, StatusBar, StyleSheet, Text, View, ActivityIndicator } from 'react-native';
import RTNDeviceInfo from 'rtn-device-info/js/NativeDeviceInfo';

function App(): React.JSX.Element {
  const [result, setResult] = useState<string | null>(null);
  const [loading, setLoading] = useState<boolean>(true);

  useEffect(() => {
    fetchAndroidVersion();
  }, []);

  const fetchAndroidVersion = async () => {
    try {
      const version = await RTNDeviceInfo?.getAndroidVersion();
      setResult(version ?? null);
      setLoading(false);
    } catch (error) {
      console.error('Error fetching Android version:', error);
      setResult(null);
      setLoading(false);
    }
  };

  return (
    <SafeAreaView style={styles.container}>
      <StatusBar barStyle={'dark-content'} />
      <View style={styles.content}>
        <Text style={styles.label}>
          Android version:
        </Text>
        {loading ? (
          <ActivityIndicator style={styles.activityIndicator} size="small" color="#0000ff" />
        ) : (
          <Text style={styles.text}>
            {result ?? 'Failed to fetch Android version'}
          </Text>
        )}
      </View>
    </SafeAreaView>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
  },
  content: {
    flexDirection: 'row',
    alignItems: 'center',
  },
  label: {
    marginLeft: 20,
    fontSize: 18,
    fontWeight: 'bold',
  },
  text: {
    marginLeft: 10,
    fontSize: 18,
  },
  activityIndicator: {
    marginLeft: 10,
  },
});

export default App;

Enter fullscreen mode Exit fullscreen mode

Resultado

Print Resultado

Espero que ajude a entender um pouco, apesar do post ser mais prático que teórico, então, por isso, não deixe de ler sobre algo que não entendeu e qualquer dúvida, não deixe de comentar! :)

Top comments (0)