How to render full HTML document with flutter_html package?

6.2k views Asked by At

I'm trying to render a full HTML document in a scrollable view but I always got this error

════════ Exception caught by rendering library ═════════════════════════════════
RenderBox was not laid out: _RenderSingleChildViewport#1741c relayoutBoundary=up7 NEEDS-PAINT
'package:flutter/src/rendering/box.dart':
Failed assertion: line 1940 pos 12: 'hasSize'

The relevant error-causing widget was
SingleChildScrollView

My code looks like this

Scaffold(
  appBar: AppBar(),
  body: SingleChildScrollView(
    child: Column(
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        (...),
        Padding(
          padding: EdgeInsets.all(16),
          child: Html.fromDom(
            document: dom.Document.html(data['generated']), 
            shrinkWrap: true,
          )
        )
      ],
    ),
  )
)

The error appears only when I use Html.fromDom, it works perfectly with Html for rendering simple elements.

I've also tried with a ListView, constainted the widget, use Expanded or Flexible but nothing works and it's always the same error.

What's the right way for do this ?

Thanks you

4

There are 4 answers

4
Marco Domingos On

Strangely I just run your code on this example and worked like a charm, meaning that made the it's not the widgets and yes the data['generated'] or maybe somenthing inside the widgets that you occult, this is the example that I used:

  var htmlData = r"""
  <p id='top'><a href='#bottom'>Scroll to bottom</a></p>
  <h1>Header 1</h1>
  <h2>Header 2</h2>
  <h3>Header 3</h3>
  <h4>Header 4</h4>
  <h5>Header 5</h5>
  <h6>Header 6</h6>
  <h3>Ruby Support:</h3>
  <p>
    <ruby>
      漢<rt>かん</rt>
      字<rt>じ</rt>
    </ruby>
    &nbsp;is Japanese Kanji.
  </p>
  <h3>Support for maxLines:</h3>
  <h5>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vestibulum sapien feugiat lorem tempor, id porta orci elementum. Fusce sed justo id arcu egestas congue. Fusce tincidunt lacus ipsum, in imperdiet felis ultricies eu. In ullamcorper risus felis, ac maximus dui bibendum vel. Integer ligula tortor, facilisis eu mauris ut, ultrices hendrerit ex. Donec scelerisque massa consequat, eleifend mauris eu, mollis dui. Donec placerat augue tortor, et tincidunt quam tempus non. Quisque sagittis enim nisi, eu condimentum lacus egestas ac. Nam facilisis luctus ipsum, at aliquam urna fermentum a. Quisque tortor dui, faucibus in ante eget, pellentesque mattis nibh. In augue dolor, euismod vitae eleifend nec, tempus vel urna. Donec vitae augue accumsan ligula fringilla ultrices et vel ex.</h5>
  <h3>Support for <code>sub</code>/<code>sup</code></h3>
  Solve for <var>x<sub>n</sub></var>: log<sub>2</sub>(<var>x</var><sup>2</sup>+<var>n</var>) = 9<sup>3</sup>
  <p>One of the most <span>common</span> equations in all of physics is <br /><var>E</var>=<var>m</var><var>c</var><sup>2</sup>.</p>
  <h3>Inline Styles:</h3>
  <p>The should be <span style='color: blue;'>BLUE style='color: blue;'</span></p>
  <p>The should be <span style='color: red;'>RED style='color: red;'</span></p>
  <p>The should be <span style='color: rgba(0, 0, 0, 0.10);'>BLACK with 10% alpha style='color: rgba(0, 0, 0, 0.10);</span></p>
  <p>The should be <span style='color: rgb(0, 97, 0);'>GREEN style='color: rgb(0, 97, 0);</span></p>
  <p>The should be <span style='background-color: red; color: rgb(0, 97, 0);'>GREEN style='color: rgb(0, 97, 0);</span></p>
  <p style="text-align: center;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
  <p style="text-align: right;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
  <p style="text-align: justify;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
  <p style="text-align: center;"><span style="color: rgba(0, 0, 0, 0.95);">blasdafjklasdlkjfkl</span></p>
  <h3>Table support (with custom styling!):</h3>
  <p>
  <q>Famous quote...</q>
  </p>
  <table>
  <colgroup>
    <col width="50%" />
    <col span="2" width="25%" />
  </colgroup>
  <thead>
  <tr><th>One</th><th>Two</th><th>Three</th></tr>
  </thead>
  <tbody>
  <tr>
    <td rowspan='2'>Rowspan\nRowspan\nRowspan\nRowspan\nRowspan\nRowspan\nRowspan\nRowspan\nRowspan\nRowspan</td><td>Data</td><td>Data</td>
  </tr>
  <tr>
    <td colspan="2"><img alt='Google' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' /></td>
  </tr>
  </tbody>
  <tfoot>
  <tr><td>fData</td><td>fData</td><td>fData</td></tr>
  </tfoot>
  </table>
  <h3>Custom Element Support (inline: <bird></bird> and as block):</h3>
  <flutter></flutter>
  <flutter horizontal></flutter>
  <h3 id='middle'>SVG support:</h3>
  <svg id='svg1' viewBox='0 0 100 100' xmlns='http://www.w3.org/2000/svg'>
        <circle r="32" cx="35" cy="65" fill="#F00" opacity="0.5"/>
        <circle r="32" cx="65" cy="65" fill="#0F0" opacity="0.5"/>
        <circle r="32" cx="50" cy="35" fill="#00F" opacity="0.5"/>
  </svg>
  <h3>List support:</h3>
  <ol>
        <li>This</li>
        <li><p>is</p></li>
        <li>an</li>
        <li>
        ordered
        <ul>
        <li>With<br /><br />...</li>
        <li>a</li>
        <li>nested</li>
        <li>unordered
        <ol style="list-style-type: lower-alpha;" start="5">
        <li>With a nested</li>
        <li>ordered list</li>
        <li>with a lower alpha list style</li>
        <li>starting at letter e</li>
        </ol>
        </li>
        <li>list</li>
        </ul>
        </li>
        <li>list! Lorem ipsum dolor sit amet.</li>
        <li><h2>Header 2</h2></li>
        <h2><li>Header 2</li></h2>
  </ol>
  <h3>Link support:</h3>
  <p>
    Linking to <a href='https://github.com'>websites</a> has never been easier.
  </p>
  <h3>Image support:</h3>
  <h3>Network png</h3>
  <img alt='Google' src='https://www.google.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' />
  <h3>Network svg</h3>
  <img src='https://dev.w3.org/SVG/tools/svgweb/samples/svg-files/android.svg' />
  <h3>Local asset png</h3>
  <img src='asset:assets/html5.png' width='100' />
  <h3>Local asset svg</h3>
  <img src='asset:assets/mac.svg' width='100' />
  <h3>Data uri (with base64 support)</h3>
  <img alt='Red dot (png)' src='data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO9TXL0Y4OHwAAAABJRU5ErkJggg==' />
  <img alt='Green dot (base64 svg)' src='data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz4KPHN2ZyB2aWV3Qm94PSIwIDAgMzAgMjAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+CjxjaXJjbGUgY3g9IjE1IiBjeT0iMTAiIHI9IjEwIiBmaWxsPSJncmVlbiIvPgo8L3N2Zz4=' />
  <img alt='Green dot (plain svg)' src='data:image/svg+xml,%3C?xml version="1.0" encoding="UTF-8"?%3E%3Csvg viewBox="0 0 30 20" xmlns="http://www.w3.org/2000/svg"%3E%3Ccircle cx="15" cy="10" r="10" fill="yellow"/%3E%3C/svg%3E' />
  <h3>Custom source matcher (relative paths)</h3>
  <img src='/wikipedia/commons/thumb/e/ef/Octicons-logo-github.svg/200px-Octicons-logo-github.svg.png' />
  <h3>Custom image render (flutter.dev)</h3>
  <img src='https://flutter.dev/images/flutter-mono-81x100.png' />
  <h3>No image source</h3>
  <img alt='No source' />
  <img alt='Empty source' src='' />
  <h3>Broken network image</h3>
  <img alt='Broken image' src='https://www.notgoogle.com/images/branding/googlelogo/2x/googlelogo_color_92x30dp.png' />
  <h3>MathML Support:</h3>
  <math>
  <mrow>
    <mi>x</mi>
    <mo>=</mo>
    <mfrac>
      <mrow>
        <mrow>
          <mo>-</mo>
          <mi>b</mi>
        </mrow>
        <mo>&PlusMinus;</mo>
        <msqrt>
          <mrow>
            <msup>
              <mi>b</mi>
              <mn>2</mn>
            </msup>
            <mo>-</mo>
            <mrow>
              <mn>4</mn>
              <mo>&InvisibleTimes;</mo>
              <mi>a</mi>
              <mo>&InvisibleTimes;</mo>
              <mi>c</mi>
            </mrow>
          </mrow>
        </msqrt>
      </mrow>
      <mrow>
        <mn>2</mn>
        <mo>&InvisibleTimes;</mo>
        <mi>a</mi>
      </mrow>
    </mfrac>
  </mrow>
  </math>
  <math>
    <munderover >
      <mo> &int; </mo>
      <mn> 0 </mn>
      <mi> 5 </mi>
    </munderover>
    <msup>
      <mi>x</mi>
      <mn>2</mn>
   </msup>
    <mo>&sdot;</mo>
    <mi>&dd;</mi><mi>x</mi>
    <mo>=</mo>
    <mo>[</mo>
    <mfrac>
      <mn>1</mn>
      <mi>3</mi>
   </mfrac>
   <msup>
      <mi>x</mi>
      <mn>3</mn>
   </msup>
   <msubsup>
      <mo>]</mo>
      <mn>0</mn>
      <mn>5</mn>
   </msubsup>
   <mo>=</mo>
   <mfrac>
      <mn>125</mn>
      <mi>3</mi>
   </mfrac>
   <mo>-</mo>
   <mn>0</mn>
   <mo>=</mo>
   <mfrac>
      <mn>125</mn>
      <mi>3</mi>
   </mfrac>
  </math>
  <math>
    <msup>
      <mo>sin</mo>
      <mn>2</mn>
    </msup>
    <mo>&theta;</mo>
    <mo>+</mo>
    <msup>
      <mo>cos</mo>
      <mn>2</mn>
    </msup>
    <mo>&theta;</mo>
    <mo>=</mo>
    <mn>1</mn>
  </math>
  <h3>Tex Support with the custom tex tag:</h3>
  <tex>i\hbar\frac{\partial}{\partial t}\Psi(\vec x,t) = -\frac{\hbar}{2m}\nabla^2\Psi(\vec x,t)+ V(\vec x)\Psi(\vec x,t)</tex>
  <p id='bottom'><a href='#top'>Scroll to top</a></p>
""";

 @override
 Widget build(BuildContext context) {
 return Scaffold(
    appBar: AppBar(),
    body: SingleChildScrollView(
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Padding(
              padding: EdgeInsets.all(16),
              child: Html.fromDom(
                document: dom.Document.html(htmlData),
                shrinkWrap: true,
              )
          )
        ],
      ),
    )
);
}
0
AXE On

I cannot be sure the cause is the same as you haven't given the HTML which causes the error. However, I encountered the same error and traced it to a bug in flutter_html which throws when there are nested tables in the HTML. As of today the issue is still open but v3.0.0-alpha.2 of the library has a workaround which renders text "TABLE SECTION" instead of the table. Not perfect but at least doesn't crash the rendering.

0
tk_tanz On

I had been working on this issue for a while now and came to realise that flutter_html will not be good for this issue. I have used inappwebview to render the html and get dynamic height using script.

double height = 400;

AnimatedContainer(
              duration: Duration(milliseconds: 100),
              height: height + (height == 0 ? 0 : 50),
              child: InAppWebView(
                initialData: InAppWebViewInitialData(data: html),
                initialOptions: InAppWebViewGroupOptions(
                  crossPlatform: InAppWebViewOptions(
                    supportZoom: false,
                    javaScriptEnabled: true,
                    disableHorizontalScroll: false,
                    disableVerticalScroll: true,
                  ),
                ),
                onLoadError: (controller, url, code, message) =>
                    print("onLoadError: $url, $code, $message"),
                onLoadHttpError:
                    (controller, url, statusCode, description) => print(
                        "onLoadHttpError: $url, $statusCode, $description"),
                onConsoleMessage: (controller, consoleMessage) {
                  print('height: ${height}');
                  height = double.parse(consoleMessage.message);
                  setState(() {});
                },
              ),
            );

the html should be created in the following format

 html = """
 <html lang="en">
    <meta name="viewport" content="width=device-width user-scalable=no zoom=1.1">
    <style>img {max-width: 100%; height: auto}</style>
    <body>
    <div  class="id"><div class="container" id="_flutter_target_do_not_delete">${widget.data["html"]}</div>
      <script>
        function outputsize() {
          console.log(document.getElementById("_flutter_target_do_not_delete").offsetHeight);
            window.postMessage('flutterTargetHeight', document.getElementById("_flutter_target_do_not_delete").offsetHeight);
        }
        new ResizeObserver(outputsize).observe(_flutter_target_do_not_delete)
        outputsize()
    </script>
  </body>
</html>
""";

This will render your complex tables and all html content with dynamic height.

0
Celestin NIYONSABA On

Try this version should work flutter_html: ^2.2.1

Here is an example of an HTML table:

final data =
      '<figure class="table"><table style="background-color:rgb(255, 255, 255);border:2px solid rgb(0, 0, 0);"><tbody><tr><th><strong>NAME</strong></th><th>DATE</th><th>DD</th></tr><tr><th>PETER</th><th>2020</th><th>SDSD</th></tr><tr><th>SAM</th><th>2021</th><th>SSS</th></tr></tbody></table></figure>';


Then, in your Html, remember to add style with the following

                  "table": Style(
                    backgroundColor: Colors.white,
                  ),
                  "tr": Style(
                      padding: const EdgeInsets.all(2),
                      border: Border.all(color: Colors.black)),
                  "th": Style(
                      padding: const EdgeInsets.all(2),
                      border: Border.all(color: Colors.black)),
                  "td": Style(
                      padding: const EdgeInsets.all(2),
                      border: Border.all(color: Colors.black)),
                },

Full Example code:

Import this below in your header

import 'package:flutter_html/flutter_html.dart';

Then return this below:

            Container(
              child: Html(
                data:
                    '<figure class="table"><table style="background-color:rgb(255, 255, 255);border:2px solid rgb(0, 0, 0);"><tbody><tr><th><strong>NAME</strong></th><th>DATE</th><th>DD</th></tr><tr><th>PETER</th><th>2020</th><th>SDSD</th></tr><tr><th>SAM</th><th>2021</th><th>SSS</th></tr></tbody></table></figure>',
                style: {
                  "table": Style(
                    backgroundColor: Colors.white,
                  ),
                  "tr": Style(
                      padding: const EdgeInsets.all(2),
                      border: Border.all(color: Colors.black)),
                  "th": Style(
                      padding: const EdgeInsets.all(2),
                      border: Border.all(color: Colors.black)),
                  "td": Style(
                      padding: const EdgeInsets.all(2),
                      border: Border.all(color: Colors.black)),
                },
              ),
            ),

enter image description here