Skip to content

Instalação e primeiro relatório

Instalação

Adicione sulfite_core ao seu projeto Flutter:

bash
flutter pub add sulfite_core

Ou diretamente no pubspec.yaml:

yaml
dependencies:
  sulfite_core:
    git:
      url: https://github.com/rafaelgazani/sulfite.git
      path: packages/sulfite_core

Requisitos 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_core isolado 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/footer aparecem uma vez por página (ou no final, conforme regra), e detail repete para cada linha de dados.
  • Vínculo entre definição e payload: dataSource.id em report.json deve existir como chave no data.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

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 exemplo 1976d2, 000000, f0f0f0).

data.json

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)

dart
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.

dart
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
text
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,50

Preview visual (exemplo)

Preview do PDF gerado

Outros formatos

O mesmo context pode ser renderizado em qualquer formato:

dart
// 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

  1. Entender a definição do relatório →
  2. Conceitos de bands →
  3. Processamento e bindings →

Quer ir mais fundo?

Tutoriais avançados para casos reais:

Sulfite do 🇧🇷 para o mundo © 2026 Rafael S. Pinheiro