feat: add HookDecorator so a hook wrapper forwards the stages it does not override - #387
Draft
abelonogov-ld wants to merge 1 commit into
Draft
feat: add HookDecorator so a hook wrapper forwards the stages it does not override#387abelonogov-ld wants to merge 1 commit into
abelonogov-ld wants to merge 1 commit into
Conversation
… not override A hook's stages each default to doing nothing, so a wrapper that omits one swallows it rather than passing it on: wrapping a hook meant reimplementing beforeIdentify, afterIdentify, and afterTrack as pure forwarding just to keep them working, and a wrapper that forgot dropped them silently. HookDecorator forwards every stage, so a subclass overrides only the stages it changes. DedupingHook extends it and is down to the three it changes. The wrapped hook is private to the decorator, reachable only through super, so nothing can unwrap a decorator to decide what to do with what it finds. The identify and track stages are @callsuper, so lint reports an override that stops forwarding one: a decorator wrapping a DedupingHook could otherwise keep it from being told to forget what it has reported, and it would go on suppressing across an identify. The evaluation stages are not, because suppressing an evaluation series is what a decorator is for. Also drops the remaining suggestions to subclass from the deduper docs, which the iOS SDK had already removed. Co-authored-by: Cursor <cursoragent@cursor.com>
abelonogov-ld
marked this pull request as draft
August 13, 2026 22:31
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Requirements
Related issues
Follow-up to #380 (per-hook evaluation exposure deduplication).
HookDecoratorwas part of that work and was removed from it during review; this restores it without thegetDelegate()accessor that the review objected to.Describe the solution you've provided
Hook's stages each have a default implementation that does nothing, so a wrapper that omits a stage swallows it rather than passing it on. Wrapping a hook therefore meant reimplementingbeforeIdentify,afterIdentify, andafterTrackas pure forwarding just to keep them working, and a wrapper that forgot one dropped it silently.HookDecoratorforwards every stage to the hook it wraps, so a subclass overrides only the stages it changes.DedupingHookextends it and is down to the three stages it actually changes, having shedgetMetadata,afterIdentify,afterTrack, and its duplicatednameOfhelper.Two properties worth calling out:
privateto the decorator, reachable only throughsuper. There is no accessor, so nothing can unwrap a decorator and act on what it finds.beforeIdentify,afterIdentify, andafterTrackare@CallSuper, so Android Lint reports (at severityError) an override that stops forwarding one. Without it, a decorator wrapping aDedupingHookcould keep it from being told to forget what it has reported, and it would go on suppressing evaluations across an identify. The evaluation stages are deliberately not annotated, because suppressing an evaluation series is what a decorator is for, and lint's check is satisfied by a conditionalsupercall.Also drops the remaining suggestions to subclass from the
DedupingHookandEvaluationExposureDeduperdocs, which the iOS SDK had already removed, and aligns their code samples.Describe alternatives you've considered
protected getDelegate()accessor the earlier version had. It invites code to unwrap a decorator rather than treat it as the hook it stands in for, so forwarding now goes throughsuperonly.Additional context
Verified that the lint annotation works rather than assuming: a temporary decorator overriding
afterIdentifyandafterTrackwithout forwarding producedMissingSuperCallerrors ("Overriding method should callsuper.afterIdentify"); with it removed,lintDebugreports none, andjavapconfirms the annotations are written intoHookDecorator.classso a consumer's lint reads them from the AAR.DedupingHookTestgainsaDecoratorForwardsTheStagesItDoesNotOverride, and itsCountingDecoratorhelper now overrides only the evaluation stages, the way a customer's wrapper would. Full module unit tests and javadoc pass.The same change is going into the iOS SDK for parity; Swift has no
@CallSuperequivalent, so there the failure mode is documented instead.Made with Cursor