flutter_pdfview
flutter_pdfview copied to clipboard
(android only) PdfView becomes blank on device rotation (portrait -> landscape)
(android only) PdfView becomes blank on device rotation (portrait -> landscape). Does not reappear when the device is rotated back to portrait.
This does not seem to happen when PdfView is in a SizedBox with limited height.
Same Issue any solution?
This happens with the example posted with the package. Just play the example and rotate the terminal
Doctor summary (to see all details, run flutter doctor -v):
[✓] Flutter (Channel master, v1.6.1-pre.32, on Mac OS X 10.13.6 17G7024, locale
fr-FR)
[✓] Android toolchain - develop for Android devices (Android SDK version 28.0.3)
[✓] iOS toolchain - develop for iOS devices (Xcode 10.1)
[✓] Android Studio (version 3.4)
[✓] VS Code (version 1.34.0)
[✓] Connected device (3 available)
• No issues found!
Hello guys, this is a limitation from a native plugin. Related to https://github.com/barteksc/AndroidPdfViewer/issues/207 So, you guys try to re-render on device rotate.
It works if on 'didChangeMetrics' we redefine a new PDFViewController (no more final) and force redrawing the PDFView.
@egodefroy post some example code here.
Thanks.
didChangeMetrics
Could you provide an example, removing final solved another issue for me that caused a blank screen but implementing the didChangeMetrics method hasn't solved the rotation issue. (I'm also experiencing the issue whenever the keyboard is displayed).
class _PDFScreenState extends State<PDFScreen> with WidgetsBindingObserver {
Completer<PDFViewController> _controller = Completer<PDFViewController>();
PDFViewController _pdfView;
......
@override
void didChangeMetrics() {
setState(() {
_controller = new Completer<PDFViewController>();
});
}
}
EDIT: Adding "resizeToAvoidBottomPadding : false" to the scaffold solved the blank screen when keypad appears issue.
I get the same error. Here is the log:
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): Failed to handle method call
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): java.lang.IllegalStateException: Reply already submitted
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at io.flutter.embedding.engine.dart.DartMessenger$Reply.reply(DartMessenger.java:124)
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler$1.success(MethodChannel.java:204)
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at io.endigo.plugins.pdfviewflutter.FlutterPDFView.setPage(FlutterPDFView.java:119)
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at io.endigo.plugins.pdfviewflutter.FlutterPDFView.onMethodCall(FlutterPDFView.java:102)
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:201)
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:88)
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:219)
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at android.os.MessageQueue.nativePollOnce(Native Method)
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at android.os.MessageQueue.next(MessageQueue.java:356)
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at android.os.Looper.loop(Looper.java:138)
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at android.app.ActivityThread.main(ActivityThread.java:6517)
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at java.lang.reflect.Method.invoke(Native Method)
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:942)
E/MethodChannel#plugins.endigo.io/pdfview_0(22432): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832)
E/DartMessenger(22432): Uncaught exception in binary message listener
E/DartMessenger(22432): java.lang.IllegalStateException: Reply already submitted
E/DartMessenger(22432): at io.flutter.embedding.engine.dart.DartMessenger$Reply.reply(DartMessenger.java:124)
E/DartMessenger(22432): at io.flutter.plugin.common.MethodChannel$IncomingMethodCallHandler.onMessage(MethodChannel.java:219)
E/DartMessenger(22432): at io.flutter.embedding.engine.dart.DartMessenger.handleMessageFromDart(DartMessenger.java:88)
E/DartMessenger(22432): at io.flutter.embedding.engine.FlutterJNI.handlePlatformMessage(FlutterJNI.java:219)
E/DartMessenger(22432): at android.os.MessageQueue.nativePollOnce(Native Method)
E/DartMessenger(22432): at android.os.MessageQueue.next(MessageQueue.java:356)
E/DartMessenger(22432): at android.os.Looper.loop(Looper.java:138)
E/DartMessenger(22432): at android.app.ActivityThread.main(ActivityThread.java:6517)
E/DartMessenger(22432): at java.lang.reflect.Method.invoke(Native Method)
E/DartMessenger(22432): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:942)
E/DartMessenger(22432): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:832)
Here is the repo: https://github.com/iampawan/FlutterPDFViewer
i get the same error, any work around ?
My work around (not really a work around) is to disable screen rotation whilst viewing PDFs I have a single widget that handles all my PDFs so I add the following
@override
void initState(){
super.initState();
//Portrait mode only
SystemChrome.setPreferredOrientations([
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
}
@override
dispose(){
//on dispose allow full orientation
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeRight,
DeviceOrientation.landscapeLeft,
DeviceOrientation.portraitUp,
DeviceOrientation.portraitDown,
]);
super.dispose();
}
However, according to some users on StackOverflow this doesn't work on iPads https://stackoverflow.com/a/50322184/469335
My work around (not very beautiful...) is based on reinstanciating the controller in didChangeMetrics.
@override
void didChangeMetrics() {
if (Platform.isAndroid) {
// for rotations on Android
pdfController = Completer<PDFViewController>();
pdfReload = true;
}
}
and use a Timer in build to force redraw the pdf (code to adapt to your special case)
if (pdfReload) {
// for rotations on Android
Timer(Duration(milliseconds: 300), () {
setState(() {
pdfReload = false;
});
});
}
...
var stack = Stack(children: <Widget>[
pdfReload
? Container() // for rotations on Android, force recreate the PDFView
: PDFView(
filePath: filePath,
enableSwipe: true,
swipeHorizontal: true,
autoSpacing: false,
pageFling: false,
onRender: (_pages) {
setState(() {
pdfPages = _pages;
pdfIsReady = true;
pdfReload = false;
});
},
onError: (error) {
},
onPageError: (page, error) {
},
onViewCreated: (PDFViewController pdfViewController) {
pdfController.complete(pdfViewController);
},
onPageChanged: (int page, int total) {
},
),
!pdfIsReady
? Center(
child: CircularProgressIndicator(),
)
: Container()
]);
it will be very nice if @endigo fix this issue
do you know any pdf viewer out there that is more maintained than this one. Beacause i didn't find any Pdf viewer that has the same ability.
You shouldn't expect this automatically from this plugin, the underlying viewer clearly says it's the responsibility of the user to redraw with the current page stored earlier.
So, adding a new channel method to the controller helps us replenish the view:
void reload(MethodCall call, Result result) {
String path = (String)call.argument("filePath");
int page = (int)call.argument("page");
pdfView.recycle();
pdfView.fromFile(path).defaultPage(page).load();
result.success(true);
}
(Note that there is a fresh regression [https://github.com/flutter/flutter/issues/33866] that causes these calls to drop java.lang.IllegalStateException: Reply already submitted errors into logcat, it will be fixed in a coming Flutter release).
And then:
@override
void didChangeMetrics() {
if (Platform.isAndroid) {
Future.delayed(Duration(milliseconds: 500), () {
_controller?.reload(_data, _current);
});
}
}
Keep track of _current in the onPageChanged callback.
That's how I solved the problem. I check when the app rotation changes, and I replace the current pdf viewer by a new one. You can also save the current page and redirect the viewer to that page once the viewer has been rebuilt. This is useful when the viewer page is a separated widget. Hope it helped!
class PdfViewerPage extends StatefulWidget {
_PdfViewerPageState createState() => new _PdfViewerPageState();
}
class _PdfViewerPageState extends State<PdfViewerPage> {
Orientation _lastScreenOrientation;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
_lastScreenOrientation = MediaQuery.of(context).orientation;
});
}
@override
Widget build(BuildContext context) {
// We need to erase and rebuild the viewer when the user changes the screen orientation.
if (_lastScreenOrientation != null && _lastScreenOrientation != MediaQuery.of(context).orientation) {
// Completely render the page.
Future.delayed(Duration(microseconds: 100), _repushViewer);
}
return Scaffold(
resizeToAvoidBottomPadding : false,
body: PDFView(
filePath: 'YOUR PDF FILE PATH.',
onError: (e) {},
)
);
}
/// Push and replace the viewer page so the page is completely new.
_repushViewer() {
Navigator.of(context).pushReplacementNamed(/*YOUR PDF VIEWER PAGE ROUTE */);
}
}
Facing the same issue around here...
Initially, I thought of not using it but worked around on @GENL's comment. Look like we got it working except for changing the default page to the last page before rotation.
d I replace the current pdf viewer by a new one
How can we redirect the viewer to the current page once the viewer has been rebuilt? Appreciate any idea around it.
@zaheddec hello, you can use setPage function.
controller.setPage(current);
@endigo Thanks for the replay. I tried to play around with it but could not get any success with it.
- I tried to await _controller.future.then((onValue)=>onValue.setPage(5)); in onViewCreated after completer.
- Tried to add it to onRender callback too but no success.
would really appreciate any idea where to place it.
Thanks again
Note: I called the setpage() in onredender() and it all worked.
@endigo @GENL Thanks you so much guys. Finally, all worked and set to go for production. Great work, great lightweight library, and great support.
Nice to have helped
Dear @zaheddec , how did you replace your hardcoded "5" page to the current last page? I tried to set 'currentPage = page' inside onPageChanged, but it resets when the page is refreshed. Thank you, and also thanks to @endigo and @GENL for suggesting the code
My workaround with the help of @GENL comment.
Orientation _lastScreenOrientation;
UniqueKey pdfViewerKey = UniqueKey();
@override
Widget build(BuildContext context) {
return OrientationBuilder(
builder: (ctx, ori) {
Orientation newOrientation =
MediaQuery.of(context).orientation;
if (_lastScreenOrientation != null &&
_lastScreenOrientation != newOrientation) {
Future.delayed(Duration(microseconds: 100), () {
setState(() {
_lastScreenOrientation = newOrientation;
pdfViewerKey = UniqueKey();
});
});
}
return PDFView(
key: pdfViewerKey,
filePath: cachedFilePath,
onPageChanged: (page, total) {
setState(() {
currPage = page;
totalPages = total;
});
},
pageFling: true,
onViewCreated: (controller) {
if (currPage != null) controller.setPage(currPage);
},
);
},
);
}
but i still don't know why controller.setPage(currPage) not working and when orientation changes i get W/System ( 4490): A resource failed to call release. but it's working
My workaround with the help of @GENL comment.
Orientation _lastScreenOrientation; UniqueKey pdfViewerKey = UniqueKey(); @override Widget build(BuildContext context) { return OrientationBuilder( builder: (ctx, ori) { Orientation newOrientation = MediaQuery.of(context).orientation; if (_lastScreenOrientation != null && _lastScreenOrientation != newOrientation) { Future.delayed(Duration(microseconds: 100), () { setState(() { _lastScreenOrientation = newOrientation; pdfViewerKey = UniqueKey(); }); }); } return PDFView( key: pdfViewerKey, filePath: cachedFilePath, onPageChanged: (page, total) { setState(() { currPage = page; totalPages = total; }); }, pageFling: true, onViewCreated: (controller) { if (currPage != null) controller.setPage(currPage); }, ); }, ); }but i still don't know why controller.setPage(currPage) not working and when orientation changes i get W/System ( 4490): A resource failed to call release. but it's working
because, your setting page in a wrong way...
bool stopBuild = true;
bool stopBuild2 = false;
UniqueKey pdfViewerKey = UniqueKey();
Widget build(BuildContext context) {
final isLandscape = MediaQuery.of(context).orientation == Orientation.landscape;
if (isLandscape) {
if (stopBuild) {
setState(() {
_controller = Completer<PDFViewController>();
pdfViewerKey = UniqueKey();
stopBuild = false;
stopBuild2 = true;
});
}
}
if (!isLandscape) {
if (stopBuild2) {
setState(() {
_controller = Completer<PDFViewController>();
pdfViewerKey = UniqueKey();
stopBuild2 = false;
stopBuild = true;
});
}
return pdfView(
key pdfViewerKey ,);
}
//// when you setPage use do like that
FutureBuilder<PDFViewController>(
future: _controller.future,
builder:
(context, AsyncSnapshot<PDFViewController> snapshot) {
if (snapshot.hasData) {
return GestureDetector(
onTap: () {
snapshot.data.setPage(pages);
},
child: Text("end"));
}
return Container();
},
),
),
well you can try like this it's working perfectly fine for me portrait and landscape even night mode also...... if you want you can check file repo -: college_books/pdfScreen.dart
I suggest a combination of my previous suggestion and the latest ones. No need to track the orientation, or to use an OrientationBuilder, or to force the operation on any other platform than Android:
The only thing needed is:
class XXXState extends State<XXX> with WidgetsBindingObserver {
var pdfViewerKey = UniqueKey();
@override
void initState() {
super.initState();
WidgetsBinding.instance!.addObserver(this);
}
@override
void dispose() {
WidgetsBinding.instance!.removeObserver(this);
super.dispose();
}
@override
void didChangeMetrics() {
if (defaultTargetPlatform == TargetPlatform.android) {
Future.delayed(Duration(milliseconds: 250), () {
setState(() => pdfViewerKey = UniqueKey());
});
}
}
and to use the pdfViewerKey when building the PDFView. This will recreate it when the need arises.
The only remaining problem is the slight black flicker when the PlatformView rebuilds but that seems to come from Flutter itself, it's mentioned here and there.
@deakjahn This is close to the work-around I suggested on 27 Jun 2019. A bit simpler though ;-)
Yep, and also similar to my first idea from last July. That's why I said combination. :-)
@akshayyadav76 thanks for your solution it work perfectly for me ,but for fit policy i used the default one ( didn't use this option at all ) and it looks great
@deakjahn i tried your solution but it didn't work for me, as soon i change the orientation the pages doesn't show up
@redbayoub I modified the suggestion, try that. It seems that the rest is also needed to make didChangeMetrics() fire.