Instalação e primeiro relatório
Instalação
Adicione sulfite_core ao seu projeto Flutter:
flutter pub add sulfite_coreOu diretamente no pubspec.yaml:
dependencies:
sulfite_core:
git:
url: https://github.com/rafaelgazani/sulfite.git
path: packages/sulfite_coreRequisitos recomendados: Dart SDK 3.9.2+ e Flutter 3.29+ (canal estável).
Para usar o stack completo (incluindo Studio e Chat), mantenha Flutter 3.29+. O
sulfite_coreisolado aceita constraints mais amplas, mas a recomendação acima evita diferenças de tooling entre pacotes.
Estrutura mínima
Um relatório Sulfite é composto por dois arquivos JSON:
- report.json — define a estrutura (bands, elementos, data sources)
- data.json — contém os dados a serem renderizados
Antes do exemplo, dois conceitos importantes:
- Bands: um relatório é dividido em seções (
header,detail,summary,footer).header/summary/footeraparecem uma vez por página (ou no final, conforme regra), edetailrepete para cada linha de dados. - Vínculo entre definição e payload:
dataSource.idemreport.jsondeve existir como chave nodata.json.
Exemplo: se o data source tem"id": "items", o payload precisa ter"items": [...].
schema dentro de dataSources descreve os campos esperados e seus tipos (metadado de tipagem e suporte para validação/introspecção).
Ele deve refletir a estrutura real dos dados para evitar bindings inconsistentes.
report.json
{
"reportId": "invoice_001",
"reportName": "Invoice",
"version": "1.0",
"pageSettings": {
"format": "A4",
"width": 595.28,
"height": 841.89,
"unit": "pt",
"orientation": "portrait",
"margins": { "left": 40, "right": 40, "top": 40, "bottom": 40 }
},
"dataSources": [
{
"id": "items",
"type": "list",
"schema": {
"product": "string",
"quantity": "integer",
"price": "number"
}
}
],
"bands": [
{
"type": "header",
"id": "header_main",
"height": 120,
"elements": [
{
"type": "text",
"id": "title",
"x": 40,
"y": 40,
"content": "INVOICE",
"fontSize": 32,
"bold": true,
"color": "1976d2"
},
{
"type": "text",
"id": "company",
"x": 40,
"y": 85,
"content": "ACME Corporation",
"fontSize": 14
}
]
},
{
"type": "detail",
"id": "detail_items",
"dataSourceId": "items",
"height": 30,
"elements": [
{
"type": "field",
"id": "col_product",
"x": 40,
"y": 8,
"width": 250,
"binding": "product"
},
{
"type": "field",
"id": "col_qty",
"x": 300,
"y": 8,
"width": 60,
"binding": "quantity",
"format": "integer"
},
{
"type": "field",
"id": "col_price",
"x": 370,
"y": 8,
"width": 80,
"binding": "price",
"format": "currency:BRL"
}
]
},
{
"type": "summary",
"id": "summary_main",
"height": 60,
"elements": [
{
"type": "text",
"id": "total_label",
"x": 370,
"y": 30,
"content": "TOTAL:",
"fontSize": 14,
"bold": true
},
{
"type": "aggregate",
"id": "total",
"x": 460,
"y": 30,
"width": 95,
"verb": "SUM",
"dataSourceId": "items",
"targetKey": "price",
"format": "currency:BRL"
}
]
}
]
}Cores no Sulfite: use HEX sem
#(por exemplo1976d2,000000,f0f0f0).
data.json
{
"items": [
{ "product": "Notebook Dell XPS 15", "quantity": 1, "price": 2500.00 },
{ "product": "Mouse Logitech MX Master", "quantity": 2, "price": 89.90 },
{ "product": "Teclado Keychron K8", "quantity": 1, "price": 155.60 }
]
}Código — Dart CLI (script/servidor)
import 'dart:convert';
import 'dart:io';
import 'package:sulfite_core/sulfite_core.dart';
void main() async {
try {
final engine = SulfiteEngineImpl();
final reportJson = File('report.json').readAsStringSync();
final report = await engine.parseReport(reportJson);
final dataJson = jsonDecode(File('data.json').readAsStringSync());
final context = await engine.processData(report, dataJson);
final pdfBytes = await engine.renderToPdf(context);
File('invoice.pdf').writeAsBytesSync(pdfBytes);
print('PDF gerado: invoice.pdf');
} catch (e, s) {
stderr.writeln('Falha ao gerar relatório: $e');
stderr.writeln(s);
exitCode = 1;
}
}Código — Flutter (mobile/desktop/web)
No Flutter, não use dart:io para ler assets. Carregue JSON com rootBundle.
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/services.dart' show rootBundle;
import 'package:sulfite_core/sulfite_core.dart';
Future<Uint8List> generateInvoicePdf() async {
final engine = SulfiteEngineImpl();
final reportJson = await rootBundle.loadString('assets/report.json');
final report = await engine.parseReport(reportJson);
final dataRaw = await rootBundle.loadString('assets/data.json');
final dataJson = jsonDecode(dataRaw) as Map<String, dynamic>;
final context = await engine.processData(report, dataJson);
return engine.renderToPdf(context);
}Em produção, trate erros de parseReport, processData e renderToPdf para lidar com JSON inválido, bindings incorretos e falhas de renderização.
Resultado esperado
O exemplo gera um PDF com:
- header com título e nome da empresa
- detail repetindo uma linha por item em
items - summary com total calculado por
SUM
INVOICE
ACME Corporation
Notebook Dell XPS 15 1 R$ 2.500,00
Mouse Logitech MX Master 2 R$ 89,90
Teclado Keychron K8 1 R$ 155,60
TOTAL: R$ 2.745,50Preview visual (exemplo)
Outros formatos
O mesmo context pode ser renderizado em qualquer formato:
// HTML (com Chart.js para gráficos)
final html = await engine.renderToHtml(context);
// CSV
final csv = await engine.renderToCsv(context);
// Excel (.xlsx)
final xlsx = await engine.renderToExcel(context);Caminho recomendado para iniciantes
Quer ir mais fundo?
Tutoriais avançados para casos reais:
- Relatório agrupado com subtotais → — GroupBands, expressões, agregados
- Dashboard com gráficos → — Charts, RichText, Table, multi-source
- Relatório com parâmetros → — Filtros, validação, GroupBands
- Exportação multi-formato → — PDF, HTML, CSV, Excel