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:
full_pdf_viewer_plugin.dart: Lida com a comunicação nativa para abrir e gerenciar o visualizador de PDF.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);
}
}