Initialise the SDK
Do this once, in lib/main.dart, before runApp: make the facade (Crm), start it, put CrmScope above your MaterialApp, put CrmOverlay in its builder (that is where in-app messages are drawn), and add the strings.
import 'package:crm_sdk/crm_sdk.dart';import 'package:flutter/material.dart';
final crm = Crm( config: CrmConfig( apiBase: Uri.parse('https://app.purrcrm.com'), appKey: const String.fromEnvironment('CRM_APP_KEY'), // app_..., from the Install tab appVersion: '1.0.0', // your app's own version ), auth: CrmAuth.tokens(tokens), // see "Sign users in");
// Your app's navigator, which the overlay opens pages on.final navigatorKey = GlobalKey<NavigatorState>();
void main() { WidgetsFlutterBinding.ensureInitialized(); // before crm.start() // The package's pages (the messenger, a tapped push's) open on it. crm.host = crm.host.copyWith(navigator: NavigatorCrmNavigator(navigatorKey)); crm.start(); runApp(CrmScope( crm: crm, child: MaterialApp( navigatorKey: navigatorKey, localizationsDelegates: const [CrmLocalizations.delegate /*, yours */], builder: (context, child) => CrmOverlay(navigatorKey: navigatorKey, child: child!), home: const HomeScreen(), ), ));}Build with your app’s key:
flutter run --dart-define=CRM_APP_KEY=app_...crm.start() follows the app’s lifecycle, so Flutter’s binding must be there first. Without WidgetsFlutterBinding.ensureInitialized() it fails with “Binding has not yet been initialized”.
When it starts, the package reads your app’s public face from PurrCRM once: its name, accent, logo, which parts are switched on, its currency and languages. It keeps the answer on the phone, so the next start has it at once, even offline. crm.publicApp holds it.
Configuration
Section titled “Configuration”CrmConfig describes your app:
| Field | What it is |
|---|---|
apiBase | Your PurrCRM address, https://app.purrcrm.com. |
appKey | Your app’s key (app_…, or app_test_… in a staging build). Required. |
appVersion | Your app’s version, sent with every request. |
productName | What your users call your app. Left out, the name set on Settings › your app › Brand is used. Set it when the app must say its own name from the first frame. |
storagePrefix | What every key the package keeps on the phone starts with: crm unless you name one. Pick it once and never change it, or queued events are lost. |
initialFeatures | The parts to have on a phone that has never reached PurrCRM. None unless you name some. |
customEvents | Your own events, by name: see Events. |
followLifecycle | Whether the facade follows the app to the background and back. On, except in widget tests. |
autoScreenViews | Whether the package’s screens record screen_viewed themselves. |
httpClient | The HTTP client: a fake in tests (see Testing). |
languageTag | The user’s language, so refusals are worded in it. Set it to follow your app’s locale. |
flushEvery | How often queued events are sent (30 seconds). |
store | Where the package keeps what it keeps: shared_preferences unless you hand another, such as MemoryCrmStore in a test. |
With go_router
Section titled “With go_router”Mount the package’s routes among yours and hand the facade a navigator over your router:
import 'dart:async';
import 'package:crm_sdk/crm_sdk.dart';import 'package:crm_sdk/go_router.dart';import 'package:flutter/material.dart';import 'package:flutter_localizations/flutter_localizations.dart';import 'package:go_router/go_router.dart';
final crm = Crm( config: CrmConfig( apiBase: Uri.parse('https://app.purrcrm.com'), appKey: const String.fromEnvironment('CRM_APP_KEY'), appVersion: '2.4.0', productName: 'Northwind', storagePrefix: 'northwind.crm', ), auth: CrmAuth.tokens(tokens), host: CrmHost( followable: northwindLinks, // which of your own places a message may open openExternal: (url) => launchUrl(Uri.parse(url)), ),);
final router = GoRouter( routes: [ GoRoute(path: '/home', builder: (_, _) => const HomeScreen()), ...crmGoRoutes(fallback: '/home'), ],);
void main() { WidgetsFlutterBinding.ensureInitialized(); crm.host = crm.host.copyWith( navigator: GoRouterCrmNavigator(router), openInApp: router.go, ); unawaited(crm.start()); runApp( CrmScope( crm: crm, child: MaterialApp.router( routerConfig: router, localizationsDelegates: const [ CrmLocalizations.delegate, GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], builder: (context, child) => CrmOverlay( navigatorKey: router.routerDelegate.navigatorKey, child: child!, ), ), ), );}A page whose part is switched off, or anyone signed out, goes to fallback. With a plain Navigator, hand the facade NavigatorCrmNavigator(navigatorKey) instead: each page is pushed, and Back comes back. Messenger, inbox and help lists the routes.
Host hooks
Section titled “Host hooks”CrmHost is what your app does for the package. Every hook is optional; leaving one out means the package does without it.
| Hook | What it does | Without it |
|---|---|---|
followable(link) | Your allow-list: where a link in a message (/offers/123) goes in your app, or null for nowhere. | Links into your app go nowhere. |
openInApp(location) | Goes to a place followable allowed. | The same. |
navigator | Shows the package’s pages. | Its pages and tapped pushes go nowhere. |
openExternal(url) | Opens an https page in the browser. | Web links go nowhere. |
pickPictures(most) | Your picture picker (such as image_picker). | No add-picture button in the messenger. |
timeZone() | The phone’s IANA time zone. | Sent empty; your app’s time zone is used. |
askPermission(permission) | Asks the phone for a permission a carousel’s button asks for. | The button just goes on. |
onUnreadChanged(before, after) | The unread count changed: refresh a bell of your own. | Nothing else is told. |
links | The places in your app a link may go, for your team’s link picker. See Links. | Your team types paths by hand. |
Hooks that need your router or your own services can be handed in later, with crm.host = crm.host.copyWith(...).