17 | #elif __has_include(“RCTEventDispatcher.h”)
18 | #import “RCTEventDispatcher.h”
19 | #else
20 | #import “React/RCTEventDispatcher.h” // Required when used as a Pod in a Swift project
21 | #endif
22 |
23 | @implementation StartupPerformanceTrace
24 | @synthesize bridge = _bridge;
25 |
26 | static FIRTrace *_startupTrace = nil;
27 |
28 | + (FIRTrace *)startupTrace {
29 | return _startupTrace;
30 | }
31 |
32 | RCT_EXPORT_MODULE();
33 |
34 | + (void)start {
35 | _startupTrace = [[FIRPerformance sharedInstance] traceWithName:@"STARTUP_JS"];
36 | [_startupTrace start];
37 | }
38 |
39 | RCT_EXPORT_METHOD(stop)
40 | {
41 | if (_startupTrace) {
42 | [_startupTrace stop];
43 | }
44 | }
45 |
46 | - (dispatch_queue_t)methodQueue
47 | {
48 | return dispatch_get_main_queue();
49 | }
50 |
51 | + (BOOL)requiresMainQueueSetup { return YES; }
52 |
53 | @end
54 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/newarchitecture/components/MainComponentsRegistry.java:
--------------------------------------------------------------------------------
1 | package com.example.newarchitecture.components;
2 |
3 | import com.facebook.jni.HybridData;
4 | import com.facebook.proguard.annotations.DoNotStrip;
5 | import com.facebook.react.fabric.ComponentFactory;
6 | import com.facebook.soloader.SoLoader;
7 |
8 | /**
9 | * Class responsible to load the custom Fabric Components. This class has native methods and needs a
10 | * corresponding C++ implementation/header file to work correctly (already placed inside the jni/
11 | * folder for you).
12 | *
13 | * Please note that this class is used ONLY if you opt-in for the New Architecture (see the
14 | * `newArchEnabled` property). Is ignored otherwise.
15 | */
16 | @DoNotStrip
17 | public class MainComponentsRegistry {
18 | static {
19 | SoLoader.loadLibrary("fabricjni");
20 | }
21 |
22 | @DoNotStrip private final HybridData mHybridData;
23 |
24 | @DoNotStrip
25 | private native HybridData initHybrid(ComponentFactory componentFactory);
26 |
27 | @DoNotStrip
28 | private MainComponentsRegistry(ComponentFactory componentFactory) {
29 | mHybridData = initHybrid(componentFactory);
30 | }
31 |
32 | @DoNotStrip
33 | public static MainComponentsRegistry register(ComponentFactory componentFactory) {
34 | return new MainComponentsRegistry(componentFactory);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/flipper-native/android/src/main/java/tech/bam/rnperformance/flipper/FlipperPerformancePluginModule.java:
--------------------------------------------------------------------------------
1 | package tech.bam.rnperformance.flipper;
2 |
3 | import android.os.Handler;
4 |
5 | import androidx.annotation.NonNull;
6 |
7 | import com.facebook.react.bridge.Promise;
8 | import com.facebook.react.bridge.ReactApplicationContext;
9 | import com.facebook.react.bridge.ReactContextBaseJavaModule;
10 | import com.facebook.react.bridge.ReactMethod;
11 | import com.facebook.react.module.annotations.ReactModule;
12 |
13 | @ReactModule(name = FlipperPerformancePluginModule.NAME)
14 | public class FlipperPerformancePluginModule extends ReactContextBaseJavaModule {
15 | public static final String NAME = "FlipperPerformancePlugin";
16 |
17 | public FlipperPerformancePluginModule(ReactApplicationContext reactContext) {
18 | super(reactContext);
19 | }
20 |
21 | @Override
22 | @NonNull
23 | public String getName() {
24 | return NAME;
25 | }
26 |
27 | @ReactMethod
28 | public void killUIThread(int intensity, Promise promise) {
29 | final Handler mainHandler = new Handler(getReactApplicationContext().getMainLooper());
30 | mainHandler.post(() -> promise.resolve(fibonacci(intensity)));
31 | }
32 |
33 | private int fibonacci(int n) {
34 | return n < 1 ? 0 : n <= 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/expo-example/App.js:
--------------------------------------------------------------------------------
1 | import { SafeAreaView, Text, TouchableOpacity, View } from "react-native";
2 |
3 | import {
4 | killJSThread,
5 | killUIThread,
6 | } from "react-native-flipper-performance-plugin";
7 |
8 | const Button = ({ title, onPress, style, testID }) => (
9 |
22 | {title}
23 |
24 | );
25 |
26 | const App = () => {
27 | return (
28 |
29 |
38 | killJSThread(40)}
42 | style={{ marginBottom: 40, backgroundColor: "#1565c0" }}
43 | />
44 | killUIThread(40)}
48 | style={{ backgroundColor: "#7b1fa2" }}
49 | />
50 |
51 |
52 | );
53 | };
54 |
55 | export default App;
56 |
--------------------------------------------------------------------------------
/example/android/app/_BUCK:
--------------------------------------------------------------------------------
1 | # To learn about Buck see [Docs](https://buckbuild.com/).
2 | # To run your application with Buck:
3 | # - install Buck
4 | # - `npm start` - to start the packager
5 | # - `cd android`
6 | # - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
7 | # - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
8 | # - `buck install -r android/app` - compile, install and run application
9 | #
10 |
11 | load(":build_defs.bzl", "create_aar_targets", "create_jar_targets")
12 |
13 | lib_deps = []
14 |
15 | create_aar_targets(glob(["libs/*.aar"]))
16 |
17 | create_jar_targets(glob(["libs/*.jar"]))
18 |
19 | android_library(
20 | name = "all-libs",
21 | exported_deps = lib_deps,
22 | )
23 |
24 | android_library(
25 | name = "app-code",
26 | srcs = glob([
27 | "src/main/java/**/*.java",
28 | ]),
29 | deps = [
30 | ":all-libs",
31 | ":build_config",
32 | ":res",
33 | ],
34 | )
35 |
36 | android_build_config(
37 | name = "build_config",
38 | package = "com.example",
39 | )
40 |
41 | android_resource(
42 | name = "res",
43 | package = "com.example",
44 | res = "src/main/res",
45 | )
46 |
47 | android_binary(
48 | name = "app",
49 | keystore = "//android/keystores:debug",
50 | manifest = "src/main/AndroidManifest.xml",
51 | package_type = "debug",
52 | deps = [
53 | ":app-code",
54 | ],
55 | )
56 |
--------------------------------------------------------------------------------
/flipper-native/react-native-flipper-performance-plugin.podspec:
--------------------------------------------------------------------------------
1 | require "json"
2 |
3 | package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4 |
5 | folly_compiler_flags = '-DFLIPPER_OSS=1 -DFB_SONARKIT_ENABLED=1 -DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -DFOLLY_HAVE_LIBGFLAGS=0 -DFOLLY_HAVE_LIBJEMALLOC=0 -DFOLLY_HAVE_PREADV=0 -DFOLLY_HAVE_PWRITEV=0 -DFOLLY_HAVE_TFO=0 -DFOLLY_USE_SYMBOLIZER=0'
6 |
7 | Pod::Spec.new do |s|
8 | s.name = "react-native-flipper-performance-plugin"
9 | s.version = package["version"]
10 | s.summary = package["description"]
11 | s.homepage = package["homepage"]
12 | s.license = package["license"]
13 | s.authors = package["author"]
14 |
15 | s.platforms = { :ios => "10.0" }
16 | s.source = { :git => "https://github.com/bamlab/react-native-performance", :tag => "#{s.version}" }
17 |
18 | s.source_files = "ios/**/*.{h,m,mm}"
19 |
20 | s.dependency "React-Core"
21 |
22 | # This subspec is necessary since FBDefines.h is imported as
23 | # inside SKMacros.h, which is a public header file. Defining this directory as a
24 | # subspec with header_dir = 'FBDefines' allows this to work, even though it wouldn't
25 | # generally (you would need to import )
26 | s.subspec 'FBDefines' do |ss|
27 | ss.header_dir = 'FBDefines'
28 | ss.compiler_flags = folly_compiler_flags
29 | ss.source_files = 'iOS/FBDefines/**/*.h'
30 | ss.public_header_files = 'iOS/FBDefines/**/*.h'
31 | end
32 | end
33 |
--------------------------------------------------------------------------------
/example/ios/Podfile:
--------------------------------------------------------------------------------
1 | require_relative '../node_modules/react-native/scripts/react_native_pods'
2 | require_relative '../node_modules/@react-native-community/cli-platform-ios/native_modules'
3 |
4 | platform :ios, '12.4'
5 | install! 'cocoapods', :deterministic_uuids => false
6 |
7 | target 'example' do
8 | config = use_native_modules!
9 |
10 | # Flags change depending on the env values.
11 | flags = get_default_flags()
12 |
13 | use_react_native!(
14 | :path => config[:reactNativePath],
15 | # Hermes is now enabled by default. Disable by setting this flag to false.
16 | # Upcoming versions of React Native may rely on get_default_flags(), but
17 | # we make it explicit here to aid in the React Native upgrade process.
18 | :hermes_enabled => true,
19 | :fabric_enabled => flags[:fabric_enabled],
20 | # Enables Flipper.
21 | #
22 | # Note that if you have use_frameworks! enabled, Flipper will not work and
23 | # you should disable the next line.
24 | :flipper_configuration => FlipperConfiguration.enabled,
25 | # An absolute path to your application root.
26 | :app_path => "#{Pod::Config.instance.installation_root}/.."
27 | )
28 |
29 | target 'exampleTests' do
30 | inherit! :complete
31 | # Pods for testing
32 | end
33 |
34 | post_install do |installer|
35 | react_native_post_install(
36 | installer,
37 | # Set `mac_catalyst_enabled` to `true` in order to apply patches
38 | # necessary for Mac Catalyst builds
39 | :mac_catalyst_enabled => false
40 | )
41 | __apply_Xcode_12_5_M1_post_install_workaround(installer)
42 | end
43 | end
44 |
--------------------------------------------------------------------------------
/example/android/app/src/main/jni/MainApplicationTurboModuleManagerDelegate.cpp:
--------------------------------------------------------------------------------
1 | #include "MainApplicationTurboModuleManagerDelegate.h"
2 | #include "MainApplicationModuleProvider.h"
3 |
4 | namespace facebook {
5 | namespace react {
6 |
7 | jni::local_ref
8 | MainApplicationTurboModuleManagerDelegate::initHybrid(
9 | jni::alias_ref) {
10 | return makeCxxInstance();
11 | }
12 |
13 | void MainApplicationTurboModuleManagerDelegate::registerNatives() {
14 | registerHybrid({
15 | makeNativeMethod(
16 | "initHybrid", MainApplicationTurboModuleManagerDelegate::initHybrid),
17 | makeNativeMethod(
18 | "canCreateTurboModule",
19 | MainApplicationTurboModuleManagerDelegate::canCreateTurboModule),
20 | });
21 | }
22 |
23 | std::shared_ptr
24 | MainApplicationTurboModuleManagerDelegate::getTurboModule(
25 | const std::string &name,
26 | const std::shared_ptr &jsInvoker) {
27 | // Not implemented yet: provide pure-C++ NativeModules here.
28 | return nullptr;
29 | }
30 |
31 | std::shared_ptr
32 | MainApplicationTurboModuleManagerDelegate::getTurboModule(
33 | const std::string &name,
34 | const JavaTurboModule::InitParams ¶ms) {
35 | return MainApplicationModuleProvider(name, params);
36 | }
37 |
38 | bool MainApplicationTurboModuleManagerDelegate::canCreateTurboModule(
39 | const std::string &name) {
40 | return getTurboModule(name, nullptr) != nullptr ||
41 | getTurboModule(name, {.moduleName = name}) != nullptr;
42 | }
43 |
44 | } // namespace react
45 | } // namespace facebook
46 |
--------------------------------------------------------------------------------
/example/.flowconfig:
--------------------------------------------------------------------------------
1 | [ignore]
2 | ; We fork some components by platform
3 | .*/*[.]android.js
4 |
5 | ; Ignore "BUCK" generated dirs
6 | /\.buckd/
7 |
8 | ; Ignore polyfills
9 | node_modules/react-native/Libraries/polyfills/.*
10 |
11 | ; Flow doesn't support platforms
12 | .*/Libraries/Utilities/LoadingView.js
13 |
14 | .*/node_modules/resolve/test/resolver/malformed_package_json/package\.json$
15 |
16 | [untyped]
17 | .*/node_modules/@react-native-community/cli/.*/.*
18 |
19 | [include]
20 |
21 | [libs]
22 | node_modules/react-native/interface.js
23 | node_modules/react-native/flow/
24 |
25 | [options]
26 | emoji=true
27 |
28 | exact_by_default=true
29 |
30 | format.bracket_spacing=false
31 |
32 | module.file_ext=.js
33 | module.file_ext=.json
34 | module.file_ext=.ios.js
35 |
36 | munge_underscores=true
37 |
38 | module.name_mapper='^react-native/\(.*\)$' -> '/node_modules/react-native/\1'
39 | module.name_mapper='^@?[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> '/node_modules/react-native/Libraries/Image/RelativeImageStub'
40 |
41 | suppress_type=$FlowIssue
42 | suppress_type=$FlowFixMe
43 | suppress_type=$FlowFixMeProps
44 | suppress_type=$FlowFixMeState
45 |
46 | [lints]
47 | sketchy-null-number=warn
48 | sketchy-null-mixed=warn
49 | sketchy-number=warn
50 | untyped-type-import=warn
51 | nonstrict-import=warn
52 | deprecated-type=warn
53 | unsafe-getters-setters=warn
54 | unnecessary-invariant=warn
55 |
56 | [strict]
57 | deprecated-type
58 | nonstrict-import
59 | sketchy-null
60 | unclear-type
61 | unsafe-getters-setters
62 | untyped-import
63 | untyped-type-import
64 |
65 | [version]
66 | ^0.182.0
67 |
--------------------------------------------------------------------------------
/example/ios/example/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleDisplayName
8 | example
9 | CFBundleExecutable
10 | $(EXECUTABLE_NAME)
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | 1.0
21 | CFBundleSignature
22 | ????
23 | CFBundleVersion
24 | 1
25 | LSRequiresIPhoneOS
26 |
27 | NSAppTransportSecurity
28 |
29 | NSExceptionDomains
30 |
31 | localhost
32 |
33 | NSExceptionAllowsInsecureHTTPLoads
34 |
35 |
36 |
37 |
38 | NSLocationWhenInUseUsageDescription
39 |
40 | UILaunchStoryboardName
41 | LaunchScreen
42 | UIRequiredDeviceCapabilities
43 |
44 | armv7
45 |
46 | UISupportedInterfaceOrientations
47 |
48 | UIInterfaceOrientationPortrait
49 | UIInterfaceOrientationLandscapeLeft
50 | UIInterfaceOrientationLandscapeRight
51 |
52 | UIViewControllerBasedStatusBarAppearance
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/example/App.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable react-native/no-inline-styles */
2 | import React from 'react';
3 | import {
4 | SafeAreaView,
5 | StatusBar,
6 | StyleSheet,
7 | Text,
8 | TouchableOpacity,
9 | useColorScheme,
10 | View,
11 | } from 'react-native';
12 |
13 | import {
14 | killJSThread,
15 | killUIThread,
16 | } from 'react-native-flipper-performance-plugin';
17 |
18 | const Button = ({title, onPress, style, testID}) => (
19 |
31 | {title}
32 |
33 | );
34 |
35 | const App = () => {
36 | const isDarkMode = useColorScheme() === 'dark';
37 |
38 | return (
39 |
40 |
41 |
49 | killJSThread(40)}
53 | style={{marginBottom: 40, backgroundColor: '#1565c0'}}
54 | />
55 | killUIThread(40)}
59 | style={{backgroundColor: '#7b1fa2'}}
60 | />
61 |
62 |
63 | );
64 | };
65 |
66 | export default App;
67 |
--------------------------------------------------------------------------------
/react-native-startup-trace/README.md:
--------------------------------------------------------------------------------
1 |
2 | # react-native-startup-trace
3 |
4 | :warning: **You need Firebase performance installed on your project**
5 | See [the Firebase docs](https://rnfirebase.io/perf/usage)
6 |
7 | ## Getting started
8 |
9 | `$ npm install react-native-startup-trace --save`
10 |
11 | ## Usage
12 |
13 | In `ios/AppDelegate.m`:
14 |
15 | ```objc
16 | #import
17 |
18 | ...
19 |
20 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
21 | {
22 | ...
23 |
24 | // After Firebase initialization
25 | [StartupPerformanceTrace start];
26 |
27 | ...
28 | return YES;
29 | }
30 | ```
31 |
32 | In `MainApplication.java`
33 |
34 | ```java
35 | import tech.bam.rnperformance.startuptrace.StartupTraceModule;
36 |
37 | ...
38 |
39 | @Override
40 | public void onCreate() {
41 | super.onCreate();
42 | SoLoader.init(this, /* native exopackage */ false);
43 |
44 | StartupTraceModule.start();
45 | }
46 | ```
47 |
48 | In `App.js`
49 | ```javascript
50 | import { useStopStartupTrace } from 'react-native-startup-trace';
51 |
52 | const App = () => {
53 | useStopStartupTrace();
54 |
55 | return (
56 | ...
57 | )
58 | };
59 | ```
60 |
61 | ### Test it works
62 |
63 | #### Android
64 |
65 | Follow [the Firebase Android doc step 4](https://firebase.google.com/docs/perf-mon/get-started-android#view-log-messages) to view the logs.
66 | Then run:
67 | ```
68 | adb logcat | grep STARTUP_JS
69 | ```
70 | And start the app.
71 |
72 | You should see something similar to:
73 |
74 | ```
75 | FirebasePerformance: Logging trace metric - STARTUP_JS 2423.1650ms
76 | ```
77 |
78 | ## Usage with jest
79 |
80 | Import the mock in the setupFiles of your `jest.config.js`:
81 |
82 | ```javascript
83 | setupFiles: ["./node_modules/react-native-startup-trace/jest.setup.js"];
84 | ```
85 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/MainActivity.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import com.facebook.react.ReactActivity;
4 | import com.facebook.react.ReactActivityDelegate;
5 | import com.facebook.react.ReactRootView;
6 |
7 | public class MainActivity extends ReactActivity {
8 |
9 | /**
10 | * Returns the name of the main component registered from JavaScript. This is used to schedule
11 | * rendering of the component.
12 | */
13 | @Override
14 | protected String getMainComponentName() {
15 | return "example";
16 | }
17 |
18 | /**
19 | * Returns the instance of the {@link ReactActivityDelegate}. There the RootView is created and
20 | * you can specify the renderer you wish to use - the new renderer (Fabric) or the old renderer
21 | * (Paper).
22 | */
23 | @Override
24 | protected ReactActivityDelegate createReactActivityDelegate() {
25 | return new MainActivityDelegate(this, getMainComponentName());
26 | }
27 |
28 | public static class MainActivityDelegate extends ReactActivityDelegate {
29 | public MainActivityDelegate(ReactActivity activity, String mainComponentName) {
30 | super(activity, mainComponentName);
31 | }
32 |
33 | @Override
34 | protected ReactRootView createRootView() {
35 | ReactRootView reactRootView = new ReactRootView(getContext());
36 | // If you opted-in for the New Architecture, we enable the Fabric Renderer.
37 | reactRootView.setIsFabric(BuildConfig.IS_NEW_ARCHITECTURE_ENABLED);
38 | return reactRootView;
39 | }
40 |
41 | @Override
42 | protected boolean isConcurrentRootEnabled() {
43 | // If you opted-in for the New Architecture, we enable Concurrent Root (i.e. React 18).
44 | // More on this on https://reactjs.org/blog/2022/03/29/react-v18.html
45 | return BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/flipper-desktop/src/utils/getThreadMeasures.ts:
--------------------------------------------------------------------------------
1 | import { Measure, ThreadMeasure, ThreadType } from "../types/Measure";
2 |
3 | const sanitizeData = ({ frameCount, time }: ThreadMeasure) => {
4 | if (frameCount > (60 * time) / 1000) {
5 | return {
6 | frameCount: (60 * time) / 1000,
7 | time,
8 | };
9 | }
10 |
11 | if (frameCount < 0) {
12 | return {
13 | frameCount: 0,
14 | time,
15 | };
16 | }
17 |
18 | return {
19 | frameCount,
20 | time,
21 | };
22 | };
23 |
24 | const INTERVAL = 500;
25 | const ERROR_MARGIN = 50;
26 |
27 | const splitMeasure = (measure: ThreadMeasure): ThreadMeasure[] => {
28 | /**
29 | * Here we handle the thread reporting the measures being dead
30 | * In that case we could be a while with no measures actually reported
31 | *
32 | * On Android UI thread reports both UI and JS
33 | * On iOS, UI reports UI and JS reports JS
34 | *
35 | * It could happen though that the measure is a bit longer than 500ms, I experienced measures of 502ms
36 | * which doesn't necessarily mean that the JS thread is dead
37 | * so we introduce an error margin
38 | */
39 | if (measure.time <= INTERVAL + ERROR_MARGIN) {
40 | return [measure];
41 | }
42 |
43 | return [
44 | { frameCount: 0, time: INTERVAL },
45 | ...splitMeasure({
46 | frameCount: measure.frameCount,
47 | time: measure.time - INTERVAL,
48 | }),
49 | ];
50 | };
51 |
52 | export const getThreadMeasures = (
53 | measures: Measure[],
54 | thread: ThreadType
55 | ): ThreadMeasure[] => {
56 | return measures
57 | .filter((measure) => measure.thread === thread)
58 | .map((measure) => ({ frameCount: measure.frameCount, time: measure.time }))
59 | .reduce((aggr, measure) => {
60 | return [...aggr, ...splitMeasure(measure)];
61 | }, [])
62 | .map(sanitizeData);
63 | };
64 |
--------------------------------------------------------------------------------
/example/android/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext {
5 | buildToolsVersion = "31.0.0"
6 | minSdkVersion = 21
7 | compileSdkVersion = 31
8 | targetSdkVersion = 31
9 |
10 | if (System.properties['os.arch'] == "aarch64") {
11 | // For M1 Users we need to use the NDK 24 which added support for aarch64
12 | ndkVersion = "24.0.8215888"
13 | } else {
14 | // Otherwise we default to the side-by-side NDK version from AGP.
15 | ndkVersion = "21.4.7075529"
16 | }
17 | }
18 | repositories {
19 | google()
20 | mavenCentral()
21 | }
22 | dependencies {
23 | classpath("com.android.tools.build:gradle:7.2.1")
24 | classpath("com.facebook.react:react-native-gradle-plugin")
25 | classpath("de.undercouch:gradle-download-task:5.0.1")
26 | // NOTE: Do not place your application dependencies here; they belong
27 | // in the individual module build.gradle files
28 | }
29 | }
30 |
31 | allprojects {
32 | repositories {
33 | maven {
34 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
35 | url("$rootDir/../node_modules/react-native/android")
36 | }
37 | maven {
38 | // Android JSC is installed from npm
39 | url("$rootDir/../node_modules/jsc-android/dist")
40 | }
41 | mavenCentral {
42 | // We don't want to fetch react-native from Maven Central as there are
43 | // older versions over there.
44 | content {
45 | excludeGroup "com.facebook.react"
46 | }
47 | }
48 | google()
49 | maven { url 'https://www.jitpack.io' }
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/example/android/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx512m -XX:MaxMetaspaceSize=256m
13 | org.gradle.jvmargs=-Xmx2048m -XX:MaxMetaspaceSize=512m
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
19 |
20 | # AndroidX package structure to make it clearer which packages are bundled with the
21 | # Android operating system, and which are packaged with your app's APK
22 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
23 | android.useAndroidX=true
24 | # Automatically convert third-party libraries to use AndroidX
25 | android.enableJetifier=true
26 |
27 | # Version of flipper SDK to use with React Native
28 | FLIPPER_VERSION=0.125.0
29 |
30 | # Use this property to specify which architecture you want to build.
31 | # You can also override it from the CLI using
32 | # ./gradlew -PreactNativeArchitectures=x86_64
33 | reactNativeArchitectures=armeabi-v7a,arm64-v8a,x86,x86_64
34 |
35 | # Use this property to enable support to the new architecture.
36 | # This will allow you to use TurboModules and the Fabric render in
37 | # your application. You should enable this flag either if you want
38 | # to write custom TurboModules/Fabric components OR use libraries that
39 | # are providing them.
40 | newArchEnabled=false
41 |
--------------------------------------------------------------------------------
/flipper-desktop/src/components/Chart.tsx:
--------------------------------------------------------------------------------
1 | import React, { useMemo, useEffect, ComponentProps } from "react";
2 | import ReactApexChart from "react-apexcharts";
3 | import ApexCharts from "apexcharts";
4 |
5 | export const Chart = ({
6 | data,
7 | title,
8 | height,
9 | interval,
10 | timeLimit,
11 | color,
12 | }: {
13 | data: { x: number; y: number }[];
14 | title: string;
15 | height: number;
16 | interval: number;
17 | timeLimit?: number | null;
18 | color?: string;
19 | }) => {
20 | const series = useMemo(
21 | () => [
22 | {
23 | name: title,
24 | data: data,
25 | },
26 | ],
27 | [data, interval, title]
28 | );
29 |
30 | const options = useMemo["options"]>(
31 | () => ({
32 | chart: {
33 | id: title,
34 | height: 350,
35 | type: "line",
36 | animations: {
37 | enabled: true,
38 | easing: "linear",
39 | dynamicAnimation: {
40 | speed: interval,
41 | },
42 | },
43 | zoom: {
44 | enabled: false,
45 | },
46 | },
47 | title: {
48 | text: title,
49 | align: "left",
50 | },
51 | dataLabels: {
52 | enabled: false,
53 | },
54 | stroke: {
55 | curve: "smooth",
56 | },
57 | grid: {
58 | row: {
59 | colors: ["#f3f3f3", "transparent"], // takes an array which will be repeated on columns
60 | opacity: 0.5,
61 | },
62 | },
63 | xaxis: timeLimit
64 | ? { type: "numeric", min: 0, max: timeLimit }
65 | : { type: "numeric", min: 0, max: undefined },
66 | yaxis: {
67 | min: 0,
68 | max: 60,
69 | },
70 | colors: [color],
71 | }),
72 | [title, timeLimit]
73 | );
74 |
75 | return (
76 |
82 | );
83 | };
84 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/newarchitecture/modules/MainApplicationTurboModuleManagerDelegate.java:
--------------------------------------------------------------------------------
1 | package com.example.newarchitecture.modules;
2 |
3 | import com.facebook.jni.HybridData;
4 | import com.facebook.react.ReactPackage;
5 | import com.facebook.react.ReactPackageTurboModuleManagerDelegate;
6 | import com.facebook.react.bridge.ReactApplicationContext;
7 | import com.facebook.soloader.SoLoader;
8 | import java.util.List;
9 |
10 | /**
11 | * Class responsible to load the TurboModules. This class has native methods and needs a
12 | * corresponding C++ implementation/header file to work correctly (already placed inside the jni/
13 | * folder for you).
14 | *
15 | * Please note that this class is used ONLY if you opt-in for the New Architecture (see the
16 | * `newArchEnabled` property). Is ignored otherwise.
17 | */
18 | public class MainApplicationTurboModuleManagerDelegate
19 | extends ReactPackageTurboModuleManagerDelegate {
20 |
21 | private static volatile boolean sIsSoLibraryLoaded;
22 |
23 | protected MainApplicationTurboModuleManagerDelegate(
24 | ReactApplicationContext reactApplicationContext, List packages) {
25 | super(reactApplicationContext, packages);
26 | }
27 |
28 | protected native HybridData initHybrid();
29 |
30 | native boolean canCreateTurboModule(String moduleName);
31 |
32 | public static class Builder extends ReactPackageTurboModuleManagerDelegate.Builder {
33 | protected MainApplicationTurboModuleManagerDelegate build(
34 | ReactApplicationContext context, List packages) {
35 | return new MainApplicationTurboModuleManagerDelegate(context, packages);
36 | }
37 | }
38 |
39 | @Override
40 | protected synchronized void maybeLoadOtherSoLibraries() {
41 | if (!sIsSoLibraryLoaded) {
42 | // If you change the name of your application .so file in the Android.mk file,
43 | // make sure you update the name here as well.
44 | SoLoader.loadLibrary("example_appmodules");
45 | sIsSoLibraryLoaded = true;
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/flipper-desktop/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://fbflipper.com/schemas/plugin-package/v2.json",
3 | "name": "flipper-plugin-rn-perf-monitor",
4 | "id": "rn-perf-monitor",
5 | "version": "0.5.1",
6 | "main": "dist/bundle.js",
7 | "flipperBundlerEntry": "src/index.tsx",
8 | "license": "MIT",
9 | "keywords": [
10 | "flipper-plugin"
11 | ],
12 | "icon": "apps",
13 | "title": "RN Perf monitor",
14 | "description": "Performance monitor for RN, aiming to be like Lighthouse",
15 | "homepage": "https://github.com/bamlab/react-native-performance",
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/bamlab/react-native-performance"
19 | },
20 | "scripts": {
21 | "lint": "flipper-pkg lint",
22 | "prepack": "flipper-pkg lint && flipper-pkg bundle",
23 | "build": "flipper-pkg bundle",
24 | "watch": "flipper-pkg bundle --watch",
25 | "test": "yarn lint && prettier 'src/**/*.{ts,tsx}' --check && tsc && jest",
26 | "postinstall": "patch-package"
27 | },
28 | "peerDependencies": {
29 | "antd": "latest",
30 | "flipper-plugin": "*"
31 | },
32 | "devDependencies": {
33 | "@emotion/styled": "^11.3.0",
34 | "@testing-library/jest-dom": "^5.16.4",
35 | "@testing-library/react": "^12.1.2",
36 | "@types/jest": "^27.0.2",
37 | "@types/react": "^17.0.38",
38 | "@types/react-dom": "^17.0.11",
39 | "@types/react-test-renderer": "^17.0.1",
40 | "antd": "^4.18.5",
41 | "flipper-pkg": "^0.129.0",
42 | "flipper-plugin": "^0.129.0",
43 | "jest": "^27.3.1",
44 | "jest-environment-jsdom": "^27.3.1",
45 | "jest-mock-console": "^1.2.3",
46 | "patch-package": "^6.4.7",
47 | "postinstall-postinstall": "^2.1.0",
48 | "prettier": "^2.4.1",
49 | "react": "^17.0.2",
50 | "react-dom": "^17.0.2",
51 | "ts-jest": "^27.0.7",
52 | "typescript": "^4.4.4"
53 | },
54 | "dependencies": {
55 | "@material-ui/core": "^4.11.0",
56 | "@material-ui/icons": "^4.11.0",
57 | "apexcharts": "^3.33.0",
58 | "react-apexcharts": "^1.3.9"
59 | }
60 | }
61 |
--------------------------------------------------------------------------------
/example/android/app/src/main/res/drawable/rn_edit_text_material.xml:
--------------------------------------------------------------------------------
1 |
2 |
16 |
21 |
22 |
23 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/flipper-desktop/src/index.tsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { createState, PluginClient, usePlugin, useValue } from "flipper-plugin";
3 | import { Measure } from "./types/Measure";
4 | import { PerfMonitorView } from "./PerfMonitorView";
5 | import { openMigrationDialog } from "./openMigrationDialog";
6 |
7 | type Events = {
8 | addRecord: Measure;
9 | };
10 |
11 | type Methods = {
12 | startMeasuring: () => Promise;
13 | stopMeasuring: () => Promise;
14 | };
15 |
16 | export function plugin(client: PluginClient) {
17 | const measures = createState([], {
18 | persist: "measures",
19 | });
20 |
21 | client.onMessage("addRecord", (measure) => {
22 | // This happens only if users have the previous Android plugin installed
23 | // @ts-ignore
24 | if (measure.expected !== undefined) {
25 | stopMeasuring();
26 | openMigrationDialog();
27 | return;
28 | }
29 | measures.update((draft) => {
30 | draft.push(measure);
31 | });
32 | });
33 |
34 | const startMeasuring = () => {
35 | measures.update(() => []);
36 | client.send("startMeasuring", undefined);
37 | };
38 |
39 | const stopMeasuring = async () => {
40 | client.send("stopMeasuring", undefined);
41 | };
42 |
43 | return {
44 | measures: measures,
45 | startMeasuring,
46 | stopMeasuring,
47 | };
48 | }
49 |
50 | export function Component() {
51 | const instance = usePlugin(plugin);
52 | // First measure is usually 0 regardless of performance
53 | const measures = useValue(instance.measures).slice(2);
54 |
55 | const [isMeasuring, setIsMeasuring] = useState(false);
56 | const start = () => {
57 | setIsMeasuring(true);
58 | instance.startMeasuring();
59 | };
60 |
61 | const stop = () => {
62 | instance.stopMeasuring();
63 | setIsMeasuring(false);
64 | };
65 |
66 | return (
67 |
73 | );
74 | }
75 |
--------------------------------------------------------------------------------
/example/ios/exampleTests/exampleTests.m:
--------------------------------------------------------------------------------
1 | #import
2 | #import
3 |
4 | #import
5 | #import
6 |
7 | #define TIMEOUT_SECONDS 600
8 | #define TEXT_TO_LOOK_FOR @"Welcome to React"
9 |
10 | @interface exampleTests : XCTestCase
11 |
12 | @end
13 |
14 | @implementation exampleTests
15 |
16 | - (BOOL)findSubviewInView:(UIView *)view matching:(BOOL (^)(UIView *view))test
17 | {
18 | if (test(view)) {
19 | return YES;
20 | }
21 | for (UIView *subview in [view subviews]) {
22 | if ([self findSubviewInView:subview matching:test]) {
23 | return YES;
24 | }
25 | }
26 | return NO;
27 | }
28 |
29 | - (void)testRendersWelcomeScreen
30 | {
31 | UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
32 | NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
33 | BOOL foundElement = NO;
34 |
35 | __block NSString *redboxError = nil;
36 | #ifdef DEBUG
37 | RCTSetLogFunction(
38 | ^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
39 | if (level >= RCTLogLevelError) {
40 | redboxError = message;
41 | }
42 | });
43 | #endif
44 |
45 | while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
46 | [[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
47 | [[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
48 |
49 | foundElement = [self findSubviewInView:vc.view
50 | matching:^BOOL(UIView *view) {
51 | if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
52 | return YES;
53 | }
54 | return NO;
55 | }];
56 | }
57 |
58 | #ifdef DEBUG
59 | RCTSetLogFunction(RCTDefaultLogFunction);
60 | #endif
61 |
62 | XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
63 | XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
64 | }
65 |
66 | @end
67 |
--------------------------------------------------------------------------------
/flipper-native/android/src/debug/java/tech/bam/rnperformance/flipper/FPSMonitor.java:
--------------------------------------------------------------------------------
1 | package tech.bam.rnperformance.flipper;
2 |
3 | import android.os.Handler;
4 |
5 | import com.facebook.react.bridge.ReactContext;
6 | import com.facebook.react.modules.debug.FpsDebugFrameCallback;
7 |
8 | interface MonitorCallback {
9 | void setCurrentFPS(
10 | int uiFrames,
11 | int jsFrames,
12 | int timeInMs
13 | );
14 | }
15 |
16 | public class FPSMonitor {
17 | public FpsDebugFrameCallback frameCallback;
18 | private FPSMonitorRunnable runnable;
19 | private MonitorCallback monitorCallback;
20 | public static final int UPDATE_INTERVAL_MS = 500;
21 |
22 | public void start(ReactContext reactContext, MonitorCallback monitorCallback) {
23 | frameCallback = new FpsDebugFrameCallback(reactContext);
24 | this.monitorCallback = monitorCallback;
25 | runnable = new FPSMonitorRunnable();
26 |
27 | frameCallback.start();
28 | runnable.start();
29 | }
30 |
31 | public void stop() {
32 | frameCallback.stop();
33 | frameCallback.reset();
34 | runnable.stop();
35 | }
36 |
37 | private class FPSMonitorRunnable implements Runnable {
38 |
39 | private boolean mShouldStop = false;
40 | private int mTotalFramesDropped = 0;
41 | private int mTotal4PlusFrameStutters = 0;
42 |
43 | final Handler handler = new Handler();
44 |
45 | @Override
46 | public void run() {
47 | if (mShouldStop) {
48 | return;
49 | }
50 |
51 | mTotalFramesDropped += frameCallback.getExpectedNumFrames() - frameCallback.getNumFrames();
52 | mTotal4PlusFrameStutters += frameCallback.get4PlusFrameStutters();
53 | monitorCallback.setCurrentFPS(
54 | frameCallback.getNumFrames(),
55 | frameCallback.getNumJSFrames(),
56 | frameCallback.getTotalTimeMS()
57 | );
58 | frameCallback.reset();
59 |
60 | handler.postDelayed(this, UPDATE_INTERVAL_MS);
61 | }
62 |
63 | public void start() {
64 | mShouldStop = false;
65 | handler.post(this);
66 | }
67 |
68 | public void stop() {
69 | mShouldStop = true;
70 | }
71 | }
72 | }
--------------------------------------------------------------------------------
/example/android/app/src/main/jni/MainComponentsRegistry.cpp:
--------------------------------------------------------------------------------
1 | #include "MainComponentsRegistry.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | namespace facebook {
10 | namespace react {
11 |
12 | MainComponentsRegistry::MainComponentsRegistry(ComponentFactory *delegate) {}
13 |
14 | std::shared_ptr
15 | MainComponentsRegistry::sharedProviderRegistry() {
16 | auto providerRegistry = CoreComponentsRegistry::sharedProviderRegistry();
17 |
18 | // Autolinked providers registered by RN CLI
19 | rncli_registerProviders(providerRegistry);
20 |
21 | // Custom Fabric Components go here. You can register custom
22 | // components coming from your App or from 3rd party libraries here.
23 | //
24 | // providerRegistry->add(concreteComponentDescriptorProvider<
25 | // AocViewerComponentDescriptor>());
26 | return providerRegistry;
27 | }
28 |
29 | jni::local_ref
30 | MainComponentsRegistry::initHybrid(
31 | jni::alias_ref,
32 | ComponentFactory *delegate) {
33 | auto instance = makeCxxInstance(delegate);
34 |
35 | auto buildRegistryFunction =
36 | [](EventDispatcher::Weak const &eventDispatcher,
37 | ContextContainer::Shared const &contextContainer)
38 | -> ComponentDescriptorRegistry::Shared {
39 | auto registry = MainComponentsRegistry::sharedProviderRegistry()
40 | ->createComponentDescriptorRegistry(
41 | {eventDispatcher, contextContainer});
42 |
43 | auto mutableRegistry =
44 | std::const_pointer_cast(registry);
45 |
46 | mutableRegistry->setFallbackComponentDescriptor(
47 | std::make_shared(
48 | ComponentDescriptorParameters{
49 | eventDispatcher, contextContainer, nullptr}));
50 |
51 | return registry;
52 | };
53 |
54 | delegate->buildRegistryFunction = buildRegistryFunction;
55 | return instance;
56 | }
57 |
58 | void MainComponentsRegistry::registerNatives() {
59 | registerHybrid({
60 | makeNativeMethod("initHybrid", MainComponentsRegistry::initHybrid),
61 | });
62 | }
63 |
64 | } // namespace react
65 | } // namespace facebook
66 |
--------------------------------------------------------------------------------
/flipper-desktop/src/Report.tsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { CircularProgressWithLabel } from "./components/CircularProgressWithLabel";
3 | import { Table } from "./components/Table";
4 | import { round } from "./utils/round";
5 | import { ThreadMeasure } from "./types/Measure";
6 | import { getTotalTimeAndFrames } from "./utils/getTotalTimeAndFrames";
7 | import { getFPS } from "./utils/getFPS";
8 |
9 | const getColor = (score: number) => {
10 | if (score >= 90) return "#2ECC40";
11 | if (score >= 50) return "#FF851B";
12 | return "#FF4136";
13 | };
14 |
15 | const getTimeThreadlocked = (jsMeasures: ThreadMeasure[]) => {
16 | return jsMeasures.reduce((totalTimeLocked, measure) => {
17 | if (measure.frameCount < 1) {
18 | return totalTimeLocked + measure.time;
19 | }
20 |
21 | return totalTimeLocked;
22 | }, 0);
23 | };
24 |
25 | export const Report = ({
26 | jsMeasures,
27 | uiMeasures,
28 | isMeasuring,
29 | }: {
30 | jsMeasures: ThreadMeasure[];
31 | uiMeasures: ThreadMeasure[];
32 | isMeasuring: boolean;
33 | }) => {
34 | const displayPlaceholder =
35 | jsMeasures.length === 0 || uiMeasures.length === 0 || isMeasuring;
36 |
37 | const jsTotalTimeAndTotalFrames = getTotalTimeAndFrames(jsMeasures);
38 | const averageJSFPS = getFPS(jsTotalTimeAndTotalFrames);
39 | const averageUIFPS = getFPS(getTotalTimeAndFrames(uiMeasures));
40 |
41 | const totalTimeThreadlocked = getTimeThreadlocked(jsMeasures);
42 | const timePercentageThreadlocked =
43 | totalTimeThreadlocked / jsTotalTimeAndTotalFrames.time;
44 |
45 | const getScore = () => {
46 | const fpsScore = ((averageUIFPS + averageJSFPS) * 100) / 120;
47 |
48 | return round(Math.max(0, fpsScore * (1 - timePercentageThreadlocked)), 0);
49 | };
50 |
51 | const getReportRows = () => {
52 | return [
53 | {
54 | title: "Average JS FPS",
55 | value: displayPlaceholder ? "-" : round(averageJSFPS, 1),
56 | },
57 | {
58 | title: "Average UI FPS",
59 | value: displayPlaceholder ? "-" : round(averageUIFPS, 1),
60 | },
61 | {
62 | title: "JS threadlock",
63 | value: displayPlaceholder
64 | ? "-"
65 | : `${round(totalTimeThreadlocked / 1000, 3)}s (${round(
66 | timePercentageThreadlocked * 100,
67 | 2
68 | )}%)`,
69 | },
70 | ];
71 | };
72 |
73 | const score = displayPlaceholder ? 100 : getScore();
74 | const color = displayPlaceholder ? "#eeeeee50" : getColor(score);
75 |
76 | return (
77 | <>
78 |
87 |
88 |
89 |
90 | >
91 | );
92 | };
93 |
--------------------------------------------------------------------------------
/example/android/gradlew.bat:
--------------------------------------------------------------------------------
1 | @rem
2 | @rem Copyright 2015 the original author or authors.
3 | @rem
4 | @rem Licensed under the Apache License, Version 2.0 (the "License");
5 | @rem you may not use this file except in compliance with the License.
6 | @rem You may obtain a copy of the License at
7 | @rem
8 | @rem https://www.apache.org/licenses/LICENSE-2.0
9 | @rem
10 | @rem Unless required by applicable law or agreed to in writing, software
11 | @rem distributed under the License is distributed on an "AS IS" BASIS,
12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | @rem See the License for the specific language governing permissions and
14 | @rem limitations under the License.
15 | @rem
16 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/MainApplication.java:
--------------------------------------------------------------------------------
1 | package com.example;
2 |
3 | import android.app.Application;
4 | import android.content.Context;
5 | import com.facebook.react.PackageList;
6 | import com.facebook.react.ReactApplication;
7 | import com.facebook.react.ReactInstanceManager;
8 | import com.facebook.react.ReactNativeHost;
9 | import com.facebook.react.ReactPackage;
10 | import com.facebook.react.config.ReactFeatureFlags;
11 | import com.facebook.soloader.SoLoader;
12 | import com.example.newarchitecture.MainApplicationReactNativeHost;
13 | import java.lang.reflect.InvocationTargetException;
14 | import java.util.List;
15 |
16 | public class MainApplication extends Application implements ReactApplication {
17 |
18 | private final ReactNativeHost mReactNativeHost =
19 | new ReactNativeHost(this) {
20 | @Override
21 | public boolean getUseDeveloperSupport() {
22 | return BuildConfig.DEBUG;
23 | }
24 |
25 | @Override
26 | protected List getPackages() {
27 | @SuppressWarnings("UnnecessaryLocalVariable")
28 | List packages = new PackageList(this).getPackages();
29 | // Packages that cannot be autolinked yet can be added manually here, for example:
30 | // packages.add(new MyReactNativePackage());
31 | return packages;
32 | }
33 |
34 | @Override
35 | protected String getJSMainModuleName() {
36 | return "index";
37 | }
38 | };
39 |
40 | private final ReactNativeHost mNewArchitectureNativeHost =
41 | new MainApplicationReactNativeHost(this);
42 |
43 | @Override
44 | public ReactNativeHost getReactNativeHost() {
45 | if (BuildConfig.IS_NEW_ARCHITECTURE_ENABLED) {
46 | return mNewArchitectureNativeHost;
47 | } else {
48 | return mReactNativeHost;
49 | }
50 | }
51 |
52 | @Override
53 | public void onCreate() {
54 | super.onCreate();
55 | // If you opted-in for the New Architecture, we enable the TurboModule system
56 | ReactFeatureFlags.useTurboModules = BuildConfig.IS_NEW_ARCHITECTURE_ENABLED;
57 | SoLoader.init(this, /* native exopackage */ false);
58 | initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
59 | }
60 |
61 | /**
62 | * Loads Flipper in React Native templates. Call this in the onCreate method with something like
63 | * initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
64 | *
65 | * @param context
66 | * @param reactInstanceManager
67 | */
68 | private static void initializeFlipper(
69 | Context context, ReactInstanceManager reactInstanceManager) {
70 | if (BuildConfig.DEBUG) {
71 | try {
72 | /*
73 | We use reflection here to pick up the class that initializes Flipper,
74 | since Flipper library is not available in release mode
75 | */
76 | Class> aClass = Class.forName("com.example.ReactNativeFlipper");
77 | aClass
78 | .getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
79 | .invoke(null, context, reactInstanceManager);
80 | } catch (ClassNotFoundException e) {
81 | e.printStackTrace();
82 | } catch (NoSuchMethodException e) {
83 | e.printStackTrace();
84 | } catch (IllegalAccessException e) {
85 | e.printStackTrace();
86 | } catch (InvocationTargetException e) {
87 | e.printStackTrace();
88 | }
89 | }
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/flipper-native/android/src/debug/java/tech/bam/rnperformance/flipper/RNPerfMonitorPlugin.java:
--------------------------------------------------------------------------------
1 | package tech.bam.rnperformance.flipper;
2 |
3 | import android.os.Handler;
4 | import android.os.Looper;
5 | import android.util.Log;
6 |
7 | import com.facebook.flipper.core.FlipperConnection;
8 | import com.facebook.flipper.core.FlipperObject;
9 | import com.facebook.flipper.core.FlipperPlugin;
10 | import com.facebook.flipper.core.FlipperReceiver;
11 | import com.facebook.flipper.core.FlipperResponder;
12 | import com.facebook.react.ReactInstanceManager;
13 |
14 | public class RNPerfMonitorPlugin implements FlipperPlugin {
15 | private FlipperConnection connection;
16 | private ReactInstanceManager reactInstanceManager;
17 | private FPSMonitor fpsMonitor;
18 |
19 | public RNPerfMonitorPlugin(ReactInstanceManager reactInstanceManager) {
20 | this.reactInstanceManager = reactInstanceManager;
21 | fpsMonitor = new FPSMonitor();
22 | }
23 |
24 |
25 | @Override
26 | public String getId() {
27 | return "rn-perf-monitor";
28 | }
29 |
30 | @Override
31 | public void onConnect(FlipperConnection connection) throws Exception {
32 | this.connection = connection;
33 |
34 | connection.receive("startMeasuring", new FlipperReceiver() {
35 | @Override
36 | public void onReceive(FlipperObject params, FlipperResponder responder) throws Exception {
37 | RNPerfMonitorPlugin.this.startMeasuring();
38 | responder.success();
39 | }
40 | });
41 | connection.receive("stopMeasuring", new FlipperReceiver() {
42 | @Override
43 | public void onReceive(FlipperObject params, FlipperResponder responder) throws Exception {
44 | RNPerfMonitorPlugin.this.stopMeasuring();
45 | responder.success();
46 | }
47 | });
48 | }
49 |
50 | @Override
51 | public void onDisconnect() {
52 | this.connection = null;
53 | }
54 |
55 | @Override
56 | public boolean runInBackground() {
57 | return false;
58 | }
59 |
60 | public void startMeasuring() {
61 | new Handler(Looper.getMainLooper()).post(new Runnable() {
62 | @Override
63 | public void run() {
64 | fpsMonitor.start(reactInstanceManager.getCurrentReactContext(), new MonitorCallback() {
65 | @Override
66 | public void setCurrentFPS(int uiFrames, int jsFrames, int timeInMs) {
67 | if (connection == null) {
68 | Log.w("PerfMonitor", "No connection to Flipper");
69 | return;
70 | }
71 |
72 | connection.send("addRecord", new FlipperObject.Builder()
73 | .put("frameCount", jsFrames)
74 | .put("time", timeInMs)
75 | .put("thread", "JS")
76 | .build());
77 | connection.send("addRecord", new FlipperObject.Builder()
78 | .put("frameCount", uiFrames)
79 | .put("time", timeInMs)
80 | .put("thread", "UI")
81 | .build());
82 | }
83 | });
84 | }
85 | });
86 | }
87 |
88 | public void stopMeasuring() {
89 | fpsMonitor.stop();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/example/ios/example.xcodeproj/xcshareddata/xcschemes/example.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
33 |
39 |
40 |
41 |
42 |
43 |
53 |
55 |
61 |
62 |
63 |
64 |
70 |
72 |
78 |
79 |
80 |
81 |
83 |
84 |
87 |
88 |
89 |
--------------------------------------------------------------------------------
/example/android/app/src/debug/java/com/example/ReactNativeFlipper.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) Meta Platforms, Inc. and affiliates.
3 | *
4 | * This source code is licensed under the MIT license found in the LICENSE file in the root
5 | * directory of this source tree.
6 | */
7 | package com.example;
8 |
9 | import android.content.Context;
10 | import com.facebook.flipper.android.AndroidFlipperClient;
11 | import com.facebook.flipper.android.utils.FlipperUtils;
12 | import com.facebook.flipper.core.FlipperClient;
13 | import com.facebook.flipper.plugins.crashreporter.CrashReporterPlugin;
14 | import com.facebook.flipper.plugins.databases.DatabasesFlipperPlugin;
15 | import com.facebook.flipper.plugins.fresco.FrescoFlipperPlugin;
16 | import com.facebook.flipper.plugins.inspector.DescriptorMapping;
17 | import com.facebook.flipper.plugins.inspector.InspectorFlipperPlugin;
18 | import com.facebook.flipper.plugins.network.FlipperOkhttpInterceptor;
19 | import com.facebook.flipper.plugins.network.NetworkFlipperPlugin;
20 | import com.facebook.flipper.plugins.react.ReactFlipperPlugin;
21 | import com.facebook.flipper.plugins.sharedpreferences.SharedPreferencesFlipperPlugin;
22 | import com.facebook.react.ReactInstanceEventListener;
23 | import com.facebook.react.ReactInstanceManager;
24 | import com.facebook.react.bridge.ReactContext;
25 | import com.facebook.react.modules.network.NetworkingModule;
26 | import tech.bam.rnperformance.flipper.RNPerfMonitorPlugin;
27 | import okhttp3.OkHttpClient;
28 |
29 | public class ReactNativeFlipper {
30 | public static void initializeFlipper(Context context, ReactInstanceManager reactInstanceManager) {
31 | if (FlipperUtils.shouldEnableFlipper(context)) {
32 | final FlipperClient client = AndroidFlipperClient.getInstance(context);
33 |
34 | client.addPlugin(new InspectorFlipperPlugin(context, DescriptorMapping.withDefaults()));
35 | client.addPlugin(new ReactFlipperPlugin());
36 | client.addPlugin(new DatabasesFlipperPlugin(context));
37 | client.addPlugin(new SharedPreferencesFlipperPlugin(context));
38 | client.addPlugin(CrashReporterPlugin.getInstance());
39 | client.addPlugin(new RNPerfMonitorPlugin(reactInstanceManager));
40 |
41 | NetworkFlipperPlugin networkFlipperPlugin = new NetworkFlipperPlugin();
42 | NetworkingModule.setCustomClientBuilder(
43 | new NetworkingModule.CustomClientBuilder() {
44 | @Override
45 | public void apply(OkHttpClient.Builder builder) {
46 | builder.addNetworkInterceptor(new FlipperOkhttpInterceptor(networkFlipperPlugin));
47 | }
48 | });
49 | client.addPlugin(networkFlipperPlugin);
50 | client.start();
51 |
52 | // Fresco Plugin needs to ensure that ImagePipelineFactory is initialized
53 | // Hence we run if after all native modules have been initialized
54 | ReactContext reactContext = reactInstanceManager.getCurrentReactContext();
55 | if (reactContext == null) {
56 | reactInstanceManager.addReactInstanceEventListener(
57 | new ReactInstanceEventListener() {
58 | @Override
59 | public void onReactContextInitialized(ReactContext reactContext) {
60 | reactInstanceManager.removeReactInstanceEventListener(this);
61 | reactContext.runOnNativeModulesQueueThread(
62 | new Runnable() {
63 | @Override
64 | public void run() {
65 | client.addPlugin(new FrescoFlipperPlugin());
66 | }
67 | });
68 | }
69 | });
70 | } else {
71 | client.addPlugin(new FrescoFlipperPlugin());
72 | }
73 | }
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/example/ios/example/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/flipper-desktop/src/__tests__/Plugin.spec.tsx:
--------------------------------------------------------------------------------
1 | import { fireEvent } from "@testing-library/dom";
2 | import { TestUtils } from "flipper-plugin";
3 | import * as Plugin from "..";
4 |
5 | // See https://github.com/apexcharts/react-apexcharts/issues/52
6 | jest.mock("react-apexcharts", () => "apex-charts");
7 | jest.mock("apexcharts", () => ({ exec: jest.fn() }));
8 |
9 | const setupPlugin = () => {
10 | const { instance, renderer, sendEvent, onSend } =
11 | TestUtils.renderPlugin(Plugin);
12 |
13 | // First measure on Android is always 0 and is ignored by plugin
14 | sendEvent("addRecord", {
15 | frameCount: 0,
16 | thread: "JS",
17 | time: 500,
18 | });
19 | sendEvent("addRecord", {
20 | frameCount: 0,
21 | thread: "UI",
22 | time: 500,
23 | });
24 |
25 | return {
26 | addMeasure: ({
27 | JS,
28 | UI,
29 | time,
30 | }: {
31 | JS: number;
32 | UI: number;
33 | time?: number;
34 | }) => {
35 | sendEvent("addRecord", {
36 | frameCount: JS,
37 | thread: "JS",
38 | time: time || 500,
39 | });
40 | sendEvent("addRecord", {
41 | frameCount: UI,
42 | thread: "UI",
43 | time: time || 500,
44 | });
45 | },
46 | clickStart: () => fireEvent.click(renderer.getByText("Start Measuring")),
47 | clickStop: () => fireEvent.click(renderer.getByText("Stop Measuring")),
48 | expectToMatchSnapshot: () => {
49 | expect(
50 | (renderer.baseElement as HTMLBodyElement).textContent
51 | ).toMatchSnapshot();
52 | expect(renderer.baseElement).toMatchSnapshot();
53 | },
54 | setTimeLimit: (limit: number) => {
55 | const input = renderer.getByLabelText("Time limit");
56 | fireEvent.change(input, { target: { value: limit } });
57 | },
58 | clickTimeLimitCheckbox: () => {
59 | const checkbox = renderer.getByLabelText("Time limit enabled");
60 | fireEvent.click(checkbox);
61 | },
62 |
63 | renderer,
64 | instance,
65 | onSend,
66 | };
67 | };
68 |
69 | test("displays FPS data and scoring", async () => {
70 | const { addMeasure, expectToMatchSnapshot } = setupPlugin();
71 |
72 | addMeasure({ JS: 30, UI: 25 });
73 | addMeasure({ JS: 0, UI: 30 });
74 |
75 | expectToMatchSnapshot();
76 | });
77 |
78 | test("clicking start should reset measures and start measures", () => {
79 | const { addMeasure, clickStart, clickStop, instance, onSend } = setupPlugin();
80 |
81 | addMeasure({ JS: 30, UI: 25 });
82 | addMeasure({ JS: 0, UI: 30 });
83 |
84 | clickStart();
85 | expect(onSend).toHaveBeenCalledWith("startMeasuring", undefined);
86 | onSend.mockClear();
87 |
88 | expect(instance.measures.get()).toEqual([]);
89 |
90 | clickStop();
91 | expect(onSend).toHaveBeenCalledWith("stopMeasuring", undefined);
92 | });
93 |
94 | test("stops after set time limit", () => {
95 | const { addMeasure, setTimeLimit, onSend } = setupPlugin();
96 |
97 | setTimeLimit(1000);
98 |
99 | addMeasure({ JS: 30, UI: 30 });
100 | addMeasure({ JS: 30, UI: 30 });
101 | expect(onSend).not.toHaveBeenCalledWith("stopMeasuring", undefined);
102 | // 1000ms means 3 measures would be displayed: 0, 500 and 1000
103 | addMeasure({ JS: 30, UI: 30 });
104 | expect(onSend).toHaveBeenCalledWith("stopMeasuring", undefined);
105 | });
106 |
107 | test("continues after set time limit if time limit disabled", () => {
108 | const { addMeasure, clickTimeLimitCheckbox, setTimeLimit, onSend } =
109 | setupPlugin();
110 |
111 | setTimeLimit(1000);
112 | clickTimeLimitCheckbox();
113 |
114 | addMeasure({ JS: 30, UI: 30 });
115 | addMeasure({ JS: 30, UI: 30 });
116 | expect(onSend).not.toHaveBeenCalledWith("stopMeasuring", undefined);
117 | // 1000ms means 3 measures would be displayed: 0, 500 and 1000
118 | addMeasure({ JS: 30, UI: 30 });
119 | expect(onSend).not.toHaveBeenCalledWith("stopMeasuring", undefined);
120 | });
121 |
122 | test("it should sanitize data", () => {
123 | const { addMeasure, expectToMatchSnapshot } = setupPlugin();
124 |
125 | addMeasure({ JS: 120, UI: 150 });
126 |
127 | expectToMatchSnapshot();
128 | });
129 |
130 | test("it should handle time being a bit longer than 500ms but still achieving a correct frame count", () => {
131 | const { addMeasure, renderer } = setupPlugin();
132 |
133 | addMeasure({ JS: 30, UI: 30, time: 502 });
134 |
135 | expect(renderer.baseElement).toHaveTextContent("Average JS FPS59.7");
136 | });
137 |
--------------------------------------------------------------------------------
/example/android/app/src/main/java/com/example/newarchitecture/MainApplicationReactNativeHost.java:
--------------------------------------------------------------------------------
1 | package com.example.newarchitecture;
2 |
3 | import android.app.Application;
4 | import androidx.annotation.NonNull;
5 | import com.facebook.react.PackageList;
6 | import com.facebook.react.ReactInstanceManager;
7 | import com.facebook.react.ReactNativeHost;
8 | import com.facebook.react.ReactPackage;
9 | import com.facebook.react.ReactPackageTurboModuleManagerDelegate;
10 | import com.facebook.react.bridge.JSIModulePackage;
11 | import com.facebook.react.bridge.JSIModuleProvider;
12 | import com.facebook.react.bridge.JSIModuleSpec;
13 | import com.facebook.react.bridge.JSIModuleType;
14 | import com.facebook.react.bridge.JavaScriptContextHolder;
15 | import com.facebook.react.bridge.ReactApplicationContext;
16 | import com.facebook.react.bridge.UIManager;
17 | import com.facebook.react.fabric.ComponentFactory;
18 | import com.facebook.react.fabric.CoreComponentsRegistry;
19 | import com.facebook.react.fabric.FabricJSIModuleProvider;
20 | import com.facebook.react.fabric.ReactNativeConfig;
21 | import com.facebook.react.uimanager.ViewManagerRegistry;
22 | import com.example.BuildConfig;
23 | import com.example.newarchitecture.components.MainComponentsRegistry;
24 | import com.example.newarchitecture.modules.MainApplicationTurboModuleManagerDelegate;
25 | import java.util.ArrayList;
26 | import java.util.List;
27 |
28 | /**
29 | * A {@link ReactNativeHost} that helps you load everything needed for the New Architecture, both
30 | * TurboModule delegates and the Fabric Renderer.
31 | *
32 | *
Please note that this class is used ONLY if you opt-in for the New Architecture (see the
33 | * `newArchEnabled` property). Is ignored otherwise.
34 | */
35 | public class MainApplicationReactNativeHost extends ReactNativeHost {
36 | public MainApplicationReactNativeHost(Application application) {
37 | super(application);
38 | }
39 |
40 | @Override
41 | public boolean getUseDeveloperSupport() {
42 | return BuildConfig.DEBUG;
43 | }
44 |
45 | @Override
46 | protected List getPackages() {
47 | List packages = new PackageList(this).getPackages();
48 | // Packages that cannot be autolinked yet can be added manually here, for example:
49 | // packages.add(new MyReactNativePackage());
50 | // TurboModules must also be loaded here providing a valid TurboReactPackage implementation:
51 | // packages.add(new TurboReactPackage() { ... });
52 | // If you have custom Fabric Components, their ViewManagers should also be loaded here
53 | // inside a ReactPackage.
54 | return packages;
55 | }
56 |
57 | @Override
58 | protected String getJSMainModuleName() {
59 | return "index";
60 | }
61 |
62 | @NonNull
63 | @Override
64 | protected ReactPackageTurboModuleManagerDelegate.Builder
65 | getReactPackageTurboModuleManagerDelegateBuilder() {
66 | // Here we provide the ReactPackageTurboModuleManagerDelegate Builder. This is necessary
67 | // for the new architecture and to use TurboModules correctly.
68 | return new MainApplicationTurboModuleManagerDelegate.Builder();
69 | }
70 |
71 | @Override
72 | protected JSIModulePackage getJSIModulePackage() {
73 | return new JSIModulePackage() {
74 | @Override
75 | public List getJSIModules(
76 | final ReactApplicationContext reactApplicationContext,
77 | final JavaScriptContextHolder jsContext) {
78 | final List specs = new ArrayList<>();
79 |
80 | // Here we provide a new JSIModuleSpec that will be responsible of providing the
81 | // custom Fabric Components.
82 | specs.add(
83 | new JSIModuleSpec() {
84 | @Override
85 | public JSIModuleType getJSIModuleType() {
86 | return JSIModuleType.UIManager;
87 | }
88 |
89 | @Override
90 | public JSIModuleProvider getJSIModuleProvider() {
91 | final ComponentFactory componentFactory = new ComponentFactory();
92 | CoreComponentsRegistry.register(componentFactory);
93 |
94 | // Here we register a Components Registry.
95 | // The one that is generated with the template contains no components
96 | // and just provides you the one from React Native core.
97 | MainComponentsRegistry.register(componentFactory);
98 |
99 | final ReactInstanceManager reactInstanceManager = getReactInstanceManager();
100 |
101 | ViewManagerRegistry viewManagerRegistry =
102 | new ViewManagerRegistry(
103 | reactInstanceManager.getOrCreateViewManagers(reactApplicationContext));
104 |
105 | return new FabricJSIModuleProvider(
106 | reactApplicationContext,
107 | componentFactory,
108 | ReactNativeConfig.DEFAULT_CONFIG,
109 | viewManagerRegistry);
110 | }
111 | });
112 | return specs;
113 | }
114 | };
115 | }
116 | }
117 |
--------------------------------------------------------------------------------
/flipper-native/plugin/src/index.ts:
--------------------------------------------------------------------------------
1 | import {
2 | withDangerousMod,
3 | ConfigPlugin,
4 | WarningAggregator,
5 | withAppDelegate,
6 | ExportedConfigWithProps,
7 | } from "@expo/config-plugins";
8 | import {
9 | mergeContents,
10 | MergeResults,
11 | removeContents,
12 | } from "@expo/config-plugins/build/utils/generateCode";
13 | import path from "path";
14 | import fs from "fs";
15 | import { ExpoConfig } from "@expo/config-types";
16 |
17 | function modifyAppDelegateImport(src: string): MergeResults {
18 | const newSrc = `#ifdef FB_SONARKIT_ENABLED
19 | #import
20 | #import
21 | #endif`;
22 |
23 | return mergeContents({
24 | tag: "react-native-performance-plugin-expo-import",
25 | src,
26 | newSrc,
27 | anchor: /@implementation AppDelegate/,
28 | offset: -1,
29 | comment: "//",
30 | });
31 | }
32 |
33 | function modifyAppDelegateLaunchingCode(src: string): MergeResults {
34 | const newSrc = ` #ifdef FB_SONARKIT_ENABLED
35 | FlipperClient * client = [FlipperClient sharedClient];
36 | [client addPlugin: [FlipperPerformancePlugin new]];
37 | #endif`;
38 |
39 | return mergeContents({
40 | tag: "react-native-performance-plugin-expo-launchingcode",
41 | src,
42 | newSrc,
43 | anchor: /didFinishLaunchingWithOptions:/,
44 | offset: 2,
45 | comment: "//",
46 | });
47 | }
48 |
49 | function withIosPlugin(config: ExpoConfig) {
50 | return withAppDelegate(config, (config) => {
51 | if (["objc", "objcpp"].includes(config.modResults.language)) {
52 | config.modResults.contents = modifyAppDelegateImport(
53 | config.modResults.contents
54 | ).contents;
55 | config.modResults.contents = modifyAppDelegateLaunchingCode(
56 | config.modResults.contents
57 | ).contents;
58 | } else {
59 | WarningAggregator.addWarningIOS(
60 | "withReactNativePerformanceFlipperPlugin",
61 | `Cannot setup react-native-performance for Expo, the project AppDelegate is not a supported language: ${config.modResults.language}`
62 | );
63 | }
64 | return config;
65 | });
66 | }
67 |
68 | async function readFileAsync(path: string) {
69 | return fs.promises.readFile(path, "utf8");
70 | }
71 |
72 | async function saveFileAsync(path: string, content: string) {
73 | return fs.promises.writeFile(path, content, "utf8");
74 | }
75 |
76 | function getDebugRoot(projectRoot: string) {
77 | return path.join(projectRoot, "android", "app", "src", "debug", "java");
78 | }
79 |
80 | async function addReactNativePerformancePluginForExpoAndroid(
81 | config: ExportedConfigWithProps
82 | ) {
83 | if (config.android) {
84 | const projectRoot = config.modRequest.projectRoot;
85 | const packageDebugRoot = getDebugRoot(projectRoot);
86 | const packageName = config.android.package || "";
87 | const reactNativeFlipperFilePath = path.join(
88 | packageDebugRoot,
89 | `${packageName.split(".").join("/")}/ReactNativeFlipper.java`
90 | );
91 |
92 | try {
93 | // since there is no mod to get the contents of the file, we need to read it first
94 | const reactNativeFlipperContents = await readFileAsync(
95 | reactNativeFlipperFilePath
96 | );
97 |
98 | // store it for later use
99 | let patchedContents = reactNativeFlipperContents;
100 |
101 | // modify the contents of the file
102 | patchedContents = mergeContents({
103 | tag: "react-native-performance-plugin-expo-import",
104 | src: patchedContents,
105 | newSrc: "import tech.bam.rnperformance.flipper.RNPerfMonitorPlugin;",
106 | anchor: "import okhttp3.OkHttpClient;",
107 | offset: 1,
108 | comment: "//",
109 | }).contents;
110 |
111 | // modify the contents of the file
112 | patchedContents = mergeContents({
113 | tag: "react-native-performance-plugin-expo-addplugin",
114 | src: patchedContents,
115 | newSrc: ` client.addPlugin(new RNPerfMonitorPlugin(reactInstanceManager));`,
116 | anchor: /client.start()/g,
117 | offset: -1,
118 | comment: "//",
119 | }).contents;
120 |
121 | // save the file
122 | return await saveFileAsync(reactNativeFlipperFilePath, patchedContents);
123 | } catch (e) {
124 | // TODO: should we throw instead?
125 | WarningAggregator.addWarningAndroid(
126 | "react-native-performance Expo Plugin",
127 | `Couldn't modify ReactNativeFlipper.java - ${e}.`
128 | );
129 | }
130 | }
131 | }
132 |
133 | const withAndroidPlugin: ConfigPlugin = (config: ExpoConfig) => {
134 | return withDangerousMod(config, [
135 | "android",
136 | async (config) => {
137 | await addReactNativePerformancePluginForExpoAndroid(config);
138 | return config;
139 | },
140 | ]);
141 | };
142 |
143 | export const withReactNativePerformanceFlipperPluginExpo: ConfigPlugin = (
144 | config
145 | ) => {
146 | config = withIosPlugin(config);
147 | config = withAndroidPlugin(config);
148 | return config;
149 | };
150 |
151 | export default withReactNativePerformanceFlipperPluginExpo;
152 |
--------------------------------------------------------------------------------
/react-native-startup-trace/android/build.gradle:
--------------------------------------------------------------------------------
1 | // android/build.gradle
2 |
3 | import io.invertase.gradle.common.PackageJson
4 |
5 | buildscript {
6 | // The Android Gradle plugin is only required when opening the android folder stand-alone.
7 | // This avoids unnecessary downloads and potential conflicts when the library is included as a
8 | // module dependency in an application project.
9 | // ref: https://docs.gradle.org/current/userguide/tutorial_using_tasks.html#sec:build_script_external_dependencies
10 | if (project == rootProject) {
11 | repositories {
12 | google()
13 | jcenter()
14 | }
15 | dependencies {
16 | classpath 'com.android.tools.build:gradle:3.4.1'
17 | }
18 | }
19 | }
20 |
21 | plugins {
22 | id "io.invertase.gradle.build" version "1.4"
23 | }
24 |
25 | apply plugin: 'com.android.library'
26 |
27 | // based on:
28 | //
29 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/build.gradle
30 | // original location:
31 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/build.gradle
32 | //
33 | // * https://github.com/facebook/react-native/blob/0.60-stable/template/android/app/build.gradle
34 | // original location:
35 | // - https://github.com/facebook/react-native/blob/0.58-stable/local-cli/templates/HelloWorld/android/app/build.gradle
36 |
37 | def DEFAULT_COMPILE_SDK_VERSION = 28
38 | def DEFAULT_BUILD_TOOLS_VERSION = '28.0.3'
39 | def DEFAULT_MIN_SDK_VERSION = 16
40 | def DEFAULT_TARGET_SDK_VERSION = 28
41 |
42 | def safeExtGet(prop, fallback) {
43 | rootProject.ext.has(prop) ? rootProject.ext.get(prop) : fallback
44 | }
45 |
46 | android {
47 | compileSdkVersion safeExtGet('compileSdkVersion', DEFAULT_COMPILE_SDK_VERSION)
48 | buildToolsVersion safeExtGet('buildToolsVersion', DEFAULT_BUILD_TOOLS_VERSION)
49 | defaultConfig {
50 | minSdkVersion safeExtGet('minSdkVersion', DEFAULT_MIN_SDK_VERSION)
51 | targetSdkVersion safeExtGet('targetSdkVersion', DEFAULT_TARGET_SDK_VERSION)
52 | versionCode 1
53 | versionName "1.0"
54 | }
55 | lintOptions {
56 | abortOnError false
57 | }
58 | }
59 |
60 | repositories {
61 | // ref: https://www.baeldung.com/maven-local-repository
62 | mavenLocal()
63 | maven {
64 | // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
65 | url "$rootDir/../node_modules/react-native/android"
66 | }
67 | maven {
68 | // Android JSC is installed from npm
69 | url "$rootDir/../node_modules/jsc-android/dist"
70 | }
71 | google()
72 | jcenter()
73 | }
74 |
75 | // Copy paste from firebase modules
76 | def appProject
77 | if (findProject(':@react-native-firebase_app')) {
78 | appProject = project(':@react-native-firebase_app')
79 | } else if (findProject(':react-native-firebase_app')) {
80 | appProject = project(':react-native-firebase_app')
81 | } else {
82 | throw new GradleException('Could not find the react-native-firebase/app package, have you installed it?')
83 | }
84 | def packageJson = PackageJson.getForProject(project)
85 | def appPackageJson = PackageJson.getForProject(appProject)
86 | def firebaseBomVersion = appPackageJson['sdkVersions']['android']['firebase']
87 | def jsonMinSdk = appPackageJson['sdkVersions']['android']['minSdk']
88 | def jsonTargetSdk = appPackageJson['sdkVersions']['android']['targetSdk']
89 | def jsonCompileSdk = appPackageJson['sdkVersions']['android']['compileSdk']
90 | def jsonBuildTools = appPackageJson['sdkVersions']['android']['buildTools']
91 | def coreVersionDetected = appPackageJson['version']
92 | def coreVersionRequired = packageJson['peerDependencies'][appPackageJson['name']]
93 | // Only log after build completed so log warning appears at the end
94 | if (coreVersionDetected != coreVersionRequired) {
95 | gradle.buildFinished {
96 | project.logger.warn("ReactNativeFirebase WARNING: NPM package '${packageJson['name']}' depends on '${appPackageJson['name']}' v${coreVersionRequired} but found v${coreVersionDetected}, this might cause build issues or runtime crashes.")
97 | }
98 | }
99 |
100 | project.ext {
101 | set('react-native', [
102 | versions: [
103 | android : [
104 | minSdk : jsonMinSdk,
105 | targetSdk : jsonTargetSdk,
106 | compileSdk: jsonCompileSdk,
107 | // optional as gradle.buildTools comes with one by default
108 | // overriding here though to match the version RN uses
109 | buildTools: jsonBuildTools
110 | ],
111 |
112 | firebase: [
113 | bom: firebaseBomVersion,
114 | ],
115 | ],
116 | ])
117 | }
118 | // Copy paste from firebase modules
119 |
120 | dependencies {
121 | //noinspection GradleDynamicVersion
122 | implementation 'com.facebook.react:react-native:+' // From node_modules
123 |
124 | // Should come from react-native-firebase installation
125 | implementation platform("com.google.firebase:firebase-bom:${ReactNative.ext.getVersion("firebase", "bom")}")
126 | implementation "com.google.firebase:firebase-perf"
127 | }
128 |
--------------------------------------------------------------------------------
/flipper-desktop/src/PerfMonitorView.tsx:
--------------------------------------------------------------------------------
1 | import React, { ReactNode, useEffect, useState } from "react";
2 | import { Chart } from "./components/Chart";
3 | import { Report } from "./Report";
4 | import { StartButton } from "./components/StartButton";
5 | import { ScrollContainer } from "./components/ScrollContainer";
6 | import { Title } from "./components/Title";
7 | import { Measure, ThreadMeasure } from "./types/Measure";
8 | import {
9 | Checkbox,
10 | FormControlLabel,
11 | TextField,
12 | Typography,
13 | useTheme,
14 | } from "@material-ui/core";
15 | import { getTotalTimeAndFrames } from "./utils/getTotalTimeAndFrames";
16 | import { getFPS } from "./utils/getFPS";
17 | import { getThreadMeasures } from "./utils/getThreadMeasures";
18 |
19 | const getFPSGraphData = (measures: ThreadMeasure[]) =>
20 | measures.reduce<{ x: number; y: number }[]>((aggr, measure) => {
21 | return [
22 | ...aggr,
23 | {
24 | x: aggr.length > 0 ? aggr[aggr.length - 1].x + measure.time : 0,
25 | y: getFPS(measure),
26 | },
27 | ];
28 | }, []);
29 |
30 | const ControlsContainer = ({ children }: { children: ReactNode }) => (
31 |
39 | {children}
40 |
41 | );
42 |
43 | const TimeLimitControl = ({
44 | timeLimitEnabled,
45 | toggleTimeLimit,
46 | setTimeLimit,
47 | timeLimit,
48 | }: {
49 | timeLimitEnabled: boolean;
50 | timeLimit: number | null;
51 | setTimeLimit: (limit: number | null) => void;
52 | toggleTimeLimit: (checked: boolean) => void;
53 | }) => (
54 |
62 | {
70 | toggleTimeLimit(event.target.checked);
71 | }}
72 | />
73 | }
74 | label="Enable time limit of "
75 | />
76 |
82 | setTimeLimit(Math.floor(parseInt(value, 10)))
83 | }
84 | defaultValue={timeLimit}
85 | />
86 | ms
87 |
88 | );
89 |
90 | // This is the same value as defined here: https://github.com/bamlab/react-native-performance/blob/master/flipper-android/src/main/java/tech/bam/rnperformance/FPSMonitor.java#L42
91 | const MEASURE_INTERVAL = 500;
92 |
93 | export const PerfMonitorView = ({
94 | measures,
95 | startMeasuring,
96 | stopMeasuring,
97 | isMeasuring,
98 | }: {
99 | measures: Measure[];
100 | startMeasuring: () => void;
101 | stopMeasuring: () => void;
102 | isMeasuring: boolean;
103 | }) => {
104 | const JSThreadMeasures = getThreadMeasures(measures, "JS");
105 | const UIThreadMeasures = getThreadMeasures(measures, "UI");
106 |
107 | const DEFAULT_TIME_LIMIT = 10000;
108 | const [timeLimitEnabled, setTimeLimitEnabled] = useState(true);
109 | const [timeLimit, setTimeLimit] = useState(DEFAULT_TIME_LIMIT);
110 |
111 | const { time: JSMeasuresTotalTime } = getTotalTimeAndFrames(JSThreadMeasures);
112 | const { time: UIMeasuresTotalTime } = getTotalTimeAndFrames(UIThreadMeasures);
113 |
114 | useEffect(() => {
115 | if (
116 | timeLimitEnabled &&
117 | timeLimit &&
118 | (UIMeasuresTotalTime - UIThreadMeasures[0]?.time >= timeLimit ||
119 | JSMeasuresTotalTime - JSThreadMeasures[0]?.time >= timeLimit)
120 | ) {
121 | stopMeasuring();
122 | }
123 | }, [timeLimit, JSMeasuresTotalTime, UIMeasuresTotalTime, timeLimitEnabled]);
124 |
125 | const { palette } = useTheme();
126 |
127 | return (
128 |
129 |
130 |
135 |
136 |
141 |
147 |
148 |
156 |
164 |
165 | );
166 | };
167 |
--------------------------------------------------------------------------------
/flipper-desktop/src/GfxInfo.tsx:
--------------------------------------------------------------------------------
1 | interface HistogramValue {
2 | renderingTime: number;
3 | frameCount: number;
4 | }
5 |
6 | interface RenderingTimeMeasures {
7 | totalFramesRendered: number;
8 | totalRenderTime: number;
9 | }
10 |
11 | interface Measure {
12 | jankyFrames: {
13 | totalRendered: number;
14 | count: number;
15 | };
16 | renderingTime: RenderingTimeMeasures;
17 | }
18 |
19 | interface MarkerMeasure {
20 | markerName: string;
21 | measure: Measure;
22 | }
23 |
24 | const roundToDecimal = (value: number, decimalCount: number) => {
25 | const factor = Math.pow(10, decimalCount);
26 | return Math.floor(value * factor) / factor;
27 | };
28 |
29 | export class GfxInfo {
30 | androidPackage: string;
31 | measures: MarkerMeasure[];
32 |
33 | constructor({ androidPackage }: { androidPackage: string }) {
34 | this.androidPackage = androidPackage;
35 | this.measures = [];
36 | }
37 |
38 | private async executeCommand(command: string): Promise {
39 | return new Promise((resolve, reject) =>
40 | require("child_process").exec(command, function (err, stdout) {
41 | if (err) return reject(err);
42 | return resolve(stdout);
43 | })
44 | );
45 | }
46 |
47 | private async resetDumpSys(): Promise {
48 | await this.executeCommand(
49 | `adb shell dumpsys gfxinfo ${this.androidPackage} reset`
50 | );
51 | }
52 |
53 | private async getGfxInfo(): Promise {
54 | return this.executeCommand(
55 | `adb shell dumpsys gfxinfo ${this.androidPackage}`
56 | );
57 | }
58 |
59 | private parseHistogram(histogramText: string): HistogramValue[] {
60 | return histogramText.split(" ").map((renderTimeText) => {
61 | const [renderingTime, frameCount] = renderTimeText
62 | .split("ms=")
63 | .map((text) => parseInt(text, 10));
64 | return { renderingTime, frameCount };
65 | });
66 | }
67 |
68 | private getRenderingTimeMeasures(
69 | histogram: HistogramValue[]
70 | ): RenderingTimeMeasures {
71 | const { totalFramesRendered, totalRenderTime } = histogram.reduce(
72 | (aggregator, { renderingTime, frameCount }) => ({
73 | totalFramesRendered: aggregator.totalFramesRendered + frameCount,
74 | totalRenderTime:
75 | aggregator.totalRenderTime + frameCount * renderingTime,
76 | }),
77 | { totalFramesRendered: 0, totalRenderTime: 0 }
78 | );
79 |
80 | return {
81 | totalFramesRendered,
82 | totalRenderTime,
83 | };
84 | }
85 |
86 | private async measure(): Promise {
87 | const gfxOutput: { [name: string]: string } = (await this.getGfxInfo())
88 | .split("\n")
89 | .reduce((values, line) => {
90 | const [name, value] = line.split(": ");
91 | return value !== undefined ? { ...values, [name]: value } : values;
92 | }, {});
93 |
94 | const jankyFrames = {
95 | totalRendered: parseInt(gfxOutput["Total frames rendered"], 10),
96 | count: parseInt(gfxOutput["Janky frames"], 10),
97 | };
98 |
99 | const renderingTime = this.getRenderingTimeMeasures(
100 | this.parseHistogram(gfxOutput["HISTOGRAM"])
101 | );
102 |
103 | return {
104 | jankyFrames,
105 | renderingTime,
106 | };
107 | }
108 |
109 | public async addMeasureMarker(markerName: string) {
110 | const measure = await this.measure();
111 | this.measures.push({
112 | markerName,
113 | measure,
114 | });
115 | await this.resetDumpSys();
116 | }
117 |
118 | private reportMeasure({
119 | markerName,
120 | measure: {
121 | jankyFrames,
122 | renderingTime: { totalFramesRendered, totalRenderTime },
123 | },
124 | }: MarkerMeasure) {
125 | console.log(`${markerName}:
126 | Janky frames: ${jankyFrames.count}/${
127 | jankyFrames.totalRendered
128 | } (${roundToDecimal(
129 | (jankyFrames.count / jankyFrames.totalRendered) * 100,
130 | 2
131 | )}%)
132 | Average rendering time: ${roundToDecimal(
133 | totalRenderTime / totalFramesRendered,
134 | 2
135 | )}ms`);
136 | }
137 |
138 | private aggregateMeasures(): Measure {
139 | return this.measures.reduce(
140 | (aggregator, { measure }) => ({
141 | jankyFrames: {
142 | totalRendered:
143 | aggregator.jankyFrames.totalRendered +
144 | measure.jankyFrames.totalRendered,
145 | count: aggregator.jankyFrames.count + measure.jankyFrames.count,
146 | },
147 | renderingTime: {
148 | totalFramesRendered:
149 | aggregator.renderingTime.totalFramesRendered +
150 | measure.renderingTime.totalFramesRendered,
151 | totalRenderTime:
152 | aggregator.renderingTime.totalRenderTime +
153 | measure.renderingTime.totalRenderTime,
154 | },
155 | }),
156 | {
157 | jankyFrames: {
158 | totalRendered: 0,
159 | count: 0,
160 | },
161 | renderingTime: {
162 | totalFramesRendered: 0,
163 | totalRenderTime: 0,
164 | },
165 | }
166 | );
167 | }
168 |
169 | public report() {
170 | console.log(this.measures);
171 | this.measures.forEach(this.reportMeasure);
172 | this.reportMeasure({
173 | markerName: "Total",
174 | measure: this.aggregateMeasures(),
175 | });
176 | }
177 | }
178 |
--------------------------------------------------------------------------------
/example/ios/example/AppDelegate.mm:
--------------------------------------------------------------------------------
1 | #import "AppDelegate.h"
2 |
3 | #import
4 | #import
5 | #import
6 |
7 | #import
8 |
9 | #if RCT_NEW_ARCH_ENABLED
10 | #import
11 | #import
12 | #import
13 | #import
14 | #import
15 | #import
16 |
17 | #import
18 |
19 | static NSString *const kRNConcurrentRoot = @"concurrentRoot";
20 |
21 | @interface AppDelegate () {
22 | RCTTurboModuleManager *_turboModuleManager;
23 | RCTSurfacePresenterBridgeAdapter *_bridgeAdapter;
24 | std::shared_ptr _reactNativeConfig;
25 | facebook::react::ContextContainer::Shared _contextContainer;
26 | }
27 | @end
28 | #endif
29 |
30 | // Add those 4 lines before @implementation AppDelegate
31 | #ifdef FB_SONARKIT_ENABLED
32 | #import
33 | #import
34 | #endif
35 |
36 | @implementation AppDelegate
37 |
38 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
39 | {
40 | // And those 4 lines before RCTAppSetupPrepareApp
41 | #ifdef FB_SONARKIT_ENABLED
42 | FlipperClient *client = [FlipperClient sharedClient];
43 | [client addPlugin:[FlipperPerformancePlugin new]];
44 | #endif
45 | RCTAppSetupPrepareApp(application);
46 |
47 | RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
48 |
49 | #if RCT_NEW_ARCH_ENABLED
50 | _contextContainer = std::make_shared();
51 | _reactNativeConfig = std::make_shared();
52 | _contextContainer->insert("ReactNativeConfig", _reactNativeConfig);
53 | _bridgeAdapter = [[RCTSurfacePresenterBridgeAdapter alloc] initWithBridge:bridge contextContainer:_contextContainer];
54 | bridge.surfacePresenter = _bridgeAdapter.surfacePresenter;
55 | #endif
56 |
57 | NSDictionary *initProps = [self prepareInitialProps];
58 | UIView *rootView = RCTAppSetupDefaultRootView(bridge, @"example", initProps);
59 |
60 | if (@available(iOS 13.0, *)) {
61 | rootView.backgroundColor = [UIColor systemBackgroundColor];
62 | } else {
63 | rootView.backgroundColor = [UIColor whiteColor];
64 | }
65 |
66 | self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
67 | UIViewController *rootViewController = [UIViewController new];
68 | rootViewController.view = rootView;
69 | self.window.rootViewController = rootViewController;
70 | [self.window makeKeyAndVisible];
71 | return YES;
72 | }
73 |
74 | /// This method controls whether the `concurrentRoot`feature of React18 is turned on or off.
75 | ///
76 | /// @see: https://reactjs.org/blog/2022/03/29/react-v18.html
77 | /// @note: This requires to be rendering on Fabric (i.e. on the New Architecture).
78 | /// @return: `true` if the `concurrentRoot` feture is enabled. Otherwise, it returns `false`.
79 | - (BOOL)concurrentRootEnabled
80 | {
81 | // Switch this bool to turn on and off the concurrent root
82 | return true;
83 | }
84 |
85 | - (NSDictionary *)prepareInitialProps
86 | {
87 | NSMutableDictionary *initProps = [NSMutableDictionary new];
88 |
89 | #ifdef RCT_NEW_ARCH_ENABLED
90 | initProps[kRNConcurrentRoot] = @([self concurrentRootEnabled]);
91 | #endif
92 |
93 | return initProps;
94 | }
95 |
96 | - (NSURL *)sourceURLForBridge:(RCTBridge *)bridge
97 | {
98 | #if DEBUG
99 | return [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index"];
100 | #else
101 | return [[NSBundle mainBundle] URLForResource:@"main" withExtension:@"jsbundle"];
102 | #endif
103 | }
104 |
105 | #if RCT_NEW_ARCH_ENABLED
106 |
107 | #pragma mark - RCTCxxBridgeDelegate
108 |
109 | - (std::unique_ptr)jsExecutorFactoryForBridge:(RCTBridge *)bridge
110 | {
111 | _turboModuleManager = [[RCTTurboModuleManager alloc] initWithBridge:bridge
112 | delegate:self
113 | jsInvoker:bridge.jsCallInvoker];
114 | return RCTAppSetupDefaultJsExecutorFactory(bridge, _turboModuleManager);
115 | }
116 |
117 | #pragma mark RCTTurboModuleManagerDelegate
118 |
119 | - (Class)getModuleClassFromName:(const char *)name
120 | {
121 | return RCTCoreModulesClassProvider(name);
122 | }
123 |
124 | - (std::shared_ptr)getTurboModule:(const std::string &)name
125 | jsInvoker:(std::shared_ptr)jsInvoker
126 | {
127 | return nullptr;
128 | }
129 |
130 | - (std::shared_ptr)getTurboModule:(const std::string &)name
131 | initParams:
132 | (const facebook::react::ObjCTurboModule::InitParams &)params
133 | {
134 | return nullptr;
135 | }
136 |
137 | - (id)getModuleInstanceFromClass:(Class)moduleClass
138 | {
139 | return RCTAppSetupDefaultModuleFromClass(moduleClass);
140 | }
141 |
142 | #endif
143 |
144 | @end
145 |
--------------------------------------------------------------------------------
/flipper-native/ios/FlipperPerformancePlugin.m:
--------------------------------------------------------------------------------
1 | #import "FlipperPerformancePlugin.h"
2 |
3 | #if __has_include()
4 | #import
5 | // This ensures we can use [_bridge dispatchBlock]
6 | #import
7 |
8 | @interface FrameCountHolder : NSObject
9 |
10 | // See https://github.com/facebook/react-native/blob/1465c8f3874cdee8c325ab4a4916fda0b3e43bdb/React/CoreModules/RCTFPSGraph.m#L29
11 | @property (nonatomic, assign) NSUInteger frameCount;
12 | @property (nonatomic, assign) NSTimeInterval previousTime;
13 |
14 | - (void)incrementFrameCount;
15 |
16 | @end
17 |
18 | @implementation FrameCountHolder
19 |
20 | - (instancetype)init {
21 | _previousTime = -1;
22 | _frameCount = -1;
23 |
24 | return self;
25 | }
26 |
27 | - (void)incrementFrameCount {
28 | _frameCount ++;
29 | }
30 |
31 | @end
32 |
33 | @implementation FlipperPerformancePlugin {
34 | id _connection;
35 |
36 | CADisplayLink *_uiDisplayLink;
37 | CADisplayLink *_jsDisplayLink;
38 |
39 | FrameCountHolder *jsFrameCountHolder;
40 | FrameCountHolder *uiFrameCountHolder;
41 | }
42 |
43 | @synthesize bridge = _bridge;
44 | RCT_EXPORT_MODULE()
45 |
46 | /**
47 | Hack (non threadsafe) to populate Flipper plugin with the bridge automatically
48 | This flipper plugin is also a React Native native module, so that:
49 | 1. We initialize the plugin for flipper
50 | 2. RN automatically goes through init (since we have RCT_EXPORT_MODULE)
51 | 3. RN automatically sets the bridge
52 |
53 | It simplifies installation but we should remove it if it create issues
54 | */
55 | static FlipperPerformancePlugin *_pluginSingleton = nil;
56 | - (instancetype)init {
57 | if (!_pluginSingleton) {
58 | self = [self initSingleton];
59 | _pluginSingleton = self;
60 | }
61 |
62 | return _pluginSingleton;
63 | }
64 |
65 | - (instancetype)initSingleton {
66 | self = [super init];
67 |
68 | jsFrameCountHolder = [FrameCountHolder new];
69 | uiFrameCountHolder = [FrameCountHolder new];
70 |
71 | return self;
72 | }
73 |
74 | - (void)startMeasuring
75 | {
76 | [jsFrameCountHolder setPreviousTime:-1];
77 | [uiFrameCountHolder setPreviousTime:-1];
78 |
79 | // See https://github.com/facebook/react-native/blob/1465c8f3874cdee8c325ab4a4916fda0b3e43bdb/React/CoreModules/RCTPerfMonitor.mm#L338
80 | _uiDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onUIFrame:)];
81 | [_uiDisplayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
82 |
83 | [_bridge dispatchBlock:^{
84 | self->_jsDisplayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(onJSFrame:)];
85 | [self->_jsDisplayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
86 | }
87 | queue:RCTJSThread];
88 | }
89 |
90 | - (void) stopMeasuring
91 | {
92 | [self->_jsDisplayLink invalidate];
93 | self->_jsDisplayLink = nil;
94 | [self->_uiDisplayLink invalidate];
95 | self->_uiDisplayLink = nil;
96 | }
97 |
98 | - (void)onJSFrame:(CADisplayLink *)displayLink
99 | {
100 | [self onFrameTick:displayLink frameCountHolder:jsFrameCountHolder threadName:@"JS"];
101 | }
102 |
103 | - (void)onUIFrame:(CADisplayLink *)displayLink
104 | {
105 | [self onFrameTick:displayLink frameCountHolder:uiFrameCountHolder threadName:@"UI"];
106 | }
107 |
108 | - (void)onFrameTick:(CADisplayLink *)displayLink frameCountHolder:(FrameCountHolder*)frameCountHolder threadName:(NSString*)threadName
109 | {
110 | NSTimeInterval frameTimestamp = displayLink.timestamp;
111 |
112 | // See https://github.com/facebook/react-native/blob/1465c8f3874cdee8c325ab4a4916fda0b3e43bdb/React/CoreModules/RCTFPSGraph.m#L86
113 | [frameCountHolder incrementFrameCount];
114 | if ([frameCountHolder previousTime] == -1) {
115 | [frameCountHolder setPreviousTime:frameTimestamp];
116 | } else if (frameTimestamp - [frameCountHolder previousTime] >= 0.5) {
117 | [_connection send:@"addRecord" withParams:@{
118 | @"frameCount" : [NSNumber numberWithLong:[frameCountHolder frameCount]],
119 | @"time": [NSNumber numberWithLong:(frameTimestamp - [frameCountHolder previousTime]) * 1000],
120 | @"thread": threadName
121 | }];
122 |
123 | [frameCountHolder setPreviousTime:frameTimestamp];
124 | [frameCountHolder setFrameCount:0];
125 | }
126 | }
127 |
128 | - (void)didConnect:(id)connection {
129 | _connection = connection;
130 |
131 | [connection receive:@"startMeasuring"
132 | withBlock:^(NSDictionary* params, id responder) {
133 | [self startMeasuring];
134 | }];
135 |
136 | [connection receive:@"stopMeasuring" withBlock:^(NSDictionary* params, id responder) {
137 | [self stopMeasuring];
138 | }];
139 | }
140 |
141 | - (void)didDisconnect {
142 | [self stopMeasuring];
143 | }
144 |
145 | - (NSString *)identifier {
146 | return @"rn-perf-monitor";
147 | }
148 |
149 | + (BOOL)requiresMainQueueSetup {
150 | return TRUE;
151 | }
152 |
153 | RCT_REMAP_METHOD(killUIThread,
154 | withIntensity:(nonnull NSNumber*)intensity
155 | withResolver:(RCTPromiseResolveBlock)resolve
156 | withRejecter:(RCTPromiseRejectBlock)reject)
157 | {
158 | dispatch_async(dispatch_get_main_queue(), ^{
159 | [self fibonacci:[intensity intValue]];
160 | });
161 | }
162 |
163 | - (int)fibonacci:(int)n {
164 | if (n < 1) {
165 | return 0;
166 | }
167 | if (n <= 2) {
168 | return 1;
169 | }
170 |
171 | return [self fibonacci:(n - 1)] + [self fibonacci:(n - 2)];
172 | }
173 |
174 | @end
175 |
176 | #else
177 |
178 | @implementation FlipperPerformancePlugin
179 |
180 | @end
181 |
182 | #endif
183 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # React Native Performance Monitor Flipper plugin
2 |
3 | [](https://app.travis-ci.com/bamlab/react-native-performance)
4 |
5 | 
6 |
7 |
8 |
9 |
10 | - [Usage](#usage)
11 | - [Install](#install)
12 | - [Flipper](#flipper)
13 | - [Install for non-Expo projects](#install-for-non-expo-projects)
14 | - [iOS](#ios)
15 | - [Android](#android)
16 | - [Migrating from flipper-plugin-rn-performance-android](#migrating-from-flipper-plugin-rn-performance-android)
17 | - [Install for Expo projects](#install-for-expo-projects)
18 | - [Best practice](#best-practice)
19 | - [Contributing](#contributing-to-flipper-desktop)
20 |
21 |
22 |
23 | ## Usage
24 |
25 | [Here's a detailed article](https://blog.bam.tech/developer-news/measuring-and-improving-performance-on-a-react-native-app) to go further
26 |
27 | 1. Disable _JS Dev Mode_ in the settings (shake your device to open the development menu, then click settings)
28 | 2. Click **Start Measuring**
29 | 3. Do stuff in your app
30 | 4. Check the score!
31 |
32 | This is how the score is calculated below, quite naively, but open to suggestions:
33 |
34 |
35 | Note that:
36 |
37 | - the score depends on the device used. We advice using a lower-end device to accentuate performance issues.
38 | - the score depends on what you do on your app while measuring. If you do nothing, your score should (hopefully) be 100!
39 |
40 | ## Install
41 |
42 | ### Flipper
43 |
44 | Search for `rn-perf-monitor` in the list of plugins.
45 |
46 | ### Install for non-Expo projects
47 |
48 | Install the plugin
49 |
50 | ```sh
51 | yarn add --dev react-native-flipper-performance-plugin
52 | ```
53 |
54 | Then go to iOS/Android section below to continue the install
55 |
56 | #### iOS
57 |
58 | - Run `cd ios && pod install`
59 |
60 | Then depending on your RN version:
61 |
62 | ##### >= v0.68.0
63 |
64 | - In `./ios/yourapp/AppDelegate.mm` (where `yourapp` depends on your app), add those 8 lines below:
65 |
66 | _(You can check how it's done in [the example folder](https://github.com/bamlab/react-native-performance/blob/master/example/ios/example/AppDelegate.mm))_
67 |
68 | ```objc
69 | // Add those 4 lines before @implementation AppDelegate
70 | #ifdef FB_SONARKIT_ENABLED
71 | #import
72 | #import
73 | #endif
74 |
75 | @implementation AppDelegate
76 |
77 | - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
78 | {
79 | // And those 4 lines before RCTAppSetupPrepareApp
80 | #ifdef FB_SONARKIT_ENABLED
81 | FlipperClient *client = [FlipperClient sharedClient];
82 | [client addPlugin:[FlipperPerformancePlugin new]];
83 | #endif
84 | RCTAppSetupPrepareApp(application);
85 | ```
86 |
87 | ##### < 0.68.0
88 |
89 | - In `./ios/yourapp/AppDelegate.m` (where `yourapp` depends on your app), add 2 lines:
90 |
91 | ```objc
92 | #ifdef FB_SONARKIT_ENABLED
93 | ...
94 | // Add this line
95 | #import
96 |
97 | static void InitializeFlipper(UIApplication *application) {
98 | ...
99 |
100 | // Add this line
101 | [client addPlugin:[FlipperPerformancePlugin new]];
102 |
103 | [client start];
104 | }
105 | #endif
106 | ```
107 |
108 | #### Android
109 |
110 | In `./android/app/src/debug/java/com/yourapp/ReactNativeFlipper.java` (where `com/yourapp` depends on your app), add:
111 |
112 | ```java
113 | import tech.bam.rnperformance.flipper.RNPerfMonitorPlugin;
114 |
115 | ...
116 |
117 | client.addPlugin(new RNPerfMonitorPlugin(reactInstanceManager));
118 | ```
119 |
120 | #### Migrating from flipper-plugin-rn-performance-android
121 |
122 | You might have previously installed `flipper-plugin-rn-performance-android`. This is now deprecated, as `react-native-flipper-performance-plugin` has autolinking and cross-platform support.
123 |
124 | You also need to run these steps:
125 |
126 | Uninstall the package:
127 |
128 | ```
129 | yarn remove flipper-plugin-rn-performance-android
130 | ```
131 |
132 | Then **remove** those lines in `./android/settings.gradle`:
133 |
134 | ```gradle
135 | include ':flipper-plugin-rn-performance-android'
136 | project(':flipper-plugin-rn-performance-android').projectDir = new File(rootProject.projectDir, '../node_modules/flipper-plugin-rn-performance-android')
137 | ```
138 |
139 | and in `./android/app/build.gradle`:
140 |
141 | ```gradle
142 | debugImplementation project(':flipper-plugin-rn-performance-android')
143 | ```
144 |
145 | ### Install for Expo projects
146 |
147 | 1. This plugin does not work with Expo Go, since it adds native code. You can use an Expo [custom-dev-client](https://docs.expo.dev/development/getting-started/) instead.
148 |
149 | 2. Install the **Expo Flipper plugin**. Documentation can be found here: [Expo Community Flipper](https://github.com/jakobo/expo-community-flipper).
150 | Install the module along with [react-native-flipper](https://www.npmjs.com/package/react-native-flipper):
151 |
152 | _TL;DR_: `yarn add -D expo-community-flipper react-native-flipper`
153 |
154 | 3. Add `expo-community-flipper` configuration to the `plugins` section of your `app.json`. Please check [Expo Community Flipper documentation](https://github.com/jakobo/expo-community-flipper) if you need further settings.
155 |
156 | 4. Add the plugin to this library **AFTER** `expo-community-flipper`
157 |
158 | ```json
159 | {
160 | "expo": {
161 | "..."
162 | "plugins": [
163 | ["expo-community-flipper"], // first
164 | ["react-native-flipper-performance-plugin"] // add this after
165 | ]
166 | }
167 | }
168 | ```
169 |
170 | Make sure to run `expo prebuild` or `expo prebuild --clean` afterwards. If the plugin is not recognized on iOS, make sure you call `npx pod-install` or `cd ios && pod install` in the root directory of your project.
171 |
172 | #### Best practice
173 |
174 | This library helps you to calculate a Lighthouse score similar to PageSpeed Insights. However, the tool is of no use to you if you cannot draw any optimizations from it. That is why it is recommended to use the plugin together with React DevTools.
175 |
176 | The easiest way to use React DevTools is to install it as follows:
177 |
178 | `yarn add -D react-devtools-core`
179 |
180 | Then edit your App.tsx/App.js with following default settings.
181 |
182 | ```tsx
183 | import { connectToDevTools } from "react-devtools-core";
184 |
185 | if (__DEV__) {
186 | connectToDevTools({
187 | host: "localhost",
188 | port: 8097,
189 | });
190 | }
191 | ```
192 |
193 | With DevTools you can easily determine why your app is taking too much time for a particular task, most importantly you can find out if you are re-rendering too often. Especially with lists, this can quickly become a knitting trap. Optimize your code and measure the FPS afterwards to get a decent score.
194 |
195 | ## Contributing to flipper Desktop
196 |
197 | 1. Clone the repository.
198 | 2. Add path to your local `react-native-performance` folder in `~/.flipper/config.json` as shown on [the flipper docs](https://fbflipper.com/docs/extending/loading-custom-plugins/)
199 | 3. Run `yarn watch` inside `flipper-desktop`
200 | 4. Connect your debug app with the flipper android plugin installed.
201 | 5. You should now see your plugin appear in Flipper.
202 |
--------------------------------------------------------------------------------
/example/android/gradlew:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | #
4 | # Copyright © 2015-2021 the original authors.
5 | #
6 | # Licensed under the Apache License, Version 2.0 (the "License");
7 | # you may not use this file except in compliance with the License.
8 | # You may obtain a copy of the License at
9 | #
10 | # https://www.apache.org/licenses/LICENSE-2.0
11 | #
12 | # Unless required by applicable law or agreed to in writing, software
13 | # distributed under the License is distributed on an "AS IS" BASIS,
14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 | # See the License for the specific language governing permissions and
16 | # limitations under the License.
17 | #
18 |
19 | ##############################################################################
20 | #
21 | # Gradle start up script for POSIX generated by Gradle.
22 | #
23 | # Important for running:
24 | #
25 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
26 | # noncompliant, but you have some other compliant shell such as ksh or
27 | # bash, then to run this script, type that shell name before the whole
28 | # command line, like:
29 | #
30 | # ksh Gradle
31 | #
32 | # Busybox and similar reduced shells will NOT work, because this script
33 | # requires all of these POSIX shell features:
34 | # * functions;
35 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
36 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»;
37 | # * compound commands having a testable exit status, especially «case»;
38 | # * various built-in commands including «command», «set», and «ulimit».
39 | #
40 | # Important for patching:
41 | #
42 | # (2) This script targets any POSIX shell, so it avoids extensions provided
43 | # by Bash, Ksh, etc; in particular arrays are avoided.
44 | #
45 | # The "traditional" practice of packing multiple parameters into a
46 | # space-separated string is a well documented source of bugs and security
47 | # problems, so this is (mostly) avoided, by progressively accumulating
48 | # options in "$@", and eventually passing that to Java.
49 | #
50 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
51 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
52 | # see the in-line comments for details.
53 | #
54 | # There are tweaks for specific operating systems such as AIX, CygWin,
55 | # Darwin, MinGW, and NonStop.
56 | #
57 | # (3) This script is generated from the Groovy template
58 | # https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
59 | # within the Gradle project.
60 | #
61 | # You can find Gradle at https://github.com/gradle/gradle/.
62 | #
63 | ##############################################################################
64 |
65 | # Attempt to set APP_HOME
66 |
67 | # Resolve links: $0 may be a link
68 | app_path=$0
69 |
70 | # Need this for daisy-chained symlinks.
71 | while
72 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
73 | [ -h "$app_path" ]
74 | do
75 | ls=$( ls -ld "$app_path" )
76 | link=${ls#*' -> '}
77 | case $link in #(
78 | /*) app_path=$link ;; #(
79 | *) app_path=$APP_HOME$link ;;
80 | esac
81 | done
82 |
83 | APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
84 |
85 | APP_NAME="Gradle"
86 | APP_BASE_NAME=${0##*/}
87 |
88 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
89 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
90 |
91 | # Use the maximum available, or set MAX_FD != -1 to use that value.
92 | MAX_FD=maximum
93 |
94 | warn () {
95 | echo "$*"
96 | } >&2
97 |
98 | die () {
99 | echo
100 | echo "$*"
101 | echo
102 | exit 1
103 | } >&2
104 |
105 | # OS specific support (must be 'true' or 'false').
106 | cygwin=false
107 | msys=false
108 | darwin=false
109 | nonstop=false
110 | case "$( uname )" in #(
111 | CYGWIN* ) cygwin=true ;; #(
112 | Darwin* ) darwin=true ;; #(
113 | MSYS* | MINGW* ) msys=true ;; #(
114 | NONSTOP* ) nonstop=true ;;
115 | esac
116 |
117 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
118 |
119 |
120 | # Determine the Java command to use to start the JVM.
121 | if [ -n "$JAVA_HOME" ] ; then
122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
123 | # IBM's JDK on AIX uses strange locations for the executables
124 | JAVACMD=$JAVA_HOME/jre/sh/java
125 | else
126 | JAVACMD=$JAVA_HOME/bin/java
127 | fi
128 | if [ ! -x "$JAVACMD" ] ; then
129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
130 |
131 | Please set the JAVA_HOME variable in your environment to match the
132 | location of your Java installation."
133 | fi
134 | else
135 | JAVACMD=java
136 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
137 |
138 | Please set the JAVA_HOME variable in your environment to match the
139 | location of your Java installation."
140 | fi
141 |
142 | # Increase the maximum file descriptors if we can.
143 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
144 | case $MAX_FD in #(
145 | max*)
146 | MAX_FD=$( ulimit -H -n ) ||
147 | warn "Could not query maximum file descriptor limit"
148 | esac
149 | case $MAX_FD in #(
150 | '' | soft) :;; #(
151 | *)
152 | ulimit -n "$MAX_FD" ||
153 | warn "Could not set maximum file descriptor limit to $MAX_FD"
154 | esac
155 | fi
156 |
157 | # Collect all arguments for the java command, stacking in reverse order:
158 | # * args from the command line
159 | # * the main class name
160 | # * -classpath
161 | # * -D...appname settings
162 | # * --module-path (only if needed)
163 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
164 |
165 | # For Cygwin or MSYS, switch paths to Windows format before running java
166 | if "$cygwin" || "$msys" ; then
167 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
168 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
169 |
170 | JAVACMD=$( cygpath --unix "$JAVACMD" )
171 |
172 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
173 | for arg do
174 | if
175 | case $arg in #(
176 | -*) false ;; # don't mess with options #(
177 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
178 | [ -e "$t" ] ;; #(
179 | *) false ;;
180 | esac
181 | then
182 | arg=$( cygpath --path --ignore --mixed "$arg" )
183 | fi
184 | # Roll the args list around exactly as many times as the number of
185 | # args, so each arg winds up back in the position where it started, but
186 | # possibly modified.
187 | #
188 | # NB: a `for` loop captures its iteration list before it begins, so
189 | # changing the positional parameters here affects neither the number of
190 | # iterations, nor the values presented in `arg`.
191 | shift # remove old arg
192 | set -- "$@" "$arg" # push replacement arg
193 | done
194 | fi
195 |
196 | # Collect all arguments for the java command;
197 | # * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
198 | # shell script including quotes and variable substitutions, so put them in
199 | # double quotes to make sure that they get re-expanded; and
200 | # * put everything else in single quotes, so that it's not re-expanded.
201 |
202 | set -- \
203 | "-Dorg.gradle.appname=$APP_BASE_NAME" \
204 | -classpath "$CLASSPATH" \
205 | org.gradle.wrapper.GradleWrapperMain \
206 | "$@"
207 |
208 | # Use "xargs" to parse quoted args.
209 | #
210 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed.
211 | #
212 | # In Bash we could simply go:
213 | #
214 | # readarray ARGS < <( xargs -n1 <<<"$var" ) &&
215 | # set -- "${ARGS[@]}" "$@"
216 | #
217 | # but POSIX shell has neither arrays nor command substitution, so instead we
218 | # post-process each arg (as a line of input to sed) to backslash-escape any
219 | # character that might be a shell metacharacter, then use eval to reverse
220 | # that process (while maintaining the separation between arguments), and wrap
221 | # the whole thing up as a single "set" statement.
222 | #
223 | # This will of course break if any of these variables contains a newline or
224 | # an unmatched quote.
225 | #
226 |
227 | eval "set -- $(
228 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
229 | xargs -n1 |
230 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
231 | tr '\n' ' '
232 | )" '"$@"'
233 |
234 | exec "$JAVACMD" "$@"
235 |
--------------------------------------------------------------------------------
/react-native-startup-trace/ios/StartupTrace.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXCopyFilesBuildPhase section */
10 | 58B511D91A9E6C8500147676 /* CopyFiles */ = {
11 | isa = PBXCopyFilesBuildPhase;
12 | buildActionMask = 2147483647;
13 | dstPath = "include/$(PRODUCT_NAME)";
14 | dstSubfolderSpec = 16;
15 | files = (
16 | );
17 | runOnlyForDeploymentPostprocessing = 0;
18 | };
19 | /* End PBXCopyFilesBuildPhase section */
20 |
21 | /* Begin PBXFileReference section */
22 | 134814201AA4EA6300B7C361 /* libStartupTrace.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libStartupTrace.a; sourceTree = BUILT_PRODUCTS_DIR; };
23 | /* End PBXFileReference section */
24 |
25 | /* Begin PBXFrameworksBuildPhase section */
26 | 58B511D81A9E6C8500147676 /* Frameworks */ = {
27 | isa = PBXFrameworksBuildPhase;
28 | buildActionMask = 2147483647;
29 | files = (
30 | );
31 | runOnlyForDeploymentPostprocessing = 0;
32 | };
33 | /* End PBXFrameworksBuildPhase section */
34 |
35 | /* Begin PBXGroup section */
36 | 134814211AA4EA7D00B7C361 /* Products */ = {
37 | isa = PBXGroup;
38 | children = (
39 | 134814201AA4EA6300B7C361 /* libStartupTrace.a */,
40 | );
41 | name = Products;
42 | sourceTree = "";
43 | };
44 | 58B511D21A9E6C8500147676 = {
45 | isa = PBXGroup;
46 | children = (
47 | 134814211AA4EA7D00B7C361 /* Products */,
48 | );
49 | sourceTree = "";
50 | };
51 | /* End PBXGroup section */
52 |
53 | /* Begin PBXNativeTarget section */
54 | 58B511DA1A9E6C8500147676 /* StartupTrace */ = {
55 | isa = PBXNativeTarget;
56 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "StartupTrace" */;
57 | buildPhases = (
58 | 58B511D71A9E6C8500147676 /* Sources */,
59 | 58B511D81A9E6C8500147676 /* Frameworks */,
60 | 58B511D91A9E6C8500147676 /* CopyFiles */,
61 | );
62 | buildRules = (
63 | );
64 | dependencies = (
65 | );
66 | name = StartupTrace;
67 | productName = RCTDataManager;
68 | productReference = 134814201AA4EA6300B7C361 /* libStartupTrace.a */;
69 | productType = "com.apple.product-type.library.static";
70 | };
71 | /* End PBXNativeTarget section */
72 |
73 | /* Begin PBXProject section */
74 | 58B511D31A9E6C8500147676 /* Project object */ = {
75 | isa = PBXProject;
76 | attributes = {
77 | LastUpgradeCheck = 0920;
78 | ORGANIZATIONNAME = Facebook;
79 | TargetAttributes = {
80 | 58B511DA1A9E6C8500147676 = {
81 | CreatedOnToolsVersion = 6.1.1;
82 | };
83 | };
84 | };
85 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "StartupTrace" */;
86 | compatibilityVersion = "Xcode 3.2";
87 | developmentRegion = en;
88 | hasScannedForEncodings = 0;
89 | knownRegions = (
90 | en,
91 | Base,
92 | );
93 | mainGroup = 58B511D21A9E6C8500147676;
94 | productRefGroup = 58B511D21A9E6C8500147676;
95 | projectDirPath = "";
96 | projectRoot = "";
97 | targets = (
98 | 58B511DA1A9E6C8500147676 /* StartupTrace */,
99 | );
100 | };
101 | /* End PBXProject section */
102 |
103 | /* Begin PBXSourcesBuildPhase section */
104 | 58B511D71A9E6C8500147676 /* Sources */ = {
105 | isa = PBXSourcesBuildPhase;
106 | buildActionMask = 2147483647;
107 | files = (
108 | );
109 | runOnlyForDeploymentPostprocessing = 0;
110 | };
111 | /* End PBXSourcesBuildPhase section */
112 |
113 | /* Begin XCBuildConfiguration section */
114 | 58B511ED1A9E6C8500147676 /* Debug */ = {
115 | isa = XCBuildConfiguration;
116 | buildSettings = {
117 | ALWAYS_SEARCH_USER_PATHS = NO;
118 | CLANG_ANALYZER_NONNULL = YES;
119 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
120 | CLANG_CXX_LIBRARY = "libc++";
121 | CLANG_ENABLE_MODULES = YES;
122 | CLANG_ENABLE_OBJC_ARC = YES;
123 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
124 | CLANG_WARN_BOOL_CONVERSION = YES;
125 | CLANG_WARN_COMMA = YES;
126 | CLANG_WARN_CONSTANT_CONVERSION = YES;
127 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
128 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
129 | CLANG_WARN_EMPTY_BODY = YES;
130 | CLANG_WARN_ENUM_CONVERSION = YES;
131 | CLANG_WARN_INFINITE_RECURSION = YES;
132 | CLANG_WARN_INT_CONVERSION = YES;
133 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
134 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
135 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
136 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
137 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
138 | CLANG_WARN_STRICT_PROTOTYPES = YES;
139 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
140 | CLANG_WARN_UNREACHABLE_CODE = YES;
141 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
142 | COPY_PHASE_STRIP = NO;
143 | ENABLE_STRICT_OBJC_MSGSEND = YES;
144 | ENABLE_TESTABILITY = YES;
145 | GCC_C_LANGUAGE_STANDARD = gnu99;
146 | GCC_DYNAMIC_NO_PIC = NO;
147 | GCC_NO_COMMON_BLOCKS = YES;
148 | GCC_OPTIMIZATION_LEVEL = 0;
149 | GCC_PREPROCESSOR_DEFINITIONS = (
150 | "DEBUG=1",
151 | "$(inherited)",
152 | );
153 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
154 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
155 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
156 | GCC_WARN_UNDECLARED_SELECTOR = YES;
157 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
158 | GCC_WARN_UNUSED_FUNCTION = YES;
159 | GCC_WARN_UNUSED_VARIABLE = YES;
160 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
161 | LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
162 | LIBRARY_SEARCH_PATHS = (
163 | "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
164 | "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
165 | "\"$(inherited)\"",
166 | );
167 | MTL_ENABLE_DEBUG_INFO = YES;
168 | ONLY_ACTIVE_ARCH = YES;
169 | SDKROOT = iphoneos;
170 | };
171 | name = Debug;
172 | };
173 | 58B511EE1A9E6C8500147676 /* Release */ = {
174 | isa = XCBuildConfiguration;
175 | buildSettings = {
176 | ALWAYS_SEARCH_USER_PATHS = NO;
177 | CLANG_ANALYZER_NONNULL = YES;
178 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
179 | CLANG_CXX_LIBRARY = "libc++";
180 | CLANG_ENABLE_MODULES = YES;
181 | CLANG_ENABLE_OBJC_ARC = YES;
182 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
183 | CLANG_WARN_BOOL_CONVERSION = YES;
184 | CLANG_WARN_COMMA = YES;
185 | CLANG_WARN_CONSTANT_CONVERSION = YES;
186 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
187 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
188 | CLANG_WARN_EMPTY_BODY = YES;
189 | CLANG_WARN_ENUM_CONVERSION = YES;
190 | CLANG_WARN_INFINITE_RECURSION = YES;
191 | CLANG_WARN_INT_CONVERSION = YES;
192 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
193 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
194 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
195 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
196 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
197 | CLANG_WARN_STRICT_PROTOTYPES = YES;
198 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
199 | CLANG_WARN_UNREACHABLE_CODE = YES;
200 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
201 | COPY_PHASE_STRIP = YES;
202 | ENABLE_NS_ASSERTIONS = NO;
203 | ENABLE_STRICT_OBJC_MSGSEND = YES;
204 | GCC_C_LANGUAGE_STANDARD = gnu99;
205 | GCC_NO_COMMON_BLOCKS = YES;
206 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
207 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
208 | GCC_WARN_UNDECLARED_SELECTOR = YES;
209 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
210 | GCC_WARN_UNUSED_FUNCTION = YES;
211 | GCC_WARN_UNUSED_VARIABLE = YES;
212 | IPHONEOS_DEPLOYMENT_TARGET = 9.0;
213 | LD_RUNPATH_SEARCH_PATHS = "/usr/lib/swift $(inherited)";
214 | LIBRARY_SEARCH_PATHS = (
215 | "\"$(TOOLCHAIN_DIR)/usr/lib/swift/$(PLATFORM_NAME)\"",
216 | "\"$(TOOLCHAIN_DIR)/usr/lib/swift-5.0/$(PLATFORM_NAME)\"",
217 | "\"$(inherited)\"",
218 | );
219 | MTL_ENABLE_DEBUG_INFO = NO;
220 | SDKROOT = iphoneos;
221 | VALIDATE_PRODUCT = YES;
222 | };
223 | name = Release;
224 | };
225 | 58B511F01A9E6C8500147676 /* Debug */ = {
226 | isa = XCBuildConfiguration;
227 | buildSettings = {
228 | HEADER_SEARCH_PATHS = (
229 | "$(inherited)",
230 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
231 | "$(SRCROOT)/../../../React/**",
232 | "$(SRCROOT)/../../react-native/React/**",
233 | );
234 | LIBRARY_SEARCH_PATHS = "$(inherited)";
235 | OTHER_LDFLAGS = "-ObjC";
236 | PRODUCT_NAME = StartupTrace;
237 | SKIP_INSTALL = YES;
238 | };
239 | name = Debug;
240 | };
241 | 58B511F11A9E6C8500147676 /* Release */ = {
242 | isa = XCBuildConfiguration;
243 | buildSettings = {
244 | HEADER_SEARCH_PATHS = (
245 | "$(inherited)",
246 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
247 | "$(SRCROOT)/../../../React/**",
248 | "$(SRCROOT)/../../react-native/React/**",
249 | );
250 | LIBRARY_SEARCH_PATHS = "$(inherited)";
251 | OTHER_LDFLAGS = "-ObjC";
252 | PRODUCT_NAME = StartupTrace;
253 | SKIP_INSTALL = YES;
254 | };
255 | name = Release;
256 | };
257 | /* End XCBuildConfiguration section */
258 |
259 | /* Begin XCConfigurationList section */
260 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "StartupTrace" */ = {
261 | isa = XCConfigurationList;
262 | buildConfigurations = (
263 | 58B511ED1A9E6C8500147676 /* Debug */,
264 | 58B511EE1A9E6C8500147676 /* Release */,
265 | );
266 | defaultConfigurationIsVisible = 0;
267 | defaultConfigurationName = Release;
268 | };
269 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "StartupTrace" */ = {
270 | isa = XCConfigurationList;
271 | buildConfigurations = (
272 | 58B511F01A9E6C8500147676 /* Debug */,
273 | 58B511F11A9E6C8500147676 /* Release */,
274 | );
275 | defaultConfigurationIsVisible = 0;
276 | defaultConfigurationName = Release;
277 | };
278 | /* End XCConfigurationList section */
279 | };
280 | rootObject = 58B511D31A9E6C8500147676 /* Project object */;
281 | }
282 |
--------------------------------------------------------------------------------
/flipper-native/ios/FlipperPerformancePlugin.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 46;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 |
11 | 5E555C0D2413F4C50049A1A2 /* FlipperPerformancePlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = B3E7B5891CC2AC0600A0062D /* FlipperPerformancePlugin.m */; };
12 |
13 | /* End PBXBuildFile section */
14 |
15 | /* Begin PBXCopyFilesBuildPhase section */
16 | 58B511D91A9E6C8500147676 /* CopyFiles */ = {
17 | isa = PBXCopyFilesBuildPhase;
18 | buildActionMask = 2147483647;
19 | dstPath = "include/$(PRODUCT_NAME)";
20 | dstSubfolderSpec = 16;
21 | files = (
22 | );
23 | runOnlyForDeploymentPostprocessing = 0;
24 | };
25 | /* End PBXCopyFilesBuildPhase section */
26 |
27 | /* Begin PBXFileReference section */
28 | 134814201AA4EA6300B7C361 /* libFlipperPerformancePlugin.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libFlipperPerformancePlugin.a; sourceTree = BUILT_PRODUCTS_DIR; };
29 |
30 | B3E7B5881CC2AC0600A0062D /* FlipperPerformancePlugin.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FlipperPerformancePlugin.h; sourceTree = ""; };
31 | B3E7B5891CC2AC0600A0062D /* FlipperPerformancePlugin.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FlipperPerformancePlugin.m; sourceTree = ""; };
32 |
33 | /* End PBXFileReference section */
34 |
35 | /* Begin PBXFrameworksBuildPhase section */
36 | 58B511D81A9E6C8500147676 /* Frameworks */ = {
37 | isa = PBXFrameworksBuildPhase;
38 | buildActionMask = 2147483647;
39 | files = (
40 | );
41 | runOnlyForDeploymentPostprocessing = 0;
42 | };
43 | /* End PBXFrameworksBuildPhase section */
44 |
45 | /* Begin PBXGroup section */
46 | 134814211AA4EA7D00B7C361 /* Products */ = {
47 | isa = PBXGroup;
48 | children = (
49 | 134814201AA4EA6300B7C361 /* libFlipperPerformancePlugin.a */,
50 | );
51 | name = Products;
52 | sourceTree = "";
53 | };
54 | 58B511D21A9E6C8500147676 = {
55 | isa = PBXGroup;
56 | children = (
57 |
58 | B3E7B5881CC2AC0600A0062D /* FlipperPerformancePlugin.h */,
59 | B3E7B5891CC2AC0600A0062D /* FlipperPerformancePlugin.m */,
60 |
61 | 134814211AA4EA7D00B7C361 /* Products */,
62 | );
63 | sourceTree = "";
64 | };
65 | /* End PBXGroup section */
66 |
67 | /* Begin PBXNativeTarget section */
68 | 58B511DA1A9E6C8500147676 /* FlipperPerformancePlugin */ = {
69 | isa = PBXNativeTarget;
70 | buildConfigurationList = 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "FlipperPerformancePlugin" */;
71 | buildPhases = (
72 | 58B511D71A9E6C8500147676 /* Sources */,
73 | 58B511D81A9E6C8500147676 /* Frameworks */,
74 | 58B511D91A9E6C8500147676 /* CopyFiles */,
75 | );
76 | buildRules = (
77 | );
78 | dependencies = (
79 | );
80 | name = FlipperPerformancePlugin;
81 | productName = RCTDataManager;
82 | productReference = 134814201AA4EA6300B7C361 /* libFlipperPerformancePlugin.a */;
83 | productType = "com.apple.product-type.library.static";
84 | };
85 | /* End PBXNativeTarget section */
86 |
87 | /* Begin PBXProject section */
88 | 58B511D31A9E6C8500147676 /* Project object */ = {
89 | isa = PBXProject;
90 | attributes = {
91 | LastUpgradeCheck = 0920;
92 | ORGANIZATIONNAME = Facebook;
93 | TargetAttributes = {
94 | 58B511DA1A9E6C8500147676 = {
95 | CreatedOnToolsVersion = 6.1.1;
96 | };
97 | };
98 | };
99 | buildConfigurationList = 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "FlipperPerformancePlugin" */;
100 | compatibilityVersion = "Xcode 3.2";
101 | developmentRegion = English;
102 | hasScannedForEncodings = 0;
103 | knownRegions = (
104 | English,
105 | en,
106 | );
107 | mainGroup = 58B511D21A9E6C8500147676;
108 | productRefGroup = 58B511D21A9E6C8500147676;
109 | projectDirPath = "";
110 | projectRoot = "";
111 | targets = (
112 | 58B511DA1A9E6C8500147676 /* FlipperPerformancePlugin */,
113 | );
114 | };
115 | /* End PBXProject section */
116 |
117 | /* Begin PBXSourcesBuildPhase section */
118 | 58B511D71A9E6C8500147676 /* Sources */ = {
119 | isa = PBXSourcesBuildPhase;
120 | buildActionMask = 2147483647;
121 | files = (
122 |
123 | B3E7B58A1CC2AC0600A0062D /* FlipperPerformancePlugin.m in Sources */,
124 |
125 | );
126 | runOnlyForDeploymentPostprocessing = 0;
127 | };
128 | /* End PBXSourcesBuildPhase section */
129 |
130 | /* Begin XCBuildConfiguration section */
131 | 58B511ED1A9E6C8500147676 /* Debug */ = {
132 | isa = XCBuildConfiguration;
133 | buildSettings = {
134 | ALWAYS_SEARCH_USER_PATHS = NO;
135 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
136 | CLANG_CXX_LIBRARY = "libc++";
137 | CLANG_ENABLE_MODULES = YES;
138 | CLANG_ENABLE_OBJC_ARC = YES;
139 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
140 | CLANG_WARN_BOOL_CONVERSION = YES;
141 | CLANG_WARN_COMMA = YES;
142 | CLANG_WARN_CONSTANT_CONVERSION = YES;
143 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
144 | CLANG_WARN_EMPTY_BODY = YES;
145 | CLANG_WARN_ENUM_CONVERSION = YES;
146 | CLANG_WARN_INFINITE_RECURSION = YES;
147 | CLANG_WARN_INT_CONVERSION = YES;
148 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
149 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
150 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
151 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
152 | CLANG_WARN_STRICT_PROTOTYPES = YES;
153 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
154 | CLANG_WARN_UNREACHABLE_CODE = YES;
155 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
156 | COPY_PHASE_STRIP = NO;
157 | ENABLE_STRICT_OBJC_MSGSEND = YES;
158 | ENABLE_TESTABILITY = YES;
159 | GCC_C_LANGUAGE_STANDARD = gnu99;
160 | GCC_DYNAMIC_NO_PIC = NO;
161 | GCC_NO_COMMON_BLOCKS = YES;
162 | GCC_OPTIMIZATION_LEVEL = 0;
163 | GCC_PREPROCESSOR_DEFINITIONS = (
164 | "DEBUG=1",
165 | "$(inherited)",
166 | );
167 | GCC_SYMBOLS_PRIVATE_EXTERN = NO;
168 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
169 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
170 | GCC_WARN_UNDECLARED_SELECTOR = YES;
171 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
172 | GCC_WARN_UNUSED_FUNCTION = YES;
173 | GCC_WARN_UNUSED_VARIABLE = YES;
174 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
175 | MTL_ENABLE_DEBUG_INFO = YES;
176 | ONLY_ACTIVE_ARCH = YES;
177 | SDKROOT = iphoneos;
178 | };
179 | name = Debug;
180 | };
181 | 58B511EE1A9E6C8500147676 /* Release */ = {
182 | isa = XCBuildConfiguration;
183 | buildSettings = {
184 | ALWAYS_SEARCH_USER_PATHS = NO;
185 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
186 | CLANG_CXX_LIBRARY = "libc++";
187 | CLANG_ENABLE_MODULES = YES;
188 | CLANG_ENABLE_OBJC_ARC = YES;
189 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
190 | CLANG_WARN_BOOL_CONVERSION = YES;
191 | CLANG_WARN_COMMA = YES;
192 | CLANG_WARN_CONSTANT_CONVERSION = YES;
193 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
194 | CLANG_WARN_EMPTY_BODY = YES;
195 | CLANG_WARN_ENUM_CONVERSION = YES;
196 | CLANG_WARN_INFINITE_RECURSION = YES;
197 | CLANG_WARN_INT_CONVERSION = YES;
198 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
199 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
200 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
201 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
202 | CLANG_WARN_STRICT_PROTOTYPES = YES;
203 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
204 | CLANG_WARN_UNREACHABLE_CODE = YES;
205 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
206 | COPY_PHASE_STRIP = YES;
207 | ENABLE_NS_ASSERTIONS = NO;
208 | ENABLE_STRICT_OBJC_MSGSEND = YES;
209 | GCC_C_LANGUAGE_STANDARD = gnu99;
210 | GCC_NO_COMMON_BLOCKS = YES;
211 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
212 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
213 | GCC_WARN_UNDECLARED_SELECTOR = YES;
214 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
215 | GCC_WARN_UNUSED_FUNCTION = YES;
216 | GCC_WARN_UNUSED_VARIABLE = YES;
217 | IPHONEOS_DEPLOYMENT_TARGET = 8.0;
218 | MTL_ENABLE_DEBUG_INFO = NO;
219 | SDKROOT = iphoneos;
220 | VALIDATE_PRODUCT = YES;
221 | };
222 | name = Release;
223 | };
224 | 58B511F01A9E6C8500147676 /* Debug */ = {
225 | isa = XCBuildConfiguration;
226 | buildSettings = {
227 | HEADER_SEARCH_PATHS = (
228 | "$(inherited)",
229 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
230 | "$(SRCROOT)/../../../React/**",
231 | "$(SRCROOT)/../../react-native/React/**",
232 | );
233 | LIBRARY_SEARCH_PATHS = "$(inherited)";
234 | OTHER_LDFLAGS = "-ObjC";
235 | PRODUCT_NAME = FlipperPerformancePlugin;
236 | SKIP_INSTALL = YES;
237 |
238 | };
239 | name = Debug;
240 | };
241 | 58B511F11A9E6C8500147676 /* Release */ = {
242 | isa = XCBuildConfiguration;
243 | buildSettings = {
244 | HEADER_SEARCH_PATHS = (
245 | "$(inherited)",
246 | /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/include,
247 | "$(SRCROOT)/../../../React/**",
248 | "$(SRCROOT)/../../react-native/React/**",
249 | );
250 | LIBRARY_SEARCH_PATHS = "$(inherited)";
251 | OTHER_LDFLAGS = "-ObjC";
252 | PRODUCT_NAME = FlipperPerformancePlugin;
253 | SKIP_INSTALL = YES;
254 |
255 | };
256 | name = Release;
257 | };
258 | /* End XCBuildConfiguration section */
259 |
260 | /* Begin XCConfigurationList section */
261 | 58B511D61A9E6C8500147676 /* Build configuration list for PBXProject "FlipperPerformancePlugin" */ = {
262 | isa = XCConfigurationList;
263 | buildConfigurations = (
264 | 58B511ED1A9E6C8500147676 /* Debug */,
265 | 58B511EE1A9E6C8500147676 /* Release */,
266 | );
267 | defaultConfigurationIsVisible = 0;
268 | defaultConfigurationName = Release;
269 | };
270 | 58B511EF1A9E6C8500147676 /* Build configuration list for PBXNativeTarget "FlipperPerformancePlugin" */ = {
271 | isa = XCConfigurationList;
272 | buildConfigurations = (
273 | 58B511F01A9E6C8500147676 /* Debug */,
274 | 58B511F11A9E6C8500147676 /* Release */,
275 | );
276 | defaultConfigurationIsVisible = 0;
277 | defaultConfigurationName = Release;
278 | };
279 | /* End XCConfigurationList section */
280 | };
281 | rootObject = 58B511D31A9E6C8500147676 /* Project object */;
282 | }
283 |
--------------------------------------------------------------------------------
/example/android/app/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.android.application"
2 |
3 | import com.android.build.OutputFile
4 | import org.apache.tools.ant.taskdefs.condition.Os
5 |
6 | /**
7 | * The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
8 | * and bundleReleaseJsAndAssets).
9 | * These basically call `react-native bundle` with the correct arguments during the Android build
10 | * cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
11 | * bundle directly from the development server. Below you can see all the possible configurations
12 | * and their defaults. If you decide to add a configuration block, make sure to add it before the
13 | * `apply from: "../../node_modules/react-native/react.gradle"` line.
14 | *
15 | * project.ext.react = [
16 | * // the name of the generated asset file containing your JS bundle
17 | * bundleAssetName: "index.android.bundle",
18 | *
19 | * // the entry file for bundle generation. If none specified and
20 | * // "index.android.js" exists, it will be used. Otherwise "index.js" is
21 | * // default. Can be overridden with ENTRY_FILE environment variable.
22 | * entryFile: "index.android.js",
23 | *
24 | * // https://reactnative.dev/docs/performance#enable-the-ram-format
25 | * bundleCommand: "ram-bundle",
26 | *
27 | * // whether to bundle JS and assets in debug mode
28 | * bundleInDebug: false,
29 | *
30 | * // whether to bundle JS and assets in release mode
31 | * bundleInRelease: true,
32 | *
33 | * // whether to bundle JS and assets in another build variant (if configured).
34 | * // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
35 | * // The configuration property can be in the following formats
36 | * // 'bundleIn${productFlavor}${buildType}'
37 | * // 'bundleIn${buildType}'
38 | * // bundleInFreeDebug: true,
39 | * // bundleInPaidRelease: true,
40 | * // bundleInBeta: true,
41 | *
42 | * // whether to disable dev mode in custom build variants (by default only disabled in release)
43 | * // for example: to disable dev mode in the staging build type (if configured)
44 | * devDisabledInStaging: true,
45 | * // The configuration property can be in the following formats
46 | * // 'devDisabledIn${productFlavor}${buildType}'
47 | * // 'devDisabledIn${buildType}'
48 | *
49 | * // the root of your project, i.e. where "package.json" lives
50 | * root: "../../",
51 | *
52 | * // where to put the JS bundle asset in debug mode
53 | * jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
54 | *
55 | * // where to put the JS bundle asset in release mode
56 | * jsBundleDirRelease: "$buildDir/intermediates/assets/release",
57 | *
58 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
59 | * // require('./image.png')), in debug mode
60 | * resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
61 | *
62 | * // where to put drawable resources / React Native assets, e.g. the ones you use via
63 | * // require('./image.png')), in release mode
64 | * resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
65 | *
66 | * // by default the gradle tasks are skipped if none of the JS files or assets change; this means
67 | * // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
68 | * // date; if you have any other folders that you want to ignore for performance reasons (gradle
69 | * // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
70 | * // for example, you might want to remove it from here.
71 | * inputExcludes: ["android/**", "ios/**"],
72 | *
73 | * // override which node gets called and with what additional arguments
74 | * nodeExecutableAndArgs: ["node"],
75 | *
76 | * // supply additional arguments to the packager
77 | * extraPackagerArgs: []
78 | * ]
79 | */
80 |
81 | project.ext.react = [
82 | enableHermes: true, // clean and rebuild if changing
83 | ]
84 |
85 | apply from: "../../node_modules/react-native/react.gradle"
86 |
87 | /**
88 | * Set this to true to create two separate APKs instead of one:
89 | * - An APK that only works on ARM devices
90 | * - An APK that only works on x86 devices
91 | * The advantage is the size of the APK is reduced by about 4MB.
92 | * Upload all the APKs to the Play Store and people will download
93 | * the correct one based on the CPU architecture of their device.
94 | */
95 | def enableSeparateBuildPerCPUArchitecture = false
96 |
97 | /**
98 | * Run Proguard to shrink the Java bytecode in release builds.
99 | */
100 | def enableProguardInReleaseBuilds = false
101 |
102 | /**
103 | * The preferred build flavor of JavaScriptCore.
104 | *
105 | * For example, to use the international variant, you can use:
106 | * `def jscFlavor = 'org.webkit:android-jsc-intl:+'`
107 | *
108 | * The international variant includes ICU i18n library and necessary data
109 | * allowing to use e.g. `Date.toLocaleString` and `String.localeCompare` that
110 | * give correct results when using with locales other than en-US. Note that
111 | * this variant is about 6MiB larger per architecture than default.
112 | */
113 | def jscFlavor = 'org.webkit:android-jsc:+'
114 |
115 | /**
116 | * Whether to enable the Hermes VM.
117 | *
118 | * This should be set on project.ext.react and that value will be read here. If it is not set
119 | * on project.ext.react, JavaScript will not be compiled to Hermes Bytecode
120 | * and the benefits of using Hermes will therefore be sharply reduced.
121 | */
122 | def enableHermes = project.ext.react.get("enableHermes", false);
123 |
124 | /**
125 | * Architectures to build native code for.
126 | */
127 | def reactNativeArchitectures() {
128 | def value = project.getProperties().get("reactNativeArchitectures")
129 | return value ? value.split(",") : ["armeabi-v7a", "x86", "x86_64", "arm64-v8a"]
130 | }
131 |
132 | android {
133 | ndkVersion rootProject.ext.ndkVersion
134 |
135 | compileSdkVersion rootProject.ext.compileSdkVersion
136 |
137 | defaultConfig {
138 | applicationId "com.example"
139 | minSdkVersion rootProject.ext.minSdkVersion
140 | targetSdkVersion rootProject.ext.targetSdkVersion
141 | versionCode 1
142 | versionName "1.0"
143 | buildConfigField "boolean", "IS_NEW_ARCHITECTURE_ENABLED", isNewArchitectureEnabled().toString()
144 |
145 | if (isNewArchitectureEnabled()) {
146 | // We configure the CMake build only if you decide to opt-in for the New Architecture.
147 | externalNativeBuild {
148 | cmake {
149 | arguments "-DPROJECT_BUILD_DIR=$buildDir",
150 | "-DREACT_ANDROID_DIR=$rootDir/../node_modules/react-native/ReactAndroid",
151 | "-DREACT_ANDROID_BUILD_DIR=$rootDir/../node_modules/react-native/ReactAndroid/build",
152 | "-DNODE_MODULES_DIR=$rootDir/../node_modules",
153 | "-DANDROID_STL=c++_shared"
154 | }
155 | }
156 | if (!enableSeparateBuildPerCPUArchitecture) {
157 | ndk {
158 | abiFilters (*reactNativeArchitectures())
159 | }
160 | }
161 | }
162 | }
163 |
164 | if (isNewArchitectureEnabled()) {
165 | // We configure the NDK build only if you decide to opt-in for the New Architecture.
166 | externalNativeBuild {
167 | cmake {
168 | path "$projectDir/src/main/jni/CMakeLists.txt"
169 | }
170 | }
171 | def reactAndroidProjectDir = project(':ReactAndroid').projectDir
172 | def packageReactNdkDebugLibs = tasks.register("packageReactNdkDebugLibs", Copy) {
173 | dependsOn(":ReactAndroid:packageReactNdkDebugLibsForBuck")
174 | from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib")
175 | into("$buildDir/react-ndk/exported")
176 | }
177 | def packageReactNdkReleaseLibs = tasks.register("packageReactNdkReleaseLibs", Copy) {
178 | dependsOn(":ReactAndroid:packageReactNdkReleaseLibsForBuck")
179 | from("$reactAndroidProjectDir/src/main/jni/prebuilt/lib")
180 | into("$buildDir/react-ndk/exported")
181 | }
182 | afterEvaluate {
183 | // If you wish to add a custom TurboModule or component locally,
184 | // you should uncomment this line.
185 | // preBuild.dependsOn("generateCodegenArtifactsFromSchema")
186 | preDebugBuild.dependsOn(packageReactNdkDebugLibs)
187 | preReleaseBuild.dependsOn(packageReactNdkReleaseLibs)
188 |
189 | // Due to a bug inside AGP, we have to explicitly set a dependency
190 | // between configureCMakeDebug* tasks and the preBuild tasks.
191 | // This can be removed once this is solved: https://issuetracker.google.com/issues/207403732
192 | configureCMakeRelWithDebInfo.dependsOn(preReleaseBuild)
193 | configureCMakeDebug.dependsOn(preDebugBuild)
194 | reactNativeArchitectures().each { architecture ->
195 | tasks.findByName("configureCMakeDebug[${architecture}]")?.configure {
196 | dependsOn("preDebugBuild")
197 | }
198 | tasks.findByName("configureCMakeRelWithDebInfo[${architecture}]")?.configure {
199 | dependsOn("preReleaseBuild")
200 | }
201 | }
202 | }
203 | }
204 |
205 | splits {
206 | abi {
207 | reset()
208 | enable enableSeparateBuildPerCPUArchitecture
209 | universalApk false // If true, also generate a universal APK
210 | include (*reactNativeArchitectures())
211 | }
212 | }
213 | signingConfigs {
214 | debug {
215 | storeFile file('debug.keystore')
216 | storePassword 'android'
217 | keyAlias 'androiddebugkey'
218 | keyPassword 'android'
219 | }
220 | }
221 | buildTypes {
222 | debug {
223 | signingConfig signingConfigs.debug
224 | }
225 | release {
226 | // Caution! In production, you need to generate your own keystore file.
227 | // see https://reactnative.dev/docs/signed-apk-android.
228 | signingConfig signingConfigs.debug
229 | minifyEnabled enableProguardInReleaseBuilds
230 | proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
231 | }
232 | }
233 |
234 | // applicationVariants are e.g. debug, release
235 | applicationVariants.all { variant ->
236 | variant.outputs.each { output ->
237 | // For each separate APK per architecture, set a unique version code as described here:
238 | // https://developer.android.com/studio/build/configure-apk-splits.html
239 | // Example: versionCode 1 will generate 1001 for armeabi-v7a, 1002 for x86, etc.
240 | def versionCodes = ["armeabi-v7a": 1, "x86": 2, "arm64-v8a": 3, "x86_64": 4]
241 | def abi = output.getFilter(OutputFile.ABI)
242 | if (abi != null) { // null for the universal-debug, universal-release variants
243 | output.versionCodeOverride =
244 | defaultConfig.versionCode * 1000 + versionCodes.get(abi)
245 | }
246 |
247 | }
248 | }
249 | }
250 |
251 | dependencies {
252 | implementation fileTree(dir: "libs", include: ["*.jar"])
253 |
254 | //noinspection GradleDynamicVersion
255 | implementation "com.facebook.react:react-native:+" // From node_modules
256 |
257 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
258 |
259 | debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
260 | exclude group:'com.facebook.fbjni'
261 | }
262 |
263 | debugImplementation("com.facebook.flipper:flipper-network-plugin:${FLIPPER_VERSION}") {
264 | exclude group:'com.facebook.flipper'
265 | exclude group:'com.squareup.okhttp3', module:'okhttp'
266 | }
267 |
268 | debugImplementation("com.facebook.flipper:flipper-fresco-plugin:${FLIPPER_VERSION}") {
269 | exclude group:'com.facebook.flipper'
270 | }
271 |
272 | if (enableHermes) {
273 | //noinspection GradleDynamicVersion
274 | implementation("com.facebook.react:hermes-engine:+") { // From node_modules
275 | exclude group:'com.facebook.fbjni'
276 | }
277 | } else {
278 | implementation jscFlavor
279 | }
280 | }
281 |
282 | if (isNewArchitectureEnabled()) {
283 | // If new architecture is enabled, we let you build RN from source
284 | // Otherwise we fallback to a prebuilt .aar bundled in the NPM package.
285 | // This will be applied to all the imported transtitive dependency.
286 | configurations.all {
287 | resolutionStrategy.dependencySubstitution {
288 | substitute(module("com.facebook.react:react-native"))
289 | .using(project(":ReactAndroid"))
290 | .because("On New Architecture we're building React Native from source")
291 | substitute(module("com.facebook.react:hermes-engine"))
292 | .using(project(":ReactAndroid:hermes-engine"))
293 | .because("On New Architecture we're building Hermes from source")
294 | }
295 | }
296 | }
297 |
298 | // Run this once to be able to run the application with BUCK
299 | // puts all compile dependencies into folder libs for BUCK to use
300 | task copyDownloadableDepsToLibs(type: Copy) {
301 | from configurations.implementation
302 | into 'libs'
303 | }
304 |
305 | apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)
306 |
307 | def isNewArchitectureEnabled() {
308 | // To opt-in for the New Architecture, you can either:
309 | // - Set `newArchEnabled` to true inside the `gradle.properties` file
310 | // - Invoke gradle with `-newArchEnabled=true`
311 | // - Set an environment variable `ORG_GRADLE_PROJECT_newArchEnabled=true`
312 | return project.hasProperty("newArchEnabled") && project.newArchEnabled == "true"
313 | }
314 |
--------------------------------------------------------------------------------