Skip to content

Latest commit

 

History

151 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

react-native-hotswap

@salve-software/react-native-hotswap

Fast Refresh covers your JavaScript. This covers the other half: the Kotlin, Swift and C++ underneath it.

hotswap  watching android/src/main/java  →  127.0.0.1:8099
  ✅ probe/android/src/main/java/com/probe/ProbeValues.kt  1234ms
  ✅ probe/cpp/probe.cpp  435ms
Save to running
Rebuild and reinstall 10–30s, and the app restarts from scratch
hotswap, Kotlin ~1s, same process
hotswap, C++ ~0.5s Android, ~3s iOS
hotswap, Swift ~6s, same process

Install

npm install --save-dev @salve-software/react-native-hotswap

Two lines in metro.config.js, and Android is done:

const { withHotswap } = require('@salve-software/react-native-hotswap/metro.cjs');

// `roots` is optional. Pass one per place you keep native code.
module.exports = withHotswap(mergeConfig(getDefaultConfig(__dirname), config));

The watcher starts with Metro and the agent attaches when the app launches. Everything else is read out of your Gradle build. There is nothing to add to build.gradle and nothing to call from your code.

On Expo, add "@salve-software/react-native-hotswap" to plugins in app.json instead of editing native files by hand, and use a development build. docs/expo.md covers it.

Swift needs two more lines in the Podfile. Without them a Swift method cannot be replaced at all:

require Pod::Executable.execute_command('node', ['-p',
  'require.resolve("@salve-software/react-native-hotswap/hotswap.rb", {paths: [process.argv[1]]})',
  __dir__]).strip

# inside post_install
hotswap_post_install(installer)

New files and removed methods

Patching rewrites code that is already loaded, so it cannot add a class or remove a method. When the runtime refuses for that reason, hotswap publishes a generation instead: it rebuilds the module, loads it beside what is running, and React Native builds its next instance from it.

  ♻️  probe/android/.../BornAtRuntime.kt  reloaded from a new generation  942ms

The cost is your JS state. React rebuilds its instance, so the app goes back to its first screen. The process keeps running, and patching is still tried first.

Generations need one line per platform:

// MainApplication.kt
override val reactHost: ReactHost by lazy {
  HotswapReactHost.create(applicationContext) { PackageList(this).packages }
}
// your RCTDefaultReactNativeFactoryDelegate subclass
import RNHotswap

@objc(getModuleClassFromName:)
func hotswapModuleClass(_ name: UnsafePointer<CChar>) -> AnyClass? {
  Hotswap.moduleClass(fromName: name)
}

Leave them out and patching still works. Only generations stop.

What you see

A successful swap draws a violet pill over the running app. It is not React Native's blue bar on purpose, so you can tell which half of the app changed when both show up. A failed swap draws nothing, and the terminal says why.

When a save seems to do nothing, run npx hotswap --check. It lists what is wired up and what is not. Usually the app is not running, or the build was not debuggable.

Limits

Change Result
Method body, anywhere ✅ patched
New method or field on a class ✅ patched, Android 11+
A class in a new file ✅ a generation
Removing a method or field ✅ a generation
MainActivity, MainApplication patched only. A reload keeps the Activity
Objective-C, inlined code, iOS devices ⛔ never
Release builds ⛔ by design
Requires
Android API 28 to attach, API 30 for structural changes
iOS simulator only
Build debuggable, build tools 34–36 (not 37)

Docs

example/ is a React Native app with its own native code and a module beside it, both wired up. Run cd example && npm install && npx react-native start, then edit one of the files it lists on screen.

Doc What is in it
Expo the config plugin, and why a dev build is required
How it works JVMTI, dynamic replacement, vtables, and prior art
Generations the second mechanism, in full
Limits everything that does and does not swap

Licence

MIT.

android/src/main/cpp/vendor/jvmti.h comes from AOSP and is GPLv2 with the Classpath Exception, so linking against it imposes nothing on this package. The header is kept as published, with its licence notice intact.

About

Fast Refresh for the native half of React Native. Kotlin, Swift and C++, swapped into the running app in about a second.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages