Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

How to display custom html instead of URL #23

Open
oexza opened this issue Dec 29, 2017 · 43 comments
Open

How to display custom html instead of URL #23

oexza opened this issue Dec 29, 2017 · 43 comments

Comments

@oexza
Copy link

oexza commented Dec 29, 2017

I want to display some custom html string instead of loading from a url. Is this possible today?

@lejard-h
Copy link
Collaborator

not possible yet, but a hack would be to create a server with dart:io in the app on localhost, like here, https://medium.com/@segaud.kevin/facebook-oauth-login-flow-with-flutter-9adb717c9f2e

@zoechi
Copy link

zoechi commented Feb 22, 2018

new WebviewScaffold(
              url: new Uri.dataFromString('<html><body>hello world</body></html>', mimeType: 'text/html').toString()

worked for me as well

@zoechi
Copy link

zoechi commented Feb 22, 2018

Loading data URIs seems quite limiting.
I found it difficult to load other data on click

Any plans to support

https://developer.android.com/reference/android/webkit/WebView.html#loadDataWithBaseURL(java.lang.String, java.lang.String, java.lang.String, java.lang.String, java.lang.String)

and the other similar methods?

@lejard-h
Copy link
Collaborator

yes I planned to do it,

I can't find time to work on the plugin at the moment and I also need to find an equivalent on iOS which is not my domain of expertise :/

@lejard-h
Copy link
Collaborator

I guess this is the equivalent function on iOS

https://developer.apple.com/documentation/uikit/uiwebview/1617941-loaddata?language=objc

@zoechi
Copy link

zoechi commented Feb 22, 2018

I understand. I haven't done native mobile development and not the time to dive into it right now,
therefore depend on plugins of others ;-)
I guess I'll figure out some workaround.
Thanks a lot for your work on the plugin 👍

@Kleak
Copy link
Collaborator

Kleak commented Apr 10, 2018

To create a server easily you have an example in the readme of this repo

@vascoV
Copy link

vascoV commented Apr 23, 2018

Hello guys,
new Uri.dataFromString('<html><body><h1>hello world</h1></body></html>', mimeType: 'text/html').toString(); this example also worked for me. But I have another question how can I render a whole .html file in flutter? Fot example insted using this string <html><body><h1>hello world</h1></body></html> just load the whole file new Uri.dataFromString('assets/test.html', mimeType: 'text/html').toString();

This was referenced Apr 25, 2018
@rodydavis
Copy link
Member

@zoechi Your approach works!

new WebviewScaffold(
              url: new Uri.dataFromString('<html><body>hello world</body></html>', mimeType: 'text/html').toString()

Although it doesn't load any images from img=src from the network. Any ideas how to force load them?

@zoechi
Copy link

zoechi commented Apr 25, 2018

@AppleEducate Images work for me

I added parameters: { 'charset': 'utf-8' }, don't remember why or if it is related
(or pass encoding like shown in #68 (comment))

@rodydavis
Copy link
Member

Thanks!

@MaskyS
Copy link

MaskyS commented May 8, 2018

@lejard-h , how to load images/data that are stored locally when launching a webview scaffold that as above?

@zoechi
Copy link

zoechi commented May 8, 2018

@MaskyS see the link in the 2nd comment

@MaskyS
Copy link

MaskyS commented May 9, 2018

@zoechi I think I'm probably missing something, but that link leads to android documentation? I checked the flutter_webview_plugin documentation and code but there seem to be no implementation of loadData() or loadDataWithBaseUrl() Could you help me understand with an example or clarification?

@zoechi
Copy link

zoechi commented May 9, 2018

@MaskyS the 2nd comment, not my 2nd comment
#23 (comment)

@MaskyS
Copy link

MaskyS commented May 12, 2018

@zoechi I had a look at that article; I tried it out and couldn't serve multiple files (I have html1.html, html2.html, etc along with their data folders.). Is it possible? am I missing something?

@zoechi
Copy link

zoechi commented May 12, 2018

@MaskyS just because it doesn't demonstrate how you can serve multiple files, doesn't mean it's not possible. Just serve different files based on the URL

@MaskyS
Copy link

MaskyS commented May 12, 2018

Thank you will try that.

@Sun3
Copy link

Sun3 commented May 19, 2018

I would suggest on the iOS side to use Swift and not Objective C. Most new projects in iOS are written in Swift. Just my thoughts.

@lucidappstudio
Copy link

lucidappstudio commented Aug 8, 2018

got error java.lang.ClassCastException: android.webkit.WebView cannot be cast to com.flutter_webview_plugin.ObservableWebView for the following code

new WebviewScaffold(
              url: new Uri.dataFromString('<html><body>hello world</body></html>', mimeType: 'text/html').toString()

@krmao
Copy link

krmao commented Sep 10, 2018

need optimized for the local html data string

I/flutter (23463): [response]
I/flutter (23463): -------------------------------------------------------->
I/flutter (23463): [data]={code: 1, msg: 获取成功, time: 1536571706, data: {id: 15, contentlist_id: 4, title: 建立百万人基因数据库和健康管理系统,“双百”工程助力健康中国, desc: 健康工程, image: http://ym.51xixi.me/uploads/20180831/d38192d4b4956f939aeacb85dbfaf1f2.jpg, weigh: 12, auth: admin, recommend: 0, top: 0, index: 0, on: 0, content: <p style="margin-top:12.8pt;text-align:justify;text-justify:inter-ideograph;
I/flutter (23463): line-height:18.0pt;mso-pagination:widow-orphan"><span lang="EN-US" style="font-family: Arial, sans-serif; color: rgb(51, 51, 51); background-image: initial; background-position: initial; background-size: initial; background-repeat: initial; background-attachment: initial; background-origin: initial; background-clip: initial;">&nbsp; &nbsp; &nbsp; &nbsp;7</span><span style="font-family: 宋体; color: rgb(51, 51, 51); background-image: initial; background-position: initial; background-size: initial; background
I/flutter (23463): ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
I/flutter (23463): The following ArgumentError was thrown building Builder(dirty):
I/flutter (23463): Invalid argument(s): String contains invalid characters.
I/flutter (23463): 
I/flutter (23463): When the exception was thrown, this was the stack:
I/flutter (23463): #0      _UnicodeSubsetEncoder.convert (dart:convert/ascii.dart:95:9)
I/flutter (23463): #1      AsciiCodec.encode (dart:convert/ascii.dart:45:46)
I/flutter (23463): #2      new UriData.fromString (dart:core/uri.dart:3214:44)
I/flutter (23463): #3      new Uri.dataFromString (dart:core/uri.dart:304:24)
I/flutter (23463): #4      DetailPageState._body (file:///Users/maokangren/workspace/flutter_template/lib/detail/Detail.dart:101:142)
I/flutter (23463): #5      DetailPageState.build.<anonymous closure> (file:///Users/maokangren/workspace/flutter_template/lib/detail/Detail.dart:49:20)
I/flutter (23463): #6      Builder.build (package:flutter/src/widgets/basic.dart:5590:41)
I/flutter (23463): #7      StatelessElement.build (package:flutter/src/widgets/framework.dart:3741:28)
I/flutter (23463): #8      ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3688:15)
I/flutter (23463): #9      Element.rebuild (package:flutter/src/widgets/framework.dart:3541:5)
I/flutter (23463): #10     StatelessElement.update (package:flutter/src/widgets/framework.dart:3748:5)
I/flutter (23463): #11     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #12     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3699:16)
I/flutter (23463): #13     Element.rebuild (package:flutter/src/widgets/framework.dart:3541:5)
I/flutter (23463): #14     ProxyElement.update (package:flutter/src/widgets/framework.dart:3957:5)
I/flutter (23463): #15     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #16     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3699:16)
I/flutter (23463): #17     Element.rebuild (package:flutter/src/widgets/framework.dart:3541:5)
I/flutter (23463): #18     ProxyElement.update (package:flutter/src/widgets/framework.dart:3957:5)
I/flutter (23463): #19     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #20     RenderObjectElement.updateChildren (package:flutter/src/widgets/framework.dart:4531:32)
I/flutter (23463): #21     MultiChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4921:17)
I/flutter (23463): #22     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #23     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3699:16)
I/flutter (23463): #24     Element.rebuild (package:flutter/src/widgets/framework.dart:3541:5)
I/flutter (23463): #25     StatefulElement.update (package:flutter/src/widgets/framework.dart:3845:5)
I/flutter (23463): #26     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #27     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3699:16)
I/flutter (23463): #28     Element.rebuild (package:flutter/src/widgets/framework.dart:3541:5)
I/flutter (23463): #29     ProxyElement.update (package:flutter/src/widgets/framework.dart:3957:5)
I/flutter (23463): #30     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #31     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3699:16)
I/flutter (23463): #32     Element.rebuild (package:flutter/src/widgets/framework.dart:3541:5)
I/flutter (23463): #33     StatefulElement.update (package:flutter/src/widgets/framework.dart:3845:5)
I/flutter (23463): #34     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #35     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4813:14)
I/flutter (23463): #36     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #37     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3699:16)
I/flutter (23463): #38     Element.rebuild (package:flutter/src/widgets/framework.dart:3541:5)
I/flutter (23463): #39     StatelessElement.update (package:flutter/src/widgets/framework.dart:3748:5)
I/flutter (23463): #40     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #41     SingleChildRenderObjectElement.update (package:flutter/src/widgets/framework.dart:4813:14)
I/flutter (23463): #42     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #43     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3699:16)
I/flutter (23463): #44     Element.rebuild (package:flutter/src/widgets/framework.dart:3541:5)
I/flutter (23463): #45     StatefulElement.update (package:flutter/src/widgets/framework.dart:3845:5)
I/flutter (23463): #46     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #47     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3699:16)
I/flutter (23463): #48     Element.rebuild (package:flutter/src/widgets/framework.dart:3541:5)
I/flutter (23463): #49     StatefulElement.update (package:flutter/src/widgets/framework.dart:3845:5)
I/flutter (23463): #50     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #51     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3699:16)
I/flutter (23463): #52     Element.rebuild (package:flutter/src/widgets/framework.dart:3541:5)
I/flutter (23463): #53     ProxyElement.update (package:flutter/src/widgets/framework.dart:3957:5)
I/flutter (23463): #54     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #55     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3699:16)
I/flutter (23463): #56     Element.rebuild (package:flutter/src/widgets/framework.dart:3541:5)
I/flutter (23463): #57     ProxyElement.update (package:flutter/src/widgets/framework.dart:3957:5)
I/flutter (23463): #58     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #59     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3699:16)
I/flutter (23463): #60     Element.rebuild (package:flutter/src/widgets/framework.dart:3541:5)
I/flutter (23463): #61     StatefulElement.update (package:flutter/src/widgets/framework.dart:3845:5)
I/flutter (23463): #62     Element.updateChild (package:flutter/src/widgets/framework.dart:2729:15)
I/flutter (23463): #63     ComponentElement.performRebuild (package:flutter/src/widgets/framework.dart:3699:16)
I/flutter (23463): #64     Element.rebuild (package:flutter/src/widgets/framework.dart:3541:5)
I/flutter (23463): #65     BuildOwner.buildScope (package:flutter/src/widgets/framework.dart:2273:33)
I/flutter (23463): #66     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding&WidgetsBinding.drawFrame (package:flutter/src/widgets/binding.dart:653:20)
I/flutter (23463): #67     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding&PaintingBinding&SemanticsBinding&RendererBinding._handlePersistentFrameCallback (package:flutter/src/rendering/binding.dart:208:5)
I/flutter (23463): #68     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._invokeFrameCallback (package:flutter/src/scheduler/binding.dart:990:15)
I/flutter (23463): #69     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding.handleDrawFrame (package:flutter/src/scheduler/binding.dart:930:9)
I/flutter (23463): #70     _WidgetsFlutterBinding&BindingBase&GestureBinding&ServicesBinding&SchedulerBinding._handleDrawFrame (package:flutter/src/scheduler/binding.dart:842:5)
I/flutter (23463): #71     _invoke (dart:ui/hooks.dart:128:13)
I/flutter (23463): #72     _drawFrame (dart:ui/hooks.dart:117:3)

@awazgyawali
Copy link

awazgyawali commented Feb 3, 2019

Was working on this, it would be great if you guys tested it out. flutter/plugins#1137

@iKK001
Copy link

iKK001 commented Feb 6, 2019

@MaskyS: In my case, I was able to eliminate the error Invalid argumnent(s): String contains invalid characters by removing the following character: (aprostrophe). Afterwards I was able to load a HTML-string into a WebView in Flutter...

@zoechi
Copy link

zoechi commented Feb 8, 2019

@Androidsignal
Copy link

Having issue to display html string to normal string in flutter.. i was try all libs but not working suggest me new thank you......

@vvlong-can
Copy link

NSString* key = [_registrar lookupKeyForAsset:@"assets/xxx.html"];
NSURL* nsUrl = [[NSBundle mainBundle] URLForResource:key withExtension:nil];
[self.webview loadFileURL:nsUrl allowingReadAccessToURL:[NSURL URLWithString:@"file:///"]];
can solve this problem, but you should modify open source code for your url schema rule. (get _registrar from plugin entrance )

ref: flutter/plugins#1247

@neelu004
Copy link

Hi, I want to load the local Html file which is placed in my assets folder as "assets/file/privacy.html" this would be the path and I am using the same plugin and replacing the url as "assets/privacy.html" but still i am not able to view the html file content. Its showing the error as could not found the file.
Please suggest some solution.

@iKK001
Copy link

iKK001 commented Apr 18, 2019

@neelu004: I saw that you use a subfolder (i.e. "assets/file/...") for your html file. The Flutter-webview plugin is very tedious if it comes to folder/subfolder/ locations. Try: 1. eliminate the subfolder and place it directly into "assets/privacy.html" instead. Or 2. make sure you also provide the subfolder in your URL when calling the webview. Also, if you have references to other html files or folders from within your html-file that the webview calls (normally index.html with references to other files and folders ... or in your case privacy.html), make sure you use a webserver together with the webview-plugin.

Here is what works in my case:

(Again: This server is only needed in the case you have references to files and folders from your root html you are trying to show in your webview). If you only show one single html-file (without references), then you don't need this Jaguar server...

But if you do have a webpage with references to other files and folders (all in your assets), then your webview-URL must be called with localhost such as: http://localhost:8080/fullPathName as can be seen below...

The webserver I set up at the very beginning (inside main())...

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// import 'dart:async';
import 'package:jaguar/jaguar.dart';
import 'package:jaguar_flutter_asset/jaguar_flutter_asset.dart';

void main() async {
  final server = Jaguar();
  server.addRoute(serveFlutterAssets());
  await server.serve(logRequests: false);
  // await server.serve(logRequests: true);
  // server.log.onRecord.listen((r) => print(r));
  SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
    DeviceOrientation.portraitDown,
  ]).then((_) {
    runApp(MyApp());
  });
}

If you want to navigate to your webview from somewhere:

  await navigateToWebView(context, 'file/privacy.html');

The navigation:

Future<String> navigateToWebView(
      BuildContext context, String pathName) async {
    return await Navigator.push(
        context,
        MaterialPageRoute(
            builder: (context) => WebViewExample(pathName)));
  }

And here the webview implementation with the localhost-URL call:

(small detail: the comments are an alternative if you don't want the snackbar)

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

class WebViewExample extends StatelessWidget {
  final String pathName;

  WebViewExample(this.pathName);

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

  @override
  Widget build(BuildContext context) {
    final String serverURL = 'http://localhost:8080/' + this.pathName;
    final double screenWidth = MediaQuery.of(context).size.shortestSide;
    final double screenHeight = MediaQuery.of(context).size.longestSide;
    return Scaffold(
      appBar: MyAppBar(
        title: Text(
          'My Title',
          style: TextStyle(
              fontSize: 16.0, color: Colors.white, fontWeight: FontWeight.w500),
        ),
      ),
      // body: FutureBuilder<String>(
      //   future: loadAsset(),
      //   builder: (context, snapshot) {
      //     if (snapshot.hasData) {
      //       return WebView(
      //         initialUrl:
      //             Uri.dataFromString(snapshot.data, mimeType: 'text/html')
      //                 .toString(),
      //         javascriptMode: JavascriptMode.unrestricted,
      //         onWebViewCreated: (WebViewController webViewController) {
      //           _controller.complete(webViewController);
      //         },
      //         javascriptChannels: <JavascriptChannel>[
      //           _toasterJavascriptChannel(context),
      //         ].toSet(),
      //       );
      //     } else if (snapshot.hasError) {
      //       return Text("${snapshot.error}");
      //     }
      //     return CircularProgressIndicator();
      //   },
      // ),
      // We're using a Builder here so we have a context that is below the Scaffold
      // to allow calling Scaffold.of(context) so we can show a snackbar.
      body: Builder(builder: (BuildContext context) {
        return Container(
          color: Color.fromRGBO(
              0x3c, 0x3c, 0x3c, 1), // color for bottom part of iOS safe-area
          child: SafeArea(
            child: Stack(
              fit: StackFit.expand,
              alignment: AlignmentDirectional.topStart,
              children: <Widget>[
                Positioned(
                  left: 0.0,
                  top: 0.0,
                  width: screenWidth,
                  height: screenHeight,
                  child: Container(
                    child: WebView(
                      initialUrl: serverURL,
                      javascriptMode: JavascriptMode.unrestricted,
                      onWebViewCreated: (WebViewController webViewController) {
                        _controller.complete(webViewController);
                      },
                      javascriptChannels: <JavascriptChannel>[
                        _toasterJavascriptChannel(context),
                      ].toSet(),
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      }),

      floatingActionButton: favoriteButton(),
    );
  }

  // Future<String> loadAsset() async {
  //   return await rootBundle.loadString('assets/web1/index.html');
  // }

  JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
    return JavascriptChannel(
        name: 'Toaster',
        onMessageReceived: (JavascriptMessage message) {
          Scaffold.of(context).showSnackBar(
            SnackBar(content: Text(message.message)),
          );
        });
  }

  Widget favoriteButton() {
    return FutureBuilder<WebViewController>(
        future: _controller.future,
        builder: (BuildContext context,
            AsyncSnapshot<WebViewController> controller) {
          if (controller.hasData) {
            return FloatingActionButton(
              onPressed: () async {
                final String url = await controller.data.currentUrl();
                Scaffold.of(context).showSnackBar(
                  SnackBar(content: Text('Favorited $url')),
                );
              },
              child: const Icon(Icons.favorite),
            );
          }
          return Container();
        });
  }
}

enum MenuOptions {
  showUserAgent,
  toast,
}

class SampleMenu extends StatelessWidget {
  SampleMenu(this.controller);
  final Future<WebViewController> controller;

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<WebViewController>(
      future: controller,
      builder:
          (BuildContext context, AsyncSnapshot<WebViewController> controller) {
        return PopupMenuButton<MenuOptions>(
          onSelected: (MenuOptions value) {
            switch (value) {
              case MenuOptions.showUserAgent:
                _onShowUserAgent(controller.data, context);
                break;
              case MenuOptions.toast:
                Scaffold.of(context).showSnackBar(
                  SnackBar(
                    content: Text('You selected: $value'),
                  ),
                );
                break;
            }
          },
          itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
                PopupMenuItem<MenuOptions>(
                  value: MenuOptions.showUserAgent,
                  child: const Text('Show user agent'),
                  enabled: controller.hasData,
                ),
                const PopupMenuItem<MenuOptions>(
                  value: MenuOptions.toast,
                  child: Text('Make a toast'),
                ),
              ],
        );
      },
    );
  }

  void _onShowUserAgent(
      WebViewController controller, BuildContext context) async {
    // Send a message with the user agent string to the Toaster JavaScript channel we registered
    // with the WebView.
    controller.evaluateJavascript(
        'Toaster.postMessage("User Agent: " + navigator.userAgent);');
  }
}

class NavigationControls extends StatelessWidget {
  const NavigationControls(this._webViewControllerFuture)
      : assert(_webViewControllerFuture != null);

  final Future<WebViewController> _webViewControllerFuture;

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<WebViewController>(
      future: _webViewControllerFuture,
      builder:
          (BuildContext context, AsyncSnapshot<WebViewController> snapshot) {
        final bool webViewReady =
            snapshot.connectionState == ConnectionState.done;
        final WebViewController controller = snapshot.data;
        return Row(
          children: <Widget>[
            IconButton(
              icon: const Icon(Icons.arrow_back_ios),
              onPressed: !webViewReady
                  ? null
                  : () async {
                      if (await controller.canGoBack()) {
                        controller.goBack();
                      } else {
                        Scaffold.of(context).showSnackBar(
                          const SnackBar(content: Text("No back history item")),
                        );
                        return;
                      }
                    },
            ),
            IconButton(
              icon: const Icon(Icons.arrow_forward_ios),
              onPressed: !webViewReady
                  ? null
                  : () async {
                      if (await controller.canGoForward()) {
                        controller.goForward();
                      } else {
                        Scaffold.of(context).showSnackBar(
                          const SnackBar(
                              content: Text("No forward history item")),
                        );
                        return;
                      }
                    },
            ),
            IconButton(
              icon: const Icon(Icons.replay),
              onPressed: !webViewReady
                  ? null
                  : () {
                      controller.reload();
                    },
            ),
          ],
        );
      },
    );
  }
}

class MyAppBar extends PreferredSize {
  MyAppBar({Key key, Widget title})
      : super(
          key: key,
          preferredSize: Size.fromHeight(app_bar_height), // set AppBar height
          child: AppBar(
            elevation: 0.0, // represents shaddow of AppBar
            brightness: Brightness
                .dark, // makes statusbar shine in dark AppBar environment
            backgroundColor:
                Color.fromRGBO(0x3c, 0x3c, 0x3c, 1), // sets AppBar color
            title: title, // sets AppBar title
            // maybe other AppBar properties
          ),
        );
}

@vvlong-can
Copy link

vvlong-can commented Apr 30, 2019 via email

@oms10
Copy link

oms10 commented Jun 19, 2019

@neelu004: I saw that you use a subfolder (i.e. "assets/file/...") for your html file. The Flutter-webview plugin is very tedious if it comes to folder/subfolder/ locations. Try: 1. eliminate the subfolder and place it directly into "assets/privacy.html" instead. Or 2. make sure you also provide the subfolder in your URL when calling the webview. Also, if you have references to other html files or folders from within your html-file that the webview calls (normally index.html with references to other files and folders ... or in your case privacy.html), make sure you use a webserver together with the webview-plugin.

Here is what works in my case:

(Again: This server is only needed in the case you have references to files and folders from your root html you are trying to show in your webview). If you only show one single html-file (without references), then you don't need this Jaguar server...

But if you do have a webpage with references to other files and folders (all in your assets), then your webview-URL must be called with localhost such as: http://localhost:8080/fullPathName as can be seen below...

The webserver I set up at the very beginning (inside main())...

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
// import 'dart:async';
import 'package:jaguar/jaguar.dart';
import 'package:jaguar_flutter_asset/jaguar_flutter_asset.dart';

void main() async {
  final server = Jaguar();
  server.addRoute(serveFlutterAssets());
  await server.serve(logRequests: false);
  // await server.serve(logRequests: true);
  // server.log.onRecord.listen((r) => print(r));
  SystemChrome.setPreferredOrientations([
    DeviceOrientation.portraitUp,
    DeviceOrientation.portraitDown,
  ]).then((_) {
    runApp(MyApp());
  });
}

If you want to navigate to your webview from somewhere:

  await navigateToWebView(context, 'file/privacy.html');

The navigation:

Future<String> navigateToWebView(
      BuildContext context, String pathName) async {
    return await Navigator.push(
        context,
        MaterialPageRoute(
            builder: (context) => WebViewExample(pathName)));
  }

And here the webview implementation with the localhost-URL call:

(small detail: the comments are an alternative if you don't want the snackbar)

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

class WebViewExample extends StatelessWidget {
  final String pathName;

  WebViewExample(this.pathName);

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

  @override
  Widget build(BuildContext context) {
    final String serverURL = 'http://localhost:8080/' + this.pathName;
    final double screenWidth = MediaQuery.of(context).size.shortestSide;
    final double screenHeight = MediaQuery.of(context).size.longestSide;
    return Scaffold(
      appBar: MyAppBar(
        title: Text(
          'My Title',
          style: TextStyle(
              fontSize: 16.0, color: Colors.white, fontWeight: FontWeight.w500),
        ),
      ),
      // body: FutureBuilder<String>(
      //   future: loadAsset(),
      //   builder: (context, snapshot) {
      //     if (snapshot.hasData) {
      //       return WebView(
      //         initialUrl:
      //             Uri.dataFromString(snapshot.data, mimeType: 'text/html')
      //                 .toString(),
      //         javascriptMode: JavascriptMode.unrestricted,
      //         onWebViewCreated: (WebViewController webViewController) {
      //           _controller.complete(webViewController);
      //         },
      //         javascriptChannels: <JavascriptChannel>[
      //           _toasterJavascriptChannel(context),
      //         ].toSet(),
      //       );
      //     } else if (snapshot.hasError) {
      //       return Text("${snapshot.error}");
      //     }
      //     return CircularProgressIndicator();
      //   },
      // ),
      // We're using a Builder here so we have a context that is below the Scaffold
      // to allow calling Scaffold.of(context) so we can show a snackbar.
      body: Builder(builder: (BuildContext context) {
        return Container(
          color: Color.fromRGBO(
              0x3c, 0x3c, 0x3c, 1), // color for bottom part of iOS safe-area
          child: SafeArea(
            child: Stack(
              fit: StackFit.expand,
              alignment: AlignmentDirectional.topStart,
              children: <Widget>[
                Positioned(
                  left: 0.0,
                  top: 0.0,
                  width: screenWidth,
                  height: screenHeight,
                  child: Container(
                    child: WebView(
                      initialUrl: serverURL,
                      javascriptMode: JavascriptMode.unrestricted,
                      onWebViewCreated: (WebViewController webViewController) {
                        _controller.complete(webViewController);
                      },
                      javascriptChannels: <JavascriptChannel>[
                        _toasterJavascriptChannel(context),
                      ].toSet(),
                    ),
                  ),
                ),
              ],
            ),
          ),
        );
      }),

      floatingActionButton: favoriteButton(),
    );
  }

  // Future<String> loadAsset() async {
  //   return await rootBundle.loadString('assets/web1/index.html');
  // }

  JavascriptChannel _toasterJavascriptChannel(BuildContext context) {
    return JavascriptChannel(
        name: 'Toaster',
        onMessageReceived: (JavascriptMessage message) {
          Scaffold.of(context).showSnackBar(
            SnackBar(content: Text(message.message)),
          );
        });
  }

  Widget favoriteButton() {
    return FutureBuilder<WebViewController>(
        future: _controller.future,
        builder: (BuildContext context,
            AsyncSnapshot<WebViewController> controller) {
          if (controller.hasData) {
            return FloatingActionButton(
              onPressed: () async {
                final String url = await controller.data.currentUrl();
                Scaffold.of(context).showSnackBar(
                  SnackBar(content: Text('Favorited $url')),
                );
              },
              child: const Icon(Icons.favorite),
            );
          }
          return Container();
        });
  }
}

enum MenuOptions {
  showUserAgent,
  toast,
}

class SampleMenu extends StatelessWidget {
  SampleMenu(this.controller);
  final Future<WebViewController> controller;

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<WebViewController>(
      future: controller,
      builder:
          (BuildContext context, AsyncSnapshot<WebViewController> controller) {
        return PopupMenuButton<MenuOptions>(
          onSelected: (MenuOptions value) {
            switch (value) {
              case MenuOptions.showUserAgent:
                _onShowUserAgent(controller.data, context);
                break;
              case MenuOptions.toast:
                Scaffold.of(context).showSnackBar(
                  SnackBar(
                    content: Text('You selected: $value'),
                  ),
                );
                break;
            }
          },
          itemBuilder: (BuildContext context) => <PopupMenuItem<MenuOptions>>[
                PopupMenuItem<MenuOptions>(
                  value: MenuOptions.showUserAgent,
                  child: const Text('Show user agent'),
                  enabled: controller.hasData,
                ),
                const PopupMenuItem<MenuOptions>(
                  value: MenuOptions.toast,
                  child: Text('Make a toast'),
                ),
              ],
        );
      },
    );
  }

  void _onShowUserAgent(
      WebViewController controller, BuildContext context) async {
    // Send a message with the user agent string to the Toaster JavaScript channel we registered
    // with the WebView.
    controller.evaluateJavascript(
        'Toaster.postMessage("User Agent: " + navigator.userAgent);');
  }
}

class NavigationControls extends StatelessWidget {
  const NavigationControls(this._webViewControllerFuture)
      : assert(_webViewControllerFuture != null);

  final Future<WebViewController> _webViewControllerFuture;

  @override
  Widget build(BuildContext context) {
    return FutureBuilder<WebViewController>(
      future: _webViewControllerFuture,
      builder:
          (BuildContext context, AsyncSnapshot<WebViewController> snapshot) {
        final bool webViewReady =
            snapshot.connectionState == ConnectionState.done;
        final WebViewController controller = snapshot.data;
        return Row(
          children: <Widget>[
            IconButton(
              icon: const Icon(Icons.arrow_back_ios),
              onPressed: !webViewReady
                  ? null
                  : () async {
                      if (await controller.canGoBack()) {
                        controller.goBack();
                      } else {
                        Scaffold.of(context).showSnackBar(
                          const SnackBar(content: Text("No back history item")),
                        );
                        return;
                      }
                    },
            ),
            IconButton(
              icon: const Icon(Icons.arrow_forward_ios),
              onPressed: !webViewReady
                  ? null
                  : () async {
                      if (await controller.canGoForward()) {
                        controller.goForward();
                      } else {
                        Scaffold.of(context).showSnackBar(
                          const SnackBar(
                              content: Text("No forward history item")),
                        );
                        return;
                      }
                    },
            ),
            IconButton(
              icon: const Icon(Icons.replay),
              onPressed: !webViewReady
                  ? null
                  : () {
                      controller.reload();
                    },
            ),
          ],
        );
      },
    );
  }
}

class MyAppBar extends PreferredSize {
  MyAppBar({Key key, Widget title})
      : super(
          key: key,
          preferredSize: Size.fromHeight(app_bar_height), // set AppBar height
          child: AppBar(
            elevation: 0.0, // represents shaddow of AppBar
            brightness: Brightness
                .dark, // makes statusbar shine in dark AppBar environment
            backgroundColor:
                Color.fromRGBO(0x3c, 0x3c, 0x3c, 1), // sets AppBar color
            title: title, // sets AppBar title
            // maybe other AppBar properties
          ),
        );
}

You can provide the complete example of localhost. Jaguar. thank.

@jazzbpn
Copy link

jazzbpn commented Sep 18, 2019

got error java.lang.ClassCastException: android.webkit.WebView cannot be cast to com.flutter_webview_plugin.ObservableWebView for the following code

new WebviewScaffold(
              url: new Uri.dataFromString('<html><body>hello world</body></html>', mimeType: 'text/html').toString()

How to render image using HTML to create Flutter-iOS-App?

https://stackoverflow.com/questions/57702924/how-to-render-image-using-html-css-using-flutter-to-create-ios-app

@Benqstanley
Copy link

new WebviewScaffold(
              url: new Uri.dataFromString('<html><body>hello world</body></html>', mimeType: 'text/html').toString()

worked for me as well

I seem to be having trouble when the html is too large (maybe). It seems to only work half the time on iOS

@dhinakaran-nithra
Copy link

How to load custom font(ex: Baamini) in webviewScaffold..?

@garshom
Copy link

garshom commented Nov 11, 2020

this worked for me: #23 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests