Migrating an app¶
Most apps that adopt Herald already call a vendor SDK from many places, directly or through their own wrapper. Moving everything at once is risky, so move one step at a time. You can release the app after every step.
1. Put Herald in front of the old path¶
Add Herald with a single provider that passes every call to the wrapper you already have. Nothing is sent differently yet; you've only added the new way in.
// Step 1: Herald forwards to the old wrapper, so migrated and unmigrated code report the same.
struct LegacyEventTracker: EventTrackerService {
let legacy: LegacyAnalytics
func track(_ event: any Event) {
var params: [String: String] = [:]
for (key, value) in event.parameters {
params[key] = value.asString
}
legacy.logEvent(name: event.name, params: params)
}
}
func migratingHerald(legacy: LegacyAnalytics) -> Herald {
Herald(providers: [HeraldProvider(name: "legacy", events: LegacyEventTracker(legacy: legacy))])
}
// Step 1: Herald forwards to the old wrapper, so migrated and unmigrated code report the same.
final class const LegacyEventTracker(final LegacyAnalytics legacy) implements EventTrackerService {
@override
Future<void> track(Event event) => legacy.logEvent(event.name, {
for (final MapEntry(:key, :value) in event.parameters.entries) key: value.asString,
});
}
Herald migratingHerald(LegacyAnalytics legacy) => Herald(
providers: [HeraldProvider(name: 'legacy', events: LegacyEventTracker(legacy))],
);
// Step 1: Herald forwards to the old wrapper, so migrated and unmigrated code report the same.
export class LegacyEventTracker implements EventTrackerService {
constructor(private readonly legacy: LegacyAnalytics) {}
track(event: Event): Promise<void> {
const params: Record<string, string> = {};
for (const [key, value] of Object.entries(parametersOf(event))) {
params[key] = AnalyticsValue.asString(value);
}
return this.legacy.logEvent(event.name, params);
}
}
export function migratingHerald(legacy: LegacyAnalytics): Herald {
return new Herald({
providers: [new HeraldProvider({ name: 'legacy', events: new LegacyEventTracker(legacy) })],
});
}
Give Herald to your classes through its interfaces, as in
Set up your app. In debug builds, add the log provider to see
every call in your log.
2. Move call sites to events, feature by feature¶
Replace direct calls with an event class and EventTrackerService, one feature at a time:
Moved and unmoved code still go through the same wrapper, so dashboards don't change. Each moved
feature's tests can now use FakeAnalyticsProvider.
3. Move vendors to Herald's vendor modules¶
Once your code calls Herald, move vendors over one at a time: register the vendor's Herald module, and remove that vendor from the old wrapper.
- Start with the simplest vendor, usually the one that gets every event, like Firebase. Its generic factory sends each event under its own name, as your wrapper did.
- Compare before and after with the log provider, or the vendor's own debug view.
- Move special cases into factories as you go. Tokens, special events and revenue each go to the feature that owns the event.
4. Consent and identity¶
Move sign-in, sign-out and the consent switch to IdentifiableUserService and ConsentService.
Check the before-consent table: Herald's Adjust and AppsFlyer
modules keep those vendors quiet until consent, which may differ from what your wrapper did.
5. Remove the bridge¶
When the wrapper has no vendors left, delete it and the provider that called it. From then on, adding or removing a vendor only changes the code that sets up Herald.