1

I'm pretty new to Flutter so please forgive me if this is a basic question.

I'm trying to create a Flutter app that shows only a full screen webview but I need the device back button to go back in history. The first task I achieved with ease but the second is being a struggle.

I followed the instructions in this answer, but I couldn't remove all the clutter of a 360+ lines answer just to a back button and make it work.

My code is really simple:

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

void main() => runApp(MyApp());

WebViewController _controller;

Future<void> _onWillPop(BuildContext context) async {
  if (await _controller.canGoBack()) {
    _controller.goBack();
  } else {
    exit(0);
    return Future.value(false);
  }
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    SystemChrome.setEnabledSystemUIOverlays([]);
    return WillPopScope(
      onWillPop: () => _onWillPop(context),
      child: MaterialApp(title: 'Test App', home: WebView(
      initialUrl: 'https://google.com',
      javascriptMode: JavascriptMode.unrestricted,
      onWebViewCreated: (WebViewController c) => _controller = c,
    )),
    );
  }
}

But when I try to press the device back button, it throws the following error:

java.lang.RuntimeException: Cannot execute operation because FlutterJNI is not attached to native.
    at io.flutter.embedding.engine.FlutterJNI.ensureAttachedToNative(FlutterJNI.java:227)
    at io.flutter.embedding.engine.FlutterJNI.markTextureFrameAvailable(FlutterJNI.java:554)
    at io.flutter.embedding.engine.renderer.FlutterRenderer.markTextureFrameAvailable(FlutterRenderer.java:274)
    at io.flutter.embedding.engine.renderer.FlutterRenderer.access$300(FlutterRenderer.java:38)
    at io.flutter.embedding.engine.renderer.FlutterRenderer$SurfaceTextureRegistryEntry$1.onFrameAvailable(FlutterRenderer.java:145)
    at android.graphics.SurfaceTexture$1.handleMessage(SurfaceTexture.java:211)
    at android.os.Handler.dispatchMessage(Handler.java:107)
    at android.os.Looper.loop(Looper.java:224)
    at android.app.ActivityThread.main(ActivityThread.java:7520)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:539)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:950)

There are at least 3 threads on flutter's github but none of the solutions worked for me. what am I missing here?

2
  • i think it is because you did not use a Completer<WebViewController> Commented Apr 21, 2020 at 5:53
  • Yes, but it seems that I need a statefull widget too. Commented Apr 21, 2020 at 14:07

3 Answers 3

4

You can copy paste run full code below
You can use Completer
code snippet

WebViewController controller;

final Completer<WebViewController> _controllerCompleter =
          Completer<WebViewController>();
...
onWebViewCreated: (WebViewController c) {
                _controllerCompleter.future.then((value) => controller = value);
                _controllerCompleter.complete(c);
              },

working demo

enter image description here

full code

import 'dart:io';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:webview_flutter/webview_flutter.dart';
import 'dart:async';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

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

class _MyHomePageState extends State<MyHomePage> {
  WebViewController controller;

  final Completer<WebViewController> _controllerCompleter =
      Completer<WebViewController>();

  Future<void> _onWillPop(BuildContext context) async {
    print("onwillpop");
    if (await controller.canGoBack()) {
      controller.goBack();
    } else {
      exit(0);
      return Future.value(false);
    }
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () => _onWillPop(context),
      child: Scaffold(
        body: WebView(
          initialUrl: 'https://flutter.dev/',
          javascriptMode: JavascriptMode.unrestricted,
          onWebViewCreated: (WebViewController c) {
            _controllerCompleter.future.then((value) => controller = value);
            _controllerCompleter.complete(c);
          },
        ),
      ),
    );
  }
}
Sign up to request clarification or add additional context in comments.

1 Comment

Glad to help. please mark this as answer if it helps you. thanks.
0

In Android MainActivity.kt, just add this:

override fun onDestroy() { flutterEngine?.platformViewsController?.onFlutterViewDestroyed() super.onDestroy() }

Comments

0

Here is the working solution (100% working)-

  // Use any webview plugin
  // WebView _controller;

  Future<void> _handleBack(context) async {
    var status = await _controller.canGoBack();
    if (status) {
      _controller.goBack();
    } else {
      showDialog(
          context: context,
          builder: (context) => AlertDialog(
                title: Text('Do you want to exit'),
                actions: <Widget>[
                  FlatButton(
                    onPressed: () {
                      Navigator.of(context).pop();
                    },
                    child: Text('No'),
                  ),
                  FlatButton(
                    onPressed: () {
                      SystemNavigator.pop();
                    },
                    child: Text('Yes'),
                  ),
                ],
              ));
    }
  }

Here is calling WillPopScope

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
            onWillPop: () => _handleBack(context),
            child: SafeArea(
              top: true,
              child: Scaffold(......)))

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.