skip to content

Implementing a React Native Fabric component natively, what do SimpleViewManager with a generated ViewManagerDelegate on Android and an RCTViewComponentView subclass on iOS each require?

level: seniorimportance: should knowfreq 30%

answer

  1. Android: manager implements generated interface
  2. override getDelegate or props fall back
  3. getName equals the codegenNativeComponent string
  4. iOS: updateProps diff, then call super
  5. componentDescriptorProvider plus componentProvider mapping

basics

~20 s

Android needs a SimpleViewManager implementing the generated XxxManagerInterface, returning the generated delegate from getDelegate(), registered in a package. iOS needs an Objective-C++ RCTViewComponentView subclass that diffs props in updateProps:oldProps:, returns its component descriptor and is mapped in codegenConfig.ios.

solid answer

~30 s

**Android:** write `SignaturePadManager : SimpleViewManager<SignaturePadView>(), SignaturePadManagerInterface<SignaturePadView>`. `getName()` returns `'SignaturePad'`, the string from `codegenNativeComponent`. `createViewInstance(ThemedReactContext)` builds the view, each generated setter applies one prop, and `getDelegate()` returns `SignaturePadManagerDelegate(this)`. Without that override React Native logs that a codegen manager must override `getDelegate` and falls back to reflection-based `@ReactProp` setters. The manager is returned from a package's `createViewManagers`. **iOS:** write `RCTSignaturePad : RCTViewComponentView` in a `.mm` file adopting `RCTSignaturePadViewProtocol`. In `updateProps:oldProps:`, cast both to the generated `SignaturePadProps`, apply what changed and call `super`. Return `concreteComponentDescriptorProvider<SignaturePadComponentDescriptor>()` from `+componentDescriptorProvider`, and map `"SignaturePad": "RCTSignaturePad"` in `codegenConfig.ios.componentProvider`.

code

kotlin · 34 lines
kotlin
@ReactModule(name = SignaturePadManager.NAME)
class SignaturePadManager :
    SimpleViewManager<SignaturePadView>(),
    SignaturePadManagerInterface<SignaturePadView> {

  private val delegate = SignaturePadManagerDelegate(this)

  override fun getDelegate(): ViewManagerDelegate<SignaturePadView> = delegate

  override fun getName(): String = NAME

  override fun createViewInstance(context: ThemedReactContext) = SignaturePadView(context)

  override fun setStrokeColor(view: SignaturePadView, value: Int?) {
    view.strokeColor = value ?: Color.BLACK
  }

  override fun setStrokeWidth(view: SignaturePadView, value: Float) {
    view.strokeWidthDp = value
  }

  override fun setDisabled(view: SignaturePadView, value: Boolean) {
    view.isEnabled = !value
  }

  override fun clear(view: SignaturePadView) = view.clear()

  override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> =
      mapOf("onStrokeEnd" to mapOf("registrationName" to "onStrokeEnd"))

  companion object {
    const val NAME = "SignaturePad"
  }
}

go deeper

for a junior

Remember the two base classes: SimpleViewManager with a generated interface on Android and RCTViewComponentView on iOS.

for a middle

Walk through getName, createViewInstance, the generated setters and delegate on Android, and updateProps, the descriptor provider and registration on iOS.

for a senior

Explain the manager-versus-view asymmetry, diagnose missing props from a missing getDelegate or super call, and keep per-view creation cheap.

for a principal

Decide how a team structures native component code across platforms, such as shared C++ state or thin platform views, weighing maintenance against performance.

## What Codegen gives each platform From `SignaturePadNativeComponent.ts` calling `codegenNativeComponent<NativeProps>('SignaturePad')`, **Codegen** generates the shared C++ layer — props struct, event emitter, component descriptor — plus platform glue: - **Android:** `SignaturePadManagerInterface<T>`, one setter per prop and one method per command, and `SignaturePadManagerDelegate`, which maps prop names and command names onto that interface. Both are generated into `com.facebook.react.viewmanagers`. - **iOS:** headers under `react/renderer/components/<codegenConfig.name>/` — `Props.h`, `EventEmitters.h`, `ComponentDescriptors.h`, `RCTComponentViewHelpers.h` — declaring `SignaturePadProps`, `SignaturePadEventEmitter`, `SignaturePadComponentDescriptor`, the `RCTSignaturePadViewProtocol` and the command helper. Your job on each platform is to plug a real view into that glue. ## Android: SimpleViewManager plus the generated delegate 1. **Write the view**, an ordinary Android `View` subclass that draws strokes and exposes `clear()`. 2. **Write the manager**: `class SignaturePadManager : SimpleViewManager<SignaturePadView>(), SignaturePadManagerInterface<SignaturePadView>`. `SimpleViewManager` is the base for views whose layout Yoga computes, which is the normal case. 3. **`getName()`** returns `"SignaturePad"` — React Native's guide states it must equal the name passed to `codegenNativeComponent`. 4. **`createViewInstance(context: ThemedReactContext)`** returns a new view. Keep it cheap; views are created as elements mount. 5. **Implement the interface's setters**, such as `setStrokeWidth(view, value: Float)` and `setStrokeColor(view, value: Int?)`. Colours arrive already converted to an `Int?`. 6. **Override `getDelegate()`** to return a `SignaturePadManagerDelegate(this)` held in a field. The base `ViewManager` routes every prop update and every command through its delegate. If a manager that implements a generated interface does not override it, React Native logs a soft exception saying codegen managers must override `getDelegate`, and falls back to a generic delegate driven by `@ReactProp` annotations. 7. **Export events** through `getExportedCustomDirectEventTypeConstants` or `getExportedCustomBubblingEventTypeConstants`. 8. **Register**: return the manager from the package's `createViewManagers`, as React Native's guide does. `onDropViewInstance(view)` is the Android hook for releasing per-view resources when a view is removed. ## iOS: an RCTViewComponentView subclass 1. **Header:** `@interface RCTSignaturePad : RCTViewComponentView`, importing `<React/RCTViewComponentView.h>`. 2. **Implementation in `.mm`**, because the props and descriptor are C++. Adopt `RCTSignaturePadViewProtocol` in a class extension. 3. **Host the drawing view.** Assign a plain `UIView` to `contentView`, which `RCTViewComponentView` attaches and lays out from the component's layout metrics; React Native's own example adds a subview and sizes it in `layoutSubviews` instead. 4. **`updateProps:oldProps:`** — cast `_props` (the current props) and the incoming `props` to `SignaturePadProps`, compare field by field, apply only what changed, then call `[super updateProps:props oldProps:oldProps]` so base `View` props such as `backgroundColor` and accessibility are applied too. 5. **`+componentDescriptorProvider`** returns `concreteComponentDescriptorProvider<SignaturePadComponentDescriptor>()`. 6. **Events:** cast `_eventEmitter` to `SignaturePadEventEmitter` and call `onStrokeEnd(...)`. 7. **Commands:** implement `handleCommand:args:` by calling `RCTSignaturePadHandleCommand(self, commandName, args)`. 8. **Register:** map the component name to the class under `codegenConfig.ios` — `"componentProvider": {"SignaturePad": "RCTSignaturePad"}` in the tutorial, or `ios.components.SignaturePad.className` in the Codegen reference — then re-run `pod install`. ## Keep mounting work light Both platforms create and update these views, and iOS also recycles them, while React Native mounts a new screen on the main (UI) thread. Creating the view, applying a prop and running a command should each be quick: allocate drawing buffers lazily on first touch rather than in `createViewInstance` or `init`, and move expensive work, such as encoding an exported signature image, off the main thread before reporting the result as an event. ## Side by side | Concern | Android | iOS | |---|---|---| | Base class | `SimpleViewManager<T>` (manager) + your `View` | `RCTViewComponentView` (is the view) | | Prop application | Generated delegate calls interface setters | Your `updateProps:oldProps:` diff | | Name binding | `getName()` | `codegenConfig.ios` mapping | | Commands | Delegate's `receiveCommand` calls interface methods | `handleCommand:args:` → generated helper | | Registration | Package `createViewManagers` | Codegen-generated components provider | The asymmetry is worth stating in an interview: Android separates a **manager** (a singleton factory and prop router) from the **views** it creates, while on iOS the **component view class is both**. ## Mistakes interviewers listen for - Not overriding `getDelegate()` on Android, so props silently go through reflection or not at all. - Forgetting `[super updateProps:props oldProps:oldProps]` on iOS, which drops standard `View` props. - A `getName()` or iOS mapping that differs from the `codegenNativeComponent` string. - Heavy work in `createViewInstance` or `init`, which runs for every mounted pad.

  • Why does the Android manager need getDelegate() when it already implements the generated interface?
    The base `ViewManager` applies props and commands by calling `setProperty` and `receiveCommand` on its delegate. The generated delegate maps each prop and command name to your interface method. Without the override the base class returns a generic reflection-based delegate driven by `@ReactProp` annotations, and logs a soft exception saying codegen managers must override `getDelegate`.
  • Why must updateProps:oldProps: call super at the end?
    `RCTViewComponentView`'s own implementation applies the standard `View` props your component inherits through `ViewProps`, such as background colour, borders, opacity and accessibility, and records the new props. Skipping it leaves those props unapplied and the stored props stale, so the next diff compares against the wrong values.

saying these in an interview costs you the question

  • On Android the view manager is created once per mounted view
  • The @ReactProp annotations alone are the recommended way to apply Fabric props
  • On iOS the component must subclass RCTViewManager and return a UIView
  • getName() can be any string as long as it is unique
  • Calling super in updateProps:oldProps: is optional