封装第三方原生库
了解如何使用 Expo Modules API 为两个单独的原生库创建一个简单的封装器。
Expo 模块使得在 React Native 项目中轻松使用为 Android 和 iOS 构建的原生外部库成为可能。本教程重点介绍如何利用 Expo Modules API 使用可在两个原生平台上访问的两个类似库来创建径向图表。
¥Expo modules make it possible to easily use native, external libraries built for Android and iOS in React Native projects. This tutorial focuses on utilizing the Expo Modules API to create radial charts using two similar libraries accessible on both native platforms.
iOS 库的灵感来自 Android 库,因此它们都具有相似的 API 和功能。这使它们成为本教程的一个很好的例子。
¥The iOS library is inspired by the Android library, so they both have similar API and functionality. This makes them a good example for this tutorial.

In this video you will learn how to wrap native libraries using Expo Modules API.
1
Create a new module
The following steps assume that the new module is created inside a new Expo project. However, you can create a new module inside an existing project by following the alternative instructions.
Start with a new project
Create a new empty Expo module that can be published on npm and utilized in any Expo app by running the following command:
-
npx create-expo-module expo-radial-chart
Tip: If you aren't going to ship this library, press return for all the prompts to accept the default values in the terminal window.
Now, open the newly created expo-radial-chart
directory to start editing the native code.
Start with an existing project
Alternatively, you can use the new module as a view inside the existing project. Run the following command in your project's directory:
-
npx create-expo-module --local expo-radial-chart
Now, open the newly created modules/expo-radial-chart
directory to start editing the native code.
2
Run the example project
To verify that everything is functioning correctly, let's run the example project. In a terminal window, start the TypeScript compiler to watch for changes and rebuild the module JavaScript:
# Run this in the root of the project to start the TypeScript compiler
-
npm run build
In another terminal window, compile and run the example app:
-
cd example
# Run the example app on Android
-
npx expo run:android
# Run the example app on iOS
-
npx expo run:ios
3
Add native dependencies
Add the native dependencies to the module by editing the android/build.gradle and ios/ExpoRadialChart.podspec files:
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
+ implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0'
}
s.static_framework = true
s.dependency 'ExpoModulesCore'
+ s.dependency 'DGCharts', '~> 5.1.0'
# Swift/Objective-C compatibility
Are you trying to use a .aar
dependency?
Inside the android directory, create another directory called libs and place the .aar file inside it. Then, add the file as a Gradle project from autolinking:
"android": {
+ "gradleAarProjects": [
+ {
+ "name": "test-aar",
+ "aarFilePath": "android/libs/test.aar"
+ }
+ ],
"modules": [
Finally, add the dependency to the dependencies
list in the android/build.gradle file, using the dependency's specified name with ${project.name}$
prefix:
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
+ implementation project(":${project.name}\$test-aar")
}
Inside the android directory, create another directory called libs and place the .aar file inside it. Then, add the directory as a repository:
repositories {
mavenCentral()
+ flatDir {
+ dirs 'libs'
+ }
}
Finally, add the dependency to the dependencies
list. Instead of the filename, use the package path, which includes the @aar
at the end:
dependencies {
implementation project(':expo-modules-core')
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${getKotlinVersion()}"
+ implementation 'com.github.PhilJay:MPAndroidChart:v3.1.0@aar'
}
Are you trying to use an .xcframework
or .framework
dependency?
On iOS, you can also use dependencies bundled as a framework by using the vendored_frameworks
config option.
s.static_framework = true
s.dependency 'ExpoModulesCore'
+ s.vendored_frameworks = 'Frameworks/MyFramework.framework'
# Swift/Objective-C compatibility
Note: The file pattern used to specify the path to the framework is relative to the podspec file, and doesn't support traversing the parent directory (..
), meaning you need to place the framework inside the ios directory (or a subdirectory of ios).
Once the framework is added, make sure that the source_files
option file pattern doesn't match any files inside the framework. One way to achieve this is to move your iOS source Swift files (that is ExpoRadialChartView.swift
and ExpoRadialChartModule.swift
) into a src directory separate from where you placed your framework(s) and update the source_files
option to only match the src directory:
- s.source_files = '**/*.{h,m,mm,swift,hpp,cpp}'
+ s.source_files = 'src/**/*.{h,m,mm,swift,hpp,cpp}'
Your ios directory should end up with a file structure similar to this:
Frameworks
MyFramework.framework
src
ExpoRadialChartView.swift
ExpoRadialChartModule.swift
ExpoRadialChart.podspec
4
Define an API
To use the module in the app, define the types for the props. It accepts a list of series — each with a color and a percentage value.
import { ViewStyle } from 'react-native/types';
export type ChangeEventPayload = {
value: string;
};
type Series = {
color: string;
percentage: number;
};
export type ExpoRadialChartViewProps = {
style?: ViewStyle;
data: Series[];
};
Since the module isn't implemented for web in this example, let's replace the src/ExpoRadialChartView.web.tsx file:
import * as React from 'react';
export default function ExpoRadialChartView() {
return <div>Not implemented</div>;
}
5
Implement the module on Android
Now you can implement the native functionality by editing the placeholder files with the following changes:
- Create a
PieChart
instance and set itslayoutParams
to match the parent view. Then, add it to the view hierarchy using theaddView
function. - Define a
setChartData
function that accepts a list ofSeries
objects. You can iterate over the list, create aPieEntry
for each series and store the colors in a separate list. - Create a
PieDataSet
, use it to create aPieData
object, and set it as data on thePieChart
instance.
package expo.modules.radialchart
import android.content.Context
import android.graphics.Color
import androidx.annotation.ColorInt
import com.github.mikephil.charting.charts.PieChart
import com.github.mikephil.charting.data.PieData
import com.github.mikephil.charting.data.PieDataSet
import com.github.mikephil.charting.data.PieEntry
import expo.modules.kotlin.AppContext
import expo.modules.kotlin.records.Field
import expo.modules.kotlin.records.Record
import expo.modules.kotlin.views.ExpoView
class Series : Record {
@Field
val color: String = "#ff0000"
@Field
val percentage: Float = 0.0f
}
class ExpoRadialChartView(context: Context, appContext: AppContext) : ExpoView(context, appContext) {
internal val chartView = PieChart(context).also {
it.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
addView(it)
}
fun setChartData(data: ArrayList<Series>) {
val entries: ArrayList<PieEntry> = ArrayList()
val colors: ArrayList<Int> = ArrayList()
for (series in data) {
entries.add(PieEntry(series.percentage))
colors.add(Color.parseColor(series.color))
}
val dataSet = PieDataSet(entries, "DataSet");
dataSet.colors = colors;
val pieData = PieData(dataSet);
chartView.data = pieData;
chartView.invalidate();
}
}
You also need to use the Prop
function to define the data
prop and call the native setChartData
function when the prop changes:
package expo.modules.radialchart
import expo.modules.kotlin.modules.Module
import expo.modules.kotlin.modules.ModuleDefinition
class ExpoRadialChartModule : Module() {
override fun definition() = ModuleDefinition {
Name("ExpoRadialChart")
View(ExpoRadialChartView::class) {
Prop("data") { view: ExpoRadialChartView, prop: ArrayList<Series> ->
view.setChartData(prop);
}
}
}
}
6
Implement the module on iOS
Now you can implement the native functionality by editing the placeholder files with the following changes:
- Create a new
PieChartView
instance and use theaddSubview
function to add it to the view hierarchy. - Set the
clipsToBounds
property and override thelayoutSubviews
function to make sure the chart view is always the same size as the parent view. - Create a
setChartData
function that accepts a list of series, creates aPieChartDataSet
instance with the data, and assigns it to thedata
property of thePieChartView
instance.
import ExpoModulesCore
import DGCharts
struct Series: Record {
@Field
var color: UIColor = UIColor.black
@Field
var percentage: Double = 0
}
class ExpoRadialChartView: ExpoView {
let chartView = PieChartView()
required init(appContext: AppContext? = nil) {
super.init(appContext: appContext)
clipsToBounds = true
addSubview(chartView)
}
override func layoutSubviews() {
chartView.frame = bounds
}
func setChartData(data: [Series]) {
let set1 = PieChartDataSet(entries: data.map({ (series: Series) -> PieChartDataEntry in
return PieChartDataEntry(value: series.percentage)
}))
set1.colors = data.map({ (series: Series) -> UIColor in
return series.color
})
let chartData: PieChartData = [set1]
chartView.data = chartData
}
}
You also need to use the Prop
function to define the data
prop and call the native setChartData
function when the prop changes:
import ExpoModulesCore
public class ExpoRadialChartModule: Module {
public func definition() -> ModuleDefinition {
Name("ExpoRadialChart")
View(ExpoRadialChartView.self) {
Prop("data") { (view: ExpoRadialChartView, prop: [Series]) in
view.setChartData(data: prop)
}
}
}
}
7
Write an example app to use the module
You can update the app inside the example directory to test the module. Use the ExpoRadialChartView
component to render a pie chart with three slices:
import { ExpoRadialChartView } from 'expo-radial-chart';
import { StyleSheet } from 'react-native';
export default function App() {
return (
<ExpoRadialChartView
style={styles.container}
data={[
{
color: '#ff0000',
percentage: 0.5,
},
{
color: '#00ff00',
percentage: 0.2,
},
{
color: '#0000ff',
percentage: 0.3,
},
]}
/>
);
}
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
Tip: If you created the module inside an existing app, make sure to import it directly from your modules directory by using a relative import:import { ExpoRadialChartView } from '../modules/expo-radial-chart';
8
恭喜!你已经使用 Expo Modules API 为两个单独的第三方原生库创建了第一个简单封装器。
¥Congratulations! You have created your first simple wrapper around two separate third-party native libraries using Expo Modules API.
下一步
¥Next step
使用 Kotlin 和 Swift 创建原生模块的参考。