github BlinkReceipt/blinkreceipt-android 2.5.1

3 hours ago

Recognizer SDK (2.5.1)

  • Requires activation SDK 1.3.0. Hosts using activations call ActivationClient.initialize(privacy) in Application.onCreate(), in either order with BlinkReceiptSdk.initialize(); until they do, activation features stay off and one error is logged instead of the SDK crashing.
  • Frames captured after the user has taken a photo no longer reach the OCR parser. Because the parser accumulates text across the frames of a session and resolves each line item by cross-frame vote, motion-blurred frames picked up while the user reviewed a capture could outvote the capture itself and corrupt line-item names.
  • Added RecognizerView.holdFrameAnalysisAfterCapture(boolean), off by default. takePicture() pauses analysis frames at the shutter and resumes them once the photo is delivered, so existing integrations need no change. A host that shows the captured photo for review can turn this on to keep frames paused until resumeFrameAnalysis(), protecting line-item text from frames picked up during review. The bundled camera UI does this.
  • A capture that fails, including one the camera delivers but that cannot then be processed, is now reported through CameraRecognizerCallback.onException() instead of being dropped silently.
  • A captured photo is no longer discarded by the frame queue when a sharper preview frame is already held, and a queued capture is no longer displaced by a later preview frame.
  • Camera frames are no longer handed out while still referencing the buffer the next frame is converted into, which could overwrite a frame already queued for OCR and halves per-frame bitmap allocation at the same time.
  • Merchant-detection signals are now passed to the Activation SDK through ScanResults.merchantDetection.
  • With remote OCR, the inverted-text retry now uploads the full-resolution frame instead of a downscaled one.
  • Stability fixes and improvements

Account Linking SDK (2.5.1)

  • Stability fixes and improvements

Camera SDK (2.5.1)

  • CameraView.captureImage() now suspends analysis-frame delivery at the shutter. onImageCaptured() is unaffected and the camera stays bound. RecognizerView resumes delivery once the photo is delivered, so integrations using it need no change; a host that subclasses CameraView itself resumes with resumeFrameAnalysis(). A capture that fails resumes delivery and is reported through onError(); a subclass that receives onImageCaptured() but cannot use the frame should call onCaptureFailed() so the same happens.
  • Added CameraView.suspendFrameAnalysis(), CameraView.resumeFrameAnalysis(), and the protected CameraView.isFrameAnalysisSuspended() and CameraView.onCaptureFailed().
  • CameraView.captureImage() no longer throws when called before the camera has bound; it reports the failure through onError() instead.
  • Stability fixes and improvements

Camera UI SDK (2.5.1)

  • Line-item names are no longer garbled by frames picked up while the user reviews a captured photo; the camera UI now keeps frame analysis paused during review.
  • Activation scans now start their scan session once activation becomes ready, even after the camera has opened, and a scan left without a session ends with an error. Adds the overridable activation_scan_session_missing string and rewords activation_sdk_not_initialized.
  • Stability fixes and improvements

Core SDK (2.5.1)

  • Stability fixes and improvements

Digital SDK (2.5.1)

  • Stability fixes and improvements

Digital Analytics SDK (2.5.1)

  • Stability fixes and improvements

Earnings SDK (2.5.1)

  • Stability fixes and improvements

Logcat SDK (2.5.1)

  • Stability fixes and improvements

Security SDK (2.5.1)

  • Stability fixes and improvements

Surveys SDK (2.5.1)

  • Stability fixes and improvements

Activation SDK 1.3.1 for Android

These notes cover both 1.3.0 (released October 29, 2026) and 1.3.1 (released November 1, 2026), for
Android apps upgrading from 1.2.0. 1.3.1 changes one thing on top of 1.3.0: the privacy type you
pass when starting the SDK is named Privacy instead of BlinkPrivacy. Everything below already
uses the 1.3.1 name.

This update adds privacy-aware ad consent, lets you render the curated offer feed with your own UI,
introduces merchant promos that reward total spend at a store rather than specific products, and
lets the backend override the scan reward per store. It also fixes a round of reward-accuracy,
session, and registration issues, including the 1.2.0 known issue where review requests failed with
"Scan session not found".

This release contains breaking changes. Read the Upgrade Notes before bumping.

dependencies {
    implementation(platform("com.actualplatform:activation-bom:1.3.1"))
    implementation("com.actualplatform:activation")
}

Upgrade Notes

  • ActivationClient.initialize(privacy) is now the only way to start the SDK. The no-argument
    initialize() is gone, and ActivationClient.instance no longer creates the client on first
    access, so reading it before initializing throws. Call initialize(privacy) in
    Application.onCreate(), before you start any dependency-injection graph whose singletons reach
    for the SDK. Calling it again on a started SDK replaces the privacy values instead of doing
    nothing.

    class MyApplication : Application() {
        override fun onCreate() {
            super.onCreate()
            ActivationClient.initialize(
                Privacy(
                    sellOrShareStatus = ConsentState.UNKNOWN,
                    personalizationStatus = ConsentState.UNKNOWN,
                    disclosureVersion = null,
                    rdpOwner = RdpOwner.ACTUAL,
                )
            )
        }
    }
  • If your app already has its own Privacy type, the two names will clash. Disambiguate the
    SDK's type with an import alias, such as
    import com.actualplatform.activation.Privacy as ActivationPrivacy.

  • Supply the user's privacy choices on every cold start. The privacy values you pass are held
    in memory only and never written to disk, so supply them again after every cold start, reinstall,
    and account switch. Every field is required. A value you have not resolved is UNKNOWN, and
    UNKNOWN restricts, so you may see fewer ads, or non-personalized ones, until real values arrive.
    If you set rdpOwner to RdpOwner.PUBLISHER, the SDK never writes Google's gad_rdp
    preference. When restricted processing is required and you have not turned it on, no ad is
    requested at all.

  • Register the new Ad Manager targeting keys. Every ad request now carries the applied consent
    decision as custom targeting: npa, rdp, adid_state, sdk_version, and app_version. These
    keys must be registered in Ad Manager with predefined values before they show up in your reports.

  • The Google ad events are renamed. ad_request replaces GAM_AD_REQUESTED, and ad_response
    replaces both GAM_AD_LOADED and GAM_AD_FAILED. The old adType, loadedAdType, and error
    fields are gone. Both events now carry the consent decision that governed the request.
    ad_request fires only once a request actually goes out, so loads refused by consent and
    rewarded ads served from cache no longer count as requests. Dashboards keyed on the old names
    will see no new rows from 1.3.0 onward.

  • Reward amounts change in several cases. If you reconcile rewards on your backend, expect
    these differences:

    • No merchant, no reward. A scan whose merchant can't be identified now earns nothing. The
      Receipt Summary shows the merchant as --- and a zero total, and withholds the base scan
      reward, qualified promotions, ad boosts, and banked boost credit. The scanned line items still
      appear. No error is shown, because nothing is wrong with the receipt.
    • Promotions are paid once. A promotion that covers several scanned products is now paid
      once, at the amount the promotion actually awarded. Previously it was paid once per product.
    • Overlapping promotions resolve differently. When two promotions qualify the same product,
      the last one listed now claims it.
    • The scan reward can be overridden per store. The backend can replace your configured
      ScanReward for a specific store, including with zero. The Receipt Summary and
      Rewards.ScanFinished always carry the resolved amount, so credit the shopper from those
      rather than from your own configured value.
  • Offer and ActivationAppearance.OffersWall.Colors gained parameters in the middle of their
    constructors.
    Offer gained ten fields mid-signature, and OffersWall.Colors gained
    offerRedemptionLabel. Every OffersWall.Colors parameter is a Long?, so positional
    construction still compiles, but each later color shifts onto the wrong key. Use named arguments
    for both.

  • CouponGoal.spend is removed. It read a key the backend never sends, so it was always null.
    Use the new CouponGoal.minimumSpend for a spend-based goal's threshold. CouponGoal.quantity
    reads 0 for spend-based goals.

  • GoogleSdkInstructions gained a required requiresRestrictedMode. This only affects code
    that constructs the model itself.

  • Recompile against 1.3.1. Every other model and composable change (AdUnit, AdImage,
    Activations, Placement, ScanResults, OffersWall, OffersWallFlow) adds defaulted
    parameters at the end. Those changes are source-compatible, but as with every release, your
    integration must be recompiled against the new version.

The compile target is unchanged from 1.2.0: compileSdk 37 or higher is still required, your app's
targetSdk does not need to change, and no new dependencies are introduced. The matching Receipts
SDK release for 1.3.0 is 2.5.1.

What's New & Improved

  • Ad consent. The SDK now serves personalized ads where the law allows and restricts itself
    where it doesn't. You report the user's privacy choices through initialize(privacy), and can
    update them later through ActivationClient.privacy. The Privacy values carry:

    • sellOrShareStatus and personalizationStatus, each OPTED_OUT, NOT_OPTED_OUT, or
      UNKNOWN
    • disclosureVersion, the version of the privacy notice the user saw
    • rdpOwner, which names who may write Google's gad_rdp preference

    Before every ad request, the SDK combines these values with the backend's judgement of the
    privacy notice. It then makes one of three requests: personalized, non-personalized, or none at
    all. A personalized request is never sent while restricted processing is in effect. If the
    user's privacy choices change after a rewarded ad has preloaded, that ad is dropped rather than
    shown. Each decision is reported as an ad_consent_decision event, and each refusal as
    ad_suppressed. Both carry the inputs and the applied values, so any ad request can be
    reconstructed from its events. The advertising identifier itself is never used, logged, or
    transmitted.

  • Render the offer feed with your own UI. ActivationClient.api.refreshOfferFeed() returns the
    backend-curated feed as OfferFeed, OfferGroup, and OfferSummary, in the order the backend
    sends. To follow the feed as it changes, observe ActivationClient.api.offerFeed. It is null
    until the feed first loads, and an empty feed is distinct from that. A failed refresh returns an
    OfferFeedError category. DISABLED means the feed is switched off for your client, while the
    offer wall keeps working. OfferSummary is sealed into ProductCoupon and MerchantPromo, so
    keep an else branch when matching on it. When a shopper taps one of your cards, pass the new
    initialDestination to OffersWall or OffersWallFlow:

    • OffersWallDestination.OfferDetails opens that offer's detail sheet.
    • OffersWallDestination.Group opens the wall at that group.

    If the offer is no longer available, the wall opens at its root with a short notice. Clipping an
    offer inside the SDK updates offerFeed right away, without another fetch.

  • Merchant promos. A new offer type rewards total spend at a store instead of specific
    products, and it sits alongside the existing product offers. Use Offer.couponType,
    Offer.isMerchantPromo, and Offer.minimumSpendThreshold to tell these offers apart. On the
    offer wall:

    • Merchant promos show a "SPEND $X" tag in place of "BUY N", rounded up so the threshold is never
      understated.
    • A dashed border sets them apart from standard offers.
    • An "Nd left" label marks offers with a redemption period. It is themed with
      ActivationAppearance.OffersWall.Colors.offerRedemptionLabel, which defaults to your theme's
      warning color.

    A qualifying scan earns the reward immediately and shows its own "Merchant Boost" card in the
    Receipt Summary's Your Earnings section.

  • Offer eligibility by store. New Offer.redemptionLimit, Offer.currency, and
    Offer.excludedFromMerchants describe how often an offer can be redeemed and where it can't be
    used. Offer.appliesToBanner(bannerId) combines the existing store restrictions and the new
    exclusions into one check. The offer wall's per-store lists now leave out offers excluded from
    that store.

  • Per-store scan rewards. The base scan report now includes the detected store, and the
    backend can answer with a reward for that store that replaces your configured ScanReward. The
    report is sent even when you have configured no ScanReward, so the backend can set the reward
    entirely on its own. If the backend doesn't supply an override in time, your configured amount
    stands.

  • Offer image placeholders. When a product or store image is missing or fails to load, offer
    cards, list items, and the detail screen now show a placeholder instead of an empty space.

  • Better merchant attribution. You can now attach the scanner's merchant-detection signals to a
    scan through the optional ScanResults.merchantDetection. They are forwarded with the scan, so a
    mis-detected merchant can be traced to the detector responsible. Leave it unset and nothing new
    is sent.

  • One base scan event per receipt. The base scan reward is now reported as soon as processing
    starts. This includes receipts whose verdict later withholds rewards, such as duplicate, invalid,
    too old, or over the submission limit. Each receipt produces exactly one base scan event.

  • More offer metadata. AdImage now carries the backend's aspectRatio, backgroundFill, and
    fit for normalized artwork, and Placement carries its numeric id.

Bug Fixes

  • Fixed review requests for missing fields, an unreadable receipt, or a too-old receipt failing
    with "Scan session not found". This was the Known Issue in 1.2.0. Every new scan session is now
    announced to the backend before a review can reference it.
  • Fixed a 401 Invalid API token ending the shopper's session at the first hiccup.
    Re-registration is now retried up to three times with increasing back-off, and the original
    request is replayed once a new token arrives. A replayed request now also carries its body
    correctly, so a recoverable 401 no longer turns into a hard failure.
  • Fixed an identity change (hashedEmail, hashedPhone, clientUserId, deviceId,
    appBundleId, or environment) leaving the previous user's state in play. The SDK now clears
    the cached offers immediately and cancels requests still in flight. Every request goes out under
    the identity that was current when it was made, so offerFeed and the offer wall can never show
    one user's offers to the next. Work started under the old identity is dropped instead of being
    sent with the new user's token.
  • Fixed leftover work from a closed ActivationClient affecting the next one. Calls on a closed
    client now do nothing.
  • Fixed a promotion and its product showing different reward values on the Offer Wall and Offer
    Detail than on the Receipt Summary. All three now show the full amount.
  • Fixed an offer wall response that contained a spend-based merchant promo failing to load as a
    whole, which left the offer wall blank.
  • Fixed a failed offer wall reload blanking the catalog already on screen. The wall now keeps what
    it shows.
  • Fixed the offer wall shimmering over, and blocking taps on, content it was already showing while
    a reload was in flight.
  • Fixed the system back gesture at the offer wall's root bypassing onDismiss and closing your
    host screen. Back at the root now reports through onDismiss, the same as the header's Up
    button. If you leave onDismiss null, nothing changes and your app keeps owning back.
  • Fixed reopening the offer wall bringing back a detail sheet or stores sheet left open from the
    previous visit.
  • Fixed filling in a missing date or total reporting the base scan reward twice for the same
    receipt.
  • Fixed an accepted review's base scan report being cancelled when the scan flow was closed while
    it was still sending.
  • Fixed the loading screen reporting failed ads that did not fail. A no-fill was reported twice,
    and a successful ad was reported again as failed when the receipt summary opened.
  • Fixed Test Mode not registering the device as a Google Mobile Ads test device. Ad requests are
    now correctly flagged as test requests.
  • Fixed the date picker cutting off its right-most weekday column, which left those dates
    untappable. This affected the Receipt Date dialog in Missed Earnings and the missing-date and
    too-old review forms.

Don't miss a new blinkreceipt-android release

NewReleases is sending notifications on new releases.