Apptics Flutter
Flutter plugin for Apptics, a wrapper around the native Apptics iOS and Android SDKs. Apptics is Zoho's analytics and app-management platform.
This plugin exposes the following feature modules:
- Core analytics — event, screen, session and user tracking
- Privacy / tracking controls — granular consent states (PII / usage / crash)
- In-app ratings — store review prompts, custom rating flows
- Remote configuration — server-driven config params and conditions
- In-app updates — flexible / immediate / force update prompts (native or custom UI)
- Crash tracking — fatal & non-fatal exceptions, ANR detection (Android), custom properties
- Feedback — feedback & bug-report screens, shake-to-feedback, logs & diagnostics
- Push notifications — foreground/background message, click and action handling
- API call tracking — network performance metrics (success rate, latency, status codes)
ℹ️ Push notifications and API tracking each have a dedicated companion guide:
PUSH_NOTIFICATION_README.mdandAPI_TRACKING_README.md. This README links to them from the relevant sections below.
Table of contents
- Compatibility
- Installation
- Platform setup
- Getting started
- Core analytics
- In-app ratings
- Remote configuration
- In-app updates
- Crash tracking
- Feedback
- Push notifications
- API call tracking
- Architecture
- Running the example app
- Testing
- Platform support matrix
- Troubleshooting
- Contributing & releasing
- Resources
Compatibility
| Requirement | Version |
|---|---|
| Flutter SDK | >= 3.29.0 |
| Dart SDK | >= 2.18.4 < 4.0.0 |
Android minSdkVersion |
23 |
Android compileSdk |
34 |
| Android Java / Kotlin | Java 17 / Kotlin JVM target 17 |
| iOS deployment target | 9.0 (as declared in the podspec; raise it if a native pod requires more) |
| Native Android SDK | com.zoho.apptics:* 0.3.16 (analytics, crash-tracker, appupdates, ratings, feedback, rc, pns) |
| Native iOS pods | Apptics-SDK and modules 3.3.13 (notification extensions 3.3.12) |
| Apptics Gradle plugin | com.zoho.apptics:apptics-plugin:0.2.5 |
The plugin version (
pubspec.yaml) is 0.0.14. The native SDK numbers above are not the plugin version — seeCHANGELOG.mdfor the plugin/native version mapping per release.
Installation
Add the plugin to your app's pubspec.yaml:
dependencies:
apptics_flutter: ^0.0.14
Then run:
flutter pub get
You must also complete the native platform setup below — the plugin does nothing until the native Apptics SDK is configured with your app's credentials.
Platform setup
Android setup
-
Register your app and download
apptics-config.jsonfrom the Apptics web console. -
Place
apptics-config.jsonin theapp/directory (the root of your Android app module). -
In your project-level
build.gradle, add the Apptics Gradle plugin classpath and the Zoho Maven repository:buildscript { repositories { maven { url "https://maven.zohodl.com/" } } dependencies { classpath 'com.zoho.apptics:apptics-plugin:0.2.5' } } allprojects { repositories { maven { url "https://maven.zohodl.com/" } } } -
(Optional) Configure the Apptics Gradle plugin in your app-level
build.gradle— for example to control build-time logging:apptics { showLogs = ["default": true] } -
Minimum supported Android SDK is 23. The plugin compiles against
compileSdk 34with Java 17.
The plugin's
AndroidManifest.xmlautomatically registers the push-notification broadcast receiver (AppticsPushNotificationReceiver) and its intent filters — you don't need to declare them yourself. See Push notifications.
iOS setup
-
Register your app and download
apptics-config.plistfrom the Apptics web console. -
Move
apptics-config.plistinto the Xcode project root and add it to the main target. -
Add the Apptics pre-build script. This script is mandatory — it registers the app version with the Apptics server, writes app information into
apptics-config.plist(consumed by the SDK at runtime), and optionally uploads dSYMs.Option A — via the Podfile (recommended):
source 'https://github.com/zoho/Apptics.git' source 'https://github.com/CocoaPods/Specs.git' target 'Runner' do # Registers the app version, uploads dSYMs, and injects app info into apptics-config.plist. script_phase :name => 'Apptics pre build', :script => 'sh "./Pods/Apptics-SDK/scripts/run" --upload-symbols-for-configurations="Release, AppStore"', :execution_position => :before_compile endOption B — manually in Xcode:
-
In the project navigator, select your project, then the target you want to modify.
-
Open the Build Phases tab, click + → New Run Script Phase, and rename it
Apptics pre build. -
Expand the phase and paste the script into the Shell text field:
sh "./Pods/Apptics-SDK/scripts/run" --upload-symbols-for-configurations="Release, AppStore" -
Phase ordering matters:
Apptics pre buildmust be aboveCompile Sources, andCopy Bundle Resourcesmust be belowCompile Sources.
-
-
Run
pod installfrom yourios/directory and build the Xcode project once to verify the setup.
Build-script flags
run --upload-symbols-for-configurations="Release, AppStore" \
--config-file-path="YOUR_PATH/apptics-config.plist" \
--app-group-identifier="group.com.company.application" # optional
--config-file-path— pass your config file path if it is not in the project root.--app-group-identifier— required if you have enabled an App Group for your target.--upload-symbols-for-configurations— comma-separated build configuration names (e.g.Debug, Release) for which dSYMs are uploaded automatically during App Store submission via CI/CD, without an interactive prompt.
For multi-project setups, see the iOS integration guide.
Configuration files, API keys & data centers
Apptics has no separate login/authentication call in code. Instead, each app is identified to the Apptics backend by the credentials embedded in its native config file, which is generated per-app in the web console:
| Platform | File | Key field | Server / data-center field |
|---|---|---|---|
| Android | apptics-config.json |
apid / app key (ZAK) |
service URL (e.g. https://sdk-apptics.zoho.in) |
| iOS | apptics-config.plist |
API key | server URL (e.g. https://apptics.zoho.in) |
The server URL inside the config file selects the data center your data is sent to (for example
zoho.in for India, zoho.com.au for Australia, localzoho.com for local development). You don't
configure the data center in Dart — it is baked into the config file you download. Always download the
config file from the console region that matches your Apptics account.
Keep these config files out of public version control if your app is open-source — they contain the key that authorizes data ingestion for your app.
Multi-environment / multi-product configuration
The example app demonstrates how to keep several Apptics configurations side by side (the example/ios
folder ships four plists — apptics-config.plist, apptics-config-au.plist, apptics-config-local.plist,
apptics-config-old.plist — pointing at different data centers/bundle IDs).
Important: shipping multiple config files does not by itself switch environments. At runtime the
iOS SDK reads the single file named by the AP_INFOPLIST_FILE key in Info.plist (the example points it
at apptics-config.plist). There is no build phase that swaps the file automatically. To switch
environments or products cleanly:
- iOS — create separate Xcode build configurations / schemes, and either set a different
AP_INFOPLIST_FILEvalue per configuration or pass--config-file-pathto the pre-build script for each configuration. Use distinctPRODUCT_BUNDLE_IDENTIFIERvalues per scheme if you ship multiple products. (The example's notification-service and notification-content extensions use the<bundle-id>.notificationservice/<bundle-id>.notificationcontentsuffixes — keep that prefix relationship when changing bundle IDs.) - Android — use Gradle product flavors / build variants and generate the matching
apptics-config.jsonper variant before the build. (The example app currently uses a singleapptics-config.jsonand does not define flavors.)
When you change the bundle ID / application ID, you must regenerate the config file from the console for that ID — the key is bound to the bundle/application identifier.
Getting started
AppticsFlutter is the entry point for core analytics. It is a singleton — use AppticsFlutter.instance
(or construct AppticsFlutter(), which returns the same shared instance).
import 'package:apptics_flutter/apptics_flutter.dart';
// Preferred: shared singleton.
final apptics = AppticsFlutter.instance;
// Equivalent — AppticsFlutter() also resolves to the singleton.
final apptics2 = AppticsFlutter();
Each feature module is its own singleton imported from its own file (e.g.
AppticsCrashTracker.instance, AppticsFeedback.instance). Imports are shown in each section below.
Core analytics
Event tracking
Track a configured event with an optional properties map.
// Without properties
AppticsFlutter.instance.addEvent("eventName", "eventGroupName");
// With properties
AppticsFlutter.instance.addEvent(
"eventName",
"eventGroupName",
properties: {"propKey": "propValue"},
);
Signature: Future<void> addEvent(String? event, String? group, {Map<String, dynamic>? properties}).
Predefined events
The plugin ships a DefinedEvents class of standard event/group name constants (app lifecycle,
deep links, notifications, user lifecycle, OS, network, theme/orientation, etc.). Use these for
consistency with Apptics' built-in reporting.
import 'package:apptics_flutter/defined_events.dart';
AppticsFlutter.instance.addEvent(
DefinedEvents.AP_USER_LOGIN, // event name
DefinedEvents.AP_USER_LIFE_CYCLE, // group name
);
Available groups include AP_APP_LIFE_CYCLE, AP_APPLICATION, AP_USER_LIFE_CYCLE, AP_OS, and
AP_OTHERS, each with related event constants (for example AP_APP_OPEN, AP_DEEP_LINK_OPEN,
AP_NOTIFICATION_RECEIVE, AP_USER_SIGNUP, AP_NETWORK_REACHABILITY_CHANGE,
AP_SWITCH_THEME_DARK). See lib/defined_events.dart for the complete list.
Screen tracking
// Call when the screen appears
AppticsFlutter.instance.screenAttached("screenName");
// Call when the screen disappears
AppticsFlutter.instance.screenDetached("screenName");
User identification
Tie a user ID to your stats (events, screens, crashes). User association honors the current tracking state: when the state is a "WithoutPII" variant, stats are not associated with the user ID.
⚠️ To protect user privacy, never pass personally identifiable information as the user ID.
// Set the current user (optional group ID)
AppticsFlutter.instance.setUser("userId");
AppticsFlutter.instance.setUser("userId", "groupId");
// Remove / log out the current user (optional group ID)
AppticsFlutter.instance.removeUser("userId");
AppticsFlutter.instance.removeUser("userId", "groupId");
// Check whether a user is currently logged in
bool? loggedIn = await AppticsFlutter.instance.isUserLoggedIn();
User properties
Attach properties (email, name, plan type, etc.) to the current user. Properties such as email are also used for Feedback user identification.
import 'package:apptics_flutter/apptics_user_property.dart';
final props = AppticsUserPropertyBuilder()
.setFirstName("Ada")
.setLastName("Lovelace")
.setEmailAddress("ada@example.com")
.setCompanyName("Analytical Engines")
.setPlanType("enterprise")
.addStringProperty("custom_key", "value") // arbitrary custom props
.addNumberProperty("seats", 42)
.addBooleanProperty("beta_user", true)
.build();
await AppticsFlutter.instance.setUserWithProperty(
"userId",
groupId: "groupId", // optional
props: props, // optional
);
// Read back the current user's properties (requires Android SDK 0.3.9+)
Map<String, dynamic>? current = await AppticsFlutter.instance.getUserProperties();
AppticsUserPropertyBuilder provides typed setters (setFirstName, setLastName, setCompanyName,
setContactNumber, setEmailAddress, setCountry, setRegion, setCity, setGeoLocation,
setGender, setPlanType, setTimezone, setLanguage, setEngagementScore, setDateOfBirth) plus
generic addStringProperty, addNumberProperty, and addBooleanProperty for custom keys.
Tracking settings & privacy
Apptics offers seven tracking states to control usage and crash tracking. The Dart enum values are:
TrackingState value |
Numeric | Description |
|---|---|---|
usageAndCrashTrackingWithPII |
1 | Usage + crash, associated with user ID |
onlyUsageTrackingWithPII |
2 | Usage only, with user ID |
onlyCrashTrackingWithPII |
3 | Crash only, with user ID |
usageAndCrashTrackingWithoutPII |
4 | Default — usage + crash, no user association |
onlyUsageTrackingWithoutPII |
5 | Usage only, no user association |
onlyCrashTrackingWithoutPII |
6 | Crash only, no user association |
noTracking |
-1 | Tracking disabled |
usageAndCrashTrackingWithoutPIIis the default state out of the box.
- Usage tracking covers events, APIs, screens and sessions.
- Crash tracking covers unhandled exceptions.
- PII refers to the value you set via
setUser.
import 'package:apptics_flutter/apptics_flutter_util.dart'; // for TrackingState
// Change the tracking state (note the lower-camelCase enum identifiers)
await AppticsFlutter.instance.setTrackingState(TrackingState.onlyCrashTrackingWithPII);
// Read the current tracking state
TrackingState? state = await AppticsFlutter.instance.getTrackingState();
You can also show Apptics' built-in privacy UI:
// Show the built-in privacy options popup
await AppticsFlutter.instance.presentPrivacyReviewPopup();
// Open the screen where the user can change analytics privacy settings
await AppticsFlutter.instance.openPrivacySettings();
Language & flush
// Set the language used by the Analytics SDK (iOS only; no-op on Android).
// Falls back to the default language if the code is not in the resource bundle.
await AppticsFlutter.instance.setDefaultLanguage("en");
// Force-send all buffered stats (events, screens, sessions) to the server.
await AppticsFlutter.instance.flush();
In-app ratings
import 'package:apptics_flutter/rateus/apptics_in_app_rating.dart';
Show the default rating popup
Call checkForRatingPop after the theme and navigator are set up.
AppticsInAppRating.instance.checkForRatingPop(context);
// Enable the "Send Feedback" action in the popup:
AppticsInAppRating.instance.checkForRatingPop(context, isFeedbackEnabled: true);
Signature: Future<void> checkForRatingPop(BuildContext context, {bool isFeedbackEnabled = false}).
The popup appears automatically according to the criteria configured in the web console and has two or three actions:
- Rate in Play Store — redirects to the store for ratings/reviews.
- Later — dismisses and re-prompts after a deferral period.
- Send Feedback — only shown when the Apptics Feedback SDK is integrated and
isFeedbackEnabledistrue; opens the Feedback screen. Ensure the Apptics theme is set, or the app will crash.
The default rating popup does not currently support Material 3.
Tuning the prompt cadence
If the user chooses Later, the next prompt is deferred by 10 days by default. After three consecutive Later choices, the popup is suppressed until the criteria for that app version are reconfigured in the console.
// Defer period in days (getter + setter method)
await AppticsInAppRating.instance.setDaysBeforeShowingPopupAgain(15);
int days = AppticsInAppRating.instance.daysBeforeShowingPopupAgain;
// Maximum number of times to show the popup
await AppticsInAppRating.instance.setMaxTimesToShowPopup(5);
int max = AppticsInAppRating.instance.maxTimesToShowPopup;
These are read via getters but set via methods (
setDaysBeforeShowingPopupAgain/setMaxTimesToShowPopup) — they are not assignable fields.
Build your own rating UI
import 'package:apptics_flutter/rateus/apptics_in_app_rating.dart';
import 'package:apptics_flutter/rateus/popup_action.dart';
// Returns a criteriaId (int) when a prompt is due, or null otherwise. Runs asynchronously.
int? criteriaId = await AppticsInAppRating.instance.getCriteriaId();
if (criteriaId != null) {
// ...show your custom UI, then report the action the user took:
await AppticsInAppRating.instance.sentStats(criteriaId, PopupAction.LATER_CLICKED);
}
sentStats takes positional arguments: Future<void> sentStats(int criteriaId, PopupAction popupAction).
PopupAction values are RATE_IN_STORE_CLICKED, SEND_FEEDBACK_CLICKED, and LATER_CLICKED.
Other helpers: isAppticsFeedbackModuleAvailable(), openFeedback(), openPlayStore(),
updateRatingShown(), setDisableAutoPromptOnFulFillingCriteria(bool).
Store in-app review API
AppticsInAppRating.instance.setShowStoreAlertOnFulFillingCriteria(true);
When enabled (and you use checkForRatingPop), Google Play's native in-app review flow is presented
automatically once the configured criterion is met. The Play review API has no callback indicating
whether the UI was shown, and its quotas are undocumented — so once invoked, Apptics defers the next
call by daysBeforeShowingPopupAgain.
Remote configuration
import 'package:apptics_flutter/remoteconfig/apptics_remote_config.dart';
Configure remote-config params and conditions in the web console, then fetch values at runtime:
Future<void> loadConfig() async {
String? color = await AppticsRemoteConfig.instance.getStringValue('color');
if (color != null) {
// use the value
}
}
Signature:
Future<String?> getStringValue(String key, {bool coldFetch = false, bool fallbackWithOfflineValue = false})
key— the param name configured in the web console.coldFetch— by defaultgetStringValueuses a cache to avoid frequent network calls. Whentrue, it ignores the cache and fetches from the network. Network fetches are throttled to 3 calls per minute; beyond that the method returnsnull(or the offline value iffallbackWithOfflineValueis set).fallbackWithOfflineValue— whentrue, returns the previously fetched value on network failure.
Custom condition criteria
AppticsRemoteConfig.instance.setCustomConditionValue("ConditionKey", "Criteria");
To clear cached remote-config state, use AppticsRemoteConfig.instance.hardReset().
In-app updates
import 'package:apptics_flutter/appupdate/apptics_in_app_update.dart';
Show the default update popup
Call checkAndUpdateAlert after the theme and navigator are set up.
AppticsInAppUpdates.instance.checkAndUpdateAlert(context);
The default update popup does not currently support Material 3.
Build your own update flow
getInAppUpdateData() returns a Map<String, dynamic>? you can use to render a custom popup, or
null when no update is available. It takes no arguments.
Map<String, dynamic>? data = await AppticsInAppUpdates.instance.getInAppUpdateData();
Determine the response type from the category value: 1 = normal update data, 2 = unsupported-OS
popup data. The action methods you call from your custom UI are onClickUpdate, onClickReminder,
onClickIgnore, onClickNonSupportAlert, and onSendImpressionStatus(String updateId).
Keys for a normal-update (category == 1) map:
| Key | Meaning |
|---|---|
updateid |
ID of the specific update configuration |
currentversion |
App version installed on the device |
featureTitle |
Title/heading for the version alert |
features |
Update features / "what's new" |
remindMeLaterText |
Localized text for the "Remind Me Later" action |
updateNowText |
Localized text for the "Update" action |
neverAgainText |
Localized text for the "Ignore" action |
option |
1 = flexible, 2 = immediate, 3 = force update flow |
reminderDays |
Days before re-prompting after "Remind Me Later" (console-configured) |
forceInDays |
Force-update window (console-configured) |
alertType |
0 = native UI, 1 = custom UI, 2 = Android in-app updates |
customStoreUrl |
Alternate store URL to redirect to on "Update/Download" |
Keys for an unsupported-OS (category == 2) map:
| Key | Meaning |
|---|---|
title |
Popup title |
description |
Popup description |
continueBtTxt |
Button text |
alertType |
0 = do nothing, 1 = install later, 2 = freeze |
updateid |
ID of the specific update configuration |
See the in-app updates guide.
Crash tracking
import 'package:apptics_flutter/crash_tracker/apptics_crash_tracker.dart';
Fatal crashes (automatic)
Enable automatic crash tracking once, early in your app:
AppticsCrashTracker.instance.autoCrashTracker();
For finer control you can also forward Flutter framework errors yourself:
FlutterError.onError = (details) {
AppticsCrashTracker.instance.sendFlutterException(details); // isFatal: true by default
};
// Or report an arbitrary exception:
AppticsCrashTracker.instance.sendException(error, stackTrace, isFatal: true);
Non-fatal exceptions
try {
int k = (1 ~/ 0);
} catch (e, s) {
AppticsCrashTracker.instance.sendNonFatalException(e, s);
}
// From a FlutterErrorDetails:
AppticsCrashTracker.instance.sendFlutterNonFatalException(details);
Custom properties
Attach custom properties to both fatal and non-fatal events:
await AppticsCrashTracker.instance.setCrashCustomProperty({
"name": "ABCD",
"domains": ["Data Science", "Mobile", "Web"],
});
Last crash info & consent popup (Android)
getLastCrashInfo()andshowLastSessionCrashedPopup()are Android-only. On iOS,getLastCrashInfo()returnsnull.
// Retrieve the previous crash as a stringified JSON object (Android).
String? lastCrash = await AppticsCrashTracker.instance.getLastCrashInfo();
// Show a consent popup to send the previous session's crash info (Android).
AppticsCrashTracker.instance.showLastSessionCrashedPopup();
Sample getLastCrashInfo() JSON:
{
"issuename": "divide by zero",
"message": "java.lang.ArithmeticException: divide by zero\n\tat com.zoho.apptics.MainActivity.onCreate$lambda$2(MainActivity.kt:42)",
"networkstatus": 0,
"serviceprovider": "T-Mobile",
"orientation": 0,
"batterystatus": 100,
"ram": "2.9 GB",
"rom": "5.8 GB",
"sessionstarttime": 1711445408267,
"customproperties": {},
"screenname": "com.zoho.apptics.MainActivity",
"happenedat": 1711445420908,
"happenedcount": 1,
"errortype": "native"
}
ANR detection (Android)
Application-Not-Responding detection is available on Android:
await AppticsCrashTracker.instance.enableANR();
await AppticsCrashTracker.instance.disableANR();
bool? enabled = await AppticsCrashTracker.instance.isANREnabled();
setAttemptInstantSync(bool) requests an immediate upload attempt for crashes.
Feedback
import 'package:apptics_flutter/feedback/apptics_feedback.dart';
Open the feedback / bug-report screens
AppticsFeedback.instance.openFeedback(); // open the feedback screen
AppticsFeedback.instance.reportBug(); // take + annotate a screenshot to report a bug
Shake-to-feedback
Shake-to-feedback lets users shake the device to open the feedback screen. Enabled by default.
AppticsFeedback.instance.enableShakeForFeedback();
AppticsFeedback.instance.disableShakeForFeedback();
bool? enabled = await AppticsFeedback.instance.isShakeForFeedbackEnabled();
Anonymous user alerts
Collect feedback without requiring user identification. Enabled by default.
AppticsFeedback.instance.enableAnonymousUserAlert();
AppticsFeedback.instance.disableAnonymousUserAlert();
bool? enabled = await AppticsFeedback.instance.isAnonymousUserAlertEnabled();
Identify the feedback sender
await AppticsFeedback.instance.setEmailId("user@example.com");
Send feedback / bug reports programmatically
AppticsFeedback.instance.sendFeedback(
"Great application", // feedbackMessage
true, // includeLogs
true, // includeDiagnostics
guestMailId: "user@example.com", // optional
forceToAnonymous: false, // optional
attachmentsUri: [Uri.parse('file://screenshot.png')], // optional
);
AppticsFeedback.instance.sendBugReport(
"Great app, but I found a bug!",
true, true,
guestMailId: "user@example.com",
attachmentsUri: [Uri.parse('file://screenshot.png')],
);
Both methods share the signature
(String feedbackMessage, bool includeLogs, bool includeDiagnostics, {String? guestMailId, bool forceToAnonymous = false, List<Uri>? attachmentsUri}).
Logs & diagnostics
import 'package:apptics_flutter/feedback/apptics_logs.dart';
import 'package:apptics_flutter/feedback/apptics_log_type.dart'; // the Log enum
// Write a log line. Log enum values are lower-case: verbose, debug, info, warn, error.
AppticsLogs.instance.writeLog("Some log message", Log.debug);
// Or attach a log file instead.
AppticsLogs.instance.addLogFile(File("path/to/log.txt"));
// Add grouped key/value diagnostics.
AppticsLogs.instance.addDiagnosticsInfo("HEADING", "key", "value");
// Clear accumulated logs/diagnostics.
AppticsLogs.instance.resetLogsAndDiagnostics();
If both
addLogFileandwriteLogare used, the attached file takes priority. Only one log file is allowed per feedback and it must not exceed 1 MB.
Push notifications
The plugin supports foreground/background message reception, notification clicks, and action-button
clicks. Setup involves registering top-level @pragma('vm:entry-point') background handlers in
main(), then calling AppticsPushNotification.initialize(...) with foreground handlers. On iOS you
additionally call AppticsFlutter.instance.startService() and
AppticsFlutter.instance.registerPushNotification() (both are no-ops on Android).
import 'package:apptics_flutter/push_notification/apptics_push_notification.dart';
@pragma('vm:entry-point')
Future<void> _onBackgroundMessage(Map<String, dynamic> message) async {
// handle background message
}
void main() {
WidgetsFlutterBinding.ensureInitialized();
AppticsPushNotification.setOnMessageHandlerListener(_onBackgroundMessage);
runApp(const MyApp());
}
👉 Full setup, background processing, event types, and the API reference are in
PUSH_NOTIFICATION_README.md.
API call tracking
Report network requests to the Apptics dashboard as performance metrics (success rate, latency, status codes) per endpoint. Four integration strategies are provided:
| Strategy | Best for | Setup |
|---|---|---|
Auto tracking (HttpOverrides) |
Catching all dart:io traffic |
One line |
| Dio interceptor | Projects using Dio | Add interceptor |
http.BaseClient wrapper |
Projects using the http package |
Swap client |
| Manual tracking | Custom clients / non-standard setups | Call per request |
import 'package:apptics_flutter/api_tracker/apptics_api_tracker.dart';
void main() {
AppticsApiTracker.instance.enableAutoTracking(); // captures all dart:io HTTP calls
runApp(MyApp());
}
👉 Strategy details, the API reference, URL exclusion, normalization, and troubleshooting are in
API_TRACKING_README.md.
Architecture
The plugin follows the standard federated Flutter-plugin architecture with platform channels.
┌──────────────────────────────────────────────┐
│ Your Flutter app │
└───────────────────────┬──────────────────────┘
│ public Dart APIs
┌───────────────┼───────────────────────────────┐
│ │ │
AppticsFlutter AppticsCrashTracker / Feedback / AppticsApiTracker /
(core) InAppRating / InAppUpdates / PushNotification
RemoteConfig
└───────────────┴───────────────────────────────┘
│
AppticsFlutterPlatform (platform interface)
│
MethodChannelAppticsFlutter ── MethodChannel('apptics_flutter')
│ + background channel
│ 'com.zoho.apptics.flutter/push_background'
┌───────────────┴───────────────┐
│ │
Android plugin (Kotlin) iOS plugin (Objective-C)
AppticsFlutterPlugin.kt AppticsFlutterPlugin.m
│ │
Native Apptics Android SDK Native Apptics iOS pods
(com.zoho.apptics:* 0.3.16) (Apptics-SDK 3.3.13, ...)
Key points:
-
Platform interface pattern —
AppticsFlutterPlatform(abstract) defines every method;MethodChannelAppticsFlutteris the concrete implementation. -
Single primary method channel — all native calls use
MethodChannel('apptics_flutter'). Push notifications additionally use a background channel (com.zoho.apptics.flutter/push_background) for headless isolate execution. -
Platform branching — many methods branch on
Platform.isIOS/Platform.isAndroid. Notably, the method-channel layer sends snake_case argument keys to iOS and camelCase to Android. -
Feature modules live under
lib/:Path Module lib/apptics_flutter.dartCore analytics & user/privacy APIs lib/defined_events.dartPredefined event/group constants lib/apptics_user_property.dartAppticsUserProperty+ builderlib/apptics_flutter_util.dartTrackingStateand related enumslib/rateus/In-app ratings lib/remoteconfig/Remote configuration lib/appupdate/In-app updates lib/crash_tracker/Crash & ANR tracking lib/feedback/Feedback, logs & diagnostics lib/push_notification/Push notifications lib/api_tracker/API call tracking
Running the example app
The example/ app exercises every module and is the fastest way to see the plugin working.
cd example
flutter pub get
# iOS
cd ios && pod install && cd ..
flutter run -d ios
# Android
flutter run -d android
The example app's main screen wires up buttons for events, crashes, non-fatals, flush, feedback,
in-app updates, login/logout, privacy settings, rate-us, and remote config, plus a dedicated API
Tracking screen demonstrating the auto / HTTP-wrapper / manual strategies. The example depends on the
http package (for the HTTP-wrapper demo); it does not use Dio.
Before running, ensure the native config files are present:
- Android —
apptics-config.jsoninexample/android/app/ - iOS —
apptics-config.plistreferenced byAP_INFOPLIST_FILEinexample/ios/Runner/Info.plist
Without these, native methods initialize but report no data.
Testing
flutter analyze # static analysis (uses flutter_lints)
flutter test # unit tests
flutter test --coverage # with coverage
dart format . # formatting
The current Dart unit tests (test/apptics_flutter_test.dart,
test/apptics_flutter_method_channel_test.dart) are minimal scaffolding: they verify the default
platform instance and mock the method channel, but do not exercise real native behavior. Functional
verification is done through the example app on real devices/simulators. This is a known gap —
contributions adding behavioral tests are welcome.
Platform support matrix
Most methods work on both platforms. The exceptions:
| API | iOS | Android | Notes |
|---|---|---|---|
setDefaultLanguage(String) |
✅ | — | No-op on Android |
getLastCrashInfo() |
— | ✅ | Returns null on iOS |
showLastSessionCrashedPopup() |
— | ✅ | Android only |
enableANR / disableANR / isANREnabled |
— | ✅ | ANR is an Android concept |
registerPushNotification() / startService() |
✅ | — | iOS-only wrappers (no-op on Android) |
Troubleshooting
- No data appears in the dashboard. Confirm the config file is present and correct
(
apptics-config.json/apptics-config.plist), the data-center/server URL matches your account region, and callAppticsFlutter.instance.flush()to force an immediate sync. - iOS build fails / SDK reports it isn't configured. Verify the
Apptics pre buildscript phase exists, is above Compile Sources, and thatCopy Bundle Resourcesis below it. Re-runpod install. - Rating / update popup looks wrong or crashes when opening Feedback. The default popups don't support Material 3, and the Feedback action requires the Apptics theme to be set.
writeLogwon't compile. TheLogenum values are lower-case (Log.debug, notLog.DEBUG).- Rating prompt cadence won't set.
daysBeforeShowingPopupAgain/maxTimesToShowPopupare getters; set them withsetDaysBeforeShowingPopupAgain(int)/setMaxTimesToShowPopup(int). - Push notification background handlers never fire. Mark them with
@pragma('vm:entry-point')and register them before callingAppticsPushNotification.initialize(...). See the push guide. - API calls tracked twice. Don't combine auto tracking with the Dio interceptor / HTTP wrapper for the same client. See the API-tracking guide.
For native-call debugging: iOS logs appear in the Xcode console; on Android use
adb logcat | grep apptics.
Contributing & releasing
- Run
flutter analyzeandflutter testbefore committing. - When adding functionality, update all four layers: the platform interface
(
AppticsFlutterPlatformInterface), the method channel (MethodChannelAppticsFlutter), the public API class, and the native Android + iOS implementations. - Bump the version in
pubspec.yamland the iOSpodspec, and updateCHANGELOG.md(including the native SDK versions used). - Verify changes through the example app on both platforms.
Resources
- Apptics
- iOS integration guide
- In-app updates user guide
- Android API tracking guide
PUSH_NOTIFICATION_README.md— push notificationsAPI_TRACKING_README.md— API call trackingCHANGELOG.md— release / native-SDK version history
Libraries
- api_tracker/apptics_api_tracker
- api_tracker/apptics_dio_interceptor
- Dio interceptor for tracking API calls with Apptics.
- api_tracker/apptics_http_client
- api_tracker/apptics_http_overrides
- apptics_flutter
- apptics_flutter_method_channel
- apptics_flutter_platform_interface
- apptics_flutter_util
- apptics_user_property
- appupdate/apptics_app_update_alerts
- appupdate/apptics_in_app_update
- appupdate/appupdate_util
- crash_tracker/apptics_crash_tracker
- defined_events
- feedback/apptics_feedback
- feedback/apptics_log_type
- feedback/apptics_logs
- push_notification/apptics_push_notification
- push_notification/types
- rateus/apptics_in_app_rating
- rateus/popup_action
- remoteconfig/apptics_remote_config