liquid_firebase
Firebase Analytics (Google Analytics for Firebase / GA4) destination for
liquid_analytics.
Install
dependencies:
liquid_analytics: ^0.1.1
liquid_firebase: ^0.1.1
firebase_core: ^3.0.0
Use
Initialize Firebase, then add FirebaseSink to your Liquid client:
await Firebase.initializeApp(options: DefaultFirebaseOptions.currentPlatform);
final liquid = Liquid(
sinks: [FirebaseSink()],
consent: ConsentPolicy(
requireOptIn: true,
map: {ConsentCategory.analytics: ['firebase']},
),
);
await liquid.init();
liquid.track('checkout_started', {'cart_value': 42.0, 'is_gift': true});
liquid.consent.grant(ConsentCategory.analytics);
Typed events (recommended)
The string API above is the quick path. A typed event keeps the name and
property keys in one place — and for Firebase specifically, it pins the
sanitizer-legal snake_case naming at the definition site instead of relying on
every call site to get it right:
class CheckoutStarted extends LiquidEvent {
const CheckoutStarted({required this.cartValue, this.isGift = false});
final double cartValue;
final bool isGift;
@override
String get name => 'checkout_started'; // already Firebase-legal
@override
Map<String, Object?> get properties => {
'cart_value': cartValue,
'is_gift': isGift, // encoded as 1/0 by FirebaseEventMapper
};
}
liquid.log(const CheckoutStarted(cartValue: 42, isGift: true));
No build step is involved — it is a plain class. Override category to route an
event to a different consent bucket (e.g. ConsentCategory.marketing). To
generate these from a YAML schema instead, see
liquid_codegen.
Message mapping
| liquid message | Firebase call |
|---|---|
track |
logEvent |
screen |
logScreenView |
identify |
setUserId + setUserProperty per trait |
reset |
resetAnalyticsData |
Firebase has no group or alias concept, so those are ignored and the sink
does not advertise those capabilities (the client skips it for them).
Automatic sanitization
Firebase rejects data that liquid's Map<String, Object?> allows: parameter
values must be a String or a number, names must be alphanumeric/underscore and
start with a letter, and there are length and count limits. FirebaseEventMapper
enforces all of this so events are never silently dropped by the native layer:
- Illegal name characters become underscores; names are truncated to 40 chars (24 for user properties) and forced to start with a letter.
- Booleans are encoded as
1/0(configurable), enums by.name,DateTimeas ISO-8601. Nested lists/maps are dropped. - String values are truncated to 100 chars; parameters are capped at 25.
Tune it by passing your own mapper:
FirebaseSink(mapper: const FirebaseEventMapper(boolsAsInts: false));
Firebase Consent Mode
Optionally mirror liquid's consent into Firebase Consent Mode so Firebase itself honors the user's choice:
final firebaseSink = FirebaseSink();
final liquid = Liquid(sinks: [firebaseSink]);
liquid.consent.addListener(() {
firebaseSink.applyConsent(
analyticsGranted: liquid.consent.statusOf(ConsentCategory.analytics) ==
ConsentStatus.granted,
adsGranted: liquid.consent.statusOf(ConsentCategory.marketing) ==
ConsentStatus.granted,
);
});
Testing
The sanitization logic lives in the pure FirebaseEventMapper, which is unit-
testable without a configured Firebase app. For end-to-end verification, run
your app against a real Firebase project and inspect DebugView.
Libraries
- liquid_firebase
- Firebase Analytics destination for liquid_analytics.