Mobile Development 24 min read

How WYBridge Unifies Bridge APIs Across React Native, Flutter, Weex, and Web

WYBridge provides a unified bridge layer for multiple cross‑platform containers—React Native, Flutter, Weex, and Web—standardizing API definitions, registration, parsing, and invocation across Android and iOS, while offering compatibility solutions for legacy method calls and detailing implementation specifics for each platform.

WeDoctor Frontend Technology
WeDoctor Frontend Technology
WeDoctor Frontend Technology
How WYBridge Unifies Bridge APIs Across React Native, Flutter, Weex, and Web

With the rise of cross‑platform frameworks such as React Native, Flutter, and Weex, developers focus on business logic rather than platform differences, but each platform historically required its own bridge API implementation, leading to duplicated code and a lack of standardized definitions. WYBridge consolidates bridge APIs for all containers, defining interfaces and providing unified native implementations.

Scheme Overview

Old Scheme Process

Previously, adding a bridge method required separate registration and implementation in Web, RN, Weex, and Flutter. Inconsistent implementations increased development effort and made platform migration risky.

New Scheme Process

WYBridge adapts the bridge layer at the container level, handling module registration, bridge parsing, invocation, and compatibility across all platforms.

WYBridge

WYBridge is a core library that unifies bridge definitions and implementations for four platforms. Adding a module to the library automatically registers it across all containers, eliminating duplicated code.

Android

The Android core is the BridgeModule interface, which all modules extend. It provides a method to obtain the module name.

public interface BridgeModule {
  // Module name
  String getName();
}

A BridgeMethod annotation marks methods exposed to the upper layer, supporting two signatures:

@BridgeMethod
public void xxxx(JSONObject data, BridgeJSCallBack callBack) {}

@BridgeMethod
public void xxxx(BridgeJSCallBack callBack) {}

Example implementation:

public class XXTestModule extends BaseBridgeModule {
  @Override
  public String getName() {
    return "xxtest";
  }

  @BridgeMethod
  public void getData(JSONObject data, final BridgeJSCallBack callBack) {
    // do something
  }
}

Modules are parsed and registered at runtime, achieving unified underlying behavior.

iOS

iOS uses macro definitions to expose modules and methods to JavaScript.

#define XX_EXPORT_MODULE(module_name) \
  XX_EXTERN void XXRegisterWebModule(Class); \
  XX_EXTERN void XXRegisterWeexModule(Class); \
  XX_EXTERN void RCTRegisterModule(Class); \
  XX_EXTERN void XXRegisterFlutterModule(Class); \
  + (void)load { \
    XXRegisterWebModule(self); \
    XXRegisterWeexModule(self); \
    RCTRegisterModule(self); \
    XXRegisterFlutterModule(self); \
  } \
  + (NSString *)moduleName { return @#module_name; }

The load method registers the module for Web, Weex, RN, and Flutter. The underlying RCTRegisterModule function creates a global mutable array and queue, checks protocol conformance, and stores the class for later invocation.

void RCTRegisterModule(Class moduleClass) {
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    RCTModuleClasses = [NSMutableArray new];
    RCTModuleClassesSyncQueue = dispatch_queue_create("com.facebook.react.ModuleClassesSyncQueue", DISPATCH_QUEUE_CONCURRENT);
  });
  // Register module
  dispatch_barrier_async(RCTModuleClassesSyncQueue, ^{
    [RCTModuleClasses addObject:moduleClass];
  });
}

Method registration performs three tasks: creating a global container, verifying the RCTBridgeModule protocol, and recording the class.

Create a global mutable array/dictionary and a queue.

Check that exported classes conform to RCTBridgeModule (a dummy protocol is added in WYBridge to bypass checks).

Add the class to the global container for later lookup.

Method Registration and Implementation

#define XX_EXPORT_METHOD(method) \
  XX_EXPORT_METHOD_INTERNAL(@selector(method),xx_export_method_)\
  RCT_REMAP_METHOD(, method)

#define XX_EXPORT_METHOD_INTERNAL(method, token) \
  + (NSString *)XX_CONCAT_WRAPPER(token, __LINE__) { \
    return NSStringFromSelector(method); \
  }

#define RCT_REMAP_METHOD(js_name, method) \
  _RCT_EXTERN_REMAP_METHOD(js_name, method, NO)

#define _RCT_EXTERN_REMAP_METHOD(js_name, method, is_blocking_synchronous_method) \
+ (const RCTMethodInfo *)XX_CONCAT_WRAPPER(__rct_export__, XX_CONCAT_WRAPPER(js_name, XX_CONCAT_WRAPPER(__LINE__, __COUNTER__))) { \
  static RCTMethodInfo config = {# js_name, # method, is_blocking_synchronous_method}; \
  return &config; \
}

typedef struct RCTMethodInfo {
  const char *const jsName;
  const char *const objcName;
  const BOOL isSync;
} RCTMethodInfo;

The macro defines an instance method, a static method returning the exported name for Web/Weex, and a static method returning an RCTMethodInfo object for RN.

Usage

// Module registration
XX_EXPORT_MODULE(moduleName)
// Method registration
XX_EXPORT_METHOD(methodName:success:fail:)
// Method implementation
- (void)methodName:(NSDictionary *)data success:(XXBridgeResolveBlock)success fail:(XXBridgeRejectBlock)fail {
  // ...
  success(resdic);
}

Note: RN parses parameters differently from Web/Weex, so further optimization is possible.

Web

Historically, H5 called native methods by name only, without a module prefix. The new approach prefixes the method with moduleName. and registers WYBridge during WebView initialization.

The updated flow adds module names to H5 calls and registers the bridge accordingly.

Android

Android Web bridge registers a BridgeModule class via a new registerHandler method.

boolean registerHandler(Class<? extends BridgeModule> moduleClass);

The registration creates a manager that handles module instantiation, method parsing, and invocation. Method parsing scans for @BridgeMethod annotations and stores invokers in a map.

for (Method method : mClazz.getMethods()) {
  for (Annotation anno : method.getDeclaredAnnotations()) {
    if (anno instanceof BridgeMethod) {
      String name = method.getName();
      methodMap.put(name, new Invoker(method));
      break;
    }
  }
}

Invocation passes parameters and callbacks to the stored Invoker: method.invoke(receiver, params); Compatibility is achieved by mapping old method names to new moduleName.methodName formats via a handler class implementing five mapping methods.

// Old method name
String aliasName();
// New module name
String bridgeModuleName();
// New method name
String bridgeMethodName();
// Parameter mapping
JSONObject mapping(String data);
// Callback mapping
String backMapping(String data, int code, String msg);

iOS

iOS registers APIs at app startup via the load method, storing them in a dictionary. Registration uses a block that captures the handler model, extracts the selector, and invokes the method via NSInvocation.

[self registerApi:newMethod block:^(XXHandlerModel *handlerModel) {
  // 1. Get method name and parameters
  // 2. Resolve selector
  // 3. Build method signature, set target and arguments, then invoke
}];

Method factories return a dictionary of methods whose names start with xx_export_method_, enabling lookup and invocation.

- (NSDictionary *)clazzMethodFactory {
  // 1. Use class_copyMethodList to get all methods
  // 2. Return methods prefixed with xx_export_method_
}

Compatibility mirrors Android: a mapping class translates old names, parameters, and callbacks.

React Native

Each native module has a TypeScript wrapper exposing its methods. Example for wytest:

// WYNativeTest.ts
import { NativeModules } from 'react-Native';
let WYTestModule = NativeModules.wytest;
export var WYNativeTest = {
  getData(): Promise<any> {
    return WYTestModule.getData().then((value: any) => {
      return value;
    })
  }
}

Bridge registration moves from RN initialization to post‑initialization, using extendNativeModules to inject WYBridge modules.

Android

Registration occurs after the React instance is ready:

reactInstanceManager.addReactInstanceEventListener(new ReactInstanceManager.ReactInstanceEventListener() {
  @Override
  public void onReactContextInitialized(ReactContext context) {
    // Register modules
  }
});

The extendNativeModules method receives the generated map of JavaModuleWrapper objects and registers them via JNI.

public void extendNativeModules(NativeModuleRegistry modules) {
  Collection<JavaModuleWrapper> javaModules = modules.getJavaModules(this);
  this.jniExtendNativeModules(javaModules, cxxModules);
}

Parsing now looks for @BridgeMethod instead of @ReactMethod, creating MethodWrapper objects that inherit from NativeModule.NativeMethod. Invocation passes the method ID to the appropriate wrapper, which calls the underlying bridge method. method.invoke(receiver, params); No compatibility layer is needed because RN projects are centrally managed.

iOS

iOS continues to register modules via the load method and RCTRegisterModule, with no additional adaptation required.

Weex

Weex registration mirrors RN: modules extend WXModule and are registered via registerModule. WYBridge replaces this with a ModuleFactory approach.

Android

Registration now calls

registerModule(String moduleName, ModuleFactory factory, boolean global)

, wrapping the original module inside a factory that handles method parsing and invocation.

Parsing scans for @BridgeMethod annotations and creates Invoker objects, similar to the Android Web flow.

Invocation uses the generated Invoker to call the native method.

Compatibility

Legacy calls are mapped to new moduleName.methodName formats using the same mapping class as the Web bridge.

iOS

iOS Weex follows the same registration pattern: modules are stored in WYBridgeGetModuleClassesDic and registered at launch. Parsing and invocation are unchanged, while compatibility uses the same mapping strategy as Web.

Flutter

Flutter uses a wrapper plugin named wrapper_bridge. Each module gets a Dart file exposing methods via a MethodChannel with the naming convention moduleName.methodName.

// wytest.dart
import 'dart:async';
import 'package:flutter/services.dart';
class WYTest {
  static const MethodChannel _channel = const MethodChannel('bridge');
  static const moduleName = 'wytest.';
  static Future<Map> get getData async {
    final Map data = await _channel.invokeMethod(moduleName + 'getData');
    return data;
  }
}

Usage example:

import 'package:bridge/wytest.dart';
WYTest.getData.then((value) {
  print(value.toString());
});

Android

Flutter calls native code via MethodChannel.onMethodCall. The method name follows the moduleName.methodName pattern, which is parsed to locate the corresponding WYBridge module and method.

@Override
public void onMethodCall(MethodCall call, final MethodChannel.Result result) {}

Method lookup uses reflection and checks for the @BridgeMethod annotation before invocation.

Method m = moduleClazz.getMethod(methodName, new Class[]{...});
for (Annotation anno : m.getDeclaredAnnotations()) {
  if (anno instanceof BridgeMethod) {
    m.invoke(...);
  }
}

iOS

iOS follows the same pattern: during app launch, all exposed classes are stored in WYBridgeGetModuleClassesDic. The Flutter plugin’s handleMethodCall parses the module and method name, retrieves the exported xx_export_method_ selector, builds an NSInvocation, and executes it.

Summary and Outlook

WYBridge has been adopted in several projects, replacing legacy bridges without major bugs. Future work includes completing the migration for all bridges, extending support to property bridging, and unifying native component bridges across RN, Weex, and Flutter. Collaboration from the community is welcomed.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

FlutterCross-platformiOSAndroidWeexReact NativeBridge
WeDoctor Frontend Technology
Written by

WeDoctor Frontend Technology

Official WeDoctor Group frontend public account, sharing original tech articles, events, job postings, and occasional daily updates from our tech team.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.