From 6a8a81619302e206f56c8eb1322d0caccd180285 Mon Sep 17 00:00:00 2001
From: Arif Burak Demiray
+ This documentation is for the Countly Flutter SDK version 25.1.X. The SDK source
+ code repository can be found
+ here.
+
+ Click
+ here, to
+ access the documentation for older SDK versions.
+
+ For iOS builds, this SDK requires a minimum Deployment Target iOS 10.0 (watchOS
+ 4.0, tvOS 10.0, macOS 10.14), and it requires Xcode 13.0+.
+
+ For Android builds, this SDK requires a minimum Android version of 4.2.x (API
+ Level 17).
+
+ For Web builds, this SDK is compatible with browsers that support ECMAScript
+ 5. Minimum versions of major internet browsers that fully support ES5 are:
+
+ To examine the example integrations, please have a look
+ here.
+
+ Add this to your project's
+ After you can install packages from the command line with Flutter:
+
+ The shortest way to initialize the SDK, if you want Countly SDK to take care
+ of device ID seamlessly, is to use the code below.
+
+ Please check
+ here
+ for more information on how to acquire your application key (APP_KEY) and server
+ URL.
+
+ A "CountlyConfig" object is used to configure the SDK during initialization.
+ As shown above, you would create a "CountlyConfig" object and then call its provided
+ methods to enable the functionalities you need before initializing the SDK.
+
+ Click
+ here
+ for more information about the "CountlyConfig" object functionalities.
+
+ If you are in doubt about the correctness of your Countly SDK integration
+ you can learn about the verification methods from
+ here.
+ SDK data storage locations are platform-specific:
+ If logging is enabled, then our SDK will print out debug messages about its internal
+ state and encountered problems.
+
+ We advise doing this while implementing Countly features in your application.
+
+ For more information on where to find the SDK logs you can check the documentation
+ here.
+
+ This feature allows the Countly SDK to record crash reports of either encountered
+ issues or exceptions which cause your application to crash. Those reports will
+ be sent to your Countly server for further inspection.
+
+ If a crash report can not be delivered to the server (e.g. no internet connection,
+ unavailable server), then SDK stores the crash report locally in order to try
+ again later.
+
+ If you want to enable automatic unhandled crash reporting, you need to call this
+ before init:
+
+ By doing that it will automatically catch all errors that are thrown from within
+ the Flutter framework.
+
+ You may add a key/value segment to crash reports. For example, you could set
+ which specific library or framework version you used in your app. You may then
+ figure out if there is any correlation between the specific library or another
+ segment and the crash reports.
+
+ The following call will add the provided segmentation to all recorded crashes.
+ Use the following function for this purpose:
+
+ There are multiple ways you could report a handled exception/error to Countly.
+
+ This call does not add a stacktrace automatically. If it is required, it should
+ be provided to the function. A potential use case would be to
+
+ The issue is recorded with a provided Exception object. If no stacktrace is set,
+ The exception/error is recorded through a string message. If no stack trace is
+ provided,
+ Below are some examples that how to log handled/nonfatal and unhandled/fatal
+ exceptions manually.
+
+ 1. Manually report exception
+
+ 2. Manually report exception with stack trace
+
+ 3. Manually report exception with segmentation
+
+ 4. Manually report exception with stack trace and segmentation
+
+ Throughout your app, you can leave crash breadcrumbs which would describe previous
+ steps that were taken in your app before the crash. After a crash happens, they
+ will be sent together with the crash report.
+ The following function call adds a crash breadcrumb:
+ Event is any type of action that
+ you can send to a Countly instance, e.g purchase, settings changed, view enabled
+ and so. This way it's possible to get much more information from your application
+ compared to what is sent from Flutter SDK to Countly instance by default.
+
+ All data passed to the Countly server via SDK or API should be in UTF-8.
+
+ In the SDK all event-related functionality can be browsed from the returned interface
+ on:
+
+ When providing segmentation for events, the following primitive data types are
+ supported: "String," "int," "double," and "bool." Additionally, Lists composed
+ of these primitive types are also supported. Please note that no other data types
+ will be recorded.
+
+ We will be recording a purchase event. Here is a quick summary
+ of what information each usage will provide us:
+ The function signature as follows
+ 1. Event key and count
+
+ 2. Event key, count and sum
+
+ 3. Event key and count with segmentation(s)
+
+ 4. Event key, count and sum with segmentation(s)
+
+ 5. Event key, count, sum and duration with segmentation(s)
+
+ It's possible to create timed events by defining a start and a stop moment.
+
+ You may also provide additional information when ending an event. However, in that case, you have to provide the segmentation, count, and sum. The default values for those are "null", 1 and 0.
+
+ You may cancel the started timed event in case it is not relevant anymore:
+
+ Automatic sessions tracks user activity with respect to the app visibility. Basically
+ it handles making certain requests to the server to inform it about the user
+ session. Automatic sessions are enabled by default and SDK handles the necessary
+ calls (by sending start session, update session and end session requests) to
+ track a session automatically. This is how it works:
+
+ Platform Info
+ Sometimes, it might be preferable to control the session manually instead of
+ relying on the SDK.
+ It can be enabled during init with: Afterwards it is up to the implementer to make calls to: You can use the 'sessions interface' to make these calls:
+ Platform Info
+ The SDK provides access to all view-related functionality through the interface
+ returned by:
+ You can manually track views in your application.
+ When starting a view it would return an ID. This ID can be used to further interract
+ with the view to pause, resume and stop it. You can start multiple views with
+ the same name. The ID is used to uniquely identify the view.
+
+ Views will be automatically paused when going to the background, and resumed
+ when coming back.
+
+ If you want to start a view that will be automatically stopped when starting
+ another view, use the following method:
+
+ You can also specify the custom segmentation key-value pairs while starting views:
+
+ Opposed to "auto stopped views", with regular views you can have multiple of
+ them started at the same time, and then you can control them independently. You
+ can manually start a view using the
+ You can also specify the custom segmentation key-value pairs while starting views:
+
+ Stopping a view can either be done using the view id or the name. If there are
+ multiple views with the same name (they would have different identifiers) and
+ you try to stop one with that name, the SDK would close one of those randomly.
+ Below you can see example ways of stopping views.
+ This function allows you to manually stop the tracking of a view identified by
+ its name. You can also specify the custom segmentation key-value pairs while stopping views:
+
+ You can also stop view tracking by its unique idetifier using
+
+ You can also specify the custom segmentation key-value pairs while stopping views:
+
+ You can stop all views tracking using
+
+ You can also specify the custom segmentation key-value pairs while stopping all views:
+
+ This SDK allows you to start multiple
+ views at the same time. If you are starting multiple views at the same time it
+ might be necessary for you to pause some views while others are still continuing.
+ This can be achieved by using the unique identifier you get while starting a
+ view.
+
+ You can pause view tracking by its unique identifier using
+
+ This function temporarily pauses the tracking of a view identified by its unique identifier.
+
+ You can resume view tracking by its unique identifier using
+ This function resumes the tracking of a view identified by its unique identifier.
+
+ You can add segmentation values to a view before it ends. This can be done as
+ many times as desired and the final segmentation that will be send to the server
+ would be the cumulative sum of all segmentations.
+
+ So, once added, any segmentation key/value pairs will stay until that view ends.
+ The only way to change it before it ends would be to provide a new value under
+ the key you want to update. In that case, when a particular segmentation value
+ for a specific key has been updated, only the latest value will be used.
+
+ Here is an example on how to achieve that using the view name:
+
+ Here is an example for how to add segmentation to a view using its ID:
+
+ It is possible to set global segmentation for all recorded views. This can be
+ done either during initialization or subsequently. Segmentation provided with
+ the start or stop calls will take precedence, and neither of them can override
+ segmentation keys used by the SDK internally for views.
+
+ For setting global segmentation values during SDK initialization, use the following
+ method:
+
+ If you want to change the segmentation after initialization, you can use one
+ of the following methods.
+
+ The
+ The
+ A device ID is a unique identifier for your users. You may specify the device
+ ID or allow the SDK to generate it. When providing your device ID, ensure it
+ is unique for all users. Potential sources for such an ID include the username,
+ email, or other internal ID used by your other systems.
+
+ You may provide your custom device ID when initializing the SDK:
+
+ If you need a more complicated logic or using the SDK version 24.7.0 and
+ below then you will need to use this method mentioned
+ here.
+ You may configure or change the device ID anytime using:
+ When using
+ Note:
+ If you need a more complicated logic or using the SDK version 24.7.0 and
+ below then you will need to use this method mentioned
+ here.
+
+ You may use a temporary device ID mode to keep all requests on hold until the
+ device ID is set later.
+
+ You can enable temporary device ID mode when initializing the SDK:
+
+ To enable a temporary device ID after initialization, you can call:
+
+ The SDK will be in temporary device ID mode, all requests will be on hold and
+ they will be persistently stored.
+
+ When in temporary device ID mode, method calls for presenting feedback widgets
+ and updating remote config will be ignored.
+
+ Later, when a new device ID is set using
+
+ You may want to see what the current device ID is. For that, you can use the
+ following call:
+
+ You can use
+ When the SDK is initialized for the first time with no device ID, it will generate
+ a device ID.
+
+ Here are the underlying mechanisms used to generate that value for some platforms:
+
+ For iOS: the device ID generated by the SDK is the Identifier For Vendor (IDFV).
+ For Android: the device ID generated by the SDK is the OpenUDID. For Web: the
+ device ID generated by the SDK is a random UUID.
+
+ Countly gives you the ability to send Push Notifications to your users using
+ your app with the Flutter SDK integration. For more information on how to best
+ use this feature you can check
+ this
+ article.
+
+ To make this feature work you will need to make some configurations both in your
+ app and at your Countly server. Both platforms (Android and iOS) would need different
+ steps to integrate Push Notification feature into your application, as explained
+ below.
+
+ Step 1: For FCM credentials setup please follow the instruction from
+ here.
+
+ Step 2: Make sure you have
+ Step 3: Make sure the app package name and the
+
+ Step 4: Place the
+ Step 5: Add the following line in file
+
+ Step 6: Add the following line in file
+ You can get the latest version from
+ here
+ and
+ here.
+
+ Step 7: Add the following line in file
+ First, you will need to acquire Push Notification credentials from Apple. (If
+ you don't have them you can check
+ this
+ article to learn how you can do it.)
+
+ Then you would need to upload these credentials to your Countly server. You can
+ refer to
+ this
+ article for learning how you can do that.
+
+ Finally, under the Capabilities section of Xcode, enable
+ Push Notifications and the
+ Remote notifications Background Mode for your target.
+
+ For Swift projects you might need to make sure Bridging Header File is configured
+ properly for each target as explained
+ here.
+ For this purpose you can find Some tips to find the files from deep hierarchy: You can drag and drop the file from Pod to Compile Sources.
+ First, when setting up push for the Flutter SDK, you would first select the push
+ token mode. This would allow you to choose either test or production modes, push
+ token mode should be set before init.
+
+ When you are finally ready to initialise Countly push, you would call this:
+
+ Also it is important to note that push notification is enabled for iOS by default,
+ so to disable you need to call
+ Countly Flutter SDK comes with push notification capabilities embedded. For the
+ flavor without the push notifications features (like Firebase libraries) please
+ check here.
+
+ To register a Push Notification callback after initializing the SDK, use the
+ method below.
+
+ In order to listen to notification receive and click events, Place below code
+ in Add header files Add these methods:
+ Here is the example of how data will receive in push callbacks: Data Received for iOS platform:
+ Platform Info
+ Countly allows you to send geolocation-based push notifications to your users.
+ By default, the Countly Server uses the GeoIP database to deduce a user's location.
+
+ If your app has a different way of detecting location, you may send this information
+ to the Countly Server by using the
+ When setting user location information, you would be setting these values:
+
+ All values are optional, but at least one should be set.
+
+ Geolocation recording methods may also be called at any time after the Countly
+ SDK has started. To do so, use the
+ To erase any cached location data from the device and stop further location tracking,
+ use the following method. Note that if after disabling location, the
+
+ Remote config allows you to modify how your app functions or looks by requesting key-value pairs from your Countly server. The returned values may be modified based on the user properties. For more details, please see the Remote Config documentation.
+
+ Once downloaded, Remote config values will be saved persistently and available
+ on your device between app restarts unless they are erased.
+
+ The two ways of acquiring remote config data are enabling automatic download triggers or manual requests.
+
+ If a full download of remote config values is performed, the previous list of
+ values is replaced with the new one. If a partial download is performed, only
+ the retrieved keys are updated, and values that are not part of that download
+ stay as they were. A previously valid key may return no value after a full download.
+
+ Platform Info
+ Automatic remote config triggers have been turned off by default; therefore, no remote config values will be requested without developer intervention.
+
+ The automatic download triggers that would trigger a full value download are:
+
+ To enable the automatic triggers, you have to call
+
+ Another thing you can do is to enable value caching with the
+
+ There are three ways to trigger remote config value download manually:
+
+ Each of these calls also has an optional parameter that you can provide a RCDownloadCallback to, which would be triggered when the download attempt has finished.
+
+
+ Or you might only want to update specific key values. To do so, you will need to call
+ Or you might want to update all the values except a few defined keys. To do so, call
+ When making requests with an "inclusion" or "exclusion" array, if those arrays are empty or null, they will function the same as a
+ To get a stored value, call
+ If you want to get all values together you can use
+
+ RCData object has two keys: value (Object) and isCurrentUsersData (Boolean).
+ Value holds the data sent from the server for the key that the RCData object
+ belongs to. The isCurrentUsersData is only false when there was a device ID change,
+ but somehow (or intentionally) a remote config value was not updated.
+
+ Platform Info
+ At some point, you might like to erase all the values downloaded from the server. You will need to call one function to do so.
+
+ Also, you may provide a global callback function to be informed when the remote
+ config download request is finished with
+
+ RCDownloadCallback is called when the remote config download request is finished,
+ and it would have the following parameters:
+
+
+ You can also register (or remove) callbacks to do different things after the
+ SDK initialization. You can register these callbacks multiple times:
+
+ You can enroll your users into into A/B tests for certain keys or remove them
+ from some or all existing A/B tests available.
+
+ You can enroll into the A/B tests automatically whenever you download RC
+ values from the server. To do so you have to set the following flag at the
+ config object during initialization:
+
+ You can also enroll to A/B tests while getting RC values from storage. You can use
+ To enroll a user into the A/B tests for the given keys you use the following
+ method:
+
+ Here the keys array is the mandatory parameter for this method to work.
+
+ Platform Info
+ If you want to remove users from A/B tests of certain keys you can use the following
+ function:
+
+ Here if no keys are provided it would remove the user from all A/B tests instead.
+
+ There are two ways to receive user feedback: the Star Rating Dialog and the Feedback
+ Widgets (Survey, NPS, Rating).
+
+ The Star Rating Dialog allows users to give feedback as a rating from 1 to 5.
+ Feedback Widgets allow for even more textual feedback from users.
+
+ Platform Info
+ Star Rating Dialog provides a dialog for getting user's feedback about the application.
+ It contains a title, a simple message explaining what it is for, a 1-to-5 star
+ meter for getting users' ratings, and a dismiss button in case the user does
+ not want to give a rating.
+
+ This star-rating has nothing to do with Google Play Store ratings and reviews.
+ It is just for getting brief feedback from users, to be displayed on the Countly
+ dashboard. If the user dismisses star rating dialog without giving a rating,
+ the event will not be recorded.
+
+ The star-rating dialog's title, message, and dismiss button text may be customized
+ through the following functions:
+
+ Feedback Widgets is a
+ Countly Enterprise
+ plugin.
+
+ It is possible to display 3 kinds of feedback widgets:
+ NPS,
+ Survey,
+ and
+ Rating.
+
+ For more detailed information about Feedback Widgets, you can refer to
+ here.
+
+ Before any feedback widget can be shown, you need to create them in your
+ Countly dashboard.
+
+ After you have created widgets on your dashboard, you can reach the methods to
+ show them from the feedback interface of your Countly instance:
+
+ You can display a random active widget for the widget type you want with one
+ of these methods:
+
+ The "devCallback" parameter has two callbacks: - "onClosed" which will be called
+ when the feedback widget is closed - "onFinished" which will be called on some
+ internal errors and it will direct the error via "error" parameter.
+
+ For more in-depth information on retrieving feedback widgets, understanding object
+ structures, or presenting them yourself, please refer to the following
+ resource.
+
+ There might be some use-cases where you might to use the native UI or a custom
+ UI you have created instead of our webview solution. In those cases you would
+ have to request all the widget related information and then report the result
+ manually.
+
+ For a sample integration, have a look at our sample app in the repo.
+
+ First you would need to retrieve the available widget list with the previously
+ mentioned
+ Having the
+ If you want to use it without a callback then you can call '
+
+ After you have collected the required information from your users, you would
+ package the responses into a
+ If the user would have closed the widget, you would report that by passing a
+ "null" reportedResult.
+
+ For more information regarding how to structure the reported result, you would
+ look here.
+
+ If consents are enabled, to use Feedback Widgets, you have to provide the 'feedback'
+ consent and to use Star Rating Dialog you have to provide the 'starRating' consent
+ for these features to work.
+
+ Using the following calls, you can set key/value to the visitors user profile.
+ After you send user data, it can be viewed under the User Profiles menu.
+
+ Note that this feature is available only for Enterprise Edition.
+
+ You would call
+ If a property is set as an empty string, it will be deleted from the user on
+ the server side.
+
+ Platform Info
+ If possible set user properties during initialization. This way they would be
+ sent shortly after the SDK initialization.
+
+ Using the following call, you can set both the predefined and the custom user
+ properties during initialization:
+ The following calls can be used after init.
+ If you want to set a single property, you can call
+
+ If you want to set multiple properties at the same time, you can use:
+
+ SDK caches set properties, bundles them and sends to the server when necessary
+ with minimum amount of network requests. However if you want to manually decide
+ when to create a request you must save it by calling
+
+ If you changed your mind and want to clear the currently prepared values, call
+
+ Additionally, you can do different manipulations on your custom data values,
+ like increment current value on the server or store an array of values under
+ the same property.
+ Below is the list of available methods:
+ Platform Info
+ The SDK provides manual and automatic mechanisms for Application Performance
+ Monitoring (APM). All of the automatic mechanisms are disabled by default and
+ to start using them you would first need to enable them and give the required
+ consent if it was required:
+
+ While using APM calls, you have the ability to provide trace keys by which you
+ can track those parameters in your dashboard.
+
+ Currently, you can use custom traces to record the duration of application processes.
+ At the end of them, you can also provide any additionally gathered data.
+
+ The trace key uniquely identifies the thing you are tracking and the same name
+ will be shown in the dashboard. The SDK does not support tracking multiple events
+ with the same key.
+ To start a custom trace, use: To end a custom trace, use:
+ In this sample, a Map of integer values is provided when ending a trace. Those
+ will be added to that trace in the dashboard.
+
+ You can use the APM to track your requests. You would record the required info
+ for your selected approach of making network requests and then call this after
+ your network request is done:
+
+
+ There are a couple of performance metrics the SDK can gather for you automatically.
+ These are:
+
+ Tracking of these metrics are disabled by default and must be explicitly enabled
+ by the developer during init.
+
+ For tracking app start time automatically you will need to enable it in SDK init
+ config:
+
+ This calculates and records the app launch time for performance monitoring.
+
+ If you want to determine when the end time for this calculation should be you,
+ will have to enable the usage of manual triggers together with
+
+ Now you can call
+ If you also want to manipulate the app launch starting time instead of using
+ the SDK calculated value then you will need to call a third method on the config
+ object with the timestamp (in milliseconds) of that time you want:
+
+ Lastly if you want to enable the SDK to record the time an app is in foreground
+ or background automatically you would need to enable this option during init:
+
+ For compatibility with data protection regulations, such as GDPR, the Countly
+ Flutter SDK allows developers to enable/disable any feature at any time depending
+ on user consent. More information about GDPR
+ can be found here.
+
+ You can use CountlyConsent interface (e.g
+ By default the requirement for consent is disabled. To enable it, you have to
+ call
+ By default, no consent is given. That means that if no consent is enabled, Countly
+ will not work and no network requests, related to features, will be sent. When
+ the consent status of a feature is changed, that change will be sent to the Countly
+ server.
+
+ To give consent during initialization, you have to call
+
+ The Countly SDK does not persistently store the status of given consents except
+ push notifications. You are expected to handle receiving consent from end-users
+ using proper UIs depending on your app's context. You are also expected to store
+ them either locally or remotely. Following this step, you will need to call the
+ Ideally you would give consent during initialization.
+ The end-user can change their mind about consents at a later time.
+
+ To reflect these changes in the Countly SDK, you can use the
+
+ You can also either give or remove consent to all possible SDK features:
+
+ You can set optional
+ Make sure not to use salt on the Countly server and not on the SDK side, otherwise,
+ Countly won't accept any incoming requests.
+
+ The Android side of the SDK does not require specific proguard exclusions and
+ can be fully obfuscated.
+
+ Here is the list of functionalities "CountlyConfig" provides:
+
+ Below you can see steps to download the Countly Flutter
+ example
+ application. It assumes Flutter is installed in your system:
+
+ This example application has most of the methods mentioned in this documentation,
+ and it is an excellent way to understand how different methods work, like events,
+ custom user profiles, and views.
+
+ When you initialize Countly, you can specify a value for the setMaxRequestQueueSize
+ flag. This flag limits the number of requests that can be stored in the request
+ queue when the Countly server is unavailable or experiencing connection problems.
+
+ If the server is down, requests sent to it will be queued on the device. If the
+ number of queued requests becomes excessive, it can cause problems with delivering
+ the requests to the server, and can also take up valuable storage space on the
+ device. To prevent this from happening, the setMaxRequestQueueSize flag limits
+ the number of requests that can be stored in the queue.
+
+ If the number of requests in the queue reaches the setMaxRequestQueueSize limit,
+ the oldest requests in the queue will be dropped, and the newest requests will
+ take their place. This ensures that the queue doesn't become too large, and that
+ the most recent requests are prioritized for delivery.
+
+ If you do not specify a value for the setMaxRequestQueueSize flag, the default
+ setting of 1,000 will be used.
+
+ Countly SDKs have internal limits to prevent users from unintentionally sending
+ large amounts of data to the server. If these limits are exceeded, the data will
+ be truncated to keep it within the limit. You can check the exact parameters
+ these limits effect from
+ here.
+
+ Limits the maximum size of all user set keys (default: 128 chars):
+
+ Limits the size of all user set string segmentation (or their equivalent) values
+ (default: 256 chars):
+
+ Limits the amount of user set segmentation key-value pairs (default: 100 entries):
+
+ Limits the amount of user set breadcrumbs that can be recorded (default: 100
+ entries, exceeding this deletes the oldest one):
+
+ Limits the stack trace lines that would be recorded per thread (default: 30 lines):
+
+ Limits the characters that are allowed per stack trace line (default: 200 chars):
+
+ Platform Info
+ Countly Attribution Analytics
+ allows you to measure your marketing campaign performance by attributing installs
+ from specific campaigns. This feature is available for the Enterprise Edition.
+
+ There are 2 forms of attribution: direct Attribution and indirect Attribution.
+
+ Platform Info
+ You can pass "Campaign type" and "Campaign data". The "type" determines for what
+ purpose the attribution data is provided. Depending on the type, the expected
+ data will differ, but usually that will be a string representation of a JSON
+ object.
+
+ You can use
+ You can also use
+ Currently this feature is limited and accepts data only in a specific format
+ and for a single type. That type is "countly". It will be used to record install
+ attribution. The data also needs to be formatted in a specific way. Either with
+ the campaign id or with the campaign id and campaign user id.
+
+ This feature would be used to report things like advertising ID's. For each platform
+ those would be different values. For the most popular keys we have a class with
+ predefined values to use, it is called "AttributionKey".
+
+ You can use
+ You can also use
+ In case you would be accessing IDFA for ios, for iOS 14+ due to the changes made
+ by Apple, regarding Application Tracking, you need to ask the user for permission
+ to track the Application.
+
+ If the data sent to the server is short enough, the SDK will use HTTP GET requests.
+ In case you want an override so that HTTP POST is used in all cases, call the
+
+ Platform Info
+ If you need to include custom network request headers in the requests sent by
+ the SDK, you can easily add them using the following method.
+
+ This allows you to specify any headers your application requires for enhanced
+ functionality or security.
+
+ When recording events or activities, the requests don't always get sent immediately.
+ Events get grouped together. All the requests contain the same app key which
+ is provided in the
+ There are two ways to interact with the app key in the request queue at the moment.
+
+ 1. You can replace all requests with a different app key with the current app
+ key:
+
+ In the request queue, if there are any requests whose app key is different than
+ the current app key, these requests app key will be replaced with the current
+ app key. 2. You can remove all requests with a different app key in the request
+ queue:
+
+ In the request queue, if there are any requests whose app key is different than
+ the current app key, these requests will be removed from the request queue.
+
+ Platform Info
+ If you are concerned about your app being used sparsely over a long time frame,
+ old requests inside the request queue might not be important. If, for any reason,
+ you don't want to get data older than a certain timeframe, you can configure
+ the SDK to drop old requests:
+
+ By using the
+ Events get grouped together and are sent either every minute or after the unsent
+ event count reaches a threshold. By default it is 10. If you would like to change
+ this, call:
+
+ In case you would like to check if init has been called, you may use the following
+ function:
+
+ The Content Zone feature enhances user engagement by delivering various types
+ of content blocks, such as in-app messaging, ads, or user engagement prompts.
+ These content blocks are dynamically served from the content builder on the server,
+ ensuring that users receive relevant and up-to-date information.
+
+ To start fetching content from the server, use the following method:
+
+ This call will retrieve and display any available content for the user. It will
+ also regularly check if a new content is available, and if it is, will fetch
+ and show it to the user.
+
+ This regular check happens in every 30 seconds by default. It could be configurable
+ while initializing the SDK through and it must be greater than 15 seconds.
+
+ When you want to exit from content zone and stop SDK from checking for available
+ content you can use this method:
+
+ To get informed when a user closes a content you can register a global content
+ callback during SDK initialization:
+
+ Platform Info
+ The `contentStatus` will indicate either `ContentStatus.completed` or `ContentStatus.closed`.
+
+ Platform Info
+ The ConfigExperimental interface provides experimental configuration options
+ for enabling advanced features like view name recording and visibility tracking.
+ These features are currently in a testing phase and might change in future versions.
+ This class allows enabling two experimental features:
+ When you enable previous name recording, it will add previous view name to the
+ view segmentations (cly_pvn) and previous event name to the event segmentations
+ (cly_pen).
+
+ When you enable visibility tracking, it will add a parameter (cly_v) to each
+ recorded event's segmentation about the visibility of the app at the time of
+ its recording.
+
+ Platform Info
+ You can fetch a map of all A/B testing parameters (keys) and variants associated
+ with it:
+
+ You can provide a callback (which is optional) to be called when the fetching
+ process ends. Depending on the situation, this would return a RequestResponse
+ Enum (Success, NetworkIssue, or Error) as the first parameter and a String error
+ as the second parameter if there was an error ("null" otherwise).
+
+ When test variants are fetched, they are saved to the memory. If the memory is
+ erased, you must fetch the variants again. So a common flow is to use the fetched
+ values right after fetching them. To access all fetched values, you can use:
+
+ This would return a Future<Map<String, List<String>>> where
+ a test's parameter is associated with all variants under that parameter. The
+ parameter would be the key, and its value would be a String List of variants.
+ For example:
+ Or instead you can get the variants of a specific key:
+ This would only return a Future<List<String>> of variants for that
+ specific key. If no variants were present for a key, it would return an empty
+ list. A typical result would look like this:
+
+ After fetching A/B testing parameters and variants from your server, you next
+ would like to enroll the user to a specific variant. To do this, you can use
+ the following method:
+
+ Here the 'valueKey' would be the parameter of your A/B test, and 'variantName'
+ is the variant you have fetched and selected to enroll for. The callback function
+ is optional and works the same way as explained above in the Fetching Test Variants
+ section.
+
+ Platform Info
+ You can fetch information about the A/B tests in your server including test name,
+ description and the current variant:
+
+ You can provide a callback (which is optional) to be called when the fetching
+ process ends. Depending on the situation, this would return a RequestResponse
+ Enum (Success, NetworkIssue, or Error) as the first parameter and a String error
+ as the second parameter if there was an error ("null" otherwise).
+
+ After fetching the experiment information the SDK saves it in the RAM, so if
+ the memory is erased, you must fetch the information again. You can access this
+ information through this call:
+
+ This would return a Future<Map<String, ExperimentInformation>> where
+ the keys are experiment IDs as String and the values are the ExperimentInformation
+ Class which contains information about the experiment with that ID. This Class'
+ structure is like this:
+
+ So an example data structure you might get at the end would look something similar
+ to this:
+
+ To enroll a user into the A/B experiment using experiment ID, you use the following
+ method:
+
+ If you want to remove users from A/B experiment using experiment ID, you can
+ use the following function:
+
+ Performance risk. Changing device id with server merging
+ results in huge load on server as it is rewriting all the user history. This
+ should be done only once per user.
+ You may configure/change the device ID anytime using:
+ You may either allow the device to be counted as a new device or merge existing
+ data on the server. If the
+ You may use a temporary device ID mode for keeping all requests on hold until
+ the real device ID is set later.
+
+ You can enable temporary device ID when initializing the SDK:
+ To enable a temporary device ID after init, you would call:
+ Note: When passing
+ As long as the device ID value is
+ When in temporary device ID mode, method calls for presenting feedback widgets
+ and updating remote config will be ignored.
+
+ Later, when the real device ID is set using
+
+ The data that SDKs gather to carry out their tasks and implement the necessary
+ functionalities is mentioned in
+ here
+
+ Currently our Flutter SDK supports Android, iOS, and Web platforms.
+
- This documentation is for the Countly Flutter SDK version 25.1.X. The SDK source
+ This documentation is for the Countly Flutter SDK version 25.4.X. The SDK source
code repository can be found
here.
+
+
+
+
+
+ IE
+
+
+ Edge
+
+
+ Firefox
+
+
+ Firefox (Android)
+
+
+ Opera
+
+
+ Opera (Mobile)
+
+
+ Safari
+
+
+ Safari (iOS)
+
+
+ Chrome
+
+
+ Chrome (Android)
+
+
+
+
+10
+ 12
+ 21
+ 96
+ 15
+ 64
+ 6
+ 6
+ 23
+ 98
+ Adding the SDK to the Project
+pubspec.yaml
file:
+
+dependencies:
+ countly_flutter: ^25.1.1
+flutter pub get
SDK Integration
+Minimal Setup
+
+// Create the configuration with your app key and server URL
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
+// Initialize with that configuration
+Countly.initWithConfig(config).then((value){
+ // handle extra logic after init
+});
SDK Data Storage
+
+
+SDK Logging
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setLoggingEnabled(true);
Crash Reporting
+Automatic Crash Handling
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.enableCrashReporting()
Automatic Crash Report Segmentation
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setCustomCrashSegment(Map<String, Object> segments);
Handled Exceptions
+exception.toString()
+
+Countly.logException(String exception, bool nonfatal, [Map<String, Object> segmentation])
StackTrace.current
+ will be used.
+
+Countly.logExceptionEx(Exception exception, bool nonfatal, {StackTrace stacktrace, Map<String, Object> segmentation})
StackTrace.current
will be used.
+
+Countly.logExceptionManual(String message, bool nonfatal, {StackTrace stacktrace, Map<String, Object> segmentation})
+bool nonfatal = true; // Set it false in case of fatal exception
+// With Exception object
+Countly.logExceptionEx(EXCEPTION_OBJECT, nonfatal);
+
+// With String message
+Countly.logExceptionManual("MESSAGE_STRING", nonfatal);
+
+bool nonfatal = true; // Set it false in case of fatal exception
+// With Exception object
+Countly.logExceptionEx(EXCEPTION_OBJECT, nonfatal, stacktrace: STACK_TRACE_OBJECT);
+
+// With String message
+Countly.logExceptionManual("MESSAGE_STRING", nonfatal, stacktrace: STACK_TRACE_OBJECT);
+
+bool nonfatal = true; // Set it false in case of fatal exception
+// With Exception object
+Countly.logExceptionEx(EXCEPTION_OBJECT, nonfatal, segmentation: {"_facebook_version": "0.0.1"});
+
+// With String message
+Countly.logExceptionManual("MESSAGE_STRING", nonfatal, segmentation: {"_facebook_version": "0.0.1"});
+
+bool nonfatal = true; // Set it false in case of fatal exception
+// With Exception object
+Countly.logExceptionEx(EXCEPTION_OBJECT, nonfatal, STACK_TRACE_OBJECT, {"_facebook_version": "0.0.1"});
+
+// With String message
+Countly.logExceptionManual("MESSAGE_STRING", nonfatal, STACK_TRACE_OBJECT, {"_facebook_version": "0.0.1"});
+
Crash Breadcrumbs
+
+Countly.addCrashLog(String logs)
Events
+
+Countly.instance.events
Recording Events
+
+
+
+Future<String?> recordEvent(String key, [Map<String, Object>? segmentation, int? count, double? sum, int? duration])
+Countly.instance.events.recordEvent('purchase', null, 1);
+Countly.instance.events.recordEvent('purchase', null, 1, 0.99);
+
+Map<String, Object>? segmentation = {
+ 'country': 'Germany',
+ 'app_version': '1.0',
+ 'rating': 10,
+ 'precision': 324.54678,
+ 'timestamp': 1234567890,
+ 'clicked': false,
+ 'languages': ['en', 'de', 'fr'],
+ 'sub_names': ['John', 'Doe', 'Jane']
+};
+
+Countly.instance.events.recordEvent('purchase', segmentation, 1);
+
+Map<String, Object>? segmentation = {
+ 'country': 'Germany',
+ 'app_version': '1.0',
+ 'rating': 10,
+ 'precision': 324.54678,
+ 'timestamp': 1234567890,
+ 'clicked': false,
+ 'languages': ['en', 'de', 'fr'],
+ 'sub_names': ['John', 'Doe', 'Jane']
+};
+
+Countly.instance.events.recordEvent('purchase', segmentation, 1, 0.99);
+
+Map<String, Object>? segmentation = {
+ 'country': 'Germany',
+ 'app_version': '1.0',
+ 'rating': 10,
+ 'precision': 324.54678,
+ 'timestamp': 1234567890,
+ 'clicked': false,
+ 'languages': ['en', 'de', 'fr'],
+ 'sub_names': ['John', 'Doe', 'Jane']
+};
+
+Countly.instance.events.recordEvent('purchase', segmentation, 1, 0.99, 1);
+
Timed Events
+
+// Basic event
+Countly.instance.events.startEvent("Timed Event");
+
+Timer timer = Timer(new Duration(seconds: 5), () {
+ Countly.instance.events.endEvent('Timed Event');
+});
+
+// Event with Segment, sum and count
+Countly.instance.events.startEvent("Timed Event With Segment, Sum and Count");
+
+Timer timer = Timer(new Duration(seconds: 5), () {
+ Map<String, Object>? segmentation = {
+ 'country': 'Germany',
+ 'app_version': '1.0',
+ 'rating': 10,
+ 'precision': 324.54678,
+ 'timestamp': 1234567890,
+ 'clicked': false,
+ 'languages': ['en', 'de', 'fr'],
+ 'sub_names': ['John', 'Doe', 'Jane']
+ };
+
+ Countly.instance.events.endEvent('Timed Event With Segment, Sum and Count', segmentation, 1, 0.99);
+});
+//start some event
+Countly.instace.events.startEvent(eventName);
+
+//wait some time
+
+//cancel the event
+Countly.instance.events.cancelEvent(eventName);
+
Sessions
+Automatic Session Tracking
+
+
+Manual Sessions
+
+ The Web platform supports only Hybrid mode; manual update call not supported.
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
config.enableManualSessionHandling();
+
+Countly.instance.sessions.beginSession();
+Countly.instance.sessions.updateSession();
+Countly.instance.sessions.endSession();
+View Tracking
+
+ The Web platform supports only startAutoStoppedView call.
+
+Countly.instance.views
Manual View Recording
+Auto Stopped Views
+
+// record a view on your application
+final String? viewID = await Countly.instance.views.startAutoStoppedView("Dashboard");
+Map<String, Object> segmentation = {
+ "country": "Germany",
+ "app_version": "1.0",
+ "rating": 10,
+ "precision": 324.54678,
+ "timestamp": 1234567890,
+ "clicked": false,
+ "languages": ["en", "de", "fr"],
+ "sub_names": ["John", "Doe", "Jane"]
+};
+
+final String? anotherViewID = Countly.instance.views.startAutoStoppedView("HomePage", segmentation);
+
Regular Views
+startView
method with a view name. This will start tracking a view and return a unique identifier, and the view will remain active until explicitly stopped using stopViewWithName
or stopViewWithID
+
+// record a view on your application
+Countly.instance.views.startView("HomePage");
+final String? viewID = await Countly.instance.views.startView("Dashboard");
+Map<String, Object> segmentation = {
+ "country": "Germany",
+ "app_version": "1.0",
+ "rating": 10,
+ "precision": 324.54678,
+ "timestamp": 1234567890,
+ "clicked": false,
+ "languages": ["en", "de", "fr"],
+ "sub_names": ["John", "Doe", "Jane"]
+};
+
+final String? anotherViewID = Countly.instance.views.startView("HomePage", segmentation);
Stopping Views
+
+Countly.instance.views.stopViewWithName("HomePage");
+Countly.instance.views.stopViewWithName("HomePage", segmentation);
stopViewWithID
+
+Countly.instance.views.stopViewWithID(viewID);
+Countly.instance.views.stopViewWithID(anotherViewID, segmentation);
stopAllViews
+
+Countly.instance.views.stopAllViews();
+Countly.instance.views.stopAllViews(segmentation);
Pausing and Resuming Views
+pauseViewWithID
+
+Countly.instance.views.pauseViewWithID(viewID);
resumeViewWithID:
+
+Countly.instance.views.resumeViewWithID(viewID);
Adding Segmentation to Started Views
+
+String viewName = 'HomePage';
+await Countly.instance.views.startView(viewName);
+
+Map<String, Object> segmentation = {
+ "country": "Germany",
+ "app_version": "1.0",
+ "rating": 10,
+ "precision": 324.54678,
+ "timestamp": 1234567890,
+ "clicked": false,
+ "languages": ["en", "de", "fr"],
+ "sub_names": ["John", "Doe", "Jane"]
+};
+
+await Countly.instance.views.addSegmentationToViewWithName(viewName, segmentation);
+String? viewID = await Countly.instance.views.startView('HomePage');
+
+Map<String, Object> segmentation = {
+ "country": "Germany",
+ "app_version": "1.0",
+ "rating": 10,
+ "precision": 324.54678,
+ "timestamp": 1234567890,
+ "clicked": false,
+ "languages": ["en", "de", "fr"],
+ "sub_names": ["John", "Doe", "Jane"]
+};
+
+await Countly.instance.views.addSegmentationToViewWithID(viewID!, segmentation);
Global View Segmentation
+
+// set global segmentation at initialization
+final CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setGlobalViewSegmentation(segmentation);
setGlobalViewSegmentation
method will replace the previously
+ set values..
+
+Countly.instance.views.setGlobalViewSegmentation(segmentation);
updateGlobalViewSegmentation
method will modify the previously
+ set values and overwrite any previously set keys.
+
+Countly.instance.views.updateGlobalViewSegmentation(segmentation);
Device ID Management
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setDeviceId(DEVICE_ID);
Changing the Device ID
+
+Countly.instance.deviceId.setID(DEVICE_ID);
setID
, the SDK determines internally if the device will
+ be counted as a new device on the server or if it will merge the new and old
+ IDs. If the previous ID was generated by the SDK (DeviceIDType.SDK_GENERATED
),
+ then setID
merges the device ID. In any other case,
+ setID
changes the device ID without merging.
+setID
cannot be used to enter temporary ID mode.
+Temporary Device ID
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.enableTemporaryDeviceIDMode();
+
+// Initialize with that configuration
+Countly.initWithConfig(config);
+Countly.instance.deviceId.enableTemporaryIDMode();
Countly.instance.deviceId.setID(DEVICE_ID);
method,
+ all requests that have been kept on hold will be sent with the new device ID.
+Retrieving Current Device ID
+
+String? currentDeviceId = Countly.instance.deviceId.getID();
getIDType
method which returns a
+ DeviceIDType
to get the current device ID type. The ID type is an enum with the possible values of:
+
+
+
+DeviceIdType? deviceIdType = await Countly.instance.deviceId.getIDType();
Device ID Generation
+Push Notifications
+Integration
+Android Setup
+google-services.json
from
+ https://firebase.google.com/
+google-services.json
package_name
matches.
+google-services.json
file inside
+ android/app
+android/app/src/main/AndroidManifest.xml
inside
+ application
tag.
+
+<application ...>
+...
+ <service android:name="ly.count.dart.countly_flutter.CountlyMessagingService">
+ <intent-filter>
+ <action android:name="com.google.firebase.MESSAGING_EVENT" />
+ </intent-filter>
+ </service>
+</application>
+
android/build.gradle
+
+buildscript {
+ dependencies {
+ classpath 'com.google.gms:google-services:LATEST'
+ }
+}
+
android/app/build.gradle
+
+dependencies {
+ implementation 'ly.count.android:sdk:LATEST'
+ implementation 'com.google.firebase:firebase-messaging:LATEST'
+}
+// Add this at the bottom of the file
+apply plugin: 'com.google.gms.google-services'
+
iOS Setup
+CountlyNotificationService.h/m
file
+ under:
+
+Pods/Development Pods/Countly/{PROJECT_NAME}/ios/.symlinks/plugins/countly_flutter/ios/Classes/CountlyiOS/CountlyNotificationService.h/m
+
+
+
Enabling Push
+
+// Set messaging mode for push notifications
+Countly.pushTokenType(Countly.messagingMode["TEST"]);
+// This method will ask for permission, enables push notification and send push token to countly server.
+Countly.askForNotificationPermission();
disablePushNotifications
method:
+
+// Disable push notifications feature for iOS, by default it is enabled.
+Countly.disablePushNotifications();
Removing Push and Its Dependencies
+Handling Push Callbacks
+Countly.onNotification((String notification) {
+ print(notification);
+});
+AppDelegate.swift
+
+import countly_flutter
+
+// Required for the notification event. You must call the completion handler after handling the remote notification.
+func application(application: UIApplication, didReceiveRemoteNotification userInfo: [NSObject : AnyObject], fetchCompletionHandler completionHandler: (UIBackgroundFetchResult) -> Void) {
+ CountlyFlutterPlugin.onNotification(userInfo);
+ completionHandler(.newData);
+}
+
+@available(iOS 10.0, \*)
+override func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (_ options: UNNotificationPresentationOptions) -> Void) {
+
+ //Called when a notification is delivered to a foreground app.
+
+ let userInfo: NSDictionary = notification.request.content.userInfo as NSDictionary
+ CountlyFlutterPlugin.onNotification(userInfo as? [AnyHashable : Any])
+}
+
+@available(iOS 10.0, \*)
+override func userNotificationCenter(\_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
+
+ // Called to let your app know which action was selected by the user for a given notification.
+ let userInfo: NSDictionary = response.notification.request.content.userInfo as NSDictionary
+ // print("\(userInfo)")
+ CountlyFlutterPlugin.onNotification(userInfo as? [AnyHashable : Any])
+}
+
Data Structure Received in Push Callbacks
+
+ Data Received for Android platform:
+
{
+ "c.e.cc": "TR",
+ "c.e.dt": "mobile",
+ "Key": "value",
+ "c.i": "62b59b979f05a1f5e5592036",
+ "c.l": "https:\/\/www.google.com\/",
+ "c.m": "https:\/\/count.ly\/images\/logos\/countly-logo-mark.png?v2",
+ "c.li": "notify_icon",
+ "badge": "1",
+ "sound": "custom",
+ "title": "title",
+ "message": "Message"
+}
+{
+ Key = value;
+ aps = {
+ alert = {
+ body = Message;
+ subtitle = subtitle;
+ title = title;
+ };
+ badge = 1;
+ "mutable-content" = 1;
+ sound = custom;
+ };
+ c = {
+ a = "https://count.ly/images/logos/countly-logo-mark.png";
+ e = {
+ cc = TR;
+ dt = mobile;
+ };
+ i = 62b5b945cabedb0870e9f217;
+ l = "https://www.google.com/";
+ };
+}
+User Location
+
+ The Web platform supports only setting location through configuration.
+ Set User Location
+setLocation
of
+ CountlyConfig
during init orsetUserLocation
method
+ after init.
+
+
+countryCode
a string in ISO 3166-1 alpha-2 format country code
+ city
a string specifying city name
+ location
a string comma-separated latitude and longitude
+ IP
a string specifying an IP address in IPv4 or IPv6 formats
+
+// Example for setLocation
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setLocation(country_code: 'TR', city: 'Istanbul', gpsCoordinates: '41.0082,28.9784', ipAddress: '10.2.33.12')
setUserLocation
method as shown
+ below.
+
+// Example for setUserLocation
+Countly.setUserLocation(countryCode: 'TR', city: 'Istanbul', gpsCoordinates: '41.0082,28.9784', ipAddress: '10.2.33.12');
+
Disable Location
+setUserLocation
is called with any non-null value, tracking will
+ resume.
+
+//disable location tracking
+Countly.disableLocation();
Remote Config
+
+ Downloading Values
+
+ Automatic Remote Config Triggers
+
+ The caching is not supported in the Web platform.
+
+
+ CountlyConsent.remoteConfig
consent is given after it had been removed before (if consents are enabled)
+ enableRemoteConfigAutomaticTriggers
on the configuration
+ object you will provide during init.
+
+ CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
+ ..enableRemoteConfigAutomaticTriggers(); // necessary to enable the feature
+
enableRemoteConfigValueCaching
flag. If all values
+ were not updated, you would have metadata indicating if a value belongs to
+ the old or current user.
+ CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
+ ..enableRemoteConfigValueCaching();
+Manually Calls
+
+
+dowloadAllKeys
is the same as the automatically triggered update - it replaces all stored values with the ones from the server (all locally stored values are deleted and replaced with new ones).
+downloadSpecificKeys
to downloads new values for the wanted keys. Those are provided with a String array.
+downloadOmittingKeys
would update all values except the provided keys. The keys are provided with a String array.
+
+ Countly.instance.remoteConfig.downloadAllKeys((rResult, error, fullValueUpdate, downloadedValues) {
+ if (rResult == RequestResult.Success) {
+ // do sth
+ } else {
+ // do sth
+ }
+});
+ Countly.instance.remoteConfig.downloadSpecificKeys(List<String> keysToInclude, (rResult, error, fullValueUpdate, downloadedValues) {
+ if (rResult == RequestResult.Success) {
+ // do sth
+ } else {
+ // do sth
+ }
+});
+ Countly.instance.remoteConfig.downloadOmittingKeys(List<String> keysToExclude, (rResult, error, fullValueUpdate, downloadedValues) {
+ if (rResult == RequestResult.Success) {
+ // do sth
+ } else {
+ // do sth
+ }
+});
dowloadAllKeys
request and will update all the values. This means it will also erase all keys not returned by the server.
+Accessing Values
+getValue
with the specified
+ key. This returns an Future<RCData> object that contains the value of the
+ key and the metadata about that value's owner. If value in RCData was
+ null
+ then no value was found or the value was null
.
+
+
+Object? value_1 = await Countly.instance.remoteConfig.getValue("key_1").value;
+Object? value_2 = await Countly.instance.remoteConfig.getValue("key_2").value;
+Object? value_3 = await Countly.instance.remoteConfig.getValue("key_3").value;
+Object? value_4 = await Countly.instance.remoteConfig.getValue("key_4").value;
+
+int intValue = value1 as int;
+double doubleValue = value2 as double;
+JSONArray jArray = value3 as JSONArray;
+JSONObject jObj = value4 as JSONObject;
+
getAllValues
which returns a Future<Map<String,
+ RCData>>.
+ The SDK does not know the returned value type, so, it will return the Object
. The developer then needs to cast it to the appropriate type. The returned values may also be JSONArray
, JSONObject
,
+ or just a simple value, such as int
.
+
+Map<String, RCData> allValues = await Countly.instance.remoteConfig.getAllValues();
+
+int intValue = allValues["key_1"] as int;
+double doubleValue = allValues["key_2"] as double;
+JSONArray jArray = allValues["key_3"] as JSONArray;
+JSONObject jObj = allValues["key_4"] as JSONObject;
+Class RCData {
+ Object value;
+ Boolean isCurrentUsersData;
+}
Clearing Stored Values
+
+ This feature is not supported in the Web platform.
+ Countly.instance.remoteConfig.clearAll();
+Global Download Callbacks
+remoteConfigRegisterGlobalCallback
during the SDK initialization:
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
+ ..remoteConfigRegisterGlobalCallback((rResult, error, fullValueUpdate, downloadedValues) {
+ if (error != null) {
+ // do sth
+ }
+ })
+
+
+rResult
: RequestResult Enum (either
+ Error, Success or NetworkIssue)
+ error
: String (error message. "null" if there is
+ no error)
+ fullValueUpdate
: boolean ("true" - all values updated,
+ "false" - a subset of values updated)
+ downloadedValues
: Map<String, RCData> (the
+ whole downloaded remote config values)
+
+RCDownloadCallback {
+ void callback(RequestResult rResult, String error, boolean fullValueUpdate, Map<String, RCData> downloadedValues)
+}
+
downloadedValues
would be the downloaded remote config
+ data where the keys are remote config keys, and their value is stored in RCData
+ class with metadata showing to which user data belongs. The data owner will always
+ be the current user.
+
+// register a callback
+Countly.instance.remoteConfig.registerDownloadCallback((rResult, error, fullValueUpdate, downloadedValues) {
+ // do sth
+});
+
+// remove a callback
+Countly.instance.remoteConfig.removeDownloadCallback((rResult, error, fullValueUpdate, downloadedValues) {
+ // do sth
+});
A/B Testing
+
+ Enrollment on Download
+
+ CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY)
+..enrollABOnRCDownload();
+
+ Enrollment on Access
+
+ getValueAndEnroll
while getting a single value and getAllValuesAndEnroll
while getting all values to enroll to the keys that exist. If no value was stored for those keys these functions would not enroll the user. Both of these functions works the same way with their non-enrolling variants, namely; getValue
and getAllValues
.
+
+ Enrollment on Action
+
+ Countly.instance.remoteConfig.enrollIntoABTestsForKeys(List<String> keys);
+
+ Exiting A/B Tests
+
+
+ This feature is not supported in the Web platform.
+ Countly.instance.remoteConfig.exitABTestsForKeys(List<String> keys);
+User Feedback
+Star Rating Dialog
+
+ This feature is not supported in the Web platform.
+
+Countly.askForStarRating();
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setStarRatingTextTitle("Custom title"); // Only available for Android
+config.setStarRatingTextMessage("Custom message");
+config.setStarRatingTextDismiss("Custom message"); // Only available for Android
Feedback Widget
+
+Countly.instance.feedback
+.presentNPS([String? nameIDorTag, FeedbackCallback? feedbackCallback])
+.presentRating([String? nameIDorTag, FeedbackCallback? feedbackCallback])
+.presentSurvey([String? nameIDorTag, FeedbackCallback? feedbackCallback])
+
+// Example:
+Countly.instance.feedback.presentSurvey();
+
+// Example with show a specific widget according to its name, ID or one of its tags
+Countly.instance.feedback.presentRating("/home-page");
+
+
+// Example about need to know when the widget you are showing is closed
+Countly.instance.feedback.presentNPS("MyNetPromoterScore", FeedbackCallback(
+ onClosed: () {
+ print('NPS closed');
+ },
+ onFinished: (String error) {
+ print('NPS finished with error: $error');
+ },
+));
+
Manual Reporting
+getAvailableFeedbackWidgets
call. After that you would
+ have a list of possible CountlyPresentableFeedback
objects. You
+ would pick the one widget you would want to display.
+CountlyPresentableFeedback
object of the widget you would
+ want to display, you could use the 'getFeedbackWidgetData
'
+ method to retrieve the widget information with an optional 'onFinished' callback.
+ In case you want to use with callback then you can call 'getFeedbackWidgetData
'
+ in this way:
+
+Countly.getFeedbackWidgetData(chosenWidget, onFinished: (retrievedWidgetData, error) {
+ if (error == null) {
+ }
+});
getFeedbackWidgetData
'
+ in this way:
+
+List result = await Countly.getFeedbackWidgetData(chosenWidget);
+String? error = result[1];
+if (error == null) {
+ Map<String, dynamic> retrievedWidgetData = result[0];
+}
retrievedWidgetData
would contain a Map with all of the required
+ information to present the widget yourself.
+Map<String, Object>
and then
+ use it, the widgetInformation and the widgetData to report the feedback result
+ with the following call:
+
+//this contains the reported results
+Map<String, Object> reportedResult = {};
+
+//
+// You would fill out the results here. That step is not displayed in this sample
+//
+
+//report the results to the SDK
+Countly.reportFeedbackWidgetManually(chosenWidget, retrievedWidgetData , reportedResult);
+
Consent
+User Profiles
+Countly.instance.userProfile.
to see the available
+ functionality for modifying user properties.
+Setting User profile values during init
+
+ This feature is not supported in the Web platform.
+
+var userProperties = {
+ "customProperty": "custom Value",
+ "username": "USER_NAME",
+ "email": "USER_EMAIL"
+};
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setUserProperties(userProperties);
Setting User profile values
+Countly.instance.userProfile.setProperty(key, value)
+Countly.instance.userProfile.setProperty("specialProperty", "value");
+Countly.instance.userProfile.setUserProperties(userProperties)
+
+// example for setting user data
+Map<String, Object> userProperties= {
+ "name": "Nicola Tesla",
+ "username": "nicola",
+ "email": "info@nicola.tesla",
+ "organization": "Trust Electric Ltd",
+ "phone": "+90 822 140 2546",
+ "picture": "http://images2.fanpop.com/images/photos/3300000/Nikola-Tesla-nikola-tesla-3365940-600-738.jpg",
+ "picturePath": "",
+ "gender": "M", // "F"
+ "byear": "1919",
+ "special_value": "something special"
+};
+Countly.instance.userProfile.setUserProperties(userProperties);
Countly.instance.userProfile.save()
. This would then
+ create a request and send it to the server.
+Countly.instance.userProfile.clear()
before calling
+ "save".
+Modifying custom data
+
+//increment used value by 1
+Countly.instance.userProfile.increment("increment");
+//increment used value by provided value
+Countly.instance.userProfile.incrementBy("incrementBy", 10);
+//multiply value by provided value
+Countly.instance.userProfile.multiply("multiply", 20);
+//save maximal value
+Countly.instance.userProfile.saveMax("saveMax", 100);
+//save minimal value
+Countly.instance.userProfile.saveMin("saveMin", 50);
+//set value if it does not exist
+Countly.instance.userProfile.setOnce("setOnce", 200);
+
+//insert value to array of unique values
+Countly.instance.userProfile.pushUnique("type", "morning");;
+//insert value to array which can have duplicates
+Countly.instance.userProfile.push("type", "morning");
+//remove value from array
+Countly.instance.userProfile.pull("type", "morning");
Application Performance Monitoring
+
+ This feature is not supported in the Web platform.
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
+// this interface exposes the available APM features and their modifications.
+config.apm.
Custom Traces
+
+Countly.startTrace(traceKey);
+String traceKey = "Trace Key";
+Map<String, int> customMetric = {
+ "ABC": 1233,
+ "C44C": 1337
+};
+Countly.endTrace(traceKey, customMetric);
Network Traces
+
+Countly.recordNetworkTrace(networkTraceKey, responseCode, requestPayloadSize, responsePayloadSize, startTime, endTime);
networkTraceKey
is a unique identifier of the API endpoint you are
+ targeting or just the url you are targeting, all params should be stripped. You
+ would also provide the received response code, sent payload size in bytes, received
+ payload size in bytes, request start time timestamp in milliseconds, and request
+ end finish timestamp in milliseconds.
+Automatic Device Traces
+
+
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
+// enable it here separately with 'apm' interface.
+config.apm.enableAppStartTimeTracking();
enableAppStartTimeTracking
during init:
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
+// enable it here separately with 'apm' interface.
+config.apm.enableAppStartTimeTracking().enableManualAppLoadedTrigger();
Countly.appLoadingFinished()
any time
+ after SDK initialization to record that moment as the end of app launch time.
+ The starting time of the app load will be automatically calculated and recorded.
+ Note that the app launch time can be recorded only once per app launch. So, the
+ second and following calls to this method will be ignored.
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
+// generate the timestamp you want (or you can directly pass a ts)
+int ts = DateTime.now().millisecondsSinceEpoch - 500; // 500 ms ago as an example
+
+// this would also work with manual trigger
+config.apm.enableAppStartTimeTracking().setAppStartTimestampOverride(ts);
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+
+// enable it here separately with 'apm' interface.
+config.apm.enableForegroundBackgroundTracking();
User Consent
+CountlyConsent.sessions
)
+ to reach all possible consent options.
+ Currently, available features with consent control are as follows:
+
+
+Setup During Init
+setRequiresConsent
with true, before initializing Countly.
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setRequiresConsent(true);
setConsentEnabled
on the config object with an array
+ of consent values. Or, you can use giveAllConsents
for
+ all consent values.
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setConsentEnabled([CountlyConsent.location, CountlyConsent.sessions, CountlyConsent.attribution, CountlyConsent.push, CountlyConsent.events, CountlyConsent.views, CountlyConsent.crashes, CountlyConsent.users, CountlyConsent.push, CountlyConsent.starRating, CountlyConsent.apm, CountlyConsent.feedback, CountlyConsent.remoteConfig, CountlyConsent.content])
config.giveAllConsents()giveConsent
method on each app launch depending on the permissions
+ you managed to get from the end-users.
+Changing Consent
+removeConsent
or giveConsent
methods.
+
+//give consent values after init
+Countly.giveConsent([CountlyConsent.events, CountlyConsent.views, CountlyConsent.starRating, CountlyConsent.crashes]);
+
+//remove consent values after init
+Countly.removeConsent([CountlyConsent.events, CountlyConsent.views, CountlyConsent.starRating, CountlyConsent.crashes]);
+
+//give consent to all features
+Countly.giveAllConsent();
+
+//remove consent from all features
+Countly.removeAllConsent();
Security and Privacy
+Parameter Tampering Protection
+salt
to be used for calculating checksum of
+ request data, which will be sent with each request using
+ &checksum
field. You need to set exactly the same
+ salt
on the Countly server. If salt
on Countly server
+ is set, all requests would be checked for the validity of
+ &checksum
field before being processed.
+
+// sending data with salt
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setParameterTamperingProtectionSalt("salt");
Using Proguard
+Other Features and Notes
+SDK Config Parameters Explained
+
+
+Example Integrations
+
+# clone the Countly SDK repository
+git clone https://github.com/Countly/countly-sdk-flutter-bridge.git
+
+# dive into the cloned repo
+cd countly-sdk-flutter-bridge/example
+
+# install packages and run
+flutter pub get
+flutter run
Setting Maximum Request Queue Size
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setMaxRequestQueueSize(5000);
SDK Internal Limits
+Key Length
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.sdkInternalLimits.setMaxKeyLength(int MAX_KEY_LENGTH);
+await Countly.initWithConfig(config);
Value Size
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.sdkInternalLimits.setMaxValueSize(int MAX_VALUE_SIZE);
+await Countly.initWithConfig(config);
Segmentation Values
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.sdkInternalLimits.setMaxSegmentationValues(int MAX_SEGMENTATION_COUNT);
+await Countly.initWithConfig(config);
Breadcrumb Count
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.sdkInternalLimits.setMaxBreadcrumbCount(int MAX_BREADCRUMB_COUNT);
+await Countly.initWithConfig(config);
Stack Trace Lines Per Thread
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.sdkInternalLimits.setMaxStackTraceLinesPerThread(int MAX_STACK_THREAD);
+await Countly.initWithConfig(config);
Stack Trace Line Length
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.sdkInternalLimits.setMaxStackTraceLineLength(int MAX_STACK_LENGTH);
+await Countly.initWithConfig(config);
Attribution
+
+ This feature is not supported in the Web platform.
+
+ Direct Attribution
+
+
+ Direct attribution is only available for the Android platform.
+ recordDirectAttribution
to set attribution values during initialization.
+
+String campaignData = 'JSON_STRING';
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.recordDirectAttribution('CAMPAIN_TYPE', campaignData);
recordDirectAttribution
function to manually report
+ attribution later:
+
+String campaignData = 'JSON_STRING';
+Countly.recordDirectAttribution('CAMPAIN_TYPE', campaignData);
+String campaignData = '{cid:"[PROVIDED_CAMPAIGN_ID]", cuid:"[PROVIDED_CAMPAIGN_USER_ID]"}';
+Countly.recordDirectAttribution('countly', campaignData);
+ Indirect Attribution
+
+recordDirectAttribution
to set attribution values during initialization.
+
+Map<String, String> attributionValues = {};
+if(Platform.isIOS){
+ attributionValues[AttributionKey.IDFA] = 'IDFA';
+}
+else {
+ attributionValues[AttributionKey.AdvertisingID] = 'AdvertisingID';
+}
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.recordIndirectAttribution(attributionValues);
recordIndirectAttribution
function to manually
+ report attribution later
+
+Map<String, String> attributionValues = {};
+if(Platform.isIOS){
+ attributionValues[AttributionKey.IDFA] = 'IDFA';
+}
+else {
+ attributionValues[AttributionKey.AdvertisingID] = 'AdvertisingID';
+}
+
+Countly.recordIndirectAttribution(attributionValues);
Forcing HTTP POST
+setHttpPostForced
function after you called init
. You
+ can use the same function later in the app's life cycle to disable the override.
+ This function has to be called every time the app starts.
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setHttpPostForced(true); // default is false
Setting Custom Network Request Headers
+
+ This feature is not supported in the Web and Android platforms.
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setCustomNetworkRequestHeaders({'customHeaderKey': 'customHeaderValue'});
Interacting with the internal request queue
+init
function.
+
+//Replaces all requests with a different app key with the current app key.
+Countly.replaceAllAppKeysInQueueWithCurrentAppKey();
+//Removes all requests with a different app key in request queue.
+Countly.removeDifferentAppKeysFromQueue();
Drop Old Requests
+
+ This feature is not supported in the Web platform.
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setRequestDropAgeHours(10); // a positive integer indicating hours
setRequestDropAgeHours
method while configuring the
+ SDK initialization options, you can set a timeframe (in hours) after which the
+ requests would be removed from the request queue. For example, by setting this
+ option to 10, the SDK would ensure that no request older than 10 hours would
+ be sent to the server.
+Setting an event queue threshold
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setEventQueueSizeToSend(6);
Checking if the SDK has been initialized
+
+Countly.isInitialized();
Content Zone
+
+Countly.instance.content.enterContentZone()
+countlyConfig.content.setZoneTimerInterval(60) //in seconds
+Countly.instance.content.exitContentZone()
+ This feature is not supported in the Web platform.
+
+countlyConfig.content.setGlobalContentCallback((contentStatus, contentData))
+typedef ContentCallback = void Function(ContentStatus contentStatus, Map<String, dynamic> contentData);
Experimental Config
+
+ This feature is not supported in the Web platform.
+
+CountlyConfig config = CountlyConfig(COUNTLY_APP_KEY, COUNTLY_SERVER_URL);
+config.experimental.enablePreviousNameRecording().enableVisibilityTracking();
+
+
+config.experimental.enablePreviousNameRecording()
+config.experimental.enableVisibilityTracking()
A/B Experiment Testing
+
+ Variant Level Control
+
+
+ This feature is not supported in the Web platform.
+
+ Downloading
+
+
+Countly.instance.remoteConfig.testingDownloadVariantInformation((rResult, error){
+ // do sth
+})
+ Accessing
+
+
+Countly.sharedInstance().remoteConfig().testingGetAllVariants()
+{
+ "key_1" : ["variant_1", "variant_2"],
+ "key_2" : ["variant_3"]
+}
+
+Countly.sharedInstance().remoteConfig().testingGetVariantsForKey(String valueKey)
+["variant_1", "variant_2"]
+
+ Enrolling / Exiting
+
+
+Countly.instance.remoteConfig.testingEnrollIntoVariant(String keyName, String variantName, void Function(RequestResult, String?)? callback)
+ Experiment Level Control
+
+
+ This feature is not supported in the Web platform.
+
+ Downloading
+
+
+Countly.instance.remoteConfig.testingDownloadExperimentInformation((rResult, error){
+ // do sth
+})
+ Accessing
+
+
+Countly.sharedInstance().remoteConfig().testingGetAllExperimentInfo()
+class ExperimentInformation {
+ // same ID as used in the map
+ String experimentID;
+ // the name of the experiment
+ String experimentName;
+ // the description of the experiment
+ String experimentDescription;
+ // the name of the currently assigned variant for this user (e.g., 'Control Group', 'Variant A')
+ String currentVariant;
+ // variant information for this experiment
+ Map<String, Map<String, Object?>> variants;
+}
+{
+ some_exp_ID: {
+ experimentID: some_ID,
+ experimentName: some_name,
+ experimentDescription: some description,
+ currentVariant: variant_name,
+ variants: {
+ Control Group: {
+ key_1: val,
+ key_2: val,
+ },
+ Variant A: {
+ key_1: val,
+ key_2: val,
+ }
+ }
+ }
+}
+
+ Enrolling / Exiting
+
+Countly.instance.remoteConfig.testingEnrollIntoABExperiment(String expID);
+Countly.instance.remoteConfig.testingExitABExperiment(String expID);
+Extended Device ID Management
+
+Countly.changeDeviceId(DEVICE_ID, ON_SERVER);
onServer
bool is set to
+ true
, the old device ID on the server will be replaced with the
+ new one, and data associated with the old device ID will be merged automatically.
+ Otherwise, if onServer
bool is set to false
, the device
+ will be counted as a new device on the server.
+Temporary Device ID
+
+CountlyConfig config = CountlyConfig(SERVER_URL, APP_KEY);
+config.setDeviceId(Countly.deviceIDType["TemporaryDeviceID"]);
+
+// Initialize with that configuration
+Countly.initWithConfig(config);
+Countly.changeDeviceId(Countly.deviceIDType["TemporaryDeviceID"], ON_SERVER);
TemporaryDeviceID
for
+ deviceID
parameter, argument for onServer
parameter
+ does not matter.
+TemporaryDeviceID
, the SDK will
+ be in temporary device ID mode and all requests will be on hold, but they will
+ be persistently stored.
+Countly.changeDeviceId(DEVICE_ID, ON_SERVER);
method, all requests
+ which have been kept on hold until that point will start with the real device
+ ID
+FAQ
+What Information is Collected by the SDK?
+What Platforms are supported?
+pubspec.yaml
file:
dependencies:
- countly_flutter: ^25.1.1
+ countly_flutter: ^25.4.0
After you can install packages from the command line with Flutter:
@@ -753,15 +753,7 @@ Countly.initWithConfig(config);When the SDK is initialized for the first time with no device ID, it will generate - a device ID. -
-- Here are the underlying mechanisms used to generate that value for some platforms: -
-- For iOS: the device ID generated by the SDK is the Identifier For Vendor (IDFV). - For Android: the device ID generated by the SDK is the OpenUDID. For Web: the - device ID generated by the SDK is a random UUID. + a device ID that is a random UUID.
diff --git a/flutter/next.md b/flutter/next.md index fd721074..33ed4747 100644 --- a/flutter/next.md +++ b/flutter/next.md @@ -1,5 +1,5 @@
- This documentation is for the Countly Flutter SDK version 25.1.X. The SDK source + This documentation is for the Countly Flutter SDK version 25.4.X. The SDK source code repository can be found here.
@@ -79,7 +79,7 @@ Add this to your project'spubspec.yaml
file:
dependencies:
- countly_flutter: ^25.1.1
+ countly_flutter: ^25.4.0
After you can install packages from the command line with Flutter:
From d097d0b1a3831976703f71483bd9d2b65789898c Mon Sep 17 00:00:00 2001 From: Arif Burak Demiray+ Version 25.1 + - Version 24.11 - Version 24.7