Skip to content

Commit c2e6bf7

Browse files
authored
docs: update roadmap with custom view markers and phases for implementation (#46)
1 parent 36f8472 commit c2e6bf7

3 files changed

Lines changed: 157 additions & 0 deletions

File tree

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# Default owners — auto-requested on every pull request
2+
* @jkasprzyk17 @piotr-graczyk-dev
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
# ADR 0004: Custom view markers
2+
3+
## Status
4+
5+
Proposed
6+
7+
## Context
8+
9+
Today markers are **serialized descriptors**, not React Native child views. `<Marker />`
10+
returns `null`; its props are collected in `MapView.tsx` into `MarkerDescriptor[]` and
11+
passed as a single prop on the `MapView` HybridView. Native code renders SDK objects
12+
(`MKAnnotation` / `MKAnnotationView` on Apple, bitmap-backed `GMSMarker` on Google).
13+
Appearance customization is limited to a **bitmap image** (`MarkerDescriptor.image`); any
14+
`children` passed to `<Marker>` are ignored.
15+
16+
`react-native-maps` supports `<Marker>{arbitraryJSX}</Marker>`, where children are real RN
17+
views. This is a fundamentally different rendering model from the current bulk-descriptor
18+
pipeline and is a common migration blocker.
19+
20+
### Platform constraint (the crux)
21+
22+
| Platform / SDK | Can a marker be a "live" view? |
23+
| --- | --- |
24+
| **Apple MapKit** | Yes — `MKAnnotationView` can host any `UIView` (live, interactive RN views) |
25+
| **Google Maps (iOS + Android)** | No — the marker icon is a **bitmap only**; a view must be snapshotted to a bitmap and re-snapshotted on change |
26+
27+
`react-native-maps` handles this by hosting a live view on MapKit and snapshotting children
28+
to a bitmap on Google Maps. Any solution here must accept the same asymmetry.
29+
30+
## Decision
31+
32+
Add custom-view markers as a **separate, opt-in capability** alongside — not replacing —
33+
the existing descriptor pipeline. These are two distinct contracts:
34+
35+
- `<Marker image=... />` — the high-throughput bulk path (hundreds/thousands of markers,
36+
bitmaps, clustering, viewport culling). Unchanged.
37+
- `<MarkerView coordinate=...>{JSX}</MarkerView>` — a new opt-in, heavier path for a
38+
bounded number of custom-view markers.
39+
40+
Rendering follows **Option C (hybrid)**: live views where the SDK allows (MapKit),
41+
bitmap snapshot where it does not (Google Maps on both iOS and Android). The public API is
42+
identical across platforms; only the native implementation differs per backend.
43+
44+
Keeping these as separate types (rather than adding `children` + nullable fields to the
45+
existing `Marker` descriptor) follows the API-design rule of splitting distinct workflows
46+
into distinct types instead of overloading one object.
47+
48+
### Considered alternatives
49+
50+
- **Option A — snapshot only (JS/view-shot → existing `image` pipeline).** Fastest,
51+
cross-platform, reuses clustering. But static: no live interactivity/animation, every
52+
content change requires a re-snapshot. Adopted as the **Phase 1 MVP**, not the end state.
53+
- **Option B — live native subviews everywhere.** Not possible on Google Maps (bitmap-only
54+
icons); would require manually positioned overlay views with z-order/gesture problems.
55+
- **Option C — hybrid (chosen).** Live on MapKit, snapshot on Google. Closest to
56+
`react-native-maps` behavior with the least fighting against each SDK.
57+
58+
## Proposed API
59+
60+
### Nitro spec (new HybridView)
61+
62+
```typescript
63+
// package/src/native/specs/MarkerView.nitro.ts
64+
import type { HybridView, HybridViewMethods, HybridViewProps } from 'react-native-nitro-modules'
65+
import type { Coordinate } from '../../types/coordinate'
66+
import type { MarkerAnchor, MarkerPoint } from './overlays'
67+
68+
export interface MarkerViewProps extends HybridViewProps {
69+
coordinate: Coordinate
70+
anchor?: MarkerAnchor
71+
centerOffset?: MarkerPoint
72+
draggable?: boolean
73+
/**
74+
* Rendering strategy on backends that support live views (MapKit).
75+
* Google Maps always snapshots (SDK limitation).
76+
* @default 'auto'
77+
*/
78+
renderMode?: 'auto' | 'snapshot'
79+
zIndex?: number
80+
}
81+
82+
export interface MarkerViewMethods extends HybridViewMethods {
83+
/** Force a re-snapshot on bitmap backends after child content changes. */
84+
redraw(): Promise<void>
85+
}
86+
87+
export type MarkerView = HybridView<MarkerViewProps, MarkerViewMethods>
88+
```
89+
90+
Add a `MarkerView` autolinking entry to `nitro.json` (separate `HybridMarkerView`
91+
implementation class on iOS and Android).
92+
93+
### React usage
94+
95+
```tsx
96+
<MapView>
97+
<MarkerView coordinate={{ latitude, longitude }} onPress={...}>
98+
<View style={styles.bubble}>
99+
<Text>Custom!</Text>
100+
</View>
101+
</MarkerView>
102+
</MapView>
103+
```
104+
105+
`<MarkerView>` is a real Nitro HybridView (`getHostComponent`) that renders its children
106+
natively — unlike the null-rendering `<Marker>`.
107+
108+
## Implementation plan (phased)
109+
110+
### Phase 0 — quick win (independent)
111+
112+
- Fix the existing gap where iOS Google (`GoogleMapOverlayController.updateMarker`) sets
113+
`icon = nil` and never applies `MarkerDescriptor.image`, so `<Marker image>` is
114+
consistent across all three backends (MapKit, iOS Google, Android Google).
115+
116+
### Phase 1 — MVP custom views (Option A)
117+
118+
- Add `children` support that renders off-screen and snapshots to a bitmap (prefer a native
119+
snapshot; `react-native-view-shot` optional), feeding the existing
120+
`MarkerDescriptor.image` pipeline. Delivers working cross-platform custom markers quickly.
121+
122+
### Phase 2 — full `MarkerView` (Option C)
123+
124+
- New `MarkerView` Nitro HybridView hosting the RN child subtree natively.
125+
- `MapView` native code detects mounted child `MarkerView`s in its native view hierarchy.
126+
- **iOS Apple / MapKit:** embed the live hosted `UIView` in an `MKAnnotationView`; reuse
127+
existing anchor/center-offset logic.
128+
- **iOS Google + Android Google:** snapshot the hosted view to a bitmap
129+
(`UIGraphicsImageRenderer` / `Canvas`+`Bitmap`) → `GMSMarker.icon` / `BitmapDescriptor`;
130+
re-snapshot on `redraw()` or layout change. Reuse `MarkerIconFactory` (Android).
131+
- Implement `prepareForRecycle` (state reset) and `memorySize` (bitmaps) on the hosts.
132+
- Follow native code rules: `final` classes, one top-level type per file, converters in
133+
their own extension files.
134+
135+
## Open items to verify before Phase 2
136+
137+
- Confirm, against **current** Nitrogen docs/source, the supported model for HybridView
138+
`children` and for mounting child views into the parent map's native hierarchy (do not
139+
rely on remembered API details).
140+
141+
## Consequences
142+
143+
- The existing bulk descriptor + clustering path is untouched; custom views are additive.
144+
- Behavior is asymmetric by necessity: live/interactive on MapKit, static bitmap on Google
145+
Maps. `renderMode` and `redraw()` make the snapshot semantics explicit.
146+
- Custom-view markers are intended for a bounded count; large marker sets should keep using
147+
the descriptor/image path.
148+
- Enables a smoother migration path from `react-native-maps` for view-backed markers.

docs/roadmap.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,15 @@ Delivered incrementally during Phases 3–5; polished for platform consistency i
8686
| Clustering library | Custom vs platform-native | Platform-native (MKClusterAnnotation / maps-utils) |
8787
| Provider architecture | In-place SDK switching vs adapter remount | Provider adapter + React remount |
8888
| Overlay animations | Reanimated core path vs native descriptors | Native descriptor animations; Reanimated optional |
89+
| Custom view markers | Snapshot-only vs live/hybrid native views | Hybrid: live MapKit, snapshot Google (ADR 0004) |
8990
| Offline support | Tile caching strategy | Future consideration |
9091

92+
## Custom view markers (ADR 0004)
93+
94+
- [ ] Phase 0: apply `MarkerDescriptor.image` on iOS Google provider
95+
- [ ] Phase 1: `<Marker>` children via snapshot into existing image pipeline
96+
- [ ] Phase 2: `<MarkerView>` HybridView — live MapKit views, snapshot on Google
97+
9198
## Future provider work
9299

93100
- [ ] OpenStreetMap-backed provider

0 commit comments

Comments
 (0)