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/.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..cedfd39 --- /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). 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.
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/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.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 754cb67..e2173ca 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'; @@ -15,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(); @@ -59,72 +66,50 @@ 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( - 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( - child: const Text('Registrar Identify'), - onPressed: handleIdentify, - ), - OutlinedButton( - child: const Text('Receber Notification'), - onPressed: handleNotification, - ), - TextButton( - child: const Text('Criar notificação local'), - onPressed: handleLocalNotification, - ), - ], + 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 9eada28..36b586b 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,38 +1,29 @@ -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'; import 'package:provider/provider.dart'; import 'app.dart'; -import 'constants.dart'; -@pragma('vm:entry-point') -Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { +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(); - await dito.initializePushNotificationService((Map payload) { - print(payload); - }); + dito.notificationService().onClick = (String link) { + print(link); + }; - final notification = DataPayload.fromJson(jsonDecode(message.data["data"])); + return dito; +} - dito.notificationService().showLocalNotification( - CustomNotification( - id: message.hashCode, - title: notification.details.title, - body: notification.details.message, - payload: notification, - ), - ); +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + await _setupDito(); } void main() async { @@ -41,15 +32,7 @@ void main() async { await Firebase.initializeApp(); FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); - DitoSDK dito = DitoSDK(); - dito.initialize( - apiKey: Constants.ditoApiKey, - secretKey: Constants.ditoSecretKey, - ); - - await dito.initializePushNotificationService((Map payload) { - print(payload); - }); + final dito = await _setupDito(); runApp( MultiProvider( diff --git a/example/pubspec.lock b/example/pubspec.lock index ba0d4bc..ad694d2 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.8" fake_async: dependency: transitive description: @@ -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: @@ -193,26 +201,26 @@ packages: dependency: transitive description: name: flutter_local_notifications - sha256: ef41ae901e7529e52934feba19ed82827b11baa67336829564aeab3129460610 + sha256: "674173fd3c9eda9d4c8528da2ce0ea69f161577495a9cc835a2a4ecd7eadeb35" url: "https://pub.dev" source: hosted - version: "18.0.1" + version: "17.2.4" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - sha256: "8f685642876742c941b29c32030f6f4f6dacd0e4eaecb3efbb187d6a3812ca01" + sha256: c49bd06165cad9beeb79090b18cd1eb0296f4bf4b23b84426e37dd7c027fc3af url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "4.0.1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "6c5b83c86bf819cdb177a9247a3722067dd8cc6313827ce7c77a4b238a26fd52" + sha256: "85f8d07fe708c1bdcf45037f2c0109753b26ae077e9d9e899d55971711a4ea66" url: "https://pub.dev" source: hosted - version: "8.0.0" + version: "7.2.0" flutter_test: dependency: "direct dev" description: flutter @@ -480,10 +488,10 @@ packages: dependency: transitive description: name: timezone - sha256: ffc9d5f4d1193534ef051f9254063fa53d588609418c84299956c3db9383587d + sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d" url: "https://pub.dev" source: hosted - version: "0.10.0" + version: "0.9.4" typed_data: dependency: transitive description: diff --git a/example/pubspec.yaml b/example/pubspec.yaml index d776aaf..d6e8110 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 @@ -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/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/dito_sdk.dart b/package/lib/dito_sdk.dart index 2a488ca..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'; @@ -49,20 +47,8 @@ class DitoSDK { }; } - Future initializePushNotificationService( - {Function(Map)? onTap}) async { - await Firebase.initializeApp(); - await _notificationService.initialize(onTap); - - RemoteMessage? initialMessage = - await FirebaseMessaging.instance.getInitialMessage(); - - if (initialMessage != null) { - _notificationService.handleMessage(initialMessage); - } - - FirebaseMessaging.onMessageOpenedApp - .listen(_notificationService.handleMessage); + Future initializePushNotificationService() async { + await _notificationService.initialize(); } void _checkConfiguration() { diff --git a/package/lib/entity/data_payload.dart b/package/lib/entity/data_payload.dart index 34bec79..780c92d 100644 --- a/package/lib/entity/data_payload.dart +++ b/package/lib/entity/data_payload.dart @@ -1,48 +1,61 @@ -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 user_id; final String identifier; 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.identifier, + 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["identifier"] ?? "", + 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, + 'user_id': user_id, 'identifier': identifier, '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 b780b6e..e864cc6 100644 --- a/package/lib/services/notification_service.dart +++ b/package/lib/services/notification_service.dart @@ -6,18 +6,13 @@ 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)? _onTap; + Function(String)? onClick; AndroidNotificationDetails androidDetails = const AndroidNotificationDetails( 'dito_notifications', @@ -38,89 +33,81 @@ 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); } localNotificationsPlugin = FlutterLocalNotificationsPlugin(); - _setupNotifications(); await FirebaseMessaging.instance .setForegroundNotificationPresentationOptions( - badge: true, sound: true, alert: true); + alert: true, badge: true, sound: true); await checkPermissions(); - _onMessage(); + await _initializeNotifications(); + await _initializeMessages(); } Future getDeviceFirebaseToken() async { - if (Platform.isIOS) { - return FirebaseMessaging.instance.getAPNSToken(); - } else { - return FirebaseMessaging.instance.getToken(); - } + return FirebaseMessaging.instance.getToken(); } - _onMessage() { - FirebaseMessaging.onMessage.listen(handleMessage); - FirebaseMessaging.onMessageOpenedApp.listen(handleMessage); - } + Future _initializeMessages() async { + RemoteMessage? initialMessage = + await FirebaseMessaging.instance.getInitialMessage(); - void handleMessage(RemoteMessage message) { - if (message.data["data"] == null) { - print("Data is not defined: ${message.data}"); + if (initialMessage != null) { + _handleMessage(initialMessage); } - final notification = DataPayload.fromJson(jsonDecode(message.data["data"])); - - if (_messagingAllowed && notification.details.message.isNotEmpty) { - didReceiveLocalNotificationStream.add(CustomNotification( - id: message.hashCode, - title: notification.details.title, - body: notification.details.message, - payload: notification)); - } + FirebaseMessaging.onMessageOpenedApp.listen(_handleOnNotificationClick); + FirebaseMessaging.onMessage.listen(_handleMessage); } - addNotificationToStream(CustomNotification notification) { - didReceiveLocalNotificationStream.add(notification); + void _handleOnNotificationClick(RemoteMessage message) async { + final data = DataPayload.fromJson(message.data); + await _handleClick(data); } - Future checkPermissions() async { - var settings = await FirebaseMessaging.instance.getNotificationSettings(); + Future _handleClick(DataPayload data) async { + if (data.notification.isNotEmpty) { + await _dito.openNotification( + notificationId: data.notification, + identifier: data.user_id, + reference: data.reference); + } - if (settings.authorizationStatus != AuthorizationStatus.authorized) { - await FirebaseMessaging.instance.requestPermission(); - settings = await FirebaseMessaging.instance.getNotificationSettings(); - _messagingAllowed = - (settings.authorizationStatus == AuthorizationStatus.authorized); - } else { - _messagingAllowed = true; + if (onClick != null) { + onClick!(data.link); } } - _setupAndroidChannel() async { - const AndroidNotificationChannel channel = AndroidNotificationChannel( - 'dito_notifications', - 'Notifications sended by Dito', - importance: Importance.max, - ); - - await localNotificationsPlugin - .resolvePlatformSpecificImplementation< - AndroidFlutterLocalNotificationsPlugin>() - ?.createNotificationChannel(channel); - } + void _handleMessage(RemoteMessage message) async { + if (_dito.user.isNotValid) { + final dynamic reference = message.data["reference"]; + if (reference is String && reference.isNotEmpty) { + _dito.identify(userID: reference); + await _dito.identifyUser(); + } + } - _setupNotifications() async { - await _initializeNotifications(); - await _setupAndroidChannel(); - _listenStream(); + final data = DataPayload.fromJson(message.data); + + final token = await _dito.notificationService().getDeviceFirebaseToken(); + + 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", + }); } _initializeNotifications() async { @@ -131,30 +118,10 @@ class NotificationService { InitializationSettings(android: android, iOS: ios); await localNotificationsPlugin.initialize(initializationSettings, - onDidReceiveNotificationResponse: onTapNotification); - } + onDidReceiveNotificationResponse: (message) async { + final data = DataPayload.fromJson(jsonDecode(message.payload!)); - 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); - } - } + await _handleClick(data); }); } @@ -166,21 +133,14 @@ class NotificationService { iosDetails = details; } - showLocalNotification(CustomNotification notification) { - localNotificationsPlugin.show( - notification.id, - notification.title, - notification.body, - NotificationDetails(android: androidDetails, iOS: iosDetails), - payload: jsonEncode(notification.payload?.toJson()), - ); - } - - Future onTapNotification(NotificationResponse? response) async { - if (response?.payload != null) { - selectNotificationStream.add(response?.payload); + Future checkPermissions() async { + var settings = await FirebaseMessaging.instance.getNotificationSettings(); - _onTap!(jsonDecode(response!.payload!)["details"]); + if (settings.authorizationStatus != AuthorizationStatus.authorized) { + final newSettings = + await FirebaseMessaging.instance.requestPermission(); + _messagingAllowed = + (newSettings.authorizationStatus == AuthorizationStatus.authorized); } } } diff --git a/package/pubspec.yaml b/package/pubspec.yaml index 19b9dbb..3436a32 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: @@ -16,9 +16,9 @@ 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" + flutter_local_notifications: ">=17.1.0 <18.0.0" dev_dependencies: flutter_test: