【やりたい事】
API通信を利用してLaravelから生のPDFを返してFlutterで表示する。
ポイントは生のPDFと言う事です。
またAmazon S3、RiverPodやGoRouter等、この記事では触れていません。
【開発環境】
Laravel 8.70.2
Flutter 3.7.7
Dart 2.19.4
【PDFパッケージ】
【参考サイト】
【サンプルコード】
use Illuminate\Support\Facades\Storage;
use Illuminate\Http\Response;
public function getPDF()
{
try {
$pdf = Storage::disk('s3')->get('path/to/your/demo.pdf');
return response($pdf, 200, [
'Content-Type' => 'application/pdf',
'Content-Disposition' => 'inline; filename="your_pdf_name.pdf"'
]);
} catch (Exception $e) {
return response()->json([
'message' => $e->getMessage(),
],500, [], JSON_UNESCAPED_UNICODE);
}
}
認証処理等必要な場合は追加してください。
Storage::disk('s3')->get('path/to/your/demo.pdf');
でPDFを取得していますが、これはdiskメソッドでAmazon S3を指定して/test/demo.pdfというパスのファイルの内容を取得しています。
class API {
Future requestPDF(String _apiToken) async {
String url = 'http://your-laravel-app-url/api/get-pdf';
final authorization = "Bearer " + _apiToken;
final response = await http.get(
url,
headers: {
HttpHeaders.authorizationHeader: authorization,
HttpHeaders.acceptHeader: "application/json",
},
);
return response;
}
}
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
class ViewA extends StatefulWidget {
@override
_ViewAState createState() => _ViewAState();
}
class _ViewAState extends State<ViewA> {
Future requestPDF() async {
//getAPI()の引数に渡す為のtokenを取得する処理を追加してください。
final response = await API().requestPDF(_apiToken);
if(response.statusCode == 200){
Navigator.push(
context,
MaterialPageRoute(
builder: (context) =>
ViewB(pdfData: response.bodyBytes)));
} else {
// エラーハンドリング
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Download PDF'),
),
body: Center(
child: TextButton(
child: Text('Download PDF'),
onPressed: () async {
requestPDF();
);
},
),
),
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_pdfview/flutter_pdfview.dart';
class ViewB extends StatefulWidget {
final Uint8List pdfData;
ViewB({Key? key, required this.pdfData}) : super(key: key);
_ViewB createState() => _ViewB();
}
class _ViewB extends State<ViewB> with WidgetsBindingObserver {
final Completer<PDFViewController> _controller =
Completer<PDFViewController>();
bool _initLoading = true;
@override
Widget build(BuildContext context) {
return Scaffold(
body: PDFView(
pdfData: widget.pdfData,
enableSwipe: true,
swipeHorizontal: true,
autoSpacing: false,
pageFling: true,
pageSnap: true,
defaultPage: 0,
fitPolicy: FitPolicy.BOTH,
preventLinkNavigation: false,
onRender: (_pages) {
setState(() {
_initLoading = false;
});
},
onError: (error) {
print(error.toString());
},
onPageError: (page, error) {
print('$page: ${error.toString()}');
},
onViewCreated: (PDFViewController pdfViewController) {
_controller.complete(pdfViewController);
},
),
_initLoading
? Center(
child: CircularProgressIndicator(),
)
: Container(),
);
}
}
注意 : PDFは1ページのみの想定です。複数ページがある場合は別途追加コードが必要です。
また、戻るボタンの配置、PDFをデバイスに保存する等は実装していません。
【はまった事】
Laravelでresponseを返す時にresponse()->file()で返していました。
上記だとファイルシステム上の物理ファイルへのパスを引数として取り、ストレージから直接取得した生のPDFデータではなく、ディスク上の物理ファイルを提供する場合に使用するそうです ですのでFlutterでうまく表示がされず、原因がわかるまでFlutter側の実装に問題があると思い込み時間を使ってしまいました。
【最後に】
簡単にPDFを取得して表示できたかと思います。
時間があればデバイスに保存する記事も投稿できたらと考えています。