Implementando Visualização de PDFs em Aplicativos Flutter

A biblioteca flutter_full_pdf_viewer permite o download e a exibição de arquivos PDF diretamente de URLs na sua aplicação Flutter.

Adição da Dependência:

Primeiro, inclua as seguintes dependências no seu arquivo pubspec.yaml:


dependencies:
  # Pacote para visualização de PDFs
  flutter_full_pdf_viewer: ^1.0.6
  # Pacote para obter diretórios do sistema de arquivos
  path_provider: ^1.5.0

Exemplo de Uso:

A seguir, um exemplo prático de como utilizar o pacote:


import 'dart:async';
import 'dart:io';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_full_pdf_viewer/full_pdf_viewer_scaffold.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  runApp(MaterialApp(
    title: 'Exemplo de Plugin PDF',
    home: HomePage(),
  ));
}

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String pdfFilePath = "";

  @override
  void initState() {
    super.initState();
    _downloadAndSetPdfPath().then((file) {
      setState(() {
        pdfFilePath = file.path;
      });
    });
  }

  Future<File> _downloadAndSetPdfPath() async {
    final pdfUrl = "http://africau.edu/images/default/sample.pdf"; // URL de exemplo
    final fileName = pdfUrl.split("/").last;
    final directory = await getApplicationDocumentsDirectory();
    final filePath = '${directory.path}/$fileName';
    final file = File(filePath);

    if (!await file.exists()) {
      final httpClient = HttpClient();
      final request = await httpClient.getUrl(Uri.parse(pdfUrl));
      final response = await request.close();
      final bytes = await consolidateHttpClientResponseBytes(response);
      await file.writeAsBytes(bytes);
    }
    return file;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Visualizador de PDF')),
      body: Center(
        child: ElevatedButton(
          child: Text("Abrir PDF"),
          onPressed: pdfFilePath.isNotEmpty ? () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => PdfViewerScreen(pdfFilePath)),
            );
          } : null,
        ),
      ),
    );
  }
}

/// Componente para exibir o PDF
class PdfViewerScreen extends StatelessWidget {
  final String path;

  PdfViewerScreen(this.path);

  @override
  Widget build(BuildContext context) {
    return PDFViewerScaffold(
      appBar: AppBar(
        title: Text("Documento PDF"),
        actions: [
          IconButton(icon: Icon(Icons.share), onPressed: () {
            // Implementar funcionalidade de compartilhamento
            ScaffoldMessenger.of(context).showSnackBar(
              SnackBar(content: Text('Compartilhar funcionalidade ainda não implementada!')),
            );
          }),
        ],
      ),
      path: path,
    );
  }
}

Análise do Código Fonte:

O pacote é composto principalmente por dois arquivos:

  1. full_pdf_viewer_plugin.dart: Lida com a comunicação nativa para abrir e gerenciar o visualizador de PDF.
  2. full_pdf_viewer_scaffold.dart: Fornece um widget Scaffold pronto para uso para exibir o PDF em tela cheia ou integrado à interface do seu aplicativo.

full_pdf_viewer_plugin.dart:

Este arquivo define a interface do plugin, utilizando MethodChannel para interagir com o código nativo (Android/iOS). Ele expõe métodos como launch para iniciar a visulaização, close para fechar o visualizador e resize para ajustar as dimensões quando necessário. Um StreamController é usado para notificar sobre eventos como o fechamento do visualizador (onDestroy).


import 'dart:async';
import 'dart:ui';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

class PDFViewerPlugin {
  static PDFViewerPlugin _instance;
  final MethodChannel _channel = const MethodChannel("flutter_full_pdf_viewer");
  final StreamController<Null> _onDestroyController = StreamController<Null>.broadcast();

  factory PDFViewerPlugin() => _instance ??= PDFViewerPlugin._internal();
  PDFViewerPlugin._internal() {
    _channel.setMethodCallHandler(_handleNativeMessages);
  }

  Stream<Null> get onDestroyStream => _onDestroyController.stream;

  Future<Null> _handleNativeMessages(MethodCall call) async {
    switch (call.method) {
      case 'onDestroy':
        _onDestroyController.add(null);
        break;
    }
  }

  Future<Null> launchViewer(String filePath, {Rect? displayRect}) async {
    final arguments = <String, dynamic>{'path': filePath};
    if (displayRect != null) {
      arguments['rect'] = {
        'left': displayRect.left,
        'top': displayRect.top,
        'width': displayRect.width,
        'height': displayRect.height
      };
    }
    await _channel.invokeMethod('launch', arguments);
  }

  Future<Null> closeViewer() => _channel.invokeMethod('close');
  Future<Null> registerActivityResultListener() => _channel.invokeMethod('registerAcitivityResultListener');
  Future<Null> removeActivityResultListener() => _channel.invokeMethod('removeAcitivityResultListener');
  Future<Null> resizeViewer(Rect newRect) async {
    final arguments = {
      'rect': {
        'left': newRect.left,
        'top': newRect.top,
        'width': newRect.width,
        'height': newRect.height
      }
    };
    await _channel.invokeMethod('resize', arguments);
  }

  void dispose() {
    _onDestroyController.close();
    _instance = null;
  }
}

full_pdf_viewer_scaffold.dart:

Este widget cria uma estrutura Scaffold que encapsula o visualizador de PDF. Ele calcula a área de exibição correta, considerando a AppBar e os padding do sistema, e chama o PDFViewerPlugin para exibir o documento. Um mecanismo de redimensionamento adaptativo é incluído para lidar com mudanças de layout, como rotação de tela.


import 'dart:async';

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_full_pdf_viewer/full_pdf_viewer_plugin.dart';

class PDFViewerScaffold extends StatefulWidget {
  final PreferredSizeWidget? appBar;
  final String pdfPath;
  final bool isPrimary;

  const PDFViewerScaffold({
    Key? key,
    this.appBar,
    required this.pdfPath,
    this.isPrimary = true,
  }) : super(key: key);

  @override
  _PDFViewerScaffoldState createState() => _PDFViewerScaffoldState();
}

class _PDFViewerScaffoldState extends State<PDFViewerScaffold> {
  final PDFViewerPlugin _pdfPlugin = PDFViewerPlugin();
  Rect? _currentDisplayRect;
  Timer? _resizeDebounceTimer;

  @override
  void initState() {
    super.initState();
    _pdfPlugin.closeViewer(); // Garante que instâncias anteriores sejam fechadas
  }

  @override
  void dispose() {
    super.dispose();
    _pdfPlugin.closeViewer();
    _pdfPlugin.dispose();
    _resizeDebounceTimer?.cancel();
  }

  @override
  Widget build(BuildContext context) {
    // Calcula a área de exibição inicial e lança o visualizador
    if (_currentDisplayRect == null) {
      _currentDisplayRect = _calculateDisplayRect(context);
      _pdfPlugin.launchViewer(widget.pdfPath, displayRect: _currentDisplayRect);
    } else {
      // Verifica e atualiza a área de exibição se houver mudanças
      final updatedRect = _calculateDisplayRect(context);
      if (_currentDisplayRect != updatedRect) {
        _currentDisplayRect = updatedRect;
        _resizeDebounceTimer?.cancel();
        _resizeDebounceTimer = Timer(const Duration(milliseconds: 300), () {
          _pdfPlugin.resizeViewer(_currentDisplayRect!);
        });
      }
    }

    return Scaffold(
      appBar: widget.appBar,
      body: const Center(child: CircularProgressIndicator()), // Exibe um indicador enquanto o PDF carrega nativamente
    );
  }

  Rect _calculateDisplayRect(BuildContext context) {
    final mediaQuery = MediaQuery.of(context);
    final isFullScreen = widget.appBar == null;
    
    // Calcula o padding superior, considerando se é a tela primária
    final topPadding = widget.isPrimary ? mediaQuery.padding.top : 0.0;
    
    // Define a posição inicial (Y) do retângulo
    final topPosition = isFullScreen ? 0.0 : (widget.appBar!.preferredSize.height + topPadding);
    
    // Calcula a altura disponível para o PDF
    var availableHeight = mediaQuery.size.height - topPosition;
    if (availableHeight < 0.0) {
      availableHeight = 0.0;
    }

    // Retorna o retângulo definindo a área de exibição
    return Rect.fromLTWH(0.0, topPosition, mediaQuery.size.width, availableHeight);
  }
}

Tags: Flutter pdf visualizador path_provider plugin

Publicado em 7-7 19:13