React Native: Turbo Modules
Tags: reactnative, android, ios, react
Published: 2024-03-25 00:26:33

## 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](https://medium.com/@anisurrahmanbup/react-native-new-architecture-in-depth-hermes-jsi-fabric-fabric-renderer-yoga-turbo-module-1284a192a82b)


## 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](https://microsoft.github.io/react-native-windows/docs/native-modules-vs-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](https://reactnative.dev/docs/native-modules-intro) e se quiser ter uma noção de como é, basta acessar o [post](https://dev.to/mensonones/modulo-nativo-android-usando-react-native-3l91). 

## 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:
```shell
npx react-native@latest init DeviceInfoPractice
```
 4 - Dentro da pasta **RTNDeviceInfo**, crie duas subpastas **js** e **android** .

Sua estrutura de pastas deve ficar assim:

```
TurboModulesPractice
├── DeviceInfoPractice
└── RTNDeviceInfo
    ├── android
    └── js
```

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

NativeDeviceInfo.ts

```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;
```

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

package.json

```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"
      }
    }
  }
  ```
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

```groove
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'
}
```
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

```java
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"
  }
}
```

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

DeviceInfoPackage.kt

```java
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
    }
  }
}
```

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:

```shell
yarn add ../RTNDeviceInfo
cd android
./gradlew generateCodegenArtifactsFromSchema
```
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

```ts
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;

```

## Resultado 


![Print Resultado](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sytdd369jydz1xijyxb3.png)

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! :)