Skip to content

Commit

Permalink
Add react-native-oss-library-example package (facebook#43068)
Browse files Browse the repository at this point in the history
Summary:

This diff adds `react-native-oss-library-example` package.
It contains native module and native component example, and targets both the new and the old architecture. It has structure similar to many OSS React Native libraries, and is supposed to be used to test the integration with third-party libraries.

It is integrated with RNTester as the **OSS Library Example** screen.

{F1457510909}

**Change Background** tests native commands.
**Set Opacity** tests native props.
**Get Random Number** tests native module.

Changelog: [Internal]

Differential Revision: D50793835
  • Loading branch information
dmytrorykun authored and facebook-github-bot committed Feb 16, 2024
1 parent eca78c3 commit 78fb475
Show file tree
Hide file tree
Showing 22 changed files with 897 additions and 1 deletion.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Copyright (c) Meta Platforms, Inc. and affiliates.
#
# This source code is licensed under the MIT license found in the
# LICENSE file in the root directory of this source tree.

require 'json'

package = JSON.parse(File.read(File.join(__dir__, 'package.json')))

Pod::Spec.new do |s|
s.name = 'OSSLibraryExample'
s.version = package['version']
s.summary = package['description']
s.description = package['description']
s.homepage = package['homepage']
s.license = package['license']
s.platforms = min_supported_versions
s.author = 'Meta Platforms, Inc. and its affiliates'
s.source = { :git => package['repository'], :tag => "#{s.version}" }

s.source_files = 'ios/**/*.{h,m,mm,cpp}'

install_modules_dependencies(s)
end
9 changes: 9 additions & 0 deletions packages/react-native-oss-library-example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
## Important
This package is for testing only. It is a subject to frequent change. It does not represent the recomended structure for React native libraries. It should not be used as a referece for such purposes.

## Building
```
yarn install
yarn build
npx react-native codegen
```
50 changes: 50 additions & 0 deletions packages/react-native-oss-library-example/android/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

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.0.4")
}
}

apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'

android {
compileSdkVersion safeExtGet('compileSdkVersion', 31)

defaultConfig {
minSdkVersion safeExtGet('minSdkVersion', 23)
targetSdkVersion safeExtGet('targetSdkVersion', 31)
}

sourceSets {
main {
java.srcDirs += ['src', 'java']
}
}
}

repositories {
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
url "$projectDir/../node_modules/react-native/android"
}
mavenCentral()
google()
}

dependencies {
implementation 'com.facebook.react:react-native:+'
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.reactnative.osslibraryexample">
</manifest>
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.reactnative.osslibraryexample

import com.facebook.fbreact.specs.NativeSampleModuleSpec
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.module.annotations.ReactModule

@ReactModule(name = NativeSampleModuleSpec.NAME)
class NativeSampleModule(reactContext: ReactApplicationContext?) :
ReactContextBaseJavaModule(reactContext) {

override fun getName(): String = NAME

companion object {
const val NAME = "NativeSampleModule"
}

@ReactMethod
public fun getRandomNumber(): Int {
return (0..99).random()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.reactnative.osslibraryexample

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager

class OSSLibraryExamplePackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> =
listOf(NativeSampleModule(reactContext))

override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> =
listOf(SampleNativeComponentViewManager())
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.reactnative.osslibraryexample

import android.graphics.Color
import com.facebook.react.bridge.ReadableArray
import com.facebook.react.common.MapBuilder
import com.facebook.react.module.annotations.ReactModule
import com.facebook.react.uimanager.SimpleViewManager
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.ViewProps
import com.facebook.react.uimanager.annotations.ReactProp
import com.facebook.react.viewmanagers.SampleNativeComponentManagerInterface

/** Legacy View manager (non Fabric compatible) for {@link SampleNativeView} components. */
@ReactModule(name = SampleNativeComponentViewManager.REACT_CLASS)
internal class SampleNativeComponentViewManager :
SimpleViewManager<SampleNativeView>(), SampleNativeComponentManagerInterface<SampleNativeView> {

override fun getName(): String = REACT_CLASS

@ReactProp(name = ViewProps.OPACITY, defaultFloat = 1f)
override fun setOpacity(view: SampleNativeView, opacity: Float) {
super.setOpacity(view, opacity)
}

@ReactProp(name = ViewProps.COLOR)
fun setColor(view: SampleNativeView, color: String) {
view.setBackgroundColor(Color.parseColor(color))
}

@ReactProp(name = "cornerRadius")
fun setCornerRadius(view: SampleNativeView, cornerRadius: Float) {
if (cornerRadius !== null) {
view.setCornerRadius(cornerRadius)
}
}

override fun createViewInstance(reactContext: ThemedReactContext): SampleNativeView =
SampleNativeView(reactContext)

override fun changeBackgroundColor(view: SampleNativeView, color: String) {
view.setBackgroundColor(Color.parseColor(color))
}

override fun getExportedViewConstants(): Map<String, Any> = mapOf("PI" to 3.14)

override fun getExportedCustomBubblingEventTypeConstants(): Map<String, Any> {
return MapBuilder.builder<String, Any>()
.put(
"onColorChanged",
MapBuilder.of(
"phasedRegistrationNames",
MapBuilder.of("bubbled", "onColorChanged", "captured", "onColorChangedCapture")))
.build()
}

override fun receiveCommand(view: SampleNativeView, commandId: String, args: ReadableArray?) {
if (commandId.contentEquals("changeBackgroundColor")) {
val sentColor: Int = Color.parseColor(args?.getString(0))
view.setBackgroundColor(sentColor)
}
}

@Suppress("DEPRECATION") // We intentionally want to test against the legacy API here.
override fun receiveCommand(view: SampleNativeView, commandId: Int, args: ReadableArray?) {
when (commandId) {
COMMAND_CHANGE_BACKGROUND_COLOR -> {
val sentColor: Int = Color.parseColor(args?.getString(0))
view.setBackgroundColor(sentColor)
}
}
}

override fun getCommandsMap(): Map<String, Int> =
mapOf("changeBackgroundColor" to COMMAND_CHANGE_BACKGROUND_COLOR)

companion object {
const val REACT_CLASS = "SampleNativeComponent"
const val COMMAND_CHANGE_BACKGROUND_COLOR = 42
}

@ReactProp(name = "values")
override fun setValues(view: SampleNativeView, value: ReadableArray?) {
val values = mutableListOf<Int>()
value?.toArrayList()?.forEach { values.add((it as Double).toInt()) }
view.emitOnArrayChangedEvent(values)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

package com.reactnative.osslibraryexample

import android.graphics.Color
import android.graphics.drawable.GradientDrawable
import android.view.View
import com.facebook.react.bridge.Arguments
import com.facebook.react.bridge.ReactContext
import com.facebook.react.bridge.WritableArray
import com.facebook.react.bridge.WritableMap
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.uimanager.UIManagerHelper
import com.facebook.react.uimanager.events.Event
import com.facebook.react.uimanager.events.RCTEventEmitter

class SampleNativeView(context: ThemedReactContext) : View(context) {
private var currentColor = 0
private var background: GradientDrawable = GradientDrawable()
private var reactContext: ReactContext = context.reactApplicationContext

override fun setBackgroundColor(color: Int) {
if (color != currentColor) {
background.setColor(color)
currentColor = color
emitNativeEvent(color)
setBackground(background)
}
}

fun setCornerRadius(cornerRadius: Float) {
background.cornerRadius = cornerRadius
setBackground(background)
}

private fun emitNativeEvent(color: Int) {
val event = Arguments.createMap()
val hsv = FloatArray(3)
Color.colorToHSV(color, hsv)
val backgroundColor =
Arguments.createMap().apply {
putDouble("hue", hsv[0].toDouble())
putDouble("saturation", hsv[1].toDouble())
putDouble("brightness", hsv[2].toDouble())
putDouble("alpha", Color.alpha(color).toDouble())
}

event.putMap("backgroundColor", backgroundColor)

reactContext.getJSModule(RCTEventEmitter::class.java).receiveEvent(id, "onColorChanged", event)
}

fun emitOnArrayChangedEvent(ints: List<Int>) {
val newIntArray = Arguments.createArray()
val newBoolArray = Arguments.createArray()
val newFloatArray = Arguments.createArray()
val newDoubleArray = Arguments.createArray()
val newYesNoArray = Arguments.createArray()
val newStringArray = Arguments.createArray()
val newObjectArray = Arguments.createArray()
val newArrayArray = Arguments.createArray()

for (i in ints) {
newIntArray.pushInt(i * 2)
newBoolArray.pushBoolean(i % 2 == 1)
newFloatArray.pushDouble(i * 3.14)
newDoubleArray.pushDouble(i / 3.14)
newYesNoArray.pushString(if (i % 2 == 1) "yep" else "nope")
newStringArray.pushString(i.toString())

val latLon = Arguments.createMap()
latLon.putDouble("lat", -1.0 * i)
latLon.putDouble("lon", 2.0 * i)
newObjectArray.pushMap(latLon)

val innerArray: WritableArray = Arguments.createArray()
innerArray.pushInt(i)
innerArray.pushInt(i)
innerArray.pushInt(i)
newArrayArray.pushArray(innerArray)
}

val payload =
Arguments.createMap().apply {
putArray("values", newIntArray)
putArray("boolValues", newBoolArray)
putArray("floats", newFloatArray)
putArray("doubles", newDoubleArray)
putArray("yesNos", newYesNoArray)
putArray("strings", newStringArray)
putArray("latLons", newObjectArray)
putArray("multiArrays", newArrayArray)
}

val reactContext = context as ReactContext
val surfaceId = UIManagerHelper.getSurfaceId(reactContext)
val eventDispatcher = UIManagerHelper.getEventDispatcherForReactTag(reactContext, id)
val event = OnIntArrayChangedEvent(surfaceId, id, payload)

eventDispatcher?.dispatchEvent(event)
}

inner class OnIntArrayChangedEvent(
surfaceId: Int,
viewId: Int,
private val payload: WritableMap
) : Event<OnIntArrayChangedEvent>(surfaceId, viewId) {
override fun getEventName() = "topIntArrayChanged"

override fun getEventData() = payload
}
}
10 changes: 10 additions & 0 deletions packages/react-native-oss-library-example/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
/**
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

module.exports = {
presets: [['module:@react-native/babel-preset', {disableStaticViewConfigsCodegen: false}]],
};
Loading

0 comments on commit 78fb475

Please sign in to comment.