Activation SDK (1.2.0)
- Bundled Activation SDK updated from 1.1.2 to 1.2.0. This is the only module in this release with breaking changes; every Receipts SDK module below is source- and binary-compatible with 2.3.1.
- A receipt that can't be accepted no longer ends the offer flow or reaches your app as an error. Seven cases are now handled inside the SDK across two tiers — on device, before anything is submitted: an unreadable photo, a missing purchase date or total, and a receipt outside the rewards window; from the server: a submission limit, a duplicate, an actionable fraud flag, and the age check again. Each raises a screen that explains the problem and offers to send the receipt to a human reviewer, and dismissing it returns the shopper to the offer wall with the flow intact. Because device-tier checks run before submission, a receipt one of them stops is never registered with the backend and can never come back later as a duplicate.
- BREAKING CHANGE:
ActivationException.ReceiptAlreadyScanned,ReceiptTooOld,ReceiptSubmissionLimitReached, andReceiptInvalidwere removed. Each of those verdicts now has its own screen, so none is an error any more and none reachesonException. Delete those branches — there is nothing to replace them with — and remove any dialog you showed for them, since the SDK now raises its own screen with a review option and yours would compete with it.CameraScan,Submit,ReceiptSummary, and theRegistrationhierarchy are unchanged.
- BREAKING CHANGE:
- A "Retake photo" now adds a frame to the receipt already in hand rather than starting over. New
OffersWallFlow.onResumeScanis invoked in place ofonScanReceiptwhen a shopper adds another photo from a review screen; resume your still-open recognizer session and return the updated result for the same scan, preserving itsblinkReceiptId. Left null, the flow falls back toonScanReceipt, which starts a fresh scan and changes the id — nothing errors, but one physical receipt then reaches your backend and your webhooks as two unrelated receipts. NewOffersWallFlow.onScanAcceptedfires once a completed scan is accepted for processing and no further photo will be requested, which is the point at which you can safely finalize a still-open recognizer session.- BREAKING CHANGE:
onResumeScanandonScanAcceptedwere inserted intoOffersWallFlowdirectly afteronScanReceipt, so code that builds the flow by position no longer lines up — use named arguments. Both are optional and default to null, so an integration already using named arguments compiles unchanged.
- BREAKING CHANGE:
- A scan session now covers exactly one scan. A
scanSessionIdopens when the camera opens and closes when the camera exits, including a cancelled scan that never produced a receipt. At most oneblinkReceiptIdever appears under one session, so two receipts scanned in a single visit to the wall carry two different ids with no session open in between, and while a shopper is only browsing no session exists at all. Sessions were previously scoped to the offer wall's lifetime, so several receipts in one visit shared a single id and reward, review, and submission events from different receipts could not be told apart.- BREAKING CHANGE:
startScanSession()andendScanSession()must no longer be called around a scan when you useOffersWallorOffersWallFlow. This is behavioral, not a compile break — the methods still exist and remain supported for hosts that drive scanning without an offer wall. A session you open yourself is one the camera rides along on rather than owns, and a session the camera does not own is never registered with the backend.
- BREAKING CHANGE:
OffersWall.onScanReceiptsemantics depend on who owns the camera. WithOffersWalland.activation(true), launch your camera flow and return immediately, exactly as before — do not await the launcher result, since awaiting holds a session open across the camera launch, which is the same mistake as bracketing it by hand. If instead your own code both scans and submits the receipt, the wall's session is the only one there is and the callback must suspend for the whole scan, or the receipt is submitted without a session and rejected.OffersWallFlowis unaffected: itsonScanReceiptreturns aScanReceiptResult, so it already suspends by construction.- A scan that ends at a review request now earns nothing at scan time. The backend holds the base scan reward and grants it only if a reviewer accepts, which may be hours or days later. Design your backend for open-ended reward accrual per
blink_receipt_idand credit the shopper on the resultingRewardUpdatewebhook rather than treating the receipt summary as final. The amount itself is unchanged — whatever your app configured throughScanReward. A missing-fields review is the exception: it continues on to processing, so that scan's reward still lands immediately. - A missing store name alone no longer blocks a submission. When the merchant is the only field the scanner couldn't read, the receipt submits and the merchant is resolved server-side from the photo — the shopper is never asked to type or pick it. When merchant is missing alongside a date or total, it now shows as already available in the checkmarks and stays out of the manual-entry field. Corrections a shopper makes travel to the reviewer only; they are never merged into the scan submitted for processing, which is always exactly what the scanner read, so treat a correction as a pending claim in your own accounting and wait for the webhook.
- A clearer path to reporting a missing reward. The icon in the top right of the Receipt Summary is now a Report button, and a "Missing rewards? / Update your receipt" row appears above Your Receipt. Both open the existing Missed Earnings screen and now show on every receipt summary rather than only those missing a merchant, and both are hidden on duplicate, invalid, and pending-review summaries, which offer the banner's review option instead. Missed Earnings itself no longer has a read-only mode: it opens in edit mode instead of requiring a toolbar pencil tap, and Submit moved from a checkmark in the app bar to a full-width footer button.
- Theming for the new screens.
Activation.appearancegains a sixth per-screen record,ActivationAppearance.FrequencyCap, with nine color keys for the "You've hit your limit" screen, andActivationTheme.IconsgainsfrequencyCapIconfor its header glyph.ActivationAppearance.ReceiptSummary.Colorsgains sevenpostScanDuplicateBanner*keys for the post-scan banner — despite the naming, these style all three banner cases: duplicate, invalid receipt, and pending review — pluspurchaseRowTertiaryLabelColorfor the missing-rewards row. Every new key defaults to the theme token its element already renders with, so unset keys change nothing. On Missed Earnings,missedEarningsNavigationSaveButtonBackgroundnow themes the footer button, whilemissedEarningsNavigationEditButtonIcon,missedEarningsNavigationEditButtonBackground,missedEarningsNavigationSaveButtonIcon, andtheme.icons.submitMissedEarningsno longer render anywhere — they still exist, so nothing breaks; they simply have no effect.- BREAKING CHANGE: the seven banner keys were inserted in the middle of the
ActivationAppearance.ReceiptSummary.Colorsconstructor, so positional construction no longer lines up — use named arguments.ActivationAppearanceandActivationTheme.Iconseach gained one key too, but both were appended last, so those two still compile positionally.
- BREAKING CHANGE: the seven banner keys were inserted in the middle of the
- Declare
android:windowSoftInputMode="adjustResize"on the Activity that hosts the flow. The new review screens contain text fields and inset themselves from the window's IME insets; without it the keyboard covers the form's input and its submit button.CameraRecognizerActivityalready declares this as of 2.4.0, so theOffersWallwith.activation(true)path needs no change — this applies to hosts embeddingOffersWallFlowin their own Activity. - Known issue: do not hold a scan session open across the camera launch — neither by calling
startScanSession()yourself nor by awaiting the camera result insideOffersWall.onScanReceiptwhen the camera Activity owns the session. A session already open when the camera starts is one the camera rides along on instead of owning, so nothing registers it with the backend. Device-tier review requests are filed before the receipt is submitted for processing, so that review request becomes the first thing to reference an unregistered session and fails with "Scan session not found". Nothing throws and nothing reachesonException— the shopper simply cannot submit the review. Addressed in a later release. - Fixed rewards and review submissions being reported under whichever scan session happened to be open when the network round trip returned, rather than the one that produced the receipt; fixed
ActivationClient.close()leaving the active session open, so an id could be inherited by the nextinitialize(); fixed a scan abandoned before its results were processed leaving the offer wall's per-scan state populated for the next scan; and fixed the Missed Earnings unsaved-changes confirmation being bypassed by the Android system back control. - To exercise the new flows, turn test mode off.
TestOptions.Testresolves both validation tiers to accepted — the two device checks that describe the scan itself, an unreadable photo and a missing field, still apply under test mode, but the receipt-age check and every server verdict do not.
Recognizer SDK (2.4.0)
- Fixed: text from a previous scan could carry into the next one, producing duplicated or merged content in
ScanResults. Parser reset is now a consume-once signal, spent at the OCR call itself and handed back if that call throws or returns no result, rather than inferred from the frame index — which could advance on frames that never reached OCR, such as when the recognizer was not yet initialized, a bitmap was unusable, or a side lookup threw. Scan sessions are also generation-tracked, so a frame still queued from a superseded session can no longer consume or restore the reset belonging to the session that replaced it. This was most commonly hit by re-entering the camera screen while a scan was still in flight. - Fixed: a merchant-logo detection failure discarded the frame. Logo detection is a side lookup; an exception there is now logged and the frame continues to OCR instead of being dropped.
- Fixed: frames were lost outright when a caller passed a
HARDWARE-config bitmap throughImageClient.BitmapUtils.copy()now falls back toARGB_8888when the source bitmap isHARDWARE-config or reports no config, instead of throwing — previously eitherBitmap.copy()rejected the mutable copy or the later pixel read failed. - Added the receipt-level return flag to the recognizer pipeline:
FrameResults.isReturn()andFrameResults.Builder.isReturn(boolean),Receipt.isReturn()andReceipt.isReturn(boolean), and the JNI-populated public fieldOcrResult.isReturn. A new consumer ProGuard rule keepscom.microblink.OcrResult's no-arg constructor and fields so R8 cannot break the JNI field-set contract. No integration changes are required. - Added
BitmapFrame.resetParser()andresetParser(boolean), making the OCR-parser-reset decision explicit instead of inferred. When never set, the previousframeIndex() <= 0behavior still applies, so existing integrations are unaffected; an explicitfalseon frame 0 is now honored. Recognizer.initialize()now logs a debug warning when it is called while a previous session is still active and its in-progress receipt is about to be discarded.- Updated the bundled parser to v1.55.1.
- Stability fixes and improvements
Account Linking SDK (2.4.0)
- Stability fixes and improvements
Camera SDK (2.4.0)
- Added
CameraView.suspendCamera()andresumeCamera(LifecycleOwner), a sticky unbind alongside the existing transientpauseCamera().pauseCamera()is undone by the nextstartCamera()or lifecycle resume;suspendCamera()latches capture suspension sostartCamera()no-ops until it is cleared, and is idempotent.resumeCamera(lifecycleOwner)clears the latch and rebinds, and the protectedclearCaptureSuspension()clears it without binding, for entry points that run before aLifecycleOwnerexists. The latch is checked before the permission gate, so a permission revoked while UI covers the camera cannot fireonPermissionDenied()underneath an overlay. - Stability fixes and improvements
Camera UI SDK (2.4.0)
- Added support for the Activation SDK's new error flows. A new
ScanState.CaptureSuspendedhalts capture while keeping the recognizer session open and the camera frozen on the captured photo, so preliminary results can be handled with two exits — resume capture, adding another photo to the same scan, or terminally finalize. NewRecognizerActions back this:PreliminaryResults,ResumeCamera, andReleaseRecognizer, the last of which terminates the native session without unmounting the camera composable that hosts the action and result collectors. Activation scans only. - Fixed: returning from the background re-requested camera permission and re-ran scan initialization on a scan already in progress. Permission is now requested once per camera screen rather than on every foreground resume, and scan observer registration was hardened so repeated initialization can no longer stack duplicate collectors on the same flows — scan state and captured-frame updates could otherwise be handled more than once.
- Fixed: a stale, already-terminal scan session could be adopted by the next camera visit. Scan session lifetime is now explicitly owned —
CameraRecognizerFragmenttracks whether it started the session across configuration changes via instance state, ends it inonDestroyonly when it isn't a config change, and never ends a session adopted from a host that brackets its own, such asOffersWall.onScanReceipt. All teardown routes through a single internal path. - Fixed: delivering a scan result could race the terminal finish out of existence. The session now emits results only after the recognizer acknowledges its end-of-session work, and the preliminary-results and terminal-work watchdog budgets are paused while the host is stopped.
- Fixed the live camera being briefly visible during the Activation SDK's Receipt Summary teardown by painting an opaque backdrop between the camera and the activation overlay.
- Lookup-only emissions now omit undetermined fields instead of showing them as not captured; the final found / not-found determination comes from the settled results mapping.
CameraRecognizerActivitynow finishes when launched without scan options, instead of logging and leaving a blank Activity.CameraRecognizerActivity's manifestconfigChangeswas widened to also absorbsmallestScreenSize,uiMode,locale,fontScale, anddensity, and it now declareswindowSoftInputMode="adjustResize". This is deliberate, so a live scan session survives dark-mode, locale, font-scale, and density changes. Hosts that relied on Activity recreation for those events will no longer get it.- Added
CameraFlow.md, an authoritative reference for camera composition and teardown covering the LIVE / UNBOUND / RELEASED / DESTROYED conditions, theScanState-to-camera-condition table, and why visibility and teardown are deliberately separate concerns. - Stability fixes and improvements
Core SDK (2.4.0)
- Added
ScanResults.isReturn()andScanResults.Builder.isReturn(boolean), exposing the parser's receipt-level return verdict end-to-end across the camera, direct-OCR, and PDF paths, as well as the Linux OCR-fallback response and the server receipt record behind web and missed-earnings receipts. Defaults tofalse, and mirrors the iOS implementation. No integration changes are required.- The
ScanResultsParcel layout changed to carry the new flag, which is written and read mid-stream rather than appended at the end. In-process parceling through Intents, Bundles, orSavedStateHandleis unaffected, since the writer and reader are always the same SDK version, and the SDK never persists Parcel blobs itself. Only an integration that marshalsScanResultsto raw bytes and persists them across an SDK upgrade would misread the stream; invalidate that cache on upgrade. Field-based persistence such as JSON is unaffected and reads pre-existing results as non-returns.
- The
- Stability fixes and improvements
Digital SDK (2.4.0)
- Stability fixes and improvements
Digital Analytics SDK (2.4.0)
- Stability fixes and improvements
Earnings SDK (2.4.0)
- Stability fixes and improvements
Logcat SDK (2.4.0)
- Added
LogEvent.error(() -> String), an open function with an empty default body on the publicLogEventclass, backed byLoggingEventsatLogPriority.ERROR. Existing subclasses are unaffected. - SDK exception logging was raised from
DEBUGtoERROR, so exceptions now survive release-build log filtering. If your crash or log pipeline filters logcat by priority, expect more SDK output atERROR. - Stability fixes and improvements
Security SDK (2.4.0)
- Stability fixes and improvements
Surveys SDK (2.4.0)
- Stability fixes and improvements