Dart: Dart e Colaboração com Flutter
Última atualização: 2026-08-26
Dart é o coração do Flutter — uma única linguagem que impulsiona UI, lógica e comunicação nativa.
1. O que Você Aprenderá
- Posicionamento da camada Dart na arquitetura Flutter
- Design de um pacote de lógica de negócio compartilhada (pacote Dart puro)
- MethodChannel: Comunicação Dart ↔ Nativo
- FFI: Dart chamando bibliotecas C
- Cenário do Bob: Lógica central do DataPipeline servindo tanto CLI quanto dashboard Flutter
2. A História Real de um Desenvolvedor
(1) O Problema: CLI e App Flutter Cada Um Implementando Lógica de Negócio Separada
O DataPipeline do Bob tem dois front-ends: uma ferramenta CLI (para desenvolvedores) e um dashboard Flutter (para clientes SaaS visualizarem relatórios). Os dois sistemas implementaram sua própria lógica de processamento de dados, levando a: corrigir um bug de cálculo de imposto exigia alterações em dois lugares; uma vez, uma alteração esquecida fez com que o relatório CLI estivesse correto mas o dashboard estivesse errado, resultando em reclamações de clientes.
(2) Solução: Pacote de Lógica Compartilhada
Extrair a lógica de negócio central em um pacote Dart puro, do qual tanto o CLI quanto o Flutter dependem. Corrigir um bug requer alteração em apenas um lugar.
graph TD A[DataPipeline Core<br/>Dart puro] --> B[CLI App] A --> C[Flutter Dashboard] C --> D[MethodChannel] D --> E[iOS Nativo] D --> F[Android Nativo] C --> G[FFI] G --> H[Biblioteca C]
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
(3) Benefícios
- A lógica central é mantida em um único lugar, com CLI e Flutter sincronizados automaticamente
- O pacote Dart puro pode rodar em todas as plataformas (VM/Web/Native)
- MethodChannel permite que o Flutter acesse capacidades nativas (sensores, notificações push, etc.)
3. O Papel do Dart na Arquitetura Flutter
(1) Camadas da Arquitetura
| Camada | Tecnologia | Responsabilidade |
|---|---|---|
| Camada UI | Flutter Widget | Renderização de interface e interação |
| Camada de Lógica | Dart (puro) | Lógica de negócio, processamento de dados |
| Camada de Plataforma | Nativo (Kotlin/Swift) | Funções específicas de plataforma |
| Camada de Comunicação | MethodChannel / FFI | Ponte Dart ↔ Nativo |
▶ Exemplo
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
: Lógica Dart em um projeto Flutter
// Este código roda em AMBOS os ambientes CLI e Flutter
// lib/data_pipeline_core.dart
class OrderAnalyzer {
final double taxRate;
OrderAnalyzer({this.taxRate = 0.08});
double calculateTax(double amount) => amount * taxRate;
double calculateTotal(double amount) => amount * (1 + taxRate);
Map<String, double> groupByCategory(List``<Order>`` orders) {
final result = <String, double>{};
for (final order in orders) {
result.update(order.category, (v) => v + order.amount, ifAbsent: () => order.amount);
}
return result;
}
}
class Order {
final String id;
final double amount;
final String category;
Order({required this.id, required this.amount, required this.category});
}
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
4. Design de Pacote de Lógica de Negócio Compartilhada
(1) Estrutura de Pacote Dart Puro
▶ Exemplo
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
: Estrutura do pacote compartilhado
packages/
data_pipeline_core/
lib/
src/
models/
order.dart
product.dart
customer.dart
services/
analyzer.dart
aggregator.dart
transformer.dart
utils/
formatters.dart
validators.dart
data_pipeline_core.dart # Barrel export
test/
analyzer_test.dart
aggregator_test.dart
pubspec.yaml
```text
```text
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
▶ Exemplo
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
: Barrel export
// lib/data_pipeline_core.dart
// Barrel file - exporta todas as APIs públicas
// Models
export 'src/models/order.dart';
export 'src/models/product.dart';
export 'src/models/customer.dart';
// Services
export 'src/services/analyzer.dart';
export 'src/services/aggregator.dart';
export 'src/services/transformer.dart';
// Utils
export 'src/utils/formatters.dart';
export 'src/utils/validators.dart';
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
▶ Exemplo
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
: pubspec.yaml para pacote Dart puro
name: data_pipeline_core
description: Core business logic for DataPipeline - pure Dart, no Flutter dependency
version: 1.0.0
environment:
sdk: ^3.0.0
dependencies:
json_annotation: ^4.8.0
dev_dependencies:
test: ^1.24.0
json_serializable: ^6.7.0
build_runner: ^2.4.0
▶ Exemplo
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
: Dois consumidores
# CLI app pubspec.yaml
dependencies:
data_pipeline_core:
path: ../packages/data_pipeline_core
args: ^2.4.2
# Flutter app pubspec.yaml
dependencies:
data_pipeline_core:
path: ../packages/data_pipeline_core
flutter:
sdk: flutter
| Princípio | Descrição |
|---|---|
| Sem dependência Flutter | pubspec.yaml não depende de flutter |
| Sem uso direto de dart:io | Usar interfaces abstratas para operações de arquivo/rede |
| Sem código UI | Lógica de processamento de dados pura |
| Testes completos | Testável sem o runtime Flutter |
5. MethodChannel
(1) Comunicação Dart ↔ Nativo
▶ Exemplo
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
: MethodChannel chamando nativo
import 'package:flutter/services.dart';
class NativeService {
static const _channel = MethodChannel('com.datapipeline/native');
// Chamar método nativo
Future``<String>`` getDeviceId() async {
try {
final deviceId = await _channel.invokeMethod``<String>``('getDeviceId');
return deviceId ?? 'unknown';
} on PlatformException catch (e) {
print('Failed to get device ID: ${e.message}');
return 'error';
}
}
// Chamar com argumentos
Future``<bool>`` saveToFile(String path, String content) async {
try {
final result = await _channel.invokeMethod``<bool>``('saveToFile', {
'path': path,
'content': content,
});
return result ?? false;
} on PlatformException catch (e) {
print('Failed to save: ${e.message}');
return false;
}
}
}
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
(2) Implementação do Lado Nativo
▶ Exemplo
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
: Lado Android (Kotlin)
// Android implementation
class MainActivity : FlutterActivity() {
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger,
"com.datapipeline/native").setMethodCallHandler { call, result ->
when (call.method) {
"getDeviceId" -> {
val deviceId = Settings.Secure.getString(
contentResolver, Settings.Secure.ANDROID_ID)
result.success(deviceId)
}
"saveToFile" -> {
val path = call.argument<String>("path")
val content = call.argument<String>("content")
// Save file logic
result.success(true)
}
else -> result.notImplemented()
}
}
}
}
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
| Método de Comunicação | Direção | Tipo de Dados | Caso de Uso |
|---|---|---|---|
| MethodChannel | Dart → Nativo | Tipos padrão | Chamando métodos nativos |
| EventChannel | Nativo → Dart | Stream | Fluxos de eventos nativos |
| BasicMessageChannel | Bidirecional | String/Bytes | Mensagens bidirecionais |
6. FFI — Chamando Bibliotecas C
▶ Exemplo
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
: dart:ffi chamando uma função C
import 'dart:ffi';
import 'package:ffi/ffi.dart';
// Assinatura da função C: double process_data(double* data, int length)
typedef ProcessDataNative = Double Function(Pointer``<Double>``, Int32);
typedef ProcessDataDart = double Function(Pointer``<Double>``, int);
void main() {
// Carregar biblioteca dinâmica
final dylib = DynamicLibrary.open('libdatapipeline.so');
// Procurar função
final processData = dylib.lookupFunction<ProcessDataNative, ProcessDataDart>('process_data');
// Preparar dados
final dataPtr = calloc``<Double>``(5);
for (var i = 0; i < 5; i++) {
dataPtr[i] = (i + 1) * 100.0;
}
// Chamar função C
final result = processData(dataPtr, 5);
print('Result from C: $result');
// Liberar memória
calloc.free(dataPtr);
}
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
| Conceito FFI | Descrição |
|---|---|
DynamicLibrary |
Carregar bibliotecas de link dinâmico |
lookupFunction |
Procurar uma função C |
| `Pointer`` |
Aponta para memória nativa |
calloc / free |
Alocar/liberar memória nativa |
NativeType |
Mapeamento de tipo C |
7. Cenário do Bob: Arquitetura de Duplo Front-end do DataPipeline
▶ Exemplo
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
: Core compartilhado + duplo front-end
// packages/data_pipeline_core/lib/src/services/analyzer.dart
class OrderAnalyzer {
final double taxRate;
OrderAnalyzer({this.taxRate = 0.08});
AnalysisResult analyze(List``<Order>`` orders) {
final completed = orders.where((o) => o.status == 'completed').toList();
final revenue = completed.fold``<double>``(0, (s, o) => s + o.amount);
final tax = revenue * taxRate;
final byCategory = <String, double>{};
for (final o in completed) {
byCategory.update(o.category, (v) => v + o.amount, ifAbsent: () => o.amount);
}
return AnalysisResult(
totalOrders: orders.length,
completedOrders: completed.length,
revenue: revenue,
tax: tax,
revenueByCategory: byCategory,
);
}
}
class AnalysisResult {
final int totalOrders;
final int completedOrders;
final double revenue;
final double tax;
final Map<String, double> revenueByCategory;
AnalysisResult({
required this.totalOrders,
required this.completedOrders,
required this.revenue,
required this.tax,
required this.revenueByCategory,
});
double get totalWithTax => revenue + tax;
double get averageOrderValue => completedOrders > 0 ? revenue / completedOrders : 0;
}
class Order {
final String id;
final double amount;
final String status;
final String category;
Order({required this.id, required this.amount, required this.status, required this.category});
}
// Consumidor CLI
void main() {
final analyzer = OrderAnalyzer(taxRate: 0.08);
final orders = [
Order(id: 'ORD-001', amount: 1500.0, status: 'completed', category: 'Electronics'),
Order(id: 'ORD-002', amount: 890.0, status: 'completed', category: 'Clothing'),
];
final result = analyzer.analyze(orders);
print('Revenue: \$${result.revenue.toStringAsFixed(2)} USD');
print('Tax: \$${result.tax.toStringAsFixed(2)} USD');
}
// Consumidor Flutter usaria o mesmo OrderAnalyzer
// mas renderizaria resultados em um Widget em vez de print()
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
8. Exemplo Completo: Arquitetura Compartilhada do DataPipeline
// ============================================
// DataPipeline Shared Architecture
// Core Dart puro + consumidor CLI + consumidor Flutter
// ============================================
// ---- Biblioteca Core (Dart puro) ----
// lib/src/models/order.dart
class Order {
final String id;
final double amount;
final String status;
final String category;
final String region;
const Order({
required this.id,
required this.amount,
required this.status,
required this.category,
this.region = 'US',
});
double get tax => amount * _taxRate(region);
double get total => amount + tax;
String get formatAmount => '\$${amount.toStringAsFixed(2)} USD';
static double _taxRate(String region) => switch (region) {
'US' => 0.08,
'EU' => 0.20,
'UK' => 0.15,
_ => 0.10,
};
}
// lib/src/services/analyzer.dart
class OrderAnalyzer {
const OrderAnalyzer();
AnalysisResult analyze(List``<Order>`` orders) {
final completed = orders.where((o) => o.status == 'completed').toList();
final revenue = completed.fold``<double>``(0, (s, o) => s + o.amount);
final tax = completed.fold``<double>``(0, (s, o) => s + o.tax);
final total = revenue + tax;
final byCategory = <String, CategoryResult>{};
for (final o in completed) {
byCategory.update(
o.category,
(v) => v.addOrder(o),
ifAbsent: () => CategoryResult.fromOrder(o),
);
}
return AnalysisResult(
totalOrders: orders.length,
completedOrders: completed.length,
revenue: revenue,
tax: tax,
total: total,
byCategory: byCategory,
);
}
}
class CategoryResult {
final int count;
final double revenue;
const CategoryResult({required this.count, required this.revenue});
factory CategoryResult.fromOrder(Order order) =>
CategoryResult(count: 1, revenue: order.amount);
CategoryResult addOrder(Order order) =>
CategoryResult(count: count + 1, revenue: revenue + order.amount);
double get average => count > 0 ? revenue / count : 0;
}
class AnalysisResult {
final int totalOrders;
final int completedOrders;
final double revenue;
final double tax;
final double total;
final Map<String, CategoryResult> byCategory;
const AnalysisResult({
required this.totalOrders,
required this.completedOrders,
required this.revenue,
required this.tax,
required this.total,
required this.byCategory,
});
double get averageOrderValue => completedOrders > 0 ? revenue / completedOrders : 0;
}
// ---- Consumidor CLI ----
void main() {
final analyzer = OrderAnalyzer();
final orders = [
Order(id: 'ORD-001', amount: 1500.0, status: 'completed', category: 'Electronics'),
Order(id: 'ORD-002', amount: 890.0, status: 'completed', category: 'Clothing'),
Order(id: 'ORD-003', amount: 3200.0, status: 'pending', category: 'Electronics'),
Order(id: 'ORD-004', amount: 2100.0, status: 'completed', category: 'Electronics', region: 'EU'),
];
final result = analyzer.analyze(orders);
print('=== DataPipeline Analytics ===');
print('Orders: ${result.completedOrders}/${result.totalOrders}');
print('Revenue: \$${result.revenue.toStringAsFixed(2)} USD');
print('Tax: \$${result.tax.toStringAsFixed(2)} USD');
print('Total: \$${result.total.toStringAsFixed(2)} USD');
print('Average: \$${result.averageOrderValue.toStringAsFixed(2)} USD');
print('\nBy Category:');
final sorted = result.byCategory.entries.toList()
..sort((a, b) => b.value.revenue.compareTo(a.value.revenue));
for (final entry in sorted) {
final cat = entry.value;
print(' ${entry.key}: ${cat.count} orders, '
'\$${cat.revenue.toStringAsFixed(2)} USD (avg: \$${cat.average.toStringAsFixed(2)})');
}
print('\n--- Platform Compatibility ---');
print('Core logic: Pure Dart (CLI + Flutter + Web)');
print('CLI: dart:io + args package');
print('Flutter: Widgets + MethodChannel + FFI');
}
> **Saída:** Execute localmente no DartPad ou com `dart run`. Todos os exemplos do curso Dart são baseados em Dart 3.x / Flutter 3.x; os resultados podem variar ligeiramente dependendo das versões do SDK.
Saída:
=== DataPipeline Analytics ===
Orders: 3/4
Revenue: $4490.00 USD
Tax: $798.00 USD
Total: $5288.00 USD
Average: $1496.67 USD
By Category:
Electronics: 2 orders, $3600.00 USD (avg: $1800.00)
Clothing: 1 orders, $890.00 USD (avg: $890.00)
--- Platform Compatibility ---
Core logic: Pure Dart (CLI + Flutter + Web)
CLI: dart:io + args package
Flutter: Widgets + MethodChannel + FFI
❓ Perguntas Frequentes
P: Um pacote Dart puro pode rodar no Flutter Web? R: Sim, desde que dart:io não seja usado. A plataforma web não suporta dart:io; use interfaces abstratas (como o Client do pacote http) para substituir operações diretas de arquivo/rede.
P: Há um limite de tamanho para transferência de dados via MethodChannel? R: Não há limite rígido, mas objetos grandes requerem serialização/desserialização, o que tem desempenho ruim. Para dados grandes, considere usar FFI ou arquivos temporários.
P: FFI suporta iOS? R: Sim. No iOS, carregue .dylib ou .framework. Flutter 3.0+ FFI suporta todas as principais plataformas.
P: Quando devo usar MethodChannel vs. FFI? R: MethodChannel é adequado para chamar APIs de plataforma (sensores, notificações push, salvamento de arquivos); FFI é adequado para chamar bibliotecas C (processamento de imagens, criptografia, motores de banco de dados).
P: Testar um pacote compartilhado requer ambiente Flutter? R: Não. Um pacote Dart puro pode ser testado apenas com
dart teste não depende do Flutter. Este é um grande benefício de extrair um pacote compartilhado.
P: Como garantir que um pacote compartilhado não introduza acidentalmente dependências Flutter? R: Não dependa de flutter no pubspec.yaml, e não importe pacotes flutter. No CI, execute testes com
dart test(nãoflutter test) para verificar.
P: O pacote core do DataPipeline deve usar um monorepo ou um repositório separado? R: Um monorepo é recomendado (usando Melos ou dependências de caminho) para facilitar a sincronização entre o pacote core e os consumidores. Um repositório separado é adequado para distribuição open-source.
📖 Resumo
- Dart é a linguagem central do Flutter, impulsionando tanto a camada UI quanto a de lógica
- Um pacote de lógica compartilhada (Dart puro) permite que CLI e Flutter reutilizar o mesmo código de negócio
- MethodChannel implementa comunicação Dart ↔ Nativo para funções específicas de plataforma
- FFI chama diretamente bibliotecas C, adequado para computação de alto desempenho e integração de código C existente
- Princípio de arquitetura: Lógica central em Dart puro, camada UI customizada por plataforma
📝 Exercícios
- Básico (Dificuldade ⭐): Crie um pacote Dart puro (
dart create -t package-simple) contendo uma funçãoformatUSD. Referencie e chame-a de ambos um projeto CLI e um projeto Flutter. - Intermediário (Dificuldade ⭐⭐): Design a estrutura do pacote core do DataPipeline: 3 classes de modelo + 2 classes de serviço + 1 barrel export. Garanta que pubspec.yaml não tenha dependência Flutter, e escreva testes executáveis com
dart test. - Desafio (Dificuldade ⭐⭐⭐): Implemente uma interface abstrata
DataStorage(Dart puro), depois implemente-a usando dart:io (CLI) e SharedPreferences (Flutter) separadamente. O pacote core deve depender apenas da interface, com implementações concretas injetadas pelos consumidores.