nexus_flutter 1.9.0
nexus_flutter: ^1.9.0 copied to clipboard
Nexus — one Flutter SDK for all Inverge Nexus services (realtime, sessions, events, errors, logs, feature flags, remote config, links, session replay, push notifications, voice).
1.9.0 #
In-app messages, rebuilt around the console's new designer. Backward compatible: messages made before the designer render exactly as before, and apps on older SDKs keep receiving a plain fallback of designed messages.
Added — designed in-app messages #
Messages built in the console's new in-app designer render natively.
NexusInAppMessage.design(NexusInAppDesign) — the block document fromcontent.design: image, badge, heading, text, buttons (row or stacked), spacer, divider; solid / gradient / image frame backgrounds, radius, padding, gap, max width, shadow, border; backdrop colour + tap-to-dismiss; close button style / side / colour;fade/slide/zoomentrance; auto-dismiss;auto/ltr/rtldirection (autofollows the copy's script).NexusInAppDesignedViewdraws it;NexusInAppOverlayuses it automatically for designed messages (unless you pass amessageBuilder). Same layout rules as the console preview.- New layout
bottom_sheet. - Carousel block (
NexusInAppCarouselBlock/NexusInAppCarouselCard): up to 10 swipeable cards that snap to card boundaries, with peek, page dots and autoplay (restarts after a swipe). Card buttons and card taps are reported with their own ids. - New button action
copy— putsvalueon the clipboard.NexusInAppButton.value/NexusInAppAction.valuecarry it. - Tappable images report a click with the image block's id.
Messages without a design (made before the designer) render exactly as before.
Added — targeting, personalisation, A/B tests, test sends #
inApp.setProperties()/clearProperties()— extra properties for audience conditions and{{variables}}, on top of the identify traits.- Fetches now send the user's traits, name / email / phone, and device context
(platform, OS version, app version, language).
identify()keeps name / email / phone in memory and restores them on the next launch;reset()clears them. NexusInAppMessage.variantId/NexusInAppAction.variantId; impressions, clicks and dismissals report the variant and the install's device key.- Console test sends (
NexusInAppMessage.isTest) show immediately, skip triggers and caps, and are never reported. Registered test devices listen on a realtime room (inapp-test:<deviceKey>) for instant delivery; other installs never join it.
1.8.0 #
Added — Nexus Links, fully automatic #
Install the SDK and call Nexus.init — no link code needed (linksEnabled,
default true).
- Installs are reported once per install on first launch. Reinstalls are detected from the OS install timestamp (even when Android Auto Backup restored the app's preferences); apps that merely upgraded the SDK are not counted.
- Exact Android install attribution via the Google Play Install Referrer:
the click-id rides in the store URL's
referrer, so the install matches the link click deterministically (addscom.android.installreferrer:installreferrer). - Opens from a Nexus link are reported automatically, once per click.
- Routing: when the app's router could not have seen the link — after an
install (deferred deep link) or when the app was opened by the short link
itself — the SDK routes to the link's
deep_link_valueby delivering it as a platform deep link, so go_router, auto_route or a plain Navigator handle it unchanged. Short links (/l/<slug>,/a/<appId>/<slug>) are consumed natively before Flutter routes them, so the app never sees a path it has no route for. - App Links (Android) work with zero config: the SDK declares a verified
filter for
https://services.inverge.net/a/<applicationId>/…. Register the app in the console (Links → Apps). - Universal Links (iOS): add
applinks:services.inverge.netto the app's Associated Domains once. Both the app-delegate and UIScene lifecycles are supported. - New config:
linkDomains,autoRouteDeepLinks,onDeepLink(returntrueto take over routing). New API:links.onLink(stream),links.consumePending(),NexusDeepLink;links.attribute()acceptsslug.
Uninstall tracking is server-side (silent push probes, opt-in per app in the
console) and needs pushEnabled.
1.7.1 #
Fixed — realtime #
- Realtime now recovers after a dropped connection (screen lock/unlock, network
loss, server restart). The socket reconnected, but rooms were re-joined with the
wrong field (
roominstead ofname), so the server rejected every re-join withname_requiredand no events arrived until the app was restarted. realtime.emit()now reaches the server. It sent{room, events}; the gateway expects{name, event}(as the JS SDK sends), so every emit was rejected.- Reconnect on foreground when the app backgrounds mid-reconnect. The lifecycle
hook only reconnected if the socket was connected at the moment of backgrounding;
it now reconnects whenever realtime was in use (new
realtime.isActive). realtime.off()is permanent. The handler is no longer re-registered on the next (re)connect, and callingon()twice for one event no longer stacks two listeners on the live socket.
1.7.0 #
- Caller avatar on incoming calls. Pass
voice.placeCall(callerAvatarUrl: '…')and the callee sees the caller's photo instead of just their initial.- Android: shown on the lock-screen call notification AND the full-screen ring screen — even when the app is killed / the screen is locked. The ring is posted instantly with the initial and the photo swaps in the moment it downloads, so the ring is never delayed by the network. Missed-call notifications carry it too. Always falls back to the initial if the URL is missing or unreachable.
- In-app call screen (both platforms): rendered from
NexusCall.remoteAvatar. - iOS: the system CallKit ring shows the name only (CallKit exposes no per-call image API); the avatar appears on the in-app call screen.
- Rides in call metadata as
callerAvatar, threaded through the incoming push and the realtimecall.incomingevent. Use anhttpsURL — the native Android loader (unlike Flutter's image loader) blocks cleartexthttpon API 28+. Native avatar loads log tofiles/nexus_call.logfor on-device diagnosis.
1.6.0 #
Voice calling now works end-to-end on iOS, at full parity with Android. iOS gets
its own native calling stack — NexusVoiceManager.swift (CallKit CXProvider +
PushKit VoIP registry + missed-call notifications), the twin of Android's
self-managed Telecom stack. One Dart codebase drives both platforms over the same
method-channel contract; Android behavior is unchanged.
Added — native iOS calling #
- Killed-app ring over PushKit. An APNs VoIP push is reported to CallKit
natively, before the Flutter engine is even up — so the ring is never gated on
Dart startup. PushKit is registered at launch (requires the
voipbackground mode; the SDK skips it and logs why if the mode is absent). - Full in-call control from the system UI — answer / decline / hangup / mute / hold / DTMF flow into the same handlers Android uses. A cold-launch answer (user accepts before the engine is ready) is buffered natively and replayed the moment the voice service attaches.
- Caller cancels → missed call. The ring ends as unanswered (in system Recents) and leaves a "Missed call" notification, like Android. The backend now sends the cancel to iOS as its own VoIP push (report-then-end, as PushKit requires) rather than skipping iOS — so a killed device stops ringing instead of ringing on.
Nexus.instance.voice.openMicrophoneSettings()— deep-links to the app's Settings page for the one case iOS never re-prompts (a real permanent denial).
Changed #
- CallKit owns the audio session. LiveKit runs in
externalCallSystemmode and its audio engine is gated on CallKit's activate/deactivate window — the fix for the classic "connected but silent" CallKit bug. Outgoing calls must be reported to CallKit on iOS (or the session is never activated); Android still deliberately skips native outgoing calls. flutter_callkit_incomingremoved. Neither platform depends on a third-party calling package any more — both are fully native.useCallKit(...)remains as an advanced override seam.- Microphone is requested through iOS's own
AVCaptureDeviceAPI, not a permission package. On iOS those packages report "never asked" as denied, report a failed request as a permanent denial, and can be compiled out by a Podfile flag — in which case they answer "denied" without the OS ever being asked, so no dialog ever appears and the permission never even shows up in Settings. A calling SDK cannot carry that ambiguity. Android is unchanged and still usespermission_handler.- The mic is now prewarmed on
voice.init()and again on every foreground resume, so it is granted in a foreground session before the first call — a call answered from a background VoIP launch can show no dialog at all. - Input and output are gated separately: with no mic the call still connects and plays the other party (receive-only), and the mic is opened mid-call the instant access is granted — no need to redial.
- The mic is now prewarmed on
Fixed #
- iOS calls connected but the caller heard nothing. With the mic never granted in
a foreground session, the call joined receive-only and published no audio track;
the mic also never appeared under Settings because iOS had never actually been
asked. Both are resolved by the native
AVCaptureDevicerequest + foreground prewarm above. (If you still see it: launch the app from its icon once and accept the prompt — the grant cannot happen while answering a call.) - The CallKit audio gate was applied before any media session existed ("audio
device module is unavailable") and so never took effect. It now applies once the
room is up, with a watchdog that opens the audio engine anyway if CallKit's
didActivatenever fires — a call can no longer be stranded silent. - A call can no longer crash the app over the microphone. Opening the mic with no
NSMicrophoneUsageDescriptionin the host Info.plist terminates the process via TCC; the SDK now detects the missing key natively and reports it instead of letting an unexplained abort happen mid-call. It also warns on a missingaudiobackground mode (which silently suspends call audio on screen-lock).
One-time iOS host-app setup (see README) #
voip + audio (+ remote-notification) background modes,
NSMicrophoneUsageDescription, and an APNs VoIP key in the Nexus console. Everything
else — CallKit, PushKit, WebRTC media, mic handling — is bundled and turnkey with
voiceEnabled: true.
1.5.0 #
- Nexus Voice (CPaaS calling) — turnkey. Set
voiceEnabled: trueand call; the SDK bundles everything (WebRTC media, native CallKit/ConnectionService UI, VoIP/FCM incoming-call push registration). No adapters to write.nexus.voice:placeCall(to:, type:)(app-to-app, PSTN or SIP), plussetMuted/setSpeakerphone/setHold/sendDtmf/hangup;answer/decline.- Built-in call screen — a default full-screen call UI auto-shows on any
active call (outbound + incoming) with mute/speaker/hold/hangup and
accept/decline. Nothing to build; opt out with
autoShowOverlay: false. - Live call state via
nexus.voice.current(ValueListenable<NexusCall?>) — mirrors the server state machine (ringing/connected/onHold/…) with live MOS. - Incoming calls ring the native screen even when the app is closed — the SDK registers the device's VoIP/FCM token and the Nexus backend pushes the ring; Accept/Decline/mute/hold from the system UI are wired automatically.
- Bundles
livekit_client+flutter_callkit_incominginternally. One-time OS setup only (iOS VoIP background mode + APNs key in console); see README. - Never-crash rule: all media/native/push failures surface as a failed/ended call state, never a thrown exception.
1.4.1 #
- Hardening: the SDK can never crash the host app.
- The telemetry outbox is now memory-capped (512 KB total) and drops the oldest items under sustained pressure, so a burst of telemetry can no longer grow the queue into an out-of-memory / main-thread-stall hazard.
- Bulky, best-effort payloads (session-replay batches) are held in memory and delivered best-effort, but never persisted — keeping the durable store tiny so it can't bloat local storage or its re-serialization stall the app.
- The global error handlers now swallow any failure inside our own reporting, so error capture can never disrupt the app's error handling.
1.4.0 #
- Live Activities. One cross-platform API for a live, updating view of an
in-progress event. Enable with
liveActivityEnabled: true.- iOS — ActivityKit on the Lock Screen / Dynamic Island. A turn-key default
attributes type (
NexusLiveActivityAttributes) + a copy-paste Widget Extension template (ios/NexusLiveActivityWidget/); the SDK manages the lifecycle and registers push-to-start (17.2+) + update tokens. Or manage your own typedActivityAttributesand register tokens vianexus.liveActivity. - Android — a live ongoing notification (progress / status) the SDK renders
natively from the server's data message or via
nexus.liveActivity. - Drive it server-side from the dashboard / Live Activity API (start / update /
end), or locally with
nexus.liveActivity.start/update/end.
- iOS — ActivityKit on the Lock Screen / Dynamic Island. A turn-key default
attributes type (
1.3.0 #
- In-app messages (OneSignal-style). Set
inAppEnabled: true; the SDK fetches active messages, evaluates triggers (session start / custom event) + frequency caps locally, and shows modals / banners / center / fullscreen with buttons. Impressions and clicks are reported automatically;nexus.inApp.onActionsurfaces button taps (dismiss / open URL / track event). Compose them in the console (In-app messages). - Overlays auto-mount. Surveys and in-app messages no longer require a
MaterialApp.builder— the SDK inserts the overlay into the app's root overlay itself (autoShowOverlay, defaulttrue; setfalseto mountNexusSurveyOverlay/NexusInAppOverlayyourself).
1.2.0 #
- Push: rich notifications. Campaigns composed with action buttons, Android large/
big/small icon + lockscreen visibility + accent colour, iOS badge/relevance/
interruption level/subtitle, and web icon/image/badge are honoured on delivery.
- Android renders every notification natively (no third-party packages) in all app states — foreground, background and killed — so action buttons and rich media are consistent everywhere. Campaigns are delivered as high-priority data messages and a background handler draws them.
- iOS ships a ready-to-use Notification Service Extension
(
ios/NexusNotificationServiceExtension/) that turns the payload into action buttons + attached images in every state (one-time app setup, see README). - New
push.onOpenedcallback surfaces the open — including which action button was tapped and its URL.
1.1.0 #
Push notifications, matured — foreground display, localization, and subscriber tags.
- Foreground notifications. FCM never draws a notification while the app is
open — the SDK now renders it using its own native code (no third-party
packages): iOS presents the banner via the Firebase SDK
(
setForegroundNotificationPresentationOptions), Android posts the notification from the Nexus plugin and forwards taps back for open attribution. Configurable withpushForegroundDisplay(defaulttrue),pushAndroidChannelIdandpushAndroidChannelName. - Localized notifications.
push.setLanguage('en')attaches the user's app language to the device token; campaigns composed with per-language translations (console → Push → Localize) are delivered in each device's language, falling back to the campaign default then the base message (en-US→en). Works before or after push is enabled. - Subscriber tags.
push.setTag('role', 'client')/setTags({...})/removeTag(key)/clearTags()attach OneSignal-style key/value tags to the device token. Build tag-condition segments in the console (Tag is / is not / contains / exists / does not exist) to target campaigns and automations. Tags sync immediately when a token exists, otherwise on the next registration.
1.0.4 #
- Add Nexus Push (
context.nexus.push/Nexus.instance.push).- Turn-key: set
pushEnabled: trueand the SDK handles everything — permission, FCM token acquisition + registration (correlated to the journey identity), token refresh, and open tracking. No push code in your app; only the standard Firebase config is required. Bundlesfirebase_core+firebase_messaging. - Advanced/BYO:
registerToken/reportOpen/unregisterfor apps that manage their own tokens (e.g. raw APNs).reportOpenreadsnexus_campaign_idfor delivery + A/B outcomes.
- Turn-key: set
1.0.3 #
identify()now accepts an optionalphonenumber (alongsideemail/name/traits) — sent to the backend and persisted with the identity.- Persist the identified end-user across app launches. Once
identify()names the user, the identity is remembered on device and restored on the next cold start, so every session is attributed to that user instead of starting a new anonymous one. Never identified → still anonymous, as before.reset()clears the persisted identity (and is now async). context.nexusis now safe to call from any widget lifecycle method, includinginitState()anddispose().NexusScope.ofno longer registers an inherited-widget dependency and only consults the scope while the element is mounted (the Nexus instance is a stable singleton), fixing both the "dependOnInheritedWidgetOfExactType called before initState completed" and the "Looking up a deactivated widget's ancestor is unsafe" assertions.
1.0.2 #
- Backend base url updated.
1.0.1 #
- Drop package_info_plus (pulls dart:io/win32); source app metadata from the native device-info channel instead → WASM-compatible, clean platform graph.
- Rename the iOS/macOS Swift package dirs from
nexustonexus_flutter(the plugin name) so Flutter/pana detect Swift Package Manager support. - Result: 30 conventions + 20 docs + 20 platform + 50 analysis + 40 deps = 160.
1.0.0 #
- Identity
- Sessions
- Events (analytics)
- Logs
- Errors & crashes
- Feature flags
- Remote Config
- Deep links & attribution
- Realtime
- Session replay
- Surveys
- Lifecycle, flushing & disposal