📑

【Flutter】 PDFビューアー

2022/02/12に公開

1. はじめに

FlutterのPDFビューアのパッケージは色々あるが、結局どのパッケージが良いのか分からない方の記事です。

ざっと調べただけで5種類ほどあります。それ以外にwebview_flutterを使う方法もありそうです。

実際に使ってみると、Androidの特定の端末では、Warningが出たり、クラッシュしたり、様々な問題が発生しました。

flutter_pdfviewを採用

結論から言うと、今回は「Flutter_pdfview」を使いました。
私が想定していた、iOS14以上、Android6以上でも期待通りに動作しました。

今回は、ソースコード内にPDFを保持する場合のケースで実装方法をご説明します。
また、開発環境は、「2.10.0」です。

2. 準備作業

2.1. パッケージの準備

まずは、flutter_pdfviewパッケージを追加します。
ターミナルにて、下記のコマンドを実行します。

flutter pub add flutter_pdfview

実行後、pubspec.yamlに、該当のパッケージが追加されていることを確認します。

flutter_pdfview: ^1.2.1

合わせて、関連するパッケージも追加します。
サーバーからPDFファイルをダウンロードする場合は不要のため、スキップしてください。
ターミナルにて、下記のコマンドを実行します。

flutter pub add path_provider
flutter pub add path

実行後、pubspec.yamlに、該当のパッケージが追加されていることを確認します。

flutter_pdfview: ^1.2.1
path_provider: ^2.0.9
path: ^1.8.0

2.2. PDFファイルのパス指定

PDFファイルのパスをpubspec.yamlに設定します。
今回は、assets/docs/の配下に「sample.pdf」を配置します。

  assets:
    - assets/docs/

2.3. PDFファイルの設置

上記で指定したディレクトリを作成し、「sample.pdf」を配置します。

2.4. iOS用の設定

iPhoneで表示するために、info.plistに下記を追加します。

    <key>io.flutter.embedded_views_preview</key>
    <true/>

ここまで完了後、テーミナルにて、「pub get」してください。

3. PDFファイルの読み込み、保存

今回は、ソースコード内に配置したPDFをアプリケーション専用のディレクトリに保存します。

3.1. PDFファイルの保存

読み込んだファイルをアプリ内のディレクトリに保存するため、先に保存用のメソッドを作成します。

  Future<File> _storeFile(String url, List<int> bytes) async {
    final filename = basename(url);
    final dir = await getApplicationDocumentsDirectory();

    final file = File('${dir.path}/$filename');
    await file.writeAsBytes(bytes, flush: true);

    return file;
  }

3.2. PDFファイルの読み込み

次に、PDFファイル読み込み用のメソッドを用意します。

  Future<File?> _load(String url) async {
    final data = await rootBundle.load(url);
    final bytes = data.buffer.asUint8List();

    return await _storeFile(url, bytes);
  }

4. PDFビューアー画面の作成

4.1. 準備

PDFファイルのオブジェクト格納用の変数を用意します。

  File? file;

4.2. PDFファイルの読み込み

今回は、StatefullウィジェットのinitStateにて、PDFファイルを読み込み、fileに保存します。

  @override
  void initState() {
    _load('assets/docs/sample.pdf').then((value) {
      setState(() {
        file = value;
      });
    });

    super.initState();
  }

4.3. ビューアー画面の作成

PDFファイル読み込み中は、画面中央にCircularProgressIndicatorを表示します。
読み込み完了後に、PDFViewにファイルパスを指定し、PDFファイルを表示します。
今回は、その他のプロパティは割愛します。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('PDF Viewer'),
      ),
      body: file == null
          ? const Center(
              child: CircularProgressIndicator(),
            )
          : SizedBox(
              height: MediaQuery.of(context).size.height,
              child: PDFView(
                filePath: file!.path,
              ),
            ),
    );
  }

5. まとめ

今回は、「Flutter_pdfview」の実装方法について、まとめました。
どのパッケージを使って良いか迷っている方は、こちらの記事を参考にしてみてください。

最後にソースコードの全体像を載せておきます。

import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart';
import 'package:path/path.dart';
import 'package:path_provider/path_provider.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key}) : super(key: key);

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  File? file;

  @override
  void initState() {
    _load('assets/docs/sample.pdf').then((value) {
      setState(() {
        file = value;
      });
    });

    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('PDF Viewer'),
      ),
      body: file == null
          ? const Center(
              child: CircularProgressIndicator(),
            )
          : SizedBox(
              height: MediaQuery.of(context).size.height,
              child: PDFView(
                filePath: file!.path,
              ),
            ),
    );
  }

  Future<File> _storeFile(String url, List<int> bytes) async {
    final filename = basename(url);
    final dir = await getApplicationDocumentsDirectory();

    final file = File('${dir.path}/$filename');
    await file.writeAsBytes(bytes, flush: true);

    return file;
  }

  Future<File?> _load(String url) async {
    final data = await rootBundle.load(url);
    final bytes = data.buffer.asUint8List();

    return await _storeFile(url, bytes);
  }
}

Discussion