Writing a Native Module for React Native Using Kotlin

Authors
Piotr Drapich
Software Engineer
@
Callstack

Trying out new techniques and technologies is great, so why don’t we try out Kotlin for React Native modules?

We’re about to build a react-native-utube-player, a Native UI component that allows playing YouTube videos in our React Native app. We’re going to achieve this by using an existing Android library, android-youtube-player, created by Pierfrancesco Soffritti.

Kotlin dependencies

Kotlin is fully interoperable with Java, meaning they can call one another and coexist in one project. This gives us a great opportunity to try it out in React Native. To get started, we have to add Kotlin dependencies to our library’s build.gradle. After that, we’re ready to write some Kotlin!

Project structure

Starting from the ground up, we create a basic structure for our library.

Project structure image

Note: We keep standard android structure, with Java directory in mainsource dir, so that React Native Linker can easily find our module and link it to project.

Minimal content for a valid manifest file:

<manifest package="example.utube">
</manifest>
view raw gistfile1.txt hosted with ❤ by GitHub

Gradle setup

To build an Android module, we have to use the android-libraryplugin, alongside with kotlin-android. Let’s add those to buildscript blockand apply them.

The last step is to add Android settings block and the rest of the module dependencies. Here’s an example of finished build.gradle file:

buildscript {
ext {
kotlinVersion = '1.3.11'
androidToolsVersion = '3.2.1'
}
repositories {
google()
mavenCentral()
}
dependencies {
classpath "com.android.tools.build:gradle:${project.ext.androidToolsVersion}"
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${project.ext.kotlinVersion}"
}
}
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
repositories {
google()
}
android {
compileSdkVersion 28
defaultConfig {
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0.0"
}
lintOptions {
abortOnError false
}
}
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${project.ext.kotlinVersion}"
implementation "com.pierfrancescosoffritti.androidyoutubeplayer:core:8.0.1"
implementation "com.facebook.react:react-native:+" // From node_modules
}
view raw gistfile1.txt hosted with ❤ by GitHub

Now we’re ready to do some coding. To make a valid Native Module, we have to create two classes, one extending ReactPackage, providing information about the capabilities of the library, and another one, SimpleViewManager, to draw our widget. Let’s get into it.

ReactPackage implementation

This class provides a list of functionalities and UI components for usage in React Native. We have to override two methods:

createNativeModules — returns a list of instances of NativeModule class, which defines functions that can be called from JavaScript. We’ll not be using this one today.

createViewManagers — this function returns a list of Native UI components, instances of ViewManager. Implementation of this class is a birthplace of our widget.

// skipped package and imports information
class RNUtubePackage : ReactPackage {
override fun createViewManagers(reactContext: ReactApplicationContext):
MutableList<ViewManager<out View, out ReactShadowNode<*>>> {
return mutableListOf(
RNUtubeView() // implemented later
)
}
override fun createNativeModules(reactContext: ReactApplicationContext):
MutableList<NativeModule> {
return mutableListOf()
}
}
view raw gistfile1.txt hosted with ❤ by GitHub

UTubePackage.kt implementation

ViewManager implementation

We’ll implement SimpleViewManager, which is a convenient subclass of ViewManager — it applies common properties such as background color, opacity, and Flexbox layout.

In our class, we’re required to override two methods from the interface:

getName: a unique name for our component — will be used to import it on JavaScript side

createViewInstance: this one requires to return an instance of our view implementation.

To get YouTubePlayerView instance, we pass reactContext as the only argument to it a constructor. Call initialize method on it and pass a lambda function, which acts as a callback, called when View is ready to use. It receives YoutubePlayer instance as an implicit argument it.We use this one to load videos into our player.

// package and imports skipped
class RNUtubeView : SimpleViewManager<YouTubePlayerView>() {
override fun getName() = "RNUtubeView"
private var ytPlayerView: YouTubePlayer? = null
private var videoId: String? = null
@ReactProp(name = "videoId")
fun setVideoId(view: YouTubePlayerView, newId: String?) {
if (newId == null || newId == videoId) return
videoId = newId
ytPlayerView?.loadVideo(newId, 0f)
}
// create view
override fun createViewInstance(reactContext: ThemedReactContext?): YouTubePlayerView {
val player = YouTubePlayerView(reactContext)
player.initialize({
ytPlayerView = it
}, false)
return player
}
}
view raw gistfile1.txt hosted with ❤ by GitHub

UTubeView.kt implementation

To get data in React way, we need to be able to read props passed to a component. Let’s create a function for will be fired whenever there’d be a change in UI tree.
setVideoId method is annotated with @ReactProp, meaning it will be fired with new prop value (named videoId) of type String? (String or null). If the prop is not the same and exists, we use it to load our video.

That’s it! We can now use our widget in React Native, so let’s write a consumer for it.

To receive a reference to our widget, we’ll call requireNativeComponent with its name (the one you specify in overridden getName method). We require three props to make it work — width, height, and videoId. Otherwise, our component won’t show or play at all.

import React from 'react';
import { requireNativeComponent, Platform, View } from 'react-native';
const UTComponent = Platform.select({
// sorry iOS :(
ios: View,
// name from overriden `getName` function
android: requireNativeComponent('RNUtubeView'),
});
export default class UTubePlayerView extends React.Component {
render() {
const { width = '100%', height = 200 } = this.props;
return <UTComponent {...this.props} style={{ width, height }} />;
}
}
view raw gistfile1.txt hosted with ❤ by GitHub

Great, all set! We can import it now into our app and play some videos!

Use module in our app

Now, we’re ready to use a module in our app. Let’s import it into our view tree and see it in action.

export const VideoPlayer = ({ ytVideoId }) => (
<UTubePlayerView width="100%" height={200} videoId={ytVideoId} />
)
view raw gistfile1.txt hosted with ❤ by GitHub

And this is the outcome:

Outcome of using module in an app

Final words

I hope you enjoy this quick entry point to Kotlin in React Native. There is still big room for improvement, but we came to a nice outcome with no big effort. Full code is hosted at GitHub, so you can try it out on your own.

Insights

Learn more about Android

cover

Sep 16Article

Debugging Android Connectivity Issues with Metro Bundler

A practical guide to fixing the "Unable to load script" error in React Native when the Android emulator can’t connect to Metro.

cover

Dec 5Article

Optimize Your Android Application’s Size With Gradle Settings

Learn how to improve TTI, reduce memory usage and size of your Android app by adjusting Proguard rules to your projects.

cover

Mar 24Article

Android Drawer and StatusBar Done Right for React Native

Unlock best practices for Android Drawer and StatusBar implementation in React Native.

cover

Mar 19Tutorial

The Hard Way vs. The React Native AI SDK Way: Stop Writing Custom Modules for Every Model

Learn how React Native AI uses a unified provider model to switch between cloud and on-device LLMs without rewriting application logic, enabling reliable AI experiences online and offline.

cover

Mar 12Tutorial

Build Smarter Apps: Tool Calling & AI Orchestration Explained

Learn how on-device models in React Native can call tools, interact with external APIs, and return structured outputs you can use directly in application logic.

cover

Mar 5Tutorial

MLC LLM + React Native: On-Device AI Without the Pain

Learn how to run third-party on-device LLMs in React Native using MLC LLM. Choose your models, run them efficiently across platforms, and keep a unified JavaScript API with React Native AI.

cover

Feb 24Tutorial

Intelligent Fallbacks with Apple Intelligence in React Native

Learn how to use Apple’s built-in Foundation Models in React Native apps, reduce memory usage, and enable fast, fully on-device AI with React Native AI Apple.

cover

Feb 10Tutorial

What Is the React Native AI SDK? A Complete Intro & Quickstart

Learn why on-device AI matters for React Native apps, how local LLMs behave offline, and what problems React Native AI is designed to solve from day one.

cover

Jan 29Tutorial

Implementing an Android TurboModule with Kotlin

Learn how to implement a React Native TurboModule on Android, from the TypeScript spec and Codegen to the Kotlin implementation and package registration.

cover

Jan 19Tutorial

Working with Different Threads in Swift TurboModules

Learn how JavaScript, native module, background, and main threads interact in React Native TurboModules, and how to use GCD queues to run async work safely in Swift.

cover

Jan 9Tutorial

How to Add Type-Safe Constants to Swift TurboModules

Learn how to expose typed, immutable constants from a Swift TurboModule using Codegen, and get full end-to-end type safety between native and JavaScript.

cover

Dec 30Tutorial

Adding Event Emitters to Your TurboModule in Swift

Learn how to emit native events from a Swift TurboModule and subscribe to them in JavaScript, including proper memory management and Codegen integration.

cover

Dec 22Tutorial

Writing Your First TurboModule in Swift

Learn how to build a fully working TurboModule in Swift, integrate it with Codegen, and bridge it through Objective‑C inside an Expo app.