CollectDeviceData: Flutter Plugin¶
The CollectDeviceData Flutter plugin provides a seamless interface to the underlying native Android SDK for collecting anonymized, non-personally identifiable information (non-PII) from user devices. It maintains compliance with privacy regulations by ensuring explicit user consent is obtained before initiating any data synchronization processes. This plugin enables Flutter apps to leverage native capabilities while respecting user privacy and platform guidelines.
Requirements¶
The CollectDeviceData Flutter plugin supports Android 5.0 and above (API level 21+), requires Java 8 or higher, and is built using AndroidX. To ensure compatibility with Android 7.0 and lower, desugaring must be enabled in your project.
gradle.properties configuration¶
In your gradle.properties file in android folder, add AWS_ACCESS_KEY and AWS_SECRET_KEY.
Note: Replace
<ACCESS_KEY>and<SECRET_KEY>with the credentials provided by the Credeau team.The following will be shared by the Credeau team:
<ACCESS_KEY><SECRET_KEY>
Add the required permissions in Android.manifest file:¶
<uses-feature android:name="android.hardware.telephony" android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-feature android:name="android.hardware.telephony" android:required="false" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE" tools:ignore="ProtectedPermissions" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_SMS" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
Installing the flutter_collect_data plugin:¶
Add the flutter_collect_data plugin into the pubspec.yaml file in your flutter app to implement the functions of the CollectDeviceData SDK.
Note:
<ref_name>will vary based on the concerned geography -
- India -
3.0.6- South-East Asia -
2.0.8- For Others, pLease connect with the Credeau team.
Now run flutter pub get
Import the Class from the flutter plugin.¶
Initialize the MethodChannelFlutterCollectData instance:¶
This creates an instance of the MethodChannelFlutterCollectData class, which is the bridge between Flutter and the native (Android/iOS) code.
Request Required Permissions:¶
Before calling any sync operation, request runtime permissions. For example:
Future<void> requestPermissions() async {
final smsStatus = await Permission.sms.request();
final phoneStatus = await Permission.phone.request();
final callLogReadStatus = await Permission.accessMediaLocation.request();
final contactsStatus = await Permission.contacts.request();
final locationStatus = await Permission.location.request();
}
Note: All permissions must be granted for successful data collection.
To match the details (Fullname, Phone no., Email):¶
Device matching enhances pattern recognition to identify users based on their email address, phone number, and name. All matching is performed locally on the device, ensuring that no personal information—such as phone numbers or email addresses—is transmitted off the device.
To use this feature, initialize the function by providing the customer's email address, phone number, and name.
To disable syncs for the particular categories (sms, call logs, contacts):¶
Disable syncs function disable the sync functionality for the categories passed in the parameters. This function needs to be called before the startBackgroundSyncProcess or syncAllData functions.
To start iterative sync process:¶
startBackgroundSyncProcess is a function that syncs the sms, call logs, contacts and device meta data for extracting features. This function iteratively runs in the background after set intervals, to keep the data syncing updated.
await plugin.startBackgroundSyncProcess(
userId: _usernameController.text,
clientName: "<CLIENT_NAME>",
clientKey: "<CLIENT_KEY>",
serverUrl: "<SERVER_URL>",
gapSeconds
).
then((result) {
print("Sync Completed and background process started!");
}).
catchError((error) {
print("Error during sync: $error");
}).
whenComplete(() {
print("Sync Completed!");
});
The following will be shared by the Credeau team:
<CLIENT_NAME><CLIENT_KEY><SERVER_URL>
To sync data once:¶
syncAllData is a function that syncs the sms, call logs, contacts and device meta data for extracting features. Unlike startBackgroundSyncProcess function this function only runs once when called.
The following will be shared by the Credeau team:
<CLIENT_NAME><CLIENT_KEY><SERVER_URL>
Send Incoming Notifications to the SDK¶
Some phone manufacturers implement aggressive battery optimization techniques that terminate background apps after periods of inactivity. This behavior can interfere with the DeviceConnect SDK’s ability to perform continuous syncing, as it depends on background data collection. When this happens, Credeau's server may need to request data from the SDK if the sync process has been halted.
To handle this, the SDK leverages Firebase Cloud Messaging (FCM). Forwarding FCM notifications helps the app sync the data even if it has been stopped by the device's background processes by sending in the silent notification to the deviec enabling it to resume data collection seamlessly.
To support this functionality, add the following code inside the overridden onMessageReceived method in your service class that extends FirebaseMessagingService.
Future<void> firebaseMessagingBackgroundHandler(RemoteMessage message) async {
final Map<String, dynamic> data = {
'data': message.data,
};
final isCredNotification =
await plugin.isDeviceDataCollectionNotification(messageData: data);
if (isCredNotification) {
print("CREDEAU NOTIFICATION");
await plugin.triggerOnMessageReceived(messageData: data);
} else {
print("NON CREDEAU NOTIFICATION");
}
}
Android Only Implementation¶
This guide explains how to integrate the flutter_collect_data SDK into a Flutter application that supports both Android and iOS, while ensuring the SDK runs only on Android. As most organizations choose Flutter to unify the codebase for iOS and Android apps, and since this SDK focuses mainly on Android devices, this documentation helps implement a safe integration that runs only on Android and skips iOS.
This implementation follows this approach -
- keep the SDK in
pubspec.yaml - execute SDK logic only on Android
- skip SDK execution on iOS
- group permission handling and sync startup into one reusable function
1. Add Dependency in pubspec.yaml¶
Add the SDK and required dependencies:
dependencies:
flutter:
sdk: flutter
flutter_collect_data:
git:
url: https://github.com/Credeau/flutter_collect_data
ref: <ref_name>
permission_handler: ^11.3.1
http: ^1.4.0
firebase_core: ^3.13.1
firebase_messaging: ^15.2.6
2. Import Required Packages¶
In your main.dart or integration file:
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter_collect_data/flutter_collect_data_method_channel.dart';
3. Create Plugin Instance¶
Create a shared plugin instance:
4. Request Android Permissions¶
Create a helper method to request all required permissions before starting sync.
Future<bool> _requestAndroidPermissions() async {
final statuses = await [
Permission.sms,
Permission.phone,
Permission.contacts,
Permission.location,
].request();
final mediaLocationStatus = await Permission.accessMediaLocation.request();
final allGranted = statuses.values.every((status) => status.isGranted) &&
mediaLocationStatus.isGranted;
if (!allGranted) {
debugPrint('One or more permissions were denied.');
}
return allGranted;
}
What this step does
This method:
- requests all permissions required by the SDK
- checks whether every required permission has been granted
- returns true only if all required permissions are available
5. Create a Single Android-Only SDK Start Flow¶
This method combines:
- platform check
- permission request
- device match parameter setup
- background sync start
Future<void> startMobileGatorForAndroidOnly({
required String fullName,
required String phone,
required String email,
required String userId,
required int gapSeconds,
}) async {
if (!Platform.isAndroid) {
debugPrint('MobileGator SDK skipped: iOS is not supported.');
return;
}
try {
final permissionsGranted = await _requestAndroidPermissions();
if (!permissionsGranted) {
debugPrint('Required permissions not granted. Aborting SDK startup.');
return;
}
await plugin.setDeviceMatchParams(
fullname: fullName,
phone: phone,
email: email,
);
await plugin.startBackgroundSyncProcess(
userId: userId,
clientName: '<CLIENT_NAME>',
clientKey: '<CLIENT_KEY>',
serverUrl: '<SERVER_URL>',
gapSeconds: gapSeconds,
);
debugPrint('Sync completed and background process started!');
} catch (error) {
debugPrint('Error during sync: $error');
} finally {
debugPrint('Sync flow finished.');
}
}
6. Call the SDK Flow Automatically¶
Instead of triggering the SDK with a UI button, you should start the SDK automatically when the app or a screen loads.
The most common approach is to call the SDK inside the initState() method of a StatefulWidget.
Example: Start SDK When Screen Loads
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
void initState() {
super.initState();
_startSdk();
}
Future<void> _startSdk() async {
await startMobileGatorForAndroidOnly(
fullName: _fullNameController.text.trim(),
phone: _phoneController.text.trim(),
email: _emailController.text.trim(),
userId: _usernameController.text.trim(),
gapSeconds: gapSeconds,
);
}
@override
Widget build(BuildContext context) {
return const Scaffold(
body: Center(
child: Text("MobileGator SDK Initializing..."),
),
);
}
}
What happens in this example:
- The screen is created.
initState()runs automatically._startSdk()is called._startSdk()triggersstartMobileGatorForAndroidOnly(...).- The SDK performs:
- Android platform check
- permission request
- device match parameter setup
- background sync initialization.