Site icon Flutter Packages | Pub dev Packages – Flutter Mobile App World

Flutter Widget from HTML

A Flutter package for building Flutter widget tree from HTML with support for 70+ most popular tags.

Live demo

Getting Started

Add this to your app’s pubspec.yaml file:

dependencies:
  flutter_widget_from_html_core: ^0.5.1+3

Usage

Then you have to import the package with:

import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart';

And use HtmlWidget where appropriate:

HtmlWidget(
  // the first parameter (`html`) is required
  '''
  <h1>Heading 1</h1>
  <h2>Heading 2</h2>
  <h3>Heading 3</h3>
  <!-- anything goes here -->
  ''',

  // all other parameters are optional, a few notable params:

  // specify custom styling for an element
  // see supported inline styling below
  customStylesBuilder: (element) {
    if (element.classes.contains('foo')) {
      return {'color': 'red'};
    }

    return null;
  },

  // render a custom widget
  customWidgetBuilder: (element) {
    if (element.attributes['foo'] == 'bar') {
      return FooBarWidget();
    }

    return null;
  },

  // this callback will be triggered when user taps a link
  onTapUrl: (url) => print('tapped $url'),

  // set the default styling for text
  textStyle: TextStyle(fontSize: 14),
),

Features

HTML tags

Below tags are the ones that have special meaning / styling, all other tags will be parsed as text. Compare between Flutter rendering and browser’s.

These tags requires flutter_widget_from_html:

These tags and their contents will be ignored:

Attributes

Inline stylings

Extensibility

This package implements widget building logic with high testing coverage to ensure correctness. It tries to render an optimal tree by using RichText with specific TextStyle, merge text spans together, show images in sized box, etc. The idea is to build a solid foundation for apps to customize easily. There are two ways to alter the output widget tree.

  1. Use callbacks like customStylesBuilder or customWidgetBuilder for small changes
  2. Use a custom WidgetFactory for complete control of the rendering process

The enhanced package (flutter_widget_from_html) uses a custom WidgetFactory to handle complicated tags like IFRAME, VIDEO, etc.

Callbacks

For cosmetic changes like color, italic, etc., use customStylesBuilder to specify inline styles (see supported list above) for each DOM element. Some common conditionals:

This example changes the color for a CSS class:

HtmlWidget( ‘Hello <span class=”name”>World</span>!’, customStylesBuilder: (element) { if (element.classes.contains(‘name’)) { return {‘color’: ‘red’}; } return null; }, ),

For fairly simple widget, use customWidgetBuilder. You will need to handle the DOM element and its children manually. The next example renders a carousel (try it live):

const kHtml = '''
<p>...</p>
<div class="carousel">
  <div class="image">
    <img src="https://images.unsplash.com/photo-1514888286974-6c03e2ca1dba" />
  </div>
  ...
</div>
<p>...</p>
''';

class CustomWidgetBuilderScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: Text('CustomStylesBuilderScreen'),
        ),
        body: SingleChildScrollView(
          child: Padding(
            padding: const EdgeInsets.all(8.0),
            child: HtmlWidget(
              kHtml,
              customWidgetBuilder: (e) {
                if (!e.classes.contains('carousel')) return null;

                final srcs = <String>[];
                for (final child in e.children) {
                  for (final grandChild in child.children) {
                    srcs.add(grandChild.attributes['src']);
                  }
                }

                return CarouselSlider(
                  options: CarouselOptions(
                    autoPlay: true,
                    autoPlayAnimationDuration: const Duration(milliseconds: 250),
                    autoPlayInterval: const Duration(milliseconds: 1000),
                    enlargeCenterPage: true,
                    enlargeStrategy: CenterPageEnlargeStrategy.scale,
                  ),
                  items: srcs.map(_toItem).toList(growable: false),
                );
              },
            ),
          ),
        ),
      );

  static Widget _toItem(String src) => Container(
        child: Center(
          child: Image.network(src, fit: BoxFit.cover, width: 1000),
        ),
      );
}

Custom WidgetFactory

The HTML string is parsed into DOM elements and each element is visited once to collect BuildMetadata and prepare BuildBits. See step by step how it works:

StepIntegration point
1ParseWidgetFactory.parse(BuildMetadata)
2Inform parents if anyBuildOp.onChild(BuildMetadata)
3Populate default stylingBuildOp.defaultStyles(Element)
4Populate custom stylingHtmlWidget.customStylesBuilder
5Parse styling key+value pairs, parseStyle may be called multiple timesWidgetFactory.parseStyle(BuildMetadata, String, String)WidgetFactory.parseStyleDisplay(BuildMetadata, String)
6a. If a custom widget is provided, go to 7HtmlWidget.customWidgetBuilder
b. Loop through children elements to prepare BuildBits
7Inform build opsBuildOp.onTree(BuildMetadata, BuildTree)
8a. If not a block element, go to 10
b. Build widgets from bits using a FlattenerUse existing BuildBit or extends from it, overriding .swallowWhitespace to control whitespace, etc.
9Inform build opsBuildOp.onWidgets(BuildMetadata, Iterable<Widget>)
10The end

Notes:

// example 1: simple callback setting accent color from theme
meta.tsb((parent, _) =>
  parent.copyWith(
    style: parent.style.copyWith(
      color: parent.getDependency<ThemeData>().accentColor,
    ),
  ));

// example 2: callback using second param to set height
TextStyleHtml callback(TextStyleHtml parent, double value) =>
  parent.copyWith(height: value)

// example 2 (continue): register with some value
meta.tsb<double>(callback, 2.0);
meta.register(BuildOp(
  onTree: (meta, tree) {
    tree.add(...);
  },
  onWidgets: (meta, widgets) => widgets.map((widget) => ...),
));

The example below replaces smilie inline image with an emoji:

const kHtml = """
<p>Hello <img class="smilie smilie-1" alt=":)" src="http://domain.com/sprites.png" />!</p>
<p>How are you <img class="smilie smilie-2" alt=":P" src="http://domain.com/sprites.png" />?
""";

const kSmilies = {':)': '????'};

class SmilieScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) => Scaffold(
        appBar: AppBar(
          title: Text('SmilieScreen'),
        ),
        body: Padding(
          padding: const EdgeInsets.all(8.0),
          child: HtmlWidget(
            kHtml,
            factoryBuilder: () => _SmiliesWidgetFactory(),
          ),
        ),
      );
}

class _SmiliesWidgetFactory extends WidgetFactory {
  final smilieOp = BuildOp(
    onTree: (meta, tree) {
      final alt = meta.element.attributes['alt'];
      tree.addText(kSmilies[alt] ?? alt);
    },
  );

  @override
  void parse(BuildMetadata meta) {
    final e = meta.element;
    if (e.localName == 'img' &&
        e.classes.contains('smilie') &&
        e.attributes.containsKey('alt')) {
      meta.register(smilieOp);
      return;
    }

    return super.parse(meta);
  }
}

Download Flutter Widget from HTML source code on GitHub

https://github.com/daohoangson/flutter_widget_from_html/tree/master/packages/core

Check out Flutter Widget from HTML implementation guide on pub.dev

https://pub.dev/packages/flutter_widget_from_html_core

Exit mobile version