Skip to main content

Dependencies

dependencies:
  http: ^1.1.0
  path_provider: ^2.1.0
  open_file: ^3.3.0

Client

import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;

class FileloomClient {
  final String apiKey;
  final String baseUrl;

  FileloomClient({required this.apiKey, this.baseUrl = 'https://api.fileloom.io/v1'});

  Future<PdfResult> generatePdf({
    String? html,
    String? templateId,
    Map<String, dynamic>? templateData,
    String? filename,
  }) async {
    final payload = <String, dynamic>{};
    if (templateId != null) payload['templateId'] = templateId;
    else if (html != null) payload['htmlContent'] = html;
    if (templateData != null) payload['templateData'] = templateData;
    if (filename != null) payload['filename'] = filename;

    final response = await http.post(
      Uri.parse('$baseUrl/pdf/generate'),
      headers: {'X-API-Key': apiKey, 'Content-Type': 'application/json'},
      body: jsonEncode(payload),
    ).timeout(const Duration(seconds: 60));

    final data = jsonDecode(response.body);
    if (response.statusCode != 200) {
      throw FileloomException(
        message: data['error']?['message'] ?? 'Unknown error',
        code: data['error']?['code'] ?? 'UNKNOWN',
      );
    }
    return PdfResult.fromJson(data);
  }

  Future<File> downloadPdf(String url, String filePath) async {
    final response = await http.get(Uri.parse(url));
    final file = File(filePath);
    await file.writeAsBytes(response.bodyBytes);
    return file;
  }
}

class PdfResult {
  final String fileId, url, filename;
  final int size, remainingCredits;

  PdfResult({required this.fileId, required this.url, required this.filename,
    required this.size, required this.remainingCredits});

  factory PdfResult.fromJson(Map<String, dynamic> json) => PdfResult(
    fileId: json['data']['fileId'],
    url: json['data']['url'],
    filename: json['data']['filename'],
    size: json['data']['size'],
    remainingCredits: json['usage']['remaining'],
  );
}

class FileloomException implements Exception {
  final String message, code;
  FileloomException({required this.message, required this.code});
}

Usage

final client = FileloomClient(
  apiKey: const String.fromEnvironment('FILELOOM_API_KEY'),
);

// Generate from HTML
final result = await client.generatePdf(
  html: '<h1>Hello World</h1>',
  filename: 'hello.pdf',
);
print('URL: ${result.url}');

// Generate from template
final invoice = await client.generatePdf(
  templateId: 'tpl_invoice_v2',
  templateData: {
    'invoiceNumber': 'INV-001',
    'customer': {'name': 'Acme Corp'},
    'items': [{'description': 'Web Dev', 'quantity': 10, 'price': 150}],
    'total': 1500,
  },
  filename: 'invoice.pdf',
);

Download and Open

import 'package:path_provider/path_provider.dart';
import 'package:open_file/open_file.dart';

Future<void> generateAndOpen() async {
  final result = await client.generatePdf(html: '<h1>Report</h1>');
  final dir = await getTemporaryDirectory();
  await client.downloadPdf(result.url, '${dir.path}/report.pdf');
  await OpenFile.open('${dir.path}/report.pdf');
}

Widget

class PdfButton extends StatefulWidget {
  final String templateId;
  final Map<String, dynamic> data;
  const PdfButton({required this.templateId, required this.data});

  @override
  State<PdfButton> createState() => _PdfButtonState();
}

class _PdfButtonState extends State<PdfButton> {
  bool _loading = false;

  Future<void> _generate() async {
    setState(() => _loading = true);
    try {
      final result = await client.generatePdf(
        templateId: widget.templateId,
        templateData: widget.data,
      );
      final dir = await getTemporaryDirectory();
      await client.downloadPdf(result.url, '${dir.path}/doc.pdf');
      await OpenFile.open('${dir.path}/doc.pdf');
    } finally {
      setState(() => _loading = false);
    }
  }

  @override
  Widget build(BuildContext context) => ElevatedButton(
    onPressed: _loading ? null : _generate,
    child: Text(_loading ? 'Generating...' : 'Download PDF'),
  );
}