Skip to content

Analytics

Snowpulse exposes analytics as an always-present service through AppContext::analytics(). Application code uses one portable API while AppConfig selects AnalyticsBackend::None, AnalyticsBackend::ByteBrew, or AnalyticsBackend::GameAnalytics once during application setup.

None is the safe default and means that no provider receives data. The API still compiles, the service pointer is never null, and every operation safely returns false without logging an error, throwing, or crashing.

Configure a Backend

Configure analytics before constructing App. Credentials remain application-owned configuration and must not be committed to the engine:

snowpulse::AppConfig makeAppConfig() {
    snowpulse::AppConfig config;
    config.bundleId = "com.example.application";
    config.analytics.backend = snowpulse::AnalyticsBackend::GameAnalytics;
    config.analytics.buildVersion = "1.4.0";
    config.analytics.debugLogging = false;
    config.analytics.maxPendingEvents = 256;
    config.analytics.gameAnalytics.gameKey = loadGameAnalyticsKey();
    config.analytics.gameAnalytics.secretKey = loadGameAnalyticsSecret();

    // GameAnalytics requires these allowlists before initialization.
    config.analytics.gameAnalytics.resourceCurrencies = { "CREDITS" };
    config.analytics.gameAnalytics.resourceItemTypes = { "content" };
    return config;
}

class Application final : public snowpulse::App {
public:
    Application() : App(makeAppConfig()) {}
};

For ByteBrew, select AnalyticsBackend::ByteBrew and set analytics.byteBrew.gameId and analytics.byteBrew.sdkKey. A full web build also requires a non-empty buildVersion. Native platforms use package metadata when the build version is empty and the provider supports that fallback.

autoStart defaults to true. The engine starts the selected real provider in App::init(), advances asynchronous readiness from App::update() even while portal-paused, and flushes/shuts it down from App::shutdown(). Backend switching after setup is deliberately unsupported because it can duplicate provider sessions.

Applications that require consent before tracking should disable automatic startup:

config.analytics.autoStart = false;

// Later, after the application's consent flow succeeds:
appContext()->analytics()->start();

Calls made before start() return false and are dropped. While an asynchronous provider is Starting, calls enter a bounded FIFO. When it becomes Ready, the engine drains them in order. If the FIFO reaches maxPendingEvents, the newest call is rejected. The facade does not add offline persistence; network behavior after dispatch belongs to the selected SDK.

stop() is terminal for the current Analytics instance. It clears queued calls, ends the provider session, and makes future start() and send calls return false.

Send Events

All send methods return true when the provider dispatched the call or the facade accepted it into the startup FIFO. Callers may ignore the result.

auto* analytics = appContext()->analytics();

analytics->sendEvent("operation_started", {
    { "source", "daily" },
    { "attempt", 3 },
});

snowpulse::AnalyticsProgressionEvent progression;
progression.status = snowpulse::AnalyticsProgressionStatus::Complete;
progression.progression1 = "chapter_1";
progression.progression2 = "step_4";
progression.score = 1250;
analytics->sendProgressionEvent(progression);

snowpulse::AnalyticsResourceEvent resource;
resource.flow = snowpulse::AnalyticsResourceFlow::Sink;
resource.currency = "CREDITS";
resource.amount = 5.0;
resource.itemType = "content";
resource.itemId = "feature_access";
analytics->sendResourceEvent(resource);

snowpulse::AnalyticsPurchaseEvent purchase;
purchase.store = "application_store";
purchase.currency = "USD";
purchase.amountMinorUnits = 499;
purchase.currencyExponent = 2;  // 499 with exponent 2 represents USD 4.99.
purchase.itemType = "subscription";
purchase.itemId = "premium_monthly";
purchase.cartType = "checkout";
purchase.properties = { { "introductory_offer", true } };
analytics->sendPurchaseEvent(purchase);

snowpulse::AnalyticsAdImpression impression;
impression.action = snowpulse::AnalyticsAdAction::Show;
impression.type = snowpulse::AnalyticsAdType::RewardedVideo;
impression.network = "example_network";
impression.placement = "completion";
impression.adUnitId = "rewarded_1";
impression.revenue = 0.012;
impression.revenueCurrency = "USD";
impression.properties = { { "mediation", "example_adapter" } };
analytics->sendAdImpression(impression);

snowpulse::AnalyticsErrorEvent error;
error.severity = snowpulse::AnalyticsErrorSeverity::Warning;
error.message = "A recoverable request timed out";
error.properties = {
    { "operation", "content_fetch" },
    { "will_retry", true },
};
analytics->sendErrorEvent(error);

analytics->setUserProperty("segment", "onboarding_b");

Purchase amounts are non-negative integer minor units; currencyExponent describes how many decimal places the currency uses and must be at most 9. receipt and signature are optional provider data. Ad revenue is optional, but when present it must be finite and non-negative. Error messages must contain 1–1024 characters. Custom user-ID assignment is intentionally absent: ByteBrew exposes its generated identity and cannot preserve GameAnalytics custom-ID semantics portably.

AnalyticsValue accepts strings, signed and unsigned integers, floating-point numbers, and booleans. Event names, property keys, progression segments, currency names, item identifiers, and other portable identifiers must contain 1–64 ASCII letters, digits, _, or -. Invalid identifiers, non-finite numbers, malformed payloads, missing credentials, provider exceptions, and JavaScript/JNI failures return false at the adapter boundary.

State and Availability

Use state() for NotStarted, Starting, Ready, Unavailable, or Stopped. isAvailable() means the selected SDK and required credentials are present on this platform; isReady() means calls dispatch immediately.

An unsupported or absent provider behaves like an unavailable null service. In particular, choosing ByteBrew on desktop or selecting any provider in a PLAYABLE_AD web build is safe but never becomes ready.

Provider Mapping

Portable call GameAnalytics ByteBrew
sendEvent Design event Custom event
sendProgressionEvent Progression event snowpulse_progression_<status> custom event
sendResourceEvent Resource event snowpulse_resource_<flow> custom event
sendPurchaseEvent Business event Native purchase where available; otherwise snowpulse_purchase
sendAdImpression Ad event where supported; desktop falls back to snowpulse_ad_impression design event Native mobile ad event where compatible; otherwise snowpulse_ad_impression
sendErrorEvent Error event snowpulse_error_<severity> custom event
setUserProperty Global custom event fields Custom data attribute

ByteBrew mobile custom-event string payloads percent-encode delimiter and unsafe characters before producing the SDK's key=value; representation.

Platform Packaging

Platform GameAnalytics ByteBrew
Android 7.0.2 from the pinned provider repository, including required transitive dependencies and R8 rules Uses the vendored ByteBrew.aar when supplied
iOS Vendored 5.0.2 XCFramework, privacy manifest, and required Apple frameworks Uses the vendored ByteBrew.xcframework when supplied
Windows/macOS Pinned 5.3.1 C++ source built as a static target for the active OS/architecture Unsupported; safe unavailable backend
Web FULL Vendored local 5.0.0 IIFE/UMD asset Vendored local 1.0.1 asset
Web PLAYABLE_AD Excluded Excluded

The proprietary ByteBrew mobile packages are not publicly redistributable from the sources used by Snowpulse. Obtain project-approved copies and place them at:

  • snowpulse/external/analytics/android/bytebrew/ByteBrew.aar
  • snowpulse/external/analytics/ios/bytebrew/ByteBrew.xcframework

Record the supplied version, acquisition date, SHA-256, license, and redistribution approval in the adjacent README before distributing the engine. Without those files, ByteBrew remains safely unavailable on Android/iOS; this does not affect GameAnalytics or the portable C++ API.

Generated standalone Android projects receive repository, version-catalog, dependency, verification-checksum, and shrinker changes through the normal updater:

./snowpulse update --dir /path/to/project --android

Full generated web builds consume the provider assets directly from the engine's CMake target, so existing application source and credentials do not need migration. Generated playables retain the complete facade but omit provider bridge sources, SDK JavaScript, and provider banners from the artifact.

For provider-specific dashboard setup, see the official ByteBrew SDK overview and GameAnalytics SDK overview.