Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
144 changes: 144 additions & 0 deletions .cursor/rules/flutter.mdc
Original file line number Diff line number Diff line change
@@ -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.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,4 @@ example/build/
package/build/
.flutter-plugins*
package/test/.env-test.json
build/
165 changes: 165 additions & 0 deletions DEVELOPER_SETUP.md
Original file line number Diff line number Diff line change
@@ -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<void> 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<void> _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`.
Loading
Loading