Fiba Tech Lab
Published in

Fiba Tech Lab

React Native Module nasıl yapılır?

React Native projeleri kodlarken pek çok kütüphanenin npm paketini kolaylıkla bulunabiliyor. Fakat bazı durumlarda üçüncü parti bir native SDK kütüphaneyi projenize dahil etmeniz gerekebilir. Bu durumlarda Native modül yazarak JavaScript ve Native tarafı birbiriyle konuşturabilirsiniz. Şimdi projeyi oluşturdukta sonra örnek bir problem üretelim ve bunu Native Modules ile çözmeye çalışalım.

Projenin oluşturulması

React native projesini oluşturmak için iki farklı yol izlenebilir.

React native community cli ile:

npx react-native init NativeModules

Expo ile:

expo init NativeModules
expo login
expo eject

Ben bu projede expo ile projeyi oluşturup, native modülleri oluşturmak için eject edeceğim. Siz de istediğiniz yolu kullanabilirsiniz.

Örnek Problem

Problemi basite indirmek adına üçüncü parti native bir SDK yerine native bir ekran görüntüleyeceğimizi düşünelim. iOS tarafında ViewController, Android tarafında ise bir Activity görüntüleyeceğimizi varsayalım. Bu problemin çözümü için JavaScript (React Native), Android ve iOS kanadında kod yazmamız gerekecektir. Öncelikle JavaScript tarafından başlayalım.

JavaScript

JavaScript tarafında aşağıdaki gibi bir buton görüntülemek için App.tsx dosyasına gerekli stilleri ve View bileşenlerini ekleyelim.

// App.tsx
import { StatusBar } from 'expo-status-bar';
import React from 'react';
import { StyleSheet, Text, View, NativeModules, Platform, TouchableOpacity } from 'react-native';
export default function App() {return (
<View style={styles.container}>
<TouchableOpacity onPress={handleOpenNativeScreen} style={styles.button}>
<Text style={styles.buttonText}>Open native screen</Text>
</TouchableOpacity>
<StatusBar style="auto" />
</View>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#fff',
alignItems: 'center',
justifyContent: 'center',
},
button:{
borderRadius: 5,
backgroundColor: 'dodgerblue',
paddingVertical: 8,
paddingHorizontal: 12
},
buttonText :{
color:'white',
fontSize: 16
}
});

Kodu çalıştırdığınızda henüz handleOpenNativeScreen isimli bir metot oluşmadığı için hata verecektir. Şimdi butona basıldığında oluşturacağımız native module’ün çalışmasını gerçekleştirmek handleOpenNativeScreen metodunu için aşağıdaki gibi oluşturalım:

...
export default function App() {
const handleOpenNativeScreen = () => {
const os = Platform.OS;
const message = `Hello from ${os}`;
NativeModules.MyCustomModule.openNativeScreen(message);
}
return (
...

Kodu bu şekilde çalıştırmaya kalktığınızda, henüz MyCustomModule’ü göremediği için aşağıdaki gibi bir hata verecektir:

Kod şu an bende çalıştığı için bilerek MyCustomModulle şeklinde yazdım :)

Şimdi Android tarafında Native modül kodlamasına geçelim:

Android

Android tarafında native modül kodlamasına geçmeden önce React Native’e açtıracağımız native ekranı oluşturalım. Native modül bu ekranı açarak bir nevi native SDK açma işlemi yürütecek.

Şimdi ana paket içerisinde ui adında bir paket oluşturalım ve içerisine Kotlin ile kodlayacağımız NativeActivity isimli activity’i oluşturalım.

Öncelikle activity_native.xml dosyasına aşağıdaki gibi ekrana ortalanmış bir TextView ekleyelim:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".ui.NativeActivity">
<TextView
android:id="@+id/myTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>

Sonrasında TextView’ın metninin dışarıdan değiştirilebilmesi için NativeActivity.kt dosyasını aşağıdaki gibi kodlayalım:

package com.zaferayan.nativemodules.uiimport androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.widget.TextView
import com.zaferayan.nativemodules.R
class NativeActivity : AppCompatActivity() {
companion object {
const val MESSAGE = "MESSAGE"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_native)
val message = intent.extras?.getString(MESSAGE)
val myTextView = findViewById<TextView>(R.id.myTextView)
myTextView.text = message
}
}

Bu activity, açılırken MESSAGE parametresini ile bundle‘dan alarak TextView’ı değiştirecektir.

Şimdi bu activity’i açacak olan modülü yazalım. Aşağıdaki gibi mycustompackage paketini oluşturalım ve içerisine MyCustomModule.java dosyasını ekleyelim:

package com.zaferayan.nativemodules.mycustompackage;import android.content.Intent;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.zaferayan.nativemodules.ui.NativeActivity;
public class MyCustomModule extends ReactContextBaseJavaModule {
private final ReactApplicationContext context;
MyCustomModule (ReactApplicationContext context) {
super(context);
this.context = context;
}
@NonNull
@Override
public String getName() {
return this.getClass().getSimpleName();
// Or
// return "MyCustomModule";
}
@ReactMethod
public void openNativeScreen(String message) {
Intent intent = new Intent(context, NativeActivity.class);
intent.putExtra(NativeActivity.MESSAGE, message);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}

Burada kodu açıklamamız gerekirse:

  • ReactContextBaseJavaModule: React Native modülü oluşturmaya yarar. ReactContext instance’ına erişmek için gereken base class’tır
  • ReactApplicationContext: React ile ilişkili Context nesnesidir. Bu sayede activity açılması gibi işlemler gerçekleştirilebilir.
  • getName: Her bir modülün adının olması zorunludur. Bu isim ile JS tarafında modüle erişim yapılır. Elle “MyCustomModule” olarak verilebileceği gibi class isminden de elde edilebilir.
  • @ReactMethod: İlgili fonksiyonun React tarafına açılmasını sağlayan anotasyondur. openNativeScreen isimli oluşturulan metodun JS tarafından erişilebilir olmasını sağlar. Sadece message değil herhangi bir parametre de eklenebilir.

React Native tarafında bu modülün tanınabilmesi için bir paket haline getirilmesi gereklidir. Oluşturulan paket, içerisindeki listeye eklenen tüm modülleri gezerek React Native tarafına register etmektedir. Bu nedenle aşağıdaki gibi ReactPackage sınıfından türetilen bir paket oluşturulmalıdır:

package com.zaferayan.nativemodules.mycustompackage;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.ArrayList;
import java.util.Collections;
import java.util.List;
public class MyCustomPackage implements ReactPackage { @Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new MyCustomModule(reactContext));
return modules;
}
}

Şimdi oluşturulan bu paketi, MainApplication.java içerisinde getPackages metodunda register edelim. Dosya içerisinde sadece koyu renkli ifadeyi ve ilgili import’u eklemeniz yeterlidir.

@Override
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new ModuleRegistryAdapter(mModuleRegistryProvider));
packages.add(new MyCustomPackage());
return packages;
}

Paket de artık register edildiğine göre artık kodu android ortamında çalıştırabilirsiniz. Butona bastığınızda activity açılacak ve aşağıdaki gibi “Hello from android” ifadesi görüntülenecektir:

Android tarafını tamamladığımıza göre artık iOS tarafına geçebiliriz

iOS

iOS tarafında da aynı Android tarafında activity eklediğimiz gibi bir ViewController kodlayarak native bir ekran ekleyelim. Bunun için öncelikle ana klasörde ui isimli klasörü oluşturalım ve içerisine NativeViewController.swift dosyasını kodlayalım:

import Foundation
import UIKit
class NativeViewController: UIViewController {
var message: String = ""
override func viewDidLoad() {
super.viewDidLoad()

self.view.backgroundColor = .white

let H = self.view.frame.height * 0.5
let W = self.view.frame.width * 0.3
let X = self.view.bounds.midX - (W/2)
let Y = self.view.bounds.midY - (H/2)

let lblMessage = UILabel(frame: CGRect(x:X, y: Y, width: W, height: H))
lblMessage.textColor = UIColor.black
lblMessage.text = message
self.view.addSubview(lblMessage)
}
}

Buradaki sınıfın yaptığı iş, aslında ekran ortasına bir UILabel yerleştirerek message değişkenini içerisine eklemektir. (Basit olması açısından xib dosyası oluşturmadım)

Sonrasında modül ile ilgili dosyaları barındıracak olan MyCustomPackage isimli bir klasör oluşturalım ve içerisine NativeModules-Bridging-Header.h dosyasını ekleyelim. Bu dosya, Obj-C kodlarının Swift tarafından anlaşılabilir olmasını sağlamaktadır.

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>
#import "AppDelegate.h"

Şimdi native taraftaki ekranı açacak olak MyCustomModule.swift dosyasını oluşturalım:

@objc(MyCustomModule)
class MyCustomModule: NSObject {

@objc(openNativeScreen:)
func openNativeScreen(message: NSString) -> Void {
DispatchQueue.main.async {
let appDelegate = UIApplication.shared.delegate as! AppDelegate
let viewController = NativeViewController()
viewController.message = message as String
viewController.modalPresentationStyle = .fullScreen
appDelegate.window?.rootViewController?.present(viewController, animated: false)
}
}
}

Kodu açıklamak gerekirse:

  • @objc: ilgili sınıf ve fonksiyonun obj-c runtime’ına export edilmesi için kullanılır.
  • DispatchQueue.main.async: native ekran açma işlemi uygulamayı kilitlememesi için background thread’de asenkron olarak gerçekleştirilmesi gerektiğinden dolayı bu ifade kullanılır.

Şimdi modülün export edilmesi için MyCustomModule.m isimli objc dosyasını oluşturalım:

#import <React/RCTBridgeModule.h>@interface RCT_EXTERN_MODULE(MyCustomModule, NSObject)RCT_EXTERN_METHOD(openNativeScreen:(NSString *)message)// Required for DispatchQueue.main.async
+ (BOOL)requiresMainQueueSetup
{
return YES;
}
@end

Kodu açıklamak gerekirse:

  • RCT_EXTERN_MODULE: Modülün JS tarafına export edilmesini sağlar.
  • RCT_EXTERN_METHOD: Modülün metodunun JS tarafına export edilmesini sağlar.
  • requiresMainQueueSetup: Herhangi bir JS kodu çalıştırılmadan önce main thread’de modülün başlatılmasını sağlar. Modüldeki DispatchQueue.main.async kullanımı init fonksiyonunu override ettiği için tanımlanması gereklidir.

iOS tarafındaki modülü de kodladığımıza göre artık iOS tarafında da çalıştırabiliriz:

npm run ios

Sonuç olarak:

React Native projesi kodlanırken her ne kadar native modül yazımı elzem olmasa da bazı durumlarda native bir SDK entegrasyonu gerektiğinde yazmanız gerekebilir. Bu gibi durumlarda https://reactnative.dev/docs/native-modules-intro yazısından da faydalanabilirsiniz.

Projenin bitmiş halini react-native-modules-sample reposunda bulabilirsiniz. Bu yazım hakkında soru ve görüşlerinizi aşağıdaki yorumlar kısmından yazabilirsiniz. Bize destek vermek için alkış simgesine tıklayabilirsiniz. Sonraki yazımızda görüşmek üzere…

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store