From cc649a6737cc1989e91a1f0dff3645e4e9da14b2 Mon Sep 17 00:00:00 2001 From: Igor Gottschalg Date: Mon, 24 Mar 2025 17:36:48 -0300 Subject: [PATCH 01/11] change notification service on Click handle --- example/lib/main.dart | 15 +++--- example/pubspec.lock | 2 +- package/lib/dito_sdk.dart | 5 +- .../lib/services/notification_service.dart | 51 ++++++++++--------- 4 files changed, 39 insertions(+), 34 deletions(-) diff --git a/example/lib/main.dart b/example/lib/main.dart index 9eada28..0196cb8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -19,10 +19,7 @@ Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { secretKey: Constants.ditoSecretKey, ); - await dito.initializePushNotificationService((Map payload) { - print(payload); - }); - + await dito.initializePushNotificationService(); final notification = DataPayload.fromJson(jsonDecode(message.data["data"])); dito.notificationService().showLocalNotification( @@ -47,15 +44,19 @@ void main() async { secretKey: Constants.ditoSecretKey, ); - await dito.initializePushNotificationService((Map payload) { - print(payload); - }); + await dito.initializePushNotificationService(); runApp( MultiProvider( providers: [ Provider( create: (context) { + dito.notificationService().onClick = ( + Map payload, + ) { + print(payload); + }; + return dito; }, ), diff --git a/example/pubspec.lock b/example/pubspec.lock index ba0d4bc..f6b2231 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -103,7 +103,7 @@ packages: path: "../package" relative: true source: path - version: "0.5.6" + version: "0.5.7" fake_async: dependency: transitive description: diff --git a/package/lib/dito_sdk.dart b/package/lib/dito_sdk.dart index 2a488ca..8026f5f 100644 --- a/package/lib/dito_sdk.dart +++ b/package/lib/dito_sdk.dart @@ -49,10 +49,9 @@ class DitoSDK { }; } - Future initializePushNotificationService( - {Function(Map)? onTap}) async { + Future initializePushNotificationService() async { await Firebase.initializeApp(); - await _notificationService.initialize(onTap); + await _notificationService.initialize(); RemoteMessage? initialMessage = await FirebaseMessaging.instance.getInitialMessage(); diff --git a/package/lib/services/notification_service.dart b/package/lib/services/notification_service.dart index b780b6e..288621e 100644 --- a/package/lib/services/notification_service.dart +++ b/package/lib/services/notification_service.dart @@ -17,7 +17,7 @@ class NotificationService { StreamController.broadcast(); final StreamController selectNotificationStream = StreamController.broadcast(); - Function(Map)? _onTap; + Function(Map)? onClick; AndroidNotificationDetails androidDetails = const AndroidNotificationDetails( 'dito_notifications', @@ -38,11 +38,7 @@ class NotificationService { _dito = dito; } - Future initialize(Function(Map)? onTap) async { - if (onTap != null) { - _onTap = onTap; - } - + Future initialize() async { if (Platform.isAndroid) { await FirebaseMessaging.instance.setAutoInitEnabled(true); } @@ -51,31 +47,34 @@ class NotificationService { _setupNotifications(); await FirebaseMessaging.instance - .setForegroundNotificationPresentationOptions( - badge: true, sound: true, alert: true); + .setForegroundNotificationPresentationOptions(); await checkPermissions(); - _onMessage(); + await _initializeMessages(); } Future getDeviceFirebaseToken() async { - if (Platform.isIOS) { - return FirebaseMessaging.instance.getAPNSToken(); - } else { - return FirebaseMessaging.instance.getToken(); - } + return FirebaseMessaging.instance.getToken(); } - _onMessage() { + Future _initializeMessages() async { + NotificationAppLaunchDetails? notificationAppLaunchDetails = + await localNotificationsPlugin.getNotificationAppLaunchDetails(); + + if (notificationAppLaunchDetails != null && + notificationAppLaunchDetails.didNotificationLaunchApp) { + onClick!(notificationAppLaunchDetails.notificationResponse + as Map); + } + FirebaseMessaging.onMessage.listen(handleMessage); - FirebaseMessaging.onMessageOpenedApp.listen(handleMessage); + + FirebaseMessaging.onMessageOpenedApp.listen( + (message) => selectNotificationStream.add(message.data["data"]), + ); } void handleMessage(RemoteMessage message) { - if (message.data["data"] == null) { - print("Data is not defined: ${message.data}"); - } - final notification = DataPayload.fromJson(jsonDecode(message.data["data"])); if (_messagingAllowed && notification.details.message.isNotEmpty) { @@ -131,7 +130,7 @@ class NotificationService { InitializationSettings(android: android, iOS: ios); await localNotificationsPlugin.initialize(initializationSettings, - onDidReceiveNotificationResponse: onTapNotification); + onDidReceiveNotificationResponse: onClickNotification); } void dispose() { @@ -154,6 +153,10 @@ class NotificationService { identifier: data.identifier, reference: data.reference); } + + if (onClick != null) { + onClick!(data.details.toJson()); + } } }); } @@ -176,11 +179,13 @@ class NotificationService { ); } - Future onTapNotification(NotificationResponse? response) async { + Future onClickNotification(NotificationResponse? response) async { if (response?.payload != null) { selectNotificationStream.add(response?.payload); - _onTap!(jsonDecode(response!.payload!)["details"]); + if (onClick != null) { + onClick!(jsonDecode(response!.payload!)["details"]); + } } } } From fef6cd91a3671f5a97db0c2c8a8ad88b77aa90c2 Mon Sep 17 00:00:00 2001 From: Igor Gottschalg Date: Tue, 25 Mar 2025 17:49:58 -0300 Subject: [PATCH 02/11] refactor message to use firebase mobile by default --- example/ios/Runner/AppDelegate.swift | 10 +- example/ios/Runner/Runner.entitlements | 8 + example/lib/app_form.dart | 24 +-- example/lib/main.dart | 41 ++--- example/pubspec.lock | 72 -------- package/lib/dito_sdk.dart | 13 -- package/lib/entity/data_payload.dart | 75 ++++---- .../lib/services/notification_service.dart | 166 +++--------------- package/pubspec.lock | 72 -------- package/pubspec.yaml | 1 - 10 files changed, 99 insertions(+), 383 deletions(-) create mode 100644 example/ios/Runner/Runner.entitlements diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 6266644..78006f2 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -7,7 +7,15 @@ import UIKit _ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { - GeneratedPluginRegistrant.register(with: self) + if #available(iOS 10.0, *) { + UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate + } + + func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { + completionHandler([.alert, .badge, .sound]) + } + + GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } } diff --git a/example/ios/Runner/Runner.entitlements b/example/ios/Runner/Runner.entitlements new file mode 100644 index 0000000..903def2 --- /dev/null +++ b/example/ios/Runner/Runner.entitlements @@ -0,0 +1,8 @@ + + + + + aps-environment + development + + diff --git a/example/lib/app_form.dart b/example/lib/app_form.dart index 754cb67..b52c661 100644 --- a/example/lib/app_form.dart +++ b/example/lib/app_form.dart @@ -1,5 +1,4 @@ import 'package:dito_sdk/dito_sdk.dart'; -import 'package:dito_sdk/entity/custom_notification.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -59,21 +58,6 @@ class AppFormState extends State { } } - handleLocalNotification() { - dito.notificationService().addNotificationToStream( - CustomNotification( - id: 123, - title: "Notificação local", - body: - "Está é uma mensagem de teste, validando o stream de dados das notificações locais", - ), - ); - - ScaffoldMessenger.of( - context, - ).showSnackBar(const SnackBar(content: Text('Push enviado para a fila'))); - } - return Form( key: _formKey, child: Column( @@ -109,16 +93,12 @@ class AppFormState extends State { child: Column( children: [ FilledButton( - child: const Text('Registrar Identify'), onPressed: handleIdentify, + child: const Text('Registrar Identify'), ), OutlinedButton( - child: const Text('Receber Notification'), onPressed: handleNotification, - ), - TextButton( - child: const Text('Criar notificação local'), - onPressed: handleLocalNotification, + child: const Text('Receber Notification'), ), ], ), diff --git a/example/lib/main.dart b/example/lib/main.dart index 0196cb8..a5455e8 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,8 +1,4 @@ -import 'dart:convert'; - import 'package:dito_sdk/dito_sdk.dart'; -import 'package:dito_sdk/entity/custom_notification.dart'; -import 'package:dito_sdk/entity/data_payload.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; @@ -11,25 +7,24 @@ import 'package:provider/provider.dart'; import 'app.dart'; import 'constants.dart'; -@pragma('vm:entry-point') -Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { +Future _setupDito() async { DitoSDK dito = DitoSDK(); dito.initialize( apiKey: Constants.ditoApiKey, secretKey: Constants.ditoSecretKey, ); - await dito.initializePushNotificationService(); - final notification = DataPayload.fromJson(jsonDecode(message.data["data"])); - dito.notificationService().showLocalNotification( - CustomNotification( - id: message.hashCode, - title: notification.details.title, - body: notification.details.message, - payload: notification, - ), - ); + dito.notificationService().onClick = (String link) { + print(link); + }; + + return dito; +} + +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + await _setupDito(); } void main() async { @@ -38,25 +33,13 @@ void main() async { await Firebase.initializeApp(); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); - DitoSDK dito = DitoSDK(); - dito.initialize( - apiKey: Constants.ditoApiKey, - secretKey: Constants.ditoSecretKey, - ); - - await dito.initializePushNotificationService(); + final dito = await _setupDito(); runApp( MultiProvider( providers: [ Provider( create: (context) { - dito.notificationService().onClick = ( - Map payload, - ) { - print(payload); - }; - return dito; }, ), diff --git a/example/pubspec.lock b/example/pubspec.lock index f6b2231..6ba4947 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -9,14 +9,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.35" - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" async: dependency: transitive description: @@ -73,14 +65,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" - dbus: - dependency: transitive - description: - name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" - url: "https://pub.dev" - source: hosted - version: "0.7.11" device_info_plus: dependency: transitive description: @@ -189,30 +173,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" - flutter_local_notifications: - dependency: transitive - description: - name: flutter_local_notifications - sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610 - url: "https://pub.dev" - source: hosted - version: "18.0.1" - flutter_local_notifications_linux: - dependency: transitive - description: - name: flutter_local_notifications_linux - sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01" - url: "https://pub.dev" - source: hosted - version: "5.0.0" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52" - url: "https://pub.dev" - source: hosted - version: "8.0.0" flutter_test: dependency: "direct dev" description: flutter @@ -327,14 +287,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" - url: "https://pub.dev" - source: hosted - version: "6.1.0" platform: dependency: transitive description: @@ -476,14 +428,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" - timezone: - dependency: transitive - description: - name: timezone - sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d - url: "https://pub.dev" - source: hosted - version: "0.10.0" typed_data: dependency: transitive description: @@ -532,22 +476,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.5" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.dev" - source: hosted - version: "6.5.0" sdks: dart: ">=3.7.2 <4.0.0" flutter: ">=3.24.0" diff --git a/package/lib/dito_sdk.dart b/package/lib/dito_sdk.dart index 8026f5f..de8e5a2 100644 --- a/package/lib/dito_sdk.dart +++ b/package/lib/dito_sdk.dart @@ -3,8 +3,6 @@ library dito_sdk; import 'dart:convert'; import 'package:dito_sdk/entity/domain.dart'; -import 'package:firebase_core/firebase_core.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:http/http.dart' as http; import 'constants.dart'; @@ -50,18 +48,7 @@ class DitoSDK { } Future initializePushNotificationService() async { - await Firebase.initializeApp(); await _notificationService.initialize(); - - RemoteMessage? initialMessage = - await FirebaseMessaging.instance.getInitialMessage(); - - if (initialMessage != null) { - _notificationService.handleMessage(initialMessage); - } - - FirebaseMessaging.onMessageOpenedApp - .listen(_notificationService.handleMessage); } void _checkConfiguration() { diff --git a/package/lib/entity/data_payload.dart b/package/lib/entity/data_payload.dart index 34bec79..f094acb 100644 --- a/package/lib/entity/data_payload.dart +++ b/package/lib/entity/data_payload.dart @@ -1,48 +1,57 @@ -class Details { - final String? link; - final String message; - final String title; - - Details(this.link, this.title, this.message); - - factory Details.fromJson(dynamic json) { - assert(json is Map); - return Details(json["link"], json["title"], json["message"]); - } - - Map toJson() => { - 'link': link, - 'message': message, - 'title': title, - }; -} - class DataPayload { final String reference; - final String identifier; + final String user_id; final String notification; - final String notification_log_id; - final Details details; - - DataPayload(this.reference, this.identifier, this.notification, - this.notification_log_id, this.details); + final String log_id; + final String notification_name; + final String device_type; + final String title; + final String message; + final String link; + final String icon; + final String channel; + + DataPayload( + this.reference, + this.user_id, + this.notification, + this.log_id, + this.notification_name, + this.device_type, + this.title, + this.message, + this.link, + this.icon, + this.channel); factory DataPayload.fromJson(dynamic json) { assert(json is Map); return DataPayload( - json["reference"], - json["identifier"], - json["notification"], - json["notification_log_id"], - Details.fromJson(json["details"])); + json["reference"] ?? "", + json["user_id"] ?? "", + json["notification"] ?? "", + json["log_id"] ?? "", + json["notification_name"] ?? "", + json["device_type"] ?? "", + json["title"] ?? "", + json["message"] ?? "", + json["link"] ?? "", + json["icon"] ?? "", + json["channel"] ?? ""); } Map toJson() => { 'reference': reference, - 'identifier': identifier, + 'user_id': user_id, 'notification': notification, - 'notification_log_id': notification_log_id, - 'details': details.toJson(), + 'log_id': log_id, + 'notification_name': notification_name, + 'device_type': device_type, + 'title': title, + 'message': message, + 'link': link, + 'icon': icon, + 'channel': channel, }; } diff --git a/package/lib/services/notification_service.dart b/package/lib/services/notification_service.dart index 288621e..e012ca5 100644 --- a/package/lib/services/notification_service.dart +++ b/package/lib/services/notification_service.dart @@ -1,38 +1,14 @@ import 'dart:async'; -import 'dart:convert'; import 'dart:io'; import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import '../dito_sdk.dart'; -import '../entity/custom_notification.dart'; import '../entity/data_payload.dart'; class NotificationService { - bool _messagingAllowed = false; late DitoSDK _dito; - late FlutterLocalNotificationsPlugin localNotificationsPlugin; - final StreamController didReceiveLocalNotificationStream = - StreamController.broadcast(); - final StreamController selectNotificationStream = - StreamController.broadcast(); - Function(Map)? onClick; - - AndroidNotificationDetails androidDetails = const AndroidNotificationDetails( - 'dito_notifications', - 'Notifications sended by Dito', - channelDescription: 'Notifications sended by Dito', - importance: Importance.max, - priority: Priority.max, - enableVibration: true, - ); - - DarwinNotificationDetails iosDetails = const DarwinNotificationDetails( - presentAlert: true, - presentBadge: true, - presentSound: true, - presentBanner: true); + Function(String)? onClick; NotificationService(DitoSDK dito) { _dito = dito; @@ -43,11 +19,9 @@ class NotificationService { await FirebaseMessaging.instance.setAutoInitEnabled(true); } - localNotificationsPlugin = FlutterLocalNotificationsPlugin(); - _setupNotifications(); - await FirebaseMessaging.instance - .setForegroundNotificationPresentationOptions(); + .setForegroundNotificationPresentationOptions( + alert: true, badge: true, sound: true); await checkPermissions(); await _initializeMessages(); @@ -58,36 +32,39 @@ class NotificationService { } Future _initializeMessages() async { - NotificationAppLaunchDetails? notificationAppLaunchDetails = - await localNotificationsPlugin.getNotificationAppLaunchDetails(); + RemoteMessage? initialMessage = + await FirebaseMessaging.instance.getInitialMessage(); - if (notificationAppLaunchDetails != null && - notificationAppLaunchDetails.didNotificationLaunchApp) { - onClick!(notificationAppLaunchDetails.notificationResponse - as Map); + if (initialMessage != null) { + _handleMessage(initialMessage); } - FirebaseMessaging.onMessage.listen(handleMessage); + FirebaseMessaging.onMessageOpenedApp.listen(_handleOnNotificationClick); + FirebaseMessaging.onMessage.listen(_handleMessage); + } - FirebaseMessaging.onMessageOpenedApp.listen( - (message) => selectNotificationStream.add(message.data["data"]), - ); + void _handleOnNotificationClick(RemoteMessage message) async { + final data = DataPayload.fromJson(message.data); + await _handleClick(data); } - void handleMessage(RemoteMessage message) { - final notification = DataPayload.fromJson(jsonDecode(message.data["data"])); + Future _handleClick(DataPayload data) async { + if (data.notification.isNotEmpty) { + await _dito.openNotification( + notificationId: data.notification, + identifier: data.user_id, + reference: data.reference); + } - if (_messagingAllowed && notification.details.message.isNotEmpty) { - didReceiveLocalNotificationStream.add(CustomNotification( - id: message.hashCode, - title: notification.details.title, - body: notification.details.message, - payload: notification)); + if (onClick != null) { + onClick!(data.link); } } - addNotificationToStream(CustomNotification notification) { - didReceiveLocalNotificationStream.add(notification); + void _handleMessage(RemoteMessage message) async { + await _dito.trackEvent( + eventName: + "receive-${Platform.isIOS ? "ios" : "android"}-notification"); } Future checkPermissions() async { @@ -95,97 +72,6 @@ class NotificationService { if (settings.authorizationStatus != AuthorizationStatus.authorized) { await FirebaseMessaging.instance.requestPermission(); - settings = await FirebaseMessaging.instance.getNotificationSettings(); - _messagingAllowed = - (settings.authorizationStatus == AuthorizationStatus.authorized); - } else { - _messagingAllowed = true; - } - } - - _setupAndroidChannel() async { - const AndroidNotificationChannel channel = AndroidNotificationChannel( - 'dito_notifications', - 'Notifications sended by Dito', - importance: Importance.max, - ); - - await localNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannel(channel); - } - - _setupNotifications() async { - await _initializeNotifications(); - await _setupAndroidChannel(); - _listenStream(); - } - - _initializeNotifications() async { - const android = AndroidInitializationSettings('@mipmap/ic_launcher'); - const ios = DarwinInitializationSettings(); - - const InitializationSettings initializationSettings = - InitializationSettings(android: android, iOS: ios); - - await localNotificationsPlugin.initialize(initializationSettings, - onDidReceiveNotificationResponse: onClickNotification); - } - - void dispose() { - didReceiveLocalNotificationStream.close(); - selectNotificationStream.close(); - } - - _listenStream() { - didReceiveLocalNotificationStream.stream - .listen((CustomNotification receivedNotification) { - showLocalNotification(receivedNotification); - }); - selectNotificationStream.stream.listen((String? payload) async { - if (payload != null) { - final data = DataPayload.fromJson(jsonDecode(payload)); - - if (data.notification.isNotEmpty) { - await _dito.openNotification( - notificationId: data.notification, - identifier: data.identifier, - reference: data.reference); - } - - if (onClick != null) { - onClick!(data.details.toJson()); - } - } - }); - } - - setAndroidDetails(AndroidNotificationDetails details) { - androidDetails = details; - } - - setIosDetails(DarwinNotificationDetails details) { - iosDetails = details; - } - - showLocalNotification(CustomNotification notification) { - localNotificationsPlugin.show( - notification.id, - notification.title, - notification.body, - NotificationDetails(android: androidDetails, iOS: iosDetails), - payload: jsonEncode(notification.payload?.toJson()), - ); - } - - Future onClickNotification(NotificationResponse? response) async { - if (response?.payload != null) { - selectNotificationStream.add(response?.payload); - - if (onClick != null) { - onClick!(jsonDecode(response!.payload!)["details"]); - } } } } diff --git a/package/pubspec.lock b/package/pubspec.lock index 0edde10..6a30840 100644 --- a/package/pubspec.lock +++ b/package/pubspec.lock @@ -9,14 +9,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.53" - args: - dependency: transitive - description: - name: args - sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 - url: "https://pub.dev" - source: hosted - version: "2.7.0" async: dependency: transitive description: @@ -65,14 +57,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" - dbus: - dependency: transitive - description: - name: dbus - sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" - url: "https://pub.dev" - source: hosted - version: "0.7.11" device_info_plus: dependency: "direct main" description: @@ -174,30 +158,6 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" - flutter_local_notifications: - dependency: "direct main" - description: - name: flutter_local_notifications - sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35" - url: "https://pub.dev" - source: hosted - version: "17.2.4" - flutter_local_notifications_linux: - dependency: transitive - description: - name: flutter_local_notifications_linux - sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af - url: "https://pub.dev" - source: hosted - version: "4.0.1" - flutter_local_notifications_platform_interface: - dependency: transitive - description: - name: flutter_local_notifications_platform_interface - sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" - url: "https://pub.dev" - source: hosted - version: "7.2.0" flutter_test: dependency: "direct dev" description: flutter @@ -304,14 +264,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" - petitparser: - dependency: transitive - description: - name: petitparser - sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" - url: "https://pub.dev" - source: hosted - version: "6.1.0" platform: dependency: transitive description: @@ -445,14 +397,6 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" - timezone: - dependency: transitive - description: - name: timezone - sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" - url: "https://pub.dev" - source: hosted - version: "0.9.4" typed_data: dependency: transitive description: @@ -501,22 +445,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" - xdg_directories: - dependency: transitive - description: - name: xdg_directories - sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" - url: "https://pub.dev" - source: hosted - version: "1.1.0" - xml: - dependency: transitive - description: - name: xml - sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 - url: "https://pub.dev" - source: hosted - version: "6.5.0" sdks: dart: ">=3.7.0 <4.0.0" flutter: ">=3.24.0" diff --git a/package/pubspec.yaml b/package/pubspec.yaml index 19b9dbb..d4e4b0e 100644 --- a/package/pubspec.yaml +++ b/package/pubspec.yaml @@ -16,7 +16,6 @@ dependencies: package_info_plus: ">=4.1.0" sqflite: ">=2.3.3" firebase_messaging: ">=14.9.1" - flutter_local_notifications: ">=17.1.0" firebase_core: ">=2.30.1" sqflite_common_ffi: ">=2.3.3" From 5d205373913b8ec088b9f4be2a2accf1dc6f9fd5 Mon Sep 17 00:00:00 2001 From: Igor Gottschalg Date: Thu, 22 May 2025 11:29:00 -0300 Subject: [PATCH 03/11] refactor message to use firebase mobile by default --- example/ios/Podfile.lock | 2 +- example/lib/app.dart | 20 +- example/lib/app_form.dart | 103 ++-- example/lib/main.dart | 9 +- example/pubspec.lock | 74 ++- example/pubspec.yaml | 2 +- package/README.md | 487 ++++++------------ .../lib/services/notification_service.dart | 80 +++ package/pubspec.lock | 72 +++ package/pubspec.yaml | 3 +- 10 files changed, 462 insertions(+), 390 deletions(-) diff --git a/example/ios/Podfile.lock b/example/ios/Podfile.lock index 0035602..394c501 100644 --- a/example/ios/Podfile.lock +++ b/example/ios/Podfile.lock @@ -124,7 +124,7 @@ SPEC CHECKSUMS: FirebaseInstallations: 913cf60d0400ebd5d6b63a28b290372ab44590dd FirebaseMessaging: 88950ba9485052891ebe26f6c43a52bb62248952 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 - flutter_local_notifications: df98d66e515e1ca797af436137b4459b160ad8c9 + flutter_local_notifications: 4cde75091f6327eb8517fa068a0a5950212d2086 GoogleDataTransport: 6c09b596d841063d76d4288cc2d2f42cc36e1e2a GoogleUtilities: ea963c370a38a8069cc5f7ba4ca849a60b6d7d15 nanopb: 438bc412db1928dac798aa6fd75726007be04262 diff --git a/example/lib/app.dart b/example/lib/app.dart index ba48a06..706a8ca 100644 --- a/example/lib/app.dart +++ b/example/lib/app.dart @@ -25,19 +25,13 @@ class _AppState extends State { @override Widget build(BuildContext context) { return MaterialApp( - debugShowCheckedModeBanner: false, - title: 'Notification Demo', - theme: ThemeData( - primarySwatch: Colors.amber, - useMaterial3: true, - ), - home: Scaffold( - appBar: AppBar( - title: const Text('Test SDK Flutter'), - ), - body: const Center( - child: AppForm(), - )) + debugShowCheckedModeBanner: false, + title: 'Notification Demo', + theme: ThemeData(primarySwatch: Colors.amber, useMaterial3: true), + home: Scaffold( + appBar: AppBar(title: const Text('Test SDK Flutter')), + body: const Center(child: AppForm()), + ), ); } } diff --git a/example/lib/app_form.dart b/example/lib/app_form.dart index b52c661..e2173ca 100644 --- a/example/lib/app_form.dart +++ b/example/lib/app_form.dart @@ -14,19 +14,27 @@ class AppForm extends StatefulWidget { class AppFormState extends State { final _formKey = GlobalKey(); + final cpfController = TextEditingController(text: "33333333333"); + final emailController = TextEditingController(text: "testepush@dito.com.br"); + @override Widget build(BuildContext context) { final dito = Provider.of(context); - String cpf = "66666666666"; - String email = "testepush@dito.com.br"; + @override + void dispose() { + // Clean up the controller when the widget is disposed. + cpfController.dispose(); + emailController.dispose(); + super.dispose(); + } identify() async { dito.identify( - userID: cpf, - cpf: cpf, - name: 'Teste SDK Flutter', - email: email, + userID: cpfController.text, + cpf: cpfController.text, + name: emailController.text, + email: emailController.text, ); await dito.identifyUser(); @@ -60,51 +68,48 @@ class AppFormState extends State { return Form( key: _formKey, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - TextFormField( - onSaved: (value) { - cpf = value!; - }, - initialValue: cpf, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter some text'; - } - return null; - }, - ), - TextFormField( - onSaved: (value) { - email = value!; - }, - initialValue: email, - validator: (value) { - if (value == null || value.isEmpty) { - return 'Please enter some text'; - } - return null; - }, - ), - Center( - child: Padding( - padding: const EdgeInsets.all(15.0), - child: Column( - children: [ - FilledButton( - onPressed: handleIdentify, - child: const Text('Registrar Identify'), - ), - OutlinedButton( - onPressed: handleNotification, - child: const Text('Receber Notification'), - ), - ], + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + controller: cpfController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter some text'; + } + return null; + }, + ), + TextFormField( + controller: emailController, + validator: (value) { + if (value == null || value.isEmpty) { + return 'Please enter some text'; + } + return null; + }, + ), + Center( + child: Padding( + padding: const EdgeInsets.all(15.0), + child: Column( + children: [ + FilledButton( + onPressed: handleIdentify, + child: const Text('Registrar Identify'), + ), + OutlinedButton( + onPressed: handleNotification, + child: const Text('Receber Notification'), + ), + ], + ), ), ), - ), - ], + ], + ), ), ); } diff --git a/example/lib/main.dart b/example/lib/main.dart index a5455e8..36b586b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -5,14 +5,13 @@ import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import 'app.dart'; -import 'constants.dart'; Future _setupDito() async { + final String ditoApiKey = String.fromEnvironment('API_KEY'); + final String ditoSecretKey = String.fromEnvironment('SECRET_KEY'); + DitoSDK dito = DitoSDK(); - dito.initialize( - apiKey: Constants.ditoApiKey, - secretKey: Constants.ditoSecretKey, - ); + dito.initialize(apiKey: ditoApiKey, secretKey: ditoSecretKey); await dito.initializePushNotificationService(); dito.notificationService().onClick = (String link) { diff --git a/example/pubspec.lock b/example/pubspec.lock index 6ba4947..6ea8cea 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.35" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -65,6 +73,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.8" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" device_info_plus: dependency: transitive description: @@ -87,7 +103,7 @@ packages: path: "../package" relative: true source: path - version: "0.5.7" + version: "0.5.8" fake_async: dependency: transitive description: @@ -173,6 +189,30 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_local_notifications: + dependency: transitive + description: + name: flutter_local_notifications + sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35" + url: "https://pub.dev" + source: hosted + version: "17.2.4" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af + url: "https://pub.dev" + source: hosted + version: "4.0.1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" + url: "https://pub.dev" + source: hosted + version: "7.2.0" flutter_test: dependency: "direct dev" description: flutter @@ -287,6 +327,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + url: "https://pub.dev" + source: hosted + version: "6.1.0" platform: dependency: transitive description: @@ -428,6 +476,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + timezone: + dependency: transitive + description: + name: timezone + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" + url: "https://pub.dev" + source: hosted + version: "0.9.4" typed_data: dependency: transitive description: @@ -476,6 +532,22 @@ packages: url: "https://pub.dev" source: hosted version: "1.1.5" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" sdks: dart: ">=3.7.2 <4.0.0" flutter: ">=3.24.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index d776aaf..6640bfd 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -16,7 +16,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 1.0.0+1 +version: 1.0.0+3 environment: sdk: ^3.7.2 diff --git a/package/README.md b/package/README.md index 7a5553c..97643b7 100644 --- a/package/README.md +++ b/package/README.md @@ -1,392 +1,241 @@ -# Dito SDK (Flutter) -DitoSDK é uma biblioteca Dart que fornece métodos para integrar aplicativos com a plataforma da -Dito. Ela permite identificar usuários, registrar eventos e enviar dados personalizados. +# 📦 Dito SDK (Flutter) -## Instalação +`dito_sdk` é uma biblioteca Flutter que facilita a integração com a plataforma Dito, permitindo a identificação de usuários, o envio de eventos personalizados e a integração com notificações push. -Para instalar a biblioteca DitoSDK em seu aplicativo Flutter, você deve seguir as instruções -fornecidas [nesse link](https://pub.dev/packages/dito_sdk/install). +[![pub package](https://img.shields.io/pub/v/dito_sdk.svg)](https://pub.dev/packages/dito_sdk) -## Métodos +## ✨ Recursos -### initialize() +- 📌 Identificação de usuários +- 📊 Envio e rastreamento de eventos personalizados +- 📱 Integração com notificações push (via Firebase Cloud Messaging) +- 💾 Armazenamento local de eventos para usuários não identificados +- 🔗 Suporte a deep linking via notificações -Este método deve ser chamado antes de qualquer outra operação com o SDK. Ele inicializa as chaves de -API e SECRET necessárias para a autenticação na plataforma Dito. +--- -```dart -void initialize({required String apiKey, required String secretKey}); -``` - -#### Parâmetros - -- **apiKey** _(String, obrigatório)_: A chave de API da plataforma Dito. -- **secretKey** _(String, obrigatório)_: O segredo da chave de API da plataforma Dito. - -### initializePushNotificationService() - -Este método deve ser chamado após a inicialização da SDK. Ele inicializa as configurações e serviços -necessários para o funcionamento de push notifications da plataforma Dito. - -```dart -void initializePushNotificationService(); -``` +## 🚀 Começando -#### Parâmetros +### ✅ Pré-requisitos -- **apiKey** _(String, obrigatório)_: A chave de API da plataforma Dito. -- **secretKey** _(String, obrigatório)_: O segredo da chave de API da plataforma Dito. +- **Dart SDK**: `>=2.12.0 <3.0.0` *(ajuste conforme a versão utilizada)* +- **Flutter SDK**: `>=1.20.0` +- **Conta na Dito**: Você precisará de uma conta ativa e credenciais da plataforma Dito (API Key e Secret). -### identify() +### 📦 Instalação -Este método define o ID do usuário que será usado para todas as operações subsequentes. +Adicione o pacote no seu projeto Flutter: -```dart -void identify(String userId); +```bash +flutter pub add dito_sdk ``` -- **userID** _(String, obrigatório)_: Id para identificar o usuário na plataforma da Dito. -- **name** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **email** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **gender** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **birthday** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **location** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **customData** _(Map)_: Parâmetro para identificar o usuário na plataforma da - Dito. +### ⚙️ Inicialização -#### identifyUser() - -Este método registra o usuário na plataforma da Dito com as informações fornecidas anteriormente -usando o método `identify()`. +Importe o pacote e inicialize o SDK com suas credenciais: ```dart -Future identifyUser +import 'package:dito_sdk/dito_sdk.dart'; +void main() async { +// Certifique-se de inicializar o binding do Flutter se estiver usando em um app Flutter +WidgetsFlutterBinding. ensureInitialized(); +final String ditoApiKey = String.fromEnvironment('API_KEY'); +final String ditoSecretKey = String.fromEnvironment('SECRET_KEY'); -() async; +DitoSDK dito = DitoSDK(); +dito.initialize(apiKey: ditoApiKey, secretKey: ditoSecretKey); +await dito.initializePushNotificationService(); //está linha é necessário se for utilizar o serviço de push notification, aqui é feito o registro automático dos eventos de push; ``` -#### Exception +## 🛠️ Uso da SDK -- Caso a SDK ainda não tenha `userId` cadastrado quando esse método for chamado, irá ocorrer um erro - no aplicativo. (utilize o método `setUserId()` para definir o `userId`) - -### trackEvent() - -O método `trackEvent()` tem a finalidade de registrar um evento na plataforma da Dito. Caso o userID -já tenha sido registrado, o evento será enviado imediatamente. No entanto, caso o userID ainda não -tenha sido registrado, o evento será armazenado localmente e posteriormente enviado quando o userID -for registrado por meio do método `setUserId()`. +### 👤 Identificando um usuário ```dart -Future trackEvent -( -{ -required -String -eventName, -double? revenue, -Map -? -customData -, -} -) -async; +dito.identify( + userID: 'id_do_usuario', + cpf: 'cpf_do_usuario', + name: 'nome_do_usuario', + email: 'email_do_usuario', +); +await dito.identifyUser(); ``` +📌 Enquanto o usuário não for identificado, os eventos serão armazenados localmente. +___ -#### Parâmetros - -- **eventName** _(String, obrigatório)_: O nome do evento a ser registrado. -- **revenue** _(double, opcional)_: A receita associada ao evento. -- **customData** _(Map, opcional)_: Dados personalizados adicionais associados ao - evento. - -### registryMobileToken() - -Este método permite registrar um token mobile para o usuário. - +### 📈 Enviando eventos ```dart -Future registryMobileToken({ - required String token, - String? platform, -}); -``` - -#### Parâmetros - -- **token** _(String, obrigatório)_: O token mobile que será registrado. -- **platform** _(String, opcional)_: Nome da plataforma que o usuário está acessando o aplicativo. - Valores válidos: 'iPhone' e 'Android'. - `
`_Caso não seja passado algum valor nessa prop, a sdk irá pegar por default o valor - pelo `platform`._ - -#### Exception - -- Caso seja passado um valor diferente de 'iPhone' ou 'Android' na propriedade platform, irá ocorrer - um erro no aplicativo. -- Caso a SDK ainda não tenha `identify` cadastrado quando esse método for chamado, irá ocorrer um - erro no aplicativo. (utilize o método `identify()` para definir o id do usuário) - -### removeMobileToken() - -Este método permite remover um token mobile para o usuário. - -```dart -Future removeMobileToken({ - required String token, - String? platform, -}); +await dito.trackEvent( + eventName: 'comprou produto', + customData: { + 'produto': 'produtoX', + 'sku_produto': '99999999', + }, +); ``` +___ -#### Parâmetros - -- **token** _(String, obrigatório)_: O token mobile que será removido. -- **platform** _(String, opcional)_: Nome da plataforma que o usuário está acessando o aplicativo. - Valores válidos: 'iPhone' e 'Android'. - `
`_Caso não seja passado algum valor nessa prop, a sdk irá pegar por default o valor - pelo `platform`._ - -#### Exception - -- Caso seja passado um valor diferente de 'iPhone' ou 'Android' na propriedade platform, irá ocorrer - um erro no aplicativo. -- Caso a SDK ainda não tenha `identify` cadastrado quando esse método for chamado, irá ocorrer um - erro no aplicativo. (utilize o método `identify()` para definir o id do usuário) - -### openNotification() - -Este método permite registrar a abertura de uma notificação mobile. +### 📲 Registrando o dispositivo ```dart -Future openNotification -( -{ -required -String -notificationId, -required String identifier, -required String reference -}) async +final token = await dito.notificationService().getDeviceFirebaseToken(); +await dito.registryMobileToken(token: token); ``` +*Importante: o usuário precisa estar identificado antes do registro do token.* +___ -#### Parâmetros - -- **notificationId** _(String, obrigatório)_: Id da notificação da Dito recebida pelo aplicativo. -- **identifier** _(String, obrigatório)_: Parâmetro para dentificar a notificação na plataforma da - Dito. -- **reference** _(String, obrigatório)_: Parâmetro para identificar o usuário na plataforma da Dito. - -###### Observações - -- Esses parâmetros estarão presentes no data da notificação - -## Classes - -### User +## 🔔 Integração com Push Notifications (FCM) -Classe para manipulação dos dados do usuário. +### 1. Instale os pacotes Firebase: ```dart - -User user = User(sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); +dart pub global activate flutterfire_cli +flutter pub add firebase_core firebase_messaging +flutterfire configure ``` +Siga as instruções para configurar o Firebase para Android e iOS. -#### Parâmetros - -- **userID** _(String, obrigatório)_: Id para identificar o usuário na plataforma da Dito. -- **name** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **email** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **gender** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **birthday** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **location** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **customData** _(Map)_: Parâmetro para identificar o usuário na plataforma da - Dito. - -## Exemplos - -### Uso básico da SDK: +### 2. Exemplo de uso com notificação: ```dart import 'package:dito_sdk/dito_sdk.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; -final dito = DitoSDK(); - -// Inicializa a SDK com suas chaves de API -dito.initialize -( -apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); +Future _setupDito() async { + final String apiKey = String.fromEnvironment('API_KEY'); + final String secretKey = String.fromEnvironment('SECRET_KEY'); -// Define ou atualiza informações do usuário na instância (neste momento, ainda não há comunicação com a Dito) -dito.identify( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); + DitoSDK dito = DitoSDK(); + dito.initialize(apiKey: apiKey, secretKey: secretKey); + await dito.initializePushNotificationService(); -// Envia as informações do usuário (que foram definidas ou atualizadas pelo identify) para a Dito -await dito.identifyUser(); + dito.notificationService().onClick = (String link) { + deepLinkHandle(link); // sua lógica de navegação + }; -// Registra um evento na Dito -await dito.trackEvent(eventName: ' -login -' -); -``` - -### Uso avançado da SDK: + return dito; +} -#### main.dart +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + await _setupDito(); +} -```dart -import 'package:dito_sdk/dito_sdk.dart'; +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); -final dito = DitoSDK(); + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); -// Inicializa a SDK com suas chaves de API -dito.initialize -( -apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); + final dito = await _setupDito(); + // continuação do app... +} ``` -#### login.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; +___ -final dito = DitoSDK(); +## 📚 API da SDK -// Define o ID do usuário -dito.setUserId -('id_do_usuario -' -);dito.identify( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); -await dito.identifyUser(); -``` +`initialize()` -#### arquivoX.dart +Inicializa a SDK com suas credenciais. -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Define ou atualiza informações do usuário na instância (neste momento, ainda não há comunicação com a Dito) -dito.identify -( -sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); -await dito.identifyUser(); -await dito.registryMobileToken( -token -: -token -); +```dart +void initialize({required String apiKey, required String secretKey}); +``` + +--- +`initializePushNotificationService()` +Ativa os serviços de notificação da Dito. +```dart +Future initializePushNotificationService(); ``` + +--- -#### arquivoY.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Define ou atualiza informações do usuário na instância (neste momento, ainda não há comunicação com a Dito) -dito.identify -( -sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'Rio de Janeiro', { -'loja preferida': 'LojaX', -'canal preferido': 'Loja Física' +`identify()` +Configura os dados de identificação do usuário. +```dart +void identify({ + required String userID, + String? name, + String? email, + String? gender, + String? birthday, + String? location, + String? cpf, + Map? customData, }); -await -dito -. -identifyUser -( -); ``` -Isso resultará no envio do seguinte payload do usuário ao chamar `identifyUser()`: - -```javascript -{ - name: 'João da Silva', - email: 'joao@example.com', - location: 'Rio de Janeiro', - customData: { - 'loja preferida': 'LojaX', - 'canal preferido': 'Loja Física' - } -} -``` - -A nossa SDK é uma instância única, o que significa que, mesmo que ela seja inicializada em vários -arquivos ou mais de uma vez, ela sempre referenciará as mesmas informações previamente armazenadas. -Isso nos proporciona a flexibilidade de chamar o método `identify()` a qualquer momento para -adicionar ou atualizar os detalhes do usuário, e somente quando necessário, enviá-los através do -método `identifyUser()`. - -#### arquivoZ.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; +--- -final dito = DitoSDK(); - -// Registra um evento na Dito -dito.trackEvent -( -eventName: 'comprou produto', -revenue: 99.90, -customData: { -'produto': 'produtoX', -'sku_produto': '99999999', -'metodo_pagamento': 'Visa', -}, -); -``` +`identifyUser() ` +Registra o usuário na plataforma com base nas informações previamente definidas. +```dart +Future identifyUser(); +``` +*:warning: Gera erro se `userID` não estiver definido.* -### Uso da SDK com push notification: +--- -Para o funcionamento é necessário configurar a lib do Firebase Cloud Message (FCM), seguindo os -seguintes passos: +`trackEvent()` -```shell -dart pub global activate flutterfire_cli -flutter pub add firebase_core firebase_messaging -``` +Registra um evento para o usuário. -```shell -flutterfire configure -``` +```dart +Future trackEvent({ + required String eventName, + double? revenue, + Map? customData, +}); +``` -Siga os passos que irá aparecer na CLI, assim terá as chaves de acesso do Firebase configuradas -dentro dos App's Android e iOS. +--- -#### main.dart +`registryMobileToken() ` +Registra um token de push notification para o usuário. +```dart +Future registryMobileToken({ + required String token, + String? platform, // 'Android' ou 'Apple iPhone' +}); +``` +*:warning: Gera erro se o usuário não estiver identificado ou se a plataforma for inválida.* -```dart -import 'package:dito_sdk/dito_sdk.dart'; +--- -// Método para registrar um serviço que irá receber os push quando o app estiver totalmente fechado -@pragma('vm:entry-point') -Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { - final notification = DataPayload.fromJson(jsonDecode(message.data["data"])); +`removeMobileToken()` +Remove o token de notificação de um usuário. +```dart +Future removeMobileToken({ + required String token, + String? platform, +}); +``` +*:warning: Mesmas regras de exceção do `registryMobileToken()`.* + +___ + +`openNotification() ` +Registra a abertura de uma notificação. +```dart +Future openNotification({ + required String notificationId, + required String identifier, + required String reference, +}); +``` +:bulb: *Obs: Esses parâmetros estarão presentes no data da notificação* + +--- - dito.notificationService().showLocalNotification(CustomNotification( - id: message.hashCode, - title: notification.details.title || "O nome do aplicativo", - body: notification.details.message, - payload: notification)); -} +## 🧪 Contribuição -void main() async { - WidgetsFlutterBinding.ensureInitialized(); - - await Firebase.initializeApp(); - FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); +Contribuições são bem-vindas! Sinta-se à vontade para abrir issues ou pull requests. Antes de contribuir, por favor leia o arquivo `CONTRIBUTING.md` se disponível. - DitoSDK dito = DitoSDK(); - dito.initialize(apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); - await dito.initializePushService(); -} -``` -> Lembre-se de substituir 'sua_api_key', 'sua_secret_key' e 'id_do_usuario' pelos valores corretos -> em seu ambiente. +
Desenvolvido com 💙 pela equipe Dito.
diff --git a/package/lib/services/notification_service.dart b/package/lib/services/notification_service.dart index e012ca5..a5b1c99 100644 --- a/package/lib/services/notification_service.dart +++ b/package/lib/services/notification_service.dart @@ -1,15 +1,34 @@ import 'dart:async'; +import 'dart:convert'; import 'dart:io'; import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import '../dito_sdk.dart'; import '../entity/data_payload.dart'; class NotificationService { + bool _messagingAllowed = false; late DitoSDK _dito; + late FlutterLocalNotificationsPlugin localNotificationsPlugin; Function(String)? onClick; + AndroidNotificationDetails androidDetails = const AndroidNotificationDetails( + 'dito_notifications', + 'Notifications sended by Dito', + channelDescription: 'Notifications sended by Dito', + importance: Importance.max, + priority: Priority.max, + enableVibration: true, + ); + + DarwinNotificationDetails iosDetails = const DarwinNotificationDetails( + presentAlert: true, + presentBadge: true, + presentSound: true, + presentBanner: true); + NotificationService(DitoSDK dito) { _dito = dito; } @@ -19,11 +38,15 @@ class NotificationService { await FirebaseMessaging.instance.setAutoInitEnabled(true); } + localNotificationsPlugin = FlutterLocalNotificationsPlugin(); + await FirebaseMessaging.instance .setForegroundNotificationPresentationOptions( alert: true, badge: true, sound: true); await checkPermissions(); + await _initializeNotifications(); + await _setupAndroidChannel(); await _initializeMessages(); } @@ -62,9 +85,64 @@ class NotificationService { } void _handleMessage(RemoteMessage message) async { + if (_dito.user.isNotValid) { + _dito.identify(userID: message.data["reference"]); + await _dito.identifyUser(); + } + await _dito.trackEvent( eventName: "receive-${Platform.isIOS ? "ios" : "android"}-notification"); + + await _showLocalNotification(message); + } + + _setupAndroidChannel() async { + const AndroidNotificationChannel channel = AndroidNotificationChannel( + 'dito_notifications', + 'Notifications sended by Dito', + importance: Importance.max, + ); + + await localNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(channel); + } + + _setupNotifications() async {} + + _initializeNotifications() async { + const android = AndroidInitializationSettings('@mipmap/ic_launcher'); + const ios = DarwinInitializationSettings(); + + const InitializationSettings initializationSettings = + InitializationSettings(android: android, iOS: ios); + + await localNotificationsPlugin.initialize(initializationSettings, + onDidReceiveNotificationResponse: (message) async { + final data = DataPayload.fromJson(jsonDecode(message.payload!)); + + await _handleClick(data); + }); + } + + setAndroidDetails(AndroidNotificationDetails details) { + androidDetails = details; + } + + setIosDetails(DarwinNotificationDetails details) { + iosDetails = details; + } + + _showLocalNotification(RemoteMessage message) async { + await localNotificationsPlugin.show( + message.hashCode % 2147483647, + message.notification?.title ?? "", + message.notification?.body ?? "", + NotificationDetails(android: androidDetails, iOS: iosDetails), + payload: jsonEncode(message.data), + ); } Future checkPermissions() async { @@ -72,6 +150,8 @@ class NotificationService { if (settings.authorizationStatus != AuthorizationStatus.authorized) { await FirebaseMessaging.instance.requestPermission(); + _messagingAllowed = + (settings.authorizationStatus == AuthorizationStatus.authorized); } } } diff --git a/package/pubspec.lock b/package/pubspec.lock index 6a30840..0edde10 100644 --- a/package/pubspec.lock +++ b/package/pubspec.lock @@ -9,6 +9,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.53" + args: + dependency: transitive + description: + name: args + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 + url: "https://pub.dev" + source: hosted + version: "2.7.0" async: dependency: transitive description: @@ -57,6 +65,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.6" + dbus: + dependency: transitive + description: + name: dbus + sha256: "79e0c23480ff85dc68de79e2cd6334add97e48f7f4865d17686dd6ea81a47e8c" + url: "https://pub.dev" + source: hosted + version: "0.7.11" device_info_plus: dependency: "direct main" description: @@ -158,6 +174,30 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.0" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35" + url: "https://pub.dev" + source: hosted + version: "17.2.4" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af + url: "https://pub.dev" + source: hosted + version: "4.0.1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" + url: "https://pub.dev" + source: hosted + version: "7.2.0" flutter_test: dependency: "direct dev" description: flutter @@ -264,6 +304,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.9.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" + url: "https://pub.dev" + source: hosted + version: "6.1.0" platform: dependency: transitive description: @@ -397,6 +445,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + timezone: + dependency: transitive + description: + name: timezone + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" + url: "https://pub.dev" + source: hosted + version: "0.9.4" typed_data: dependency: transitive description: @@ -445,6 +501,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.0" + xdg_directories: + dependency: transitive + description: + name: xdg_directories + sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15" + url: "https://pub.dev" + source: hosted + version: "1.1.0" + xml: + dependency: transitive + description: + name: xml + sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226 + url: "https://pub.dev" + source: hosted + version: "6.5.0" sdks: dart: ">=3.7.0 <4.0.0" flutter: ">=3.24.0" diff --git a/package/pubspec.yaml b/package/pubspec.yaml index d4e4b0e..a4a2dbd 100644 --- a/package/pubspec.yaml +++ b/package/pubspec.yaml @@ -1,6 +1,6 @@ name: dito_sdk description: A Flutter package by Dito that enables user registration and user event tracking. -version: 0.5.7 +version: 0.5.8 homepage: https://github.com/ditointernet/sdk_mobile_flutter environment: @@ -18,6 +18,7 @@ dependencies: firebase_messaging: ">=14.9.1" firebase_core: ">=2.30.1" sqflite_common_ffi: ">=2.3.3" + flutter_local_notifications: ">=17.0.0 <18.0.0" dev_dependencies: flutter_test: From 2201cce3c05b3a6e116cba008ded15bcdd779b4b Mon Sep 17 00:00:00 2001 From: Igor Gottschalg Date: Thu, 22 May 2025 11:31:32 -0300 Subject: [PATCH 04/11] update readme --- README.md | 416 ++++++++++++++++++++++-------------------------------- 1 file changed, 166 insertions(+), 250 deletions(-) diff --git a/README.md b/README.md index b9bc54a..97643b7 100644 --- a/README.md +++ b/README.md @@ -1,325 +1,241 @@ -# Dito SDK (Flutter) -DitoSDK é uma biblioteca Dart que fornece métodos para integrar aplicativos com a plataforma da Dito. Ela permite identificar usuários, registrar eventos e enviar dados personalizados. +# 📦 Dito SDK (Flutter) -## Instalação +`dito_sdk` é uma biblioteca Flutter que facilita a integração com a plataforma Dito, permitindo a identificação de usuários, o envio de eventos personalizados e a integração com notificações push. -Para instalar a biblioteca DitoSDK em seu aplicativo Flutter, você deve seguir as instruções fornecidas [nesse link](https://pub.dev/packages/dito_sdk/install). +[![pub package](https://img.shields.io/pub/v/dito_sdk.svg)](https://pub.dev/packages/dito_sdk) -## Métodos +## ✨ Recursos -### initialize() +- 📌 Identificação de usuários +- 📊 Envio e rastreamento de eventos personalizados +- 📱 Integração com notificações push (via Firebase Cloud Messaging) +- 💾 Armazenamento local de eventos para usuários não identificados +- 🔗 Suporte a deep linking via notificações -Este método deve ser chamado antes de qualquer outra operação com o SDK. Ele inicializa as chaves de API e SECRET necessárias para a autenticação na plataforma Dito. +--- -```dart -void initialize({required String apiKey, required String secretKey}); -``` +## 🚀 Começando -#### Parâmetros +### ✅ Pré-requisitos -- **apiKey** _(String, obrigatório)_: A chave de API da plataforma Dito. -- **secretKey** _(String, obrigatório)_: O segredo da chave de API da plataforma Dito. +- **Dart SDK**: `>=2.12.0 <3.0.0` *(ajuste conforme a versão utilizada)* +- **Flutter SDK**: `>=1.20.0` +- **Conta na Dito**: Você precisará de uma conta ativa e credenciais da plataforma Dito (API Key e Secret). -### initializePushNotificationService() +### 📦 Instalação -Este método deve ser chamado após a inicialização da SDK. Ele inicializa as configurações e serviços necessários para o funcionamento de push notifications da plataforma Dito. +Adicione o pacote no seu projeto Flutter: -```dart -void initializePushNotificationService(); +```bash +flutter pub add dito_sdk ``` -#### Parâmetros - -- **apiKey** _(String, obrigatório)_: A chave de API da plataforma Dito. -- **secretKey** _(String, obrigatório)_: O segredo da chave de API da plataforma Dito. - -### identify() +### ⚙️ Inicialização -Este método define o ID do usuário que será usado para todas as operações subsequentes. +Importe o pacote e inicialize o SDK com suas credenciais: ```dart -void identify(String userId); -``` - -- **userID** _(String, obrigatório)_: Id para identificar o usuário na plataforma da Dito. -- **name** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **email** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **gender** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **birthday** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **location** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **customData** _(Map)_: Parâmetro para identificar o usuário na plataforma da Dito. - - -#### identifyUser() - -Este método registra o usuário na plataforma da Dito com as informações fornecidas anteriormente usando o método `identify()`. +import 'package:dito_sdk/dito_sdk.dart'; +void main() async { +// Certifique-se de inicializar o binding do Flutter se estiver usando em um app Flutter +WidgetsFlutterBinding. ensureInitialized(); +final String ditoApiKey = String.fromEnvironment('API_KEY'); +final String ditoSecretKey = String.fromEnvironment('SECRET_KEY'); -```dart -Future identifyUser() async; +DitoSDK dito = DitoSDK(); +dito.initialize(apiKey: ditoApiKey, secretKey: ditoSecretKey); +await dito.initializePushNotificationService(); //está linha é necessário se for utilizar o serviço de push notification, aqui é feito o registro automático dos eventos de push; ``` -#### Exception - -- Caso a SDK ainda não tenha `userId` cadastrado quando esse método for chamado, irá ocorrer um erro no aplicativo. (utilize o método `setUserId()` para definir o `userId`) +## 🛠️ Uso da SDK -### trackEvent() - -O método `trackEvent()` tem a finalidade de registrar um evento na plataforma da Dito. Caso o userID já tenha sido registrado, o evento será enviado imediatamente. No entanto, caso o userID ainda não tenha sido registrado, o evento será armazenado localmente e posteriormente enviado quando o userID for registrado por meio do método `setUserId()`. +### 👤 Identificando um usuário ```dart -Future trackEvent({ - required String eventName, - double? revenue, - Map? customData, -}) async; +dito.identify( + userID: 'id_do_usuario', + cpf: 'cpf_do_usuario', + name: 'nome_do_usuario', + email: 'email_do_usuario', +); +await dito.identifyUser(); ``` +📌 Enquanto o usuário não for identificado, os eventos serão armazenados localmente. +___ -#### Parâmetros - -- **eventName** _(String, obrigatório)_: O nome do evento a ser registrado. -- **revenue** _(double, opcional)_: A receita associada ao evento. -- **customData** _(Map, opcional)_: Dados personalizados adicionais associados ao evento. - -### registryMobileToken() - -Este método permite registrar um token mobile para o usuário. - +### 📈 Enviando eventos ```dart -Future registryMobileToken({ - required String token, - String? platform, -}); -``` - -#### Parâmetros - -- **token** _(String, obrigatório)_: O token mobile que será registrado. -- **platform** _(String, opcional)_: Nome da plataforma que o usuário está acessando o aplicativo. Valores válidos: 'iPhone' e 'Android'. - `
`_Caso não seja passado algum valor nessa prop, a sdk irá pegar por default o valor pelo `platform`._ - -#### Exception - -- Caso seja passado um valor diferente de 'iPhone' ou 'Android' na propriedade platform, irá ocorrer um erro no aplicativo. -- Caso a SDK ainda não tenha `identify` cadastrado quando esse método for chamado, irá ocorrer um erro no aplicativo. (utilize o método `identify()` para definir o id do usuário) - -### removeMobileToken() - -Este método permite remover um token mobile para o usuário. - -```dart -Future removeMobileToken({ - required String token, - String? platform, -}); +await dito.trackEvent( + eventName: 'comprou produto', + customData: { + 'produto': 'produtoX', + 'sku_produto': '99999999', + }, +); ``` +___ -#### Parâmetros - -- **token** _(String, obrigatório)_: O token mobile que será removido. -- **platform** _(String, opcional)_: Nome da plataforma que o usuário está acessando o aplicativo. Valores válidos: 'iPhone' e 'Android'. - `
`_Caso não seja passado algum valor nessa prop, a sdk irá pegar por default o valor pelo `platform`._ - -#### Exception - -- Caso seja passado um valor diferente de 'iPhone' ou 'Android' na propriedade platform, irá ocorrer um erro no aplicativo. -- Caso a SDK ainda não tenha `identify` cadastrado quando esse método for chamado, irá ocorrer um erro no aplicativo. (utilize o método `identify()` para definir o id do usuário) - -### openNotification() - -Este método permite registrar a abertura de uma notificação mobile. +### 📲 Registrando o dispositivo ```dart -Future openNotification({ - required String notificationId, - required String identifier, - required String reference -}) async +final token = await dito.notificationService().getDeviceFirebaseToken(); +await dito.registryMobileToken(token: token); ``` +*Importante: o usuário precisa estar identificado antes do registro do token.* +___ -#### Parâmetros - -- **notificationId** _(String, obrigatório)_: Id da notificação da Dito recebida pelo aplicativo. -- **identifier** _(String, obrigatório)_: Parâmetro para dentificar a notificação na plataforma da Dito. -- **reference** _(String, obrigatório)_: Parâmetro para identificar o usuário na plataforma da Dito. +## 🔔 Integração com Push Notifications (FCM) -###### Observações - -- Esses parâmetros estarão presentes no data da notificação - -## Classes - -### User - -Classe para manipulação dos dados do usuário. +### 1. Instale os pacotes Firebase: ```dart -User user = User( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); +dart pub global activate flutterfire_cli +flutter pub add firebase_core firebase_messaging +flutterfire configure ``` +Siga as instruções para configurar o Firebase para Android e iOS. -#### Parâmetros - -- **userID** _(String, obrigatório)_: Id para identificar o usuário na plataforma da Dito. -- **name** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **email** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **gender** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **birthday** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **location** _(String)_: Parâmetro para identificar o usuário na plataforma da Dito. -- **customData** _(Map)_: Parâmetro para identificar o usuário na plataforma da Dito. - -## Exemplos - -### Uso básico da SDK: +### 2. Exemplo de uso com notificação: ```dart import 'package:dito_sdk/dito_sdk.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; -final dito = DitoSDK(); - -// Inicializa a SDK com suas chaves de API -dito.initialize(apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); - -// Define ou atualiza informações do usuário na instância (neste momento, ainda não há comunicação com a Dito) -dito.identify( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); +Future _setupDito() async { + final String apiKey = String.fromEnvironment('API_KEY'); + final String secretKey = String.fromEnvironment('SECRET_KEY'); -// Envia as informações do usuário (que foram definidas ou atualizadas pelo identify) para a Dito -await dito.identifyUser(); + DitoSDK dito = DitoSDK(); + dito.initialize(apiKey: apiKey, secretKey: secretKey); + await dito.initializePushNotificationService(); -// Registra um evento na Dito -await dito.trackEvent(eventName: 'login'); -``` + dito.notificationService().onClick = (String link) { + deepLinkHandle(link); // sua lógica de navegação + }; -### Uso avançado da SDK: + return dito; +} -#### main.dart +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + await _setupDito(); +} -```dart -import 'package:dito_sdk/dito_sdk.dart'; +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); -final dito = DitoSDK(); + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); -// Inicializa a SDK com suas chaves de API -dito.initialize(apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); + final dito = await _setupDito(); + // continuação do app... +} ``` -#### login.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Define o ID do usuário -dito.setUserId('id_do_usuario'); -dito.identify( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); -await dito.identifyUser(); -``` +___ -#### arquivoX.dart +## 📚 API da SDK -```dart -import 'package:dito_sdk/dito_sdk.dart'; +`initialize()` -final dito = DitoSDK(); +Inicializa a SDK com suas credenciais. -// Define ou atualiza informações do usuário na instância (neste momento, ainda não há comunicação com a Dito) -dito.identify( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'São Paulo'); -await dito.identifyUser(); -await dito.registryMobileToken(token: token); +```dart +void initialize({required String apiKey, required String secretKey}); +``` + +--- +`initializePushNotificationService()` +Ativa os serviços de notificação da Dito. +```dart +Future initializePushNotificationService(); ``` + +--- -#### arquivoY.dart - -```dart -import 'package:dito_sdk/dito_sdk.dart'; - -final dito = DitoSDK(); - -// Define ou atualiza informações do usuário na instância (neste momento, ainda não há comunicação com a Dito) -dito.identify( sha1("joao@example.com"), 'João da Silva', 'joao@example.com', 'Rio de Janeiro', { - 'loja preferida': 'LojaX', - 'canal preferido': 'Loja Física' +`identify()` +Configura os dados de identificação do usuário. +```dart +void identify({ + required String userID, + String? name, + String? email, + String? gender, + String? birthday, + String? location, + String? cpf, + Map? customData, }); -await dito.identifyUser(); -``` - -Isso resultará no envio do seguinte payload do usuário ao chamar `identifyUser()`: - -```javascript -{ - name: 'João da Silva', - email: 'joao@example.com', - location: 'Rio de Janeiro', - customData: { - 'loja preferida': 'LojaX', - 'canal preferido': 'Loja Física' - } -} ``` -A nossa SDK é uma instância única, o que significa que, mesmo que ela seja inicializada em vários arquivos ou mais de uma vez, ela sempre referenciará as mesmas informações previamente armazenadas. Isso nos proporciona a flexibilidade de chamar o método `identify()` a qualquer momento para adicionar ou atualizar os detalhes do usuário, e somente quando necessário, enviá-los através do método `identifyUser()`. +--- -#### arquivoZ.dart +`identifyUser() ` +Registra o usuário na plataforma com base nas informações previamente definidas. +```dart +Future identifyUser(); +``` +*:warning: Gera erro se `userID` não estiver definido.* -```dart -import 'package:dito_sdk/dito_sdk.dart'; +--- -final dito = DitoSDK(); - -// Registra um evento na Dito -dito.trackEvent( - eventName: 'comprou produto', - revenue: 99.90, - customData: { - 'produto': 'produtoX', - 'sku_produto': '99999999', - 'metodo_pagamento': 'Visa', - }, -); -``` +`trackEvent()` -### Uso da SDK com push notification: +Registra um evento para o usuário. -Para o funcionamento é necessário configurar a lib do Firebase Cloud Message (FCM), seguindo os seguintes passos: +```dart +Future trackEvent({ + required String eventName, + double? revenue, + Map? customData, +}); +``` -```shell -dart pub global activate flutterfire_cli -flutter pub add firebase_core firebase_messaging -``` +--- -```shell -flutterfire configure -``` +`registryMobileToken() ` +Registra um token de push notification para o usuário. +```dart +Future registryMobileToken({ + required String token, + String? platform, // 'Android' ou 'Apple iPhone' +}); +``` +*:warning: Gera erro se o usuário não estiver identificado ou se a plataforma for inválida.* -Siga os passos que irá aparecer na CLI, assim terá as chaves de acesso do Firebase configuradas dentro dos App's Android e iOS. +--- -#### main.dart -```dart -import 'package:dito_sdk/dito_sdk.dart'; +`removeMobileToken()` +Remove o token de notificação de um usuário. +```dart +Future removeMobileToken({ + required String token, + String? platform, +}); +``` +*:warning: Mesmas regras de exceção do `registryMobileToken()`.* -// Método para registrar um serviço que irá receber os push quando o app estiver totalmente fechado -@pragma('vm:entry-point') -Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { - final notification = DataPayload.fromJson(jsonDecode(message.data["data"])); +___ - dito.notificationService().showLocalNotification(CustomNotification( - id: message.hashCode, - title: notification.details.title || "O nome do aplicativo", - body: notification.details.message, - payload: notification)); -} +`openNotification() ` +Registra a abertura de uma notificação. +```dart +Future openNotification({ + required String notificationId, + required String identifier, + required String reference, +}); +``` +:bulb: *Obs: Esses parâmetros estarão presentes no data da notificação* + +--- -void main() async { - WidgetsFlutterBinding.ensureInitialized(); +## 🧪 Contribuição - await Firebase.initializeApp(); - FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); +Contribuições são bem-vindas! Sinta-se à vontade para abrir issues ou pull requests. Antes de contribuir, por favor leia o arquivo `CONTRIBUTING.md` se disponível. - DitoSDK dito = DitoSDK(); - dito.initialize(apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); - await dito.initializePushService(); -} -``` -> Lembre-se de substituir 'sua_api_key', 'sua_secret_key' e 'id_do_usuario' pelos valores corretos em seu ambiente. +
Desenvolvido com 💙 pela equipe Dito.
From 5d64a8fe4dd5767d23e2f3f71eedf96160ed72e1 Mon Sep 17 00:00:00 2001 From: Igor Duarte Date: Mon, 12 Jan 2026 17:50:09 -0300 Subject: [PATCH 05/11] feat: implement new push notification approach and update dependencies - Added flutter_dotenv dependency for environment variable management. - Updated notification service to handle messages without body. - Created DEVELOPER_SETUP.md for setup instructions. - Added MIGRATION.md for migrating to the new push structure. - Included necessary configuration files for Firebase (iOS and Android). - Updated .gitignore to exclude build directories. --- .gitignore | 1 + DEVELOPER_SETUP.md | 165 ++++++++++++++++++ MIGRATION.md | 144 +++++++++++++++ example/pubspec.lock | 8 + example/pubspec.yaml | 1 + .../lib/services/notification_service.dart | 5 +- 6 files changed, 323 insertions(+), 1 deletion(-) create mode 100644 DEVELOPER_SETUP.md create mode 100644 MIGRATION.md diff --git a/.gitignore b/.gitignore index d0bfcf6..9ee3f1e 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,4 @@ example/build/ package/build/ .flutter-plugins* package/test/.env-test.json +build/ diff --git a/DEVELOPER_SETUP.md b/DEVELOPER_SETUP.md new file mode 100644 index 0000000..357a326 --- /dev/null +++ b/DEVELOPER_SETUP.md @@ -0,0 +1,165 @@ +# DEVELOPER SETUP — Dito SDK (passo a passo) + +Este guia descreve passo a passo o que um desenvolvedor precisa fazer para que a SDK Dito Flutter funcione corretamente, incluindo integração de push (FCM). + +1) Pré-requisitos +- Flutter e Dart compatíveis com o projeto (ver `pubspec.yaml`). +- Conta e credenciais Dito: `API_KEY` e `SECRET_KEY`. +- Firebase configurado para Android e iOS (para push notifications). + +2) Instalar dependências +- Adicione a SDK do Dito ao projeto (pub.dev) ou utilize a dependência local/monorepo. + +3) Configurar Firebase (Android / iOS) +- Execute `flutterfire configure` (requer `flutterfire_cli`) ou adicione manualmente `google-services.json` e `GoogleService-Info.plist` aos projetos nativos. + +```bash +dart pub global activate flutterfire_cli +flutterfire configure +``` + +4) Armazenar credenciais com segurança +- Nunca comite `API_KEY`/`SECRET_KEY` no repositório. +- Use variáveis de ambiente, arquivos seguros do CI, ou secret manager. + +Exemplo (iOS/Android runtime): +- Defina variáveis de ambiente no CI ou no `flutter run` se necessário: + +```bash +flutter run --dart-define=API_KEY=xxx --dart-define=SECRET_KEY=yyy +``` + +5) Inicialização (main.dart) +- Garanta que o Firebase seja inicializado antes de iniciar o serviço de push da SDK. + +Exemplo recomendado: + +```dart +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:dito_sdk/dito_sdk.dart'; + +Future main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + + final dito = DitoSDK(); + dito.initialize( + apiKey: const String.fromEnvironment('API_KEY'), + secretKey: const String.fromEnvironment('SECRET_KEY'), + ); + await dito.initializePushNotificationService(); + + // Exemplo de callback de clique em notificação + dito.notificationService().onClick = (String link) { + // deep link handler + deepLinkHandle(link); + }; + + // registrar handler de background + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + + runApp(const MyApp()); +} +``` + +6) Background handler do FCM +- Declare o handler com `@pragma('vm:entry-point')` e inicialize o SDK dentro dele (importante para funcionamento quando o app está fechado). + +```dart +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + + final dito = DitoSDK(); + dito.initialize( + apiKey: const String.fromEnvironment('API_KEY'), + secretKey: const String.fromEnvironment('SECRET_KEY'), + ); + await dito.initializePushNotificationService(); + + // processar payload / registrar abertura ou mostrar notificação local +} +``` + +7) Identificação do usuário +- Defina as informações do usuário antes de chamar `identifyUser()`. + +```dart +dito.identify( + userID: 'id_do_usuario', + name: 'Nome', + email: 'email@exemplo.com', + cpf: '00000000000', +); +await dito.identifyUser(); +``` + +- Observação: algumas versões da SDK expõem `identify(String userId)` ou `setUserId()` — confirme a assinatura e garanta que `userId` exista antes de chamar `identifyUser()`. + +8) Registro / remoção de token mobile +- Após inicializar o serviço de push e identificar o usuário, registre o token FCM: + +```dart +final token = await dito.notificationService().getDeviceFirebaseToken(); +await dito.registryMobileToken(token: token, platform: 'Android'); +// Para iOS, usar o valor aceito pela SDK, ex: 'iPhone' +``` + +- Para remover o token: + +```dart +await dito.removeMobileToken(token: token, platform: 'Android'); +``` + +- Atenção: os valores válidos para `platform` podem variar entre versões (`'Android'`, `'iPhone'`, `'Apple iPhone'`). Use o valor exato exigido pela sua versão da SDK. + +9) Envio de eventos + +```dart +await dito.trackEvent( + eventName: 'comprou produto', + customData: {'produto': 'X', 'sku': '123'}, +); +``` + +Se o usuário não estiver identificado, a SDK deve enfileirar o evento localmente e enviar após a identificação; valide esse comportamento em testes. + +10) Registro de abertura de notificação + +Se sua app precisa registrar aberturas manualmente, use `openNotification()` com os campos presentes no payload da Dito: + +```dart +await dito.openNotification( + notificationId: 'notif-id', + identifier: 'identifier', + reference: 'reference', +); +``` + +11) Testes / validação +- Execute testes manuais em dispositivos reais (Android e iOS). +- Teste inicialização no foreground, background e app fechado (background handler). +- Verifique envio de eventos enfileirados antes da identificação. +- Verifique registro e remoção de tokens. + +12) Troubleshooting comum +- Erro: "userId não definido" → Certifique-se de chamar `identify()` / `setUserId()` antes de `identifyUser()` ou de métodos que exigem usuário identificado. +- Erro: platform inválido → Use o valor exato esperado pela versão da SDK. +- Push não chega → Verifique configuração do Firebase, `google-services.json` / `GoogleService-Info.plist`, e se `Firebase.initializeApp()` é chamado antes da SDK. + +13) Checklist rápido +- [ ] `flutter pub get` executado +- [ ] Firebase configurado (Android e iOS) +- [ ] `API_KEY` e `SECRET_KEY` definidos com segurança +- [ ] `Firebase.initializeApp()` antes de `initializePushNotificationService()` +- [ ] Background handler registrado com `@pragma('vm:entry-point')` +- [ ] Testes manuais em Android e iOS realizados + +14) Próximos passos (opcionais) +- Gerar exemplos de código no diretório `example/` adaptados à nova inicialização. +- Abrir PR com alterações de inicialização em arquivos `main.dart` do exemplo (posso gerar se desejar). + +--- +Arquivo criado automaticamente: `DEVELOPER_SETUP.md`. diff --git a/MIGRATION.md b/MIGRATION.md new file mode 100644 index 0000000..966751b --- /dev/null +++ b/MIGRATION.md @@ -0,0 +1,144 @@ +# MIGRAÇÃO PARA A NOVA ESTRUTURA (feat/new-push-approach) + +Este documento descreve os passos necessários para migrar projetos que usam a SDK Dito Flutter para a nova estrutura/abordagem de push implementada na branch `feat/new-push-approach`. + +Resumo das mudanças principais +- Consolidação/ajuste da inicialização do serviço de push (métodos relacionados a push reorganizados). +- Pequenas mudanças de nome e comportamento em métodos de identificação (`identify` / `setUserId`) e registro de token. +- Atualizações de exemplo de uso para compatibilidade com handlers de background do FCM. + +Antes de começar +- Tenha o código do app em controle de versão e crie uma branch de migração. +- Atualize as dependências: execute `flutter pub get` após aplicar mudanças. +- Configure o Firebase (Android/iOS) se ainda não estiver configurado. + +1) Dependências e configurações +- Instale/atualize `firebase_core` e `firebase_messaging`: + +```bash +flutter pub add firebase_core firebase_messaging +flutter pub get +``` + +- Configure Firebase usando o `flutterfire` CLI (se ainda não configurado): + +```bash +dart pub global activate flutterfire_cli +flutterfire configure +``` + +- Inclua os arquivos de configuração das plataformas: `GoogleService-Info.plist` (iOS) e `google-services.json` (Android). + +2) Inicialização da SDK + +Antes (exemplo comum): + +```dart +final dito = DitoSDK(); +dito.initialize(apiKey: 'sua_api_key', secretKey: 'sua_secret_key'); +await dito.initializePushNotificationService(); +``` + +Após a migração: verifique que a inicialização do SDK e do serviço de push estão presentes e que o Firebase foi inicializado antes de chamar o método de push. Exemplo recomendado: + +```dart +WidgetsFlutterBinding.ensureInitialized(); +await Firebase.initializeApp(); + +final dito = DitoSDK(); +dito.initialize(apiKey: 'SUA_API_KEY', secretKey: 'SEU_SECRET'); +await dito.initializePushNotificationService(); + +// opcional: configurar callback de clique em notificações +dito.notificationService().onClick = (String link) { + // deep link handler + deepLinkHandle(link); +}; +``` + +Observação: em alguns exemplos da `main` foi usado `initializePushService` ou `initializePushNotificationService` — confirme no código da versão que você está usando qual é o nome do método e adapte o trecho acima. + +3) Background handler (FCM) + +Certifique-se de registrar um handler de background com `@pragma('vm:entry-point')` e inicializar o SDK dentro dele. Exemplo: + +```dart +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + + final dito = DitoSDK(); + dito.initialize(apiKey: 'SUA_API_KEY', secretKey: 'SEU_SECRET'); + await dito.initializePushNotificationService(); + + // manipule a mensagem (ex: mostrar notificação local ou processar payload) +} + +// no main(): +FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); +``` + +4) Identificação do usuário + +Antigo (exemplo do README antigo): + +```dart +dito.identify( + userID: 'id_do_usuario', + cpf: 'cpf_do_usuario', + name: 'nome', + email: 'email', +); +await dito.identifyUser(); +``` + +Notas de migração: +- A `main` apresenta outras variantes (`identify(String userId)` e `setUserId()`); verifique qual assinatura sua versão da SDK expõe. +- Recomendação: garantir que o `userId` esteja definido antes de chamar `identifyUser()`/`setUserId()` — caso contrário a SDK lançará erro. + +5) Registro e remoção de token mobile + +Uso comum: + +```dart +final token = await dito.notificationService().getDeviceFirebaseToken(); +await dito.registryMobileToken(token: token, platform: 'Android'); +// ou para iOS: platform: 'iPhone' (algumas versões usam 'Apple iPhone') +``` + +Observações: +- A `main` indica que valores aceitos podem ser `'iPhone'` e `'Android'`. Em outras documentações locais pode aparecer `'Apple iPhone'`. Para evitar erros, use exatamente o valor esperado pela versão da SDK que você está consumindo (ou utilize constantes se a SDK expô-las). + +6) Eventos (trackEvent) + +Uso permanece similar: + +```dart +await dito.trackEvent( + eventName: 'comprou produto', + customData: {'produto': 'X', 'sku': '123'}, +); +``` + +Se sua app dependia do comportamento de enfileiramento local (eventos enviados apenas após identificação), verifique que esse comportamento continua igual após a migração. + +7) Testes e validação +- Teste a inicialização em `main()` e no handler de background. +- Envie eventos enquanto o usuário não está identificado e confirme envio após `identifyUser()`. +- Teste registro e remoção de tokens, e o fluxo de clique em notificações (`onClick`). + +8) Checklist rápido +- [ ] Atualizar dependências e rodar `flutter pub get`. +- [ ] Confirmar nome do método de inicialização de push na versão da SDK. +- [ ] Garantir `Firebase.initializeApp()` antes de inicializar push. +- [ ] Registrar handler de background com `@pragma('vm:entry-point')`. +- [ ] Validar valores `platform` ao registrar token. +- [ ] Testar envio/recebimento de notificações em Android e iOS. + +Rollback +- Caso identifique problemas, reverta a branch e abra uma branch de hotfix para corrigir pontos específicos (ex.: nome do método, valores de `platform`, inicialização do Firebase). + +Se quiser, eu posso: +- Gerar um pull request com este arquivo `MIGRATION.md`. +- Aplicar correções automáticas no código de inicialização (se você indicar os arquivos a modificar). \ No newline at end of file diff --git a/example/pubspec.lock b/example/pubspec.lock index 6ea8cea..ad694d2 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -181,6 +181,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_dotenv: + dependency: "direct main" + description: + name: flutter_dotenv + sha256: d4130c4a43e0b13fefc593bc3961f2cb46e30cb79e253d4a526b1b5d24ae1ce4 + url: "https://pub.dev" + source: hosted + version: "6.0.0" flutter_lints: dependency: "direct dev" description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 6640bfd..d6e8110 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -39,6 +39,7 @@ dependencies: firebase_core: ^2.30.1 firebase_messaging: ^14.9.1 dito_sdk: ^0.5.6 + flutter_dotenv: ^6.0.0 dev_dependencies: flutter_test: diff --git a/package/lib/services/notification_service.dart b/package/lib/services/notification_service.dart index a5b1c99..c5f0a9a 100644 --- a/package/lib/services/notification_service.dart +++ b/package/lib/services/notification_service.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'dart:ui'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; @@ -94,7 +95,9 @@ class NotificationService { eventName: "receive-${Platform.isIOS ? "ios" : "android"}-notification"); - await _showLocalNotification(message); + if (message.notification?.body == null && message.data["message"] != null) { + await _showLocalNotification(message); + } } _setupAndroidChannel() async { From 239b16fdea26b51fdcec929df908c741060a8522 Mon Sep 17 00:00:00 2001 From: Igor Duarte Date: Mon, 12 Jan 2026 17:50:38 -0300 Subject: [PATCH 06/11] fix: update migration document to ensure proper initialization code corrections --- MIGRATION.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MIGRATION.md b/MIGRATION.md index 966751b..cedfd39 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -141,4 +141,4 @@ Rollback Se quiser, eu posso: - Gerar um pull request com este arquivo `MIGRATION.md`. -- Aplicar correções automáticas no código de inicialização (se você indicar os arquivos a modificar). \ No newline at end of file +- Aplicar correções automáticas no código de inicialização (se você indicar os arquivos a modificar). From acc19dc3b35de1859d1e278f123986ad749b7134 Mon Sep 17 00:00:00 2001 From: Igor Duarte Date: Mon, 19 Jan 2026 15:54:58 -0300 Subject: [PATCH 07/11] feat: add Flutter developer guidelines and enhance notification service - Introduced a new rules file for Flutter development best practices. - Updated notification service to improve event tracking and removed unused Android channel setup. - Streamlined notification handling by integrating data payload processing. --- .cursor/rules/flutter.mdc | 144 ++++++++++++++++++ .../lib/services/notification_service.dart | 46 ++---- 2 files changed, 158 insertions(+), 32 deletions(-) create mode 100644 .cursor/rules/flutter.mdc diff --git a/.cursor/rules/flutter.mdc b/.cursor/rules/flutter.mdc new file mode 100644 index 0000000..b091dd0 --- /dev/null +++ b/.cursor/rules/flutter.mdc @@ -0,0 +1,144 @@ +--- +name: flutter +description: Flutter developer +--- + +You are a senior Dart programmer with experience in the Flutter framework and a preference for clean programming and design patterns. +Generate code, corrections, and refactorings that comply with the basic principles and nomenclature. + +## Project Context +This project is a **pure Dart SDK** designed to integrate Dito services into Flutter applications. It acts as a wrapper around Dito's REST API to facilitate user engagement and tracking. + +## Technical Architecture +* **Networking:** Uses `package:http` for all REST API requests. +* **Persistence:** Uses **SQLite** strictly for queuing anonymous events (events triggered before the user is identified). No other local caching strategy is applied. +* **Messaging:** Integrates with **Firebase Cloud Messaging (FCM)** to handle push notification payloads. + +## Core Responsibilities +1. **User Identity:** Abstracting the `identify` method to create or update users. +2. **Event Tracking:** sending custom behavioral data via the `event` method. +3. **Anonymous Queue:** Storing events locally in SQLite when no user is identified, to be synced upon successful identification. + +## Dart General Guidelines + +### Basic Principles + +- Use English for all code and documentation. +- Always declare the type of each variable and function (parameters and return value). + - Avoid using any. + - Create necessary types. +- Don't leave blank lines within a function. +- One export per file. + +### Nomenclature + +- Use PascalCase for classes. +- Use camelCase for variables, functions, and methods. +- Use underscores_case for file and directory names. +- Use UPPERCASE for environment variables. + - Avoid magic numbers and define constants. +- Start each function with a verb. +- Use verbs for boolean variables. Example: isLoading, hasError, canDelete, etc. +- Use complete words instead of abbreviations and correct spelling. + - Except for standard abbreviations like API, URL, etc. + - Except for well-known abbreviations: + - i, j for loops + - err for errors + - ctx for contexts + - req, res, next for middleware function parameters + +### Functions + +- In this context, what is understood as a function will also apply to a method. +- Write short functions with a single purpose. Less than 20 instructions. +- Name functions with a verb and something else. + - If it returns a boolean, use isX or hasX, canX, etc. + - If it doesn't return anything, use executeX or saveX, etc. +- Avoid nesting blocks by: + - Early checks and returns. + - Extraction to utility functions. +- Use higher-order functions (map, filter, reduce, etc.) to avoid function nesting. + - Use arrow functions for simple functions (less than 3 instructions). + - Use named functions for non-simple functions. +- Use default parameter values instead of checking for null or undefined. +- Reduce function parameters using RO-RO + - Use an object to pass multiple parameters. + - Use an object to return results. + - Declare necessary types for input arguments and output. +- Use a single level of abstraction. + +### Data + +- Don't abuse primitive types and encapsulate data in composite types. +- Avoid data validations in functions and use classes with internal validation. +- Prefer immutability for data. + - Use readonly for data that doesn't change. + - Use as const for literals that don't change. + +### Classes + +- Follow SOLID principles. +- Prefer composition over inheritance. +- Declare interfaces to define contracts. +- Write small classes with a single purpose. + - Less than 200 instructions. + - Less than 10 public methods. + - Less than 10 properties. + +### Exceptions + +- Use exceptions to handle errors you don't expect. +- If you catch an exception, it should be to: + - Fix an expected problem. + - Add context. + - Otherwise, use a global handler. + +### Testing + +- Follow the Arrange-Act-Assert convention for tests. +- Name test variables clearly. + - Follow the convention: inputX, mockX, actualX, expectedX, etc. +- Write unit tests for each public function. + - Use test doubles to simulate dependencies. + - Except for third-party dependencies that are not expensive to execute. +- Write acceptance tests for each module. + - Follow the Given-When-Then convention. + +## Specific to Flutter + +### Basic Principles + +- Use clean architecture + - see modules if you need to organize code into modules + - see controllers if you need to organize code into controllers + - see services if you need to organize code into services + - see repositories if you need to organize code into repositories + - see entities if you need to organize code into entities +- Use repository pattern for data persistence + - see cache if you need to cache data +- Use controller pattern for business logic with Riverpod +- Use Riverpod to manage state + - see keepAlive if you need to keep the state alive +- Use freezed to manage UI states +- Controller always takes methods as input and updates the UI state that effects the UI +- Use getIt to manage dependencies + - Use singleton for services and repositories + - Use factory for use cases + - Use lazy singleton for controllers +- Use AutoRoute to manage routes + - Use extras to pass data between pages +- Use extensions to manage reusable code +- Use ThemeData to manage themes +- Use AppLocalizations to manage translations +- Use constants to manage constants values +- When a widget tree becomes too deep, it can lead to longer build times and increased memory usage. Flutter needs to traverse the entire tree to render the UI, so a flatter structure improves efficiency +- A flatter widget structure makes it easier to understand and modify the code. Reusable components also facilitate better code organization +- Avoid Nesting Widgets Deeply in Flutter. Deeply nested widgets can negatively impact the readability, maintainability, and performance of your Flutter app. Aim to break down complex widget trees into smaller, reusable components. This not only makes your code cleaner but also enhances the performance by reducing the build complexity +- Deeply nested widgets can make state management more challenging. By keeping the tree shallow, it becomes easier to manage state and pass data between widgets +- Break down large widgets into smaller, focused widgets +- Utilize const constructors wherever possible to reduce rebuilds + +### Testing + +- Use the standard widget testing for flutter +- Use integration tests for each api module. diff --git a/package/lib/services/notification_service.dart b/package/lib/services/notification_service.dart index c5f0a9a..2f0bfef 100644 --- a/package/lib/services/notification_service.dart +++ b/package/lib/services/notification_service.dart @@ -1,7 +1,6 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; -import 'dart:ui'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart'; @@ -47,7 +46,6 @@ class NotificationService { await checkPermissions(); await _initializeNotifications(); - await _setupAndroidChannel(); await _initializeMessages(); } @@ -91,30 +89,24 @@ class NotificationService { await _dito.identifyUser(); } - await _dito.trackEvent( - eventName: - "receive-${Platform.isIOS ? "ios" : "android"}-notification"); + final data = DataPayload.fromJson(message.data); - if (message.notification?.body == null && message.data["message"] != null) { - await _showLocalNotification(message); - } - } + final token = await _dito.notificationService().getDeviceFirebaseToken(); - _setupAndroidChannel() async { - const AndroidNotificationChannel channel = AndroidNotificationChannel( - 'dito_notifications', - 'Notifications sended by Dito', - importance: Importance.max, - ); - - await localNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannel(channel); + await _dito.trackEvent( + eventName: + "receive-${Platform.isIOS ? "ios" : "android"}-notification", + customData: { + "canal": "mobile", + "token": token ?? "", + "id-disparo": data.log_id, + "id-notificacao": data.notification, + "nome_notificacao": data.notification_name, + "provedor": "firebase", + "sistema_operacional": Platform.isIOS ? "Apple iPhone" : "Android", + }); } - _setupNotifications() async {} - _initializeNotifications() async { const android = AndroidInitializationSettings('@mipmap/ic_launcher'); const ios = DarwinInitializationSettings(); @@ -138,16 +130,6 @@ class NotificationService { iosDetails = details; } - _showLocalNotification(RemoteMessage message) async { - await localNotificationsPlugin.show( - message.hashCode % 2147483647, - message.notification?.title ?? "", - message.notification?.body ?? "", - NotificationDetails(android: androidDetails, iOS: iosDetails), - payload: jsonEncode(message.data), - ); - } - Future checkPermissions() async { var settings = await FirebaseMessaging.instance.getNotificationSettings(); From 33d103d45859f492ae56122f7ba4283f025ebc1a Mon Sep 17 00:00:00 2001 From: Igor Duarte Date: Mon, 19 Jan 2026 16:43:02 -0300 Subject: [PATCH 08/11] feat: add identifier field to DataPayload class - Introduced a new 'identifier' field in the DataPayload class to enhance data structure. - Updated constructor and JSON serialization methods to accommodate the new field. --- package/lib/entity/data_payload.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/package/lib/entity/data_payload.dart b/package/lib/entity/data_payload.dart index f094acb..780c92d 100644 --- a/package/lib/entity/data_payload.dart +++ b/package/lib/entity/data_payload.dart @@ -1,6 +1,7 @@ class DataPayload { final String reference; final String user_id; + final String identifier; final String notification; final String log_id; final String notification_name; @@ -14,6 +15,7 @@ class DataPayload { DataPayload( this.reference, this.user_id, + this.identifier, this.notification, this.log_id, this.notification_name, @@ -30,6 +32,7 @@ class DataPayload { return DataPayload( json["reference"] ?? "", json["user_id"] ?? "", + json["identifier"] ?? "", json["notification"] ?? "", json["log_id"] ?? "", json["notification_name"] ?? "", @@ -44,6 +47,7 @@ class DataPayload { Map toJson() => { 'reference': reference, 'user_id': user_id, + 'identifier': identifier, 'notification': notification, 'log_id': log_id, 'notification_name': notification_name, From 0fdbb8091eb9164a57aec411dd2257671b5e5e6b Mon Sep 17 00:00:00 2001 From: Igor Duarte <119457595+igorgsduarte@users.noreply.github.com> Date: Mon, 19 Jan 2026 18:43:37 -0300 Subject: [PATCH 09/11] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- package/lib/services/notification_service.dart | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/package/lib/services/notification_service.dart b/package/lib/services/notification_service.dart index 2f0bfef..226c05b 100644 --- a/package/lib/services/notification_service.dart +++ b/package/lib/services/notification_service.dart @@ -85,8 +85,11 @@ class NotificationService { void _handleMessage(RemoteMessage message) async { if (_dito.user.isNotValid) { - _dito.identify(userID: message.data["reference"]); - await _dito.identifyUser(); + final dynamic reference = message.data["reference"]; + if (reference is String && reference.isNotEmpty) { + _dito.identify(userID: reference); + await _dito.identifyUser(); + } } final data = DataPayload.fromJson(message.data); From 2a1e143154c9e3a6d008dc8a9faa44f67e076517 Mon Sep 17 00:00:00 2001 From: Igor Duarte <119457595+igorgsduarte@users.noreply.github.com> Date: Mon, 19 Jan 2026 18:44:38 -0300 Subject: [PATCH 10/11] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- package/lib/services/notification_service.dart | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package/lib/services/notification_service.dart b/package/lib/services/notification_service.dart index 226c05b..e864cc6 100644 --- a/package/lib/services/notification_service.dart +++ b/package/lib/services/notification_service.dart @@ -137,9 +137,10 @@ class NotificationService { var settings = await FirebaseMessaging.instance.getNotificationSettings(); if (settings.authorizationStatus != AuthorizationStatus.authorized) { - await FirebaseMessaging.instance.requestPermission(); + final newSettings = + await FirebaseMessaging.instance.requestPermission(); _messagingAllowed = - (settings.authorizationStatus == AuthorizationStatus.authorized); + (newSettings.authorizationStatus == AuthorizationStatus.authorized); } } } From 22e9ffc8e73ea94ac8ad21f6c9b94b677aa10fec Mon Sep 17 00:00:00 2001 From: Igor Duarte <119457595+igorgsduarte@users.noreply.github.com> Date: Mon, 19 Jan 2026 18:46:23 -0300 Subject: [PATCH 11/11] Apply suggestion from @Copilot Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- package/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package/pubspec.yaml b/package/pubspec.yaml index a4a2dbd..3436a32 100644 --- a/package/pubspec.yaml +++ b/package/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: firebase_messaging: ">=14.9.1" firebase_core: ">=2.30.1" sqflite_common_ffi: ">=2.3.3" - flutter_local_notifications: ">=17.0.0 <18.0.0" + flutter_local_notifications: ">=17.1.0 <18.0.0" dev_dependencies: flutter_test: