Skip to content

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.

lib/main.dart
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:

Terminal window
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.

CrmConfig describes your app:

FieldWhat it is
apiBaseYour PurrCRM address, https://app.purrcrm.com.
appKeyYour app’s key (app_…, or app_test_… in a staging build). Required.
appVersionYour app’s version, sent with every request.
productNameWhat 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.
storagePrefixWhat 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.
initialFeaturesThe parts to have on a phone that has never reached PurrCRM. None unless you name some.
customEventsYour own events, by name: see Events.
followLifecycleWhether the facade follows the app to the background and back. On, except in widget tests.
autoScreenViewsWhether the package’s screens record screen_viewed themselves.
httpClientThe HTTP client: a fake in tests (see Testing).
languageTagThe user’s language, so refusals are worded in it. Set it to follow your app’s locale.
flushEveryHow often queued events are sent (30 seconds).
storeWhere the package keeps what it keeps: shared_preferences unless you hand another, such as MemoryCrmStore in a test.

Mount the package’s routes among yours and hand the facade a navigator over your router:

lib/main.dart
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.

CrmHost is what your app does for the package. Every hook is optional; leaving one out means the package does without it.

HookWhat it doesWithout 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.
navigatorShows 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.
linksThe 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(...).

Sign users in.