Skip to content

Migration Guides

Matthew Whitaker edited this page Oct 21, 2022 · 2 revisions

3.0.0

3.0.0 modularizes the library to remove unnecessary dependencies that you may not need in your app, allowing support for all Flutter-supported platforms, and a lightweight base library that meets a lot of use cases, with the option of adding more features through 1st-party packages.

If you have any further questions after reading through this guide, feel free to make an Issue or Discussion Q&A post.

Removed / Changed Parameters

customRender

customRender has been renamed to customRenders, and its API is now significantly changed to match customImageRender.

customRender now accepts a Map<CustomRenderMatcher, CustomRender>.

Migration

Convert the keys of your map to CustomRenderMatcher like so:

"flutter" -> tagMatcher("flutter")

Convert the values of your map to CustomRender like so:

  1. If your current custom render returns a widget:
(RenderContext context, Widget child) {
              return FlutterLogo(
                style: (context.tree.element!.attributes['horizontal'] != null)
                    ? FlutterLogoStyle.horizontal
                    : FlutterLogoStyle.markOnly,
                textColor: context.style.color!,
                size: context.style.fontSize!.size! * 5,
              );
            },

becomes

CustomRender.widget(widget: (context, buildChildren) => FlutterLogo(
  style: (context.tree.element!.attributes['horizontal'] != null)
      ? FlutterLogoStyle.horizontal
      : FlutterLogoStyle.markOnly,
  textColor: context.style.color!,
  size: context.style.fontSize!.size! * 5,
)),
  1. If your current custom render returns an InlineSpan:
(RenderContext context, Widget child) {
              return TextSpan(text: "🐦");
            },

becomes

CustomRender.inlineSpan(inlineSpan: (context, buildChildren) => TextSpan(text: "🐦")),

If you were previously using the child parameter, simply change that to use buildChildren() (this change was made so the child tree is built only when requested, not for every custom render).

Full migration example
customRender: {
            "table": (context, child) {
              return SingleChildScrollView(
                scrollDirection: Axis.horizontal,
                child:
                    (context.tree as TableLayoutElement).toWidget(context),
              );
            },
            "bird": (RenderContext context, Widget child) {
              return TextSpan(text: "🐦");
            },
            "flutter": (RenderContext context, Widget child) {
              return FlutterLogo(
                style: (context.tree.element!.attributes['horizontal'] != null)
                    ? FlutterLogoStyle.horizontal
                    : FlutterLogoStyle.markOnly,
                textColor: context.style.color!,
                size: context.style.fontSize!.size! * 5,
              );
            },
          },

becomes

tagMatcher("bird"): CustomRender.inlineSpan(inlineSpan: (context, buildChildren) => TextSpan(text: "🐦")),
tagMatcher("flutter"): CustomRender.widget(widget: (context, buildChildren) => FlutterLogo(
  style: (context.tree.element!.attributes['horizontal'] != null)
      ? FlutterLogoStyle.horizontal
      : FlutterLogoStyle.markOnly,
  textColor: context.style.color!,
  size: context.style.fontSize!.size! * 5,
)),
tagMatcher("table"): CustomRender.widget(widget: (context, buildChildren) => SingleChildScrollView(
  scrollDirection: Axis.horizontal,
  child: tableRender.call().widget!.call(context, buildChildren),
)),

customImageRenders

This parameter is removed and needs to be migrated to using just customRenders. Their APIs are very similar:

ImageSourceMatcher -> CustomRenderMatcher

ImageSourceMatcher assetUriMatcher() => (attributes, element) =>
    attributes['src'] != null && attributes['src']!.startsWith("asset:");

becomes

CustomRenderMatcher assetUriMatcher() => (context) =>
    context.tree.element?.attributes != null
    && context.tree.element!.attributes['src'] != null
    && context.tree.element!.attributes['src']!.startsWith("asset:");

It is slightly more verbose, so creating a function with an attributes variable for quick access might be useful.

ImageRender -> CustomRender

ImageRender svgNetworkImageRender() => (context, attributes, element) {
      return SvgPicture.network(
        attributes["src"]!,
        width: _width(attributes),
        height: _height(attributes),
      );
    };

becomes

CustomRender svgNetworkImageRender() => CustomRender.widget(widget: (context, buildChildren) {
  if (context.tree.element?.attributes["src"] == null) {
    return Container(height: 0, width: 0);
  }
  return SvgPicture.network(
            context.tree.element!.attributes["src"]!,
            width: _width(context.tree.element!.attributes),
            height: _height(context.tree.element!.attributes),
          );
});

onMathError

This parameter is removed, to add it back please register a CustomRender with it:

customRenders: {
   mathMatcher(): mathRender(onMathError: (error, exception, exceptionWithType) {
      print(exception);
      return Text(exception);
   }),
}

navigationDelegateForIframe

This parameter is removed, to add it back please register a CustomRender with it:

customRenders: {
   iframeMatcher(): iframeRender(navigationDelegate: (request) {
        // return decision here
   }),
}

New Parameters

SelectableHtml now supports CustomRender. Follow standard customRenders docs for this.

Style Breaking Changes

If you were using the style parameter in your Html widget, there are some minor changes to the API that introduce some huge possibilites:

The Unit (changes to width, height, fontSize, margin, and lineHeight)

Version 3.0.0 introduces support for length/percent/auto units for several properties on Style.

As a basic example:

Style(
  width: 100,
)

becomes

Style(
  width: Width(100, Unit.px),
  // Including Unit.px is optional. `Unit`s generally default to px if only one argument is used in the constructor.
)

Or, if you are feeling fancy, all of the below are currently supported:

Width(1.5, Unit.em),
Width(50, Unit.percent),
Width.auto(), //Which is a shortcut for Width(0, Unit.auto), and is the default for the width parameter.

The following Style properties now have support for the Unit:

width, height, fontSize, margin, lineHeight


2.0.0

2.0.0 introduced support for Flutter Web and migrated the library to use null safety. There were few breaking changes. See the CHANGELOG for more information


1.0.0

1.0.0 is a major release with tons of new features to be excited about!

Unfortunately, to bring you these new features, there had to be a lot of breaking changes and deprecation of parameters. If you're trying to migrate your old Html widget code to 1.0.0, this is the page for you.

Breaking Changes

We completely rewrote the old parser to use a combination of RichText and Widgets so that your Html looks even more natural in your app.

useRichText now defaults to false, since the new parser has so many new features and fewer bugs than the RichTextParser. The RichTextParser has been deprecated and will be removed sometime in the next couple versions (no later than version 1.3.0).

The Easy Migration

NOTE: THIS IS THE EASY WAY OUT (i.e. you need to push a quick fix on a Friday afternoon and don't want to deal with migrating this plugin to 1.0.0)

If you would like your HTML to remain unchanged and you previously did not have useRichText set to false, then set useRichText to true to continue using the RichText parser as you were. Note that you will receive none of the benefits of 1.0.0 if you do this, so you might be better off setting your flutter_html version to 0.11.0.

Full Migration Guide

Take a look at the deprecated/changed/new parameters below, and follow the migration guide for any that you are currently using. Open an issue if you encounter any difficulties in the migration to 1.0.0. We're hoping these changes make your code much cleaner and more intuitive to you and anyone else who has to deal with your code.

Deprecated/Changed/New Parameters

data (No Changes):

data remains the same in 1.0.0 as it was before. Just pass in your HTML code and the Widget will parse it:

1.0.0:
Html(
  data: "<h1>Hello, World!</h1>",
),

css (New):

NOTE: NOT YET AVAILABLE

The css parameter is new in 1.0.0. Similar to data, but for your CSS code. Great for if you have an external css file you'd like to apply to your HTML.

1.0.0:
Html(
  data: "<h1 class='example'>Example</h1>",
  css: ".example { font-family: serif; background-color: blue; }",
),

customRender (Breaking Changes):

customRender is completely different in 1.0.0. You can also read more about customRender on its wiki page.

before 1.0.0:
Html(
  data: ...,
  customRender: (dom.Node node, children) {
    if(node is dom.Element) {
      if(node.localName == "flutter") {
        return FlutterLogo();
      }
    }
  },
),
1.0.0:
Html(
  data: ...,
  customRender: {
    "flutter": (RenderContext context, child, attributes) {
      // This example is simple and doesn't use any of the provided parameters.
      // See the linked wiki page for more examples.
      return FlutterLogo();
    }
  },
),

padding (Deprecated):

The padding parameter seems a bit silly when you can wrap the entire Html widget in a Padding widget.

Or if you still want the Html content itself to be padded, then you make the following migration:

before 1.0.0:
Html(
  data: ...,
  padding: 24,
),
1.0.0:
Html(
  data: ...,
  style: {
    "html": Style(
      padding: const EdgeInsets.all(24),
    ),
  },
),

backgroundColor (Deprecated):

The new style property makes the backgroundColor property redundant.

Here's the migration you should make:

before 1.0.0:
Html(
  data: ...,
  backgroundColor: Colors.yellow,
),
1.0.0:
Html(
  data: ...,
  style: {
    "html": Style(
      backgroundColor: Colors.yellow,
    ),
  },
),

defaultTextStyle (Deprecated):

If you were using defaultTextStyle to make the Html widget take the text style of the current theme, then you can just remove the defaultTextStyle parameter from your code. The Html widget will now automatically pull the default text style from Theme.of(context).textTheme.body1.

If you'd like your Html widget to have a different default text style, you can make the following migration:

before 1.0.0:
Html(
  data: ...,
  defaultTextStyle: myCustomTextStyle,
),
1.0.0:
Html(
  data: ...,
  style: {
    "html": Style.fromTextStyle(myCustomTextStyle),
  },
),

onLinkTap (No Changes):

The onLinkTap parameter is pretty much unchanged in version 1.0.0. However, internally, the onLinkTap callback type has been renamed from OnLinkTap to OnTap. It still has the same signature, so this shouldn't affect most people.

1.0.0:
Html(
  data: ...,
  onLinkTap: (String url) {
    //Do something (e.g. launch(url)).
  }
),

renderNewlines (Deprecated):

The new style attribute supports preserving newlines in the original code. The migration is fairly simple.

before 1.0.0:
Html(
  data: ...,
  renderNewlines: true,
),
1.0.0:
Html(
  data: ...,
  style: {
    "html": Style(whiteSpace: WhiteSpace.PRE),
  },
),

customEdgeInsets (Deprecated):

In the new parser, it's now so much easier to apply custom margins/padding to html elements. Just use the new style attribute as shown in the following example migration:

before 1.0.0:
Html(
  data: ...,
  customEdgeInsets: (dom.Node node) {
    if(node is dom.Element) {
      if(node.localName == "h1" || node.localName == "h4") {
        return EdgeInsets.all(48);
      }
    }
  },
),
1.0.0:
Html(
  data: ...,
  style: {
    "h1, h4": Style(
      margin: EdgeInsets.all(48),
    ),
  },
),

customTextStyle (Deprecated):

It's now much easier to apply custom text styles to any element using the style parameter.

before 1.0.0:
Html(
  data: ...,
  customTextStyle: (dom.Node node, TextStyle baseStyle) {
    if(node is dom.Element) {
      if(node.localName == "span" || node.localName == "h4") {
        return baseStyle.merge(TextStyle(fontFamily: 'serif'));
      }
    }
    return baseStyle;
  },
),
1.0.0:
Html(
  data: ...,
  style: {
    "span, h4": Style(
      fontFamily: 'serif',
    ),
    //Alternatively, apply a style from an existing TextStyle:
    "span, h4": Style.fromTextStyle(
      TextStyle(fontFamily: 'serif'),
    ),
  },
),

blockSpacing (Deprecated):

The new parser doesn't use a global block spacing. Now, just apply margin to the desired elements.

The following migration would be common:

before 1.0.0:
Html(
  data: ...,
  blockSpacing: 24,
),
1.0.0:
Html(
  data: ...,
  style: {
    "h1, div": Style(
      margin: EdgeInsets.symmetric(vertical: 24),
    ),
  },
),

useRichText (Deprecated):

The new parser is built off of the core concepts of the RichText parser, and brings the best of the RichText parser along with it, so you shouldn't ever need to set this parameter.

As a side note, useRichText now defaults to false and must explicitly be set to true.

customTextAlign (Deprecated):

NOTE: NOT YET AVAILABLE

Aligning text is fairly straightforward with the new style parameter.

before 1.0.0:
Html(
  data: ...,
  customTextAlign: (dom.Element elem) {
    if(elem.localName == "p") {
      return TextAlign.center;
    }
    return TextAlign.left
  },
),
1.0.0:
Html(
  data: ...,
  style: {
    //Not yet available
  },
),

onImageError (No Changes):

This parameter is unchanged.

1.0.0:
Html(
  data: ...,
  onImageError: (dynamic exception, StackTrace stackTrace) {
    //Do something when an image fails to load.
  },
),

linkStyle (Deprecated):

Just use the new style parameter to do any custom link styling.

before 1.0.0:
Html(
  data: ...,
  linkStyle: TextStyle(
    textDecoration: TextDecoration.underline,
    color: Colors.red,
  ),
),
1.0.0:
Html(
  data: ...,
  style: {
    "a": Style(
      // Note that the underline can be omitted, since the new parser merges styles with the element's 
      // default style rather than replacing that style.
      color: Colors.red,
    ),
  },
),

shrinkWrap (Changed):

Due to the way the new parser renders stuff, there may be some slight changes to the way shrink-wrapped content was rendered. For most cases, though, the result should be nearly identical to the way it was in the RichText parser.

In the past, the <body> tag was always added even if it wasn't explicitly included. Due to the new style features, the <body> tag is given a margin by default like a browser would do, so you may have to explicitly remove the margin as outlined in the migration below:

before 1.0.0:
Html(
  data: ...,
  shrinkWrap: true,
),
1.0.0:
Html(
  data: ...,
  shrinkWrap: true,
  // The <body> tag in the new parser has a default margin, so you (optionally) 
  // need to remove it to get the same result as before.
  style: {
    "body": Style(margin: EdgeInsets.zero),
  }
),

imageProperties (Deprecated):

All the different properties available in the imageProperties options are available to you either through the customRender parameter or the style parameter.

Migration guide coming soon.

onImageTap (No Changes):

There have been no changes to the onImageTap callback.

1.0.0:
Html(
  data: ...,
  onImageTap: (url) {
    //Do something with image.
  }
),

showImages (Deprecated):

We've added the more generalized parameter blacklistedElements for handling which elements are rendered or not.

before 1.0.0:
Html(
  data: ...,
  showImages: false,
),

1.0.0:

Html(
  data: ...,
  blacklistedElements: ["img"],
),

blacklistedElements (New):

This parameter is good for controlling which elements are displayed or hidden. For instance, if you didn't want <iframe> or <video> elements to be rendered, then the following could be written:

1.0.0:
Html(
  data: ...,
  blacklistedElements: ["iframe", "video"],
),

style (New):

Most of the migrations above involved this new style parameter. The style parameter is essentially a Dart object representation of a simplified subset of CSS. Styles cascade in the same way that CSS does, and this gives you fine-tuned control over every pixel of the way your HTML renders its widgets.

The style attribute takes a Map<String, Style> that maps CSS selectors to Styles.

For more info, see the style wiki page.

1.0.0:
Html(
  data: ...,
  style: {
    "body": Style(
      backgroundColor: Colors.black,
      color: Colors.white,
    ),
    "div": Style(
      border: Border(bottom: BorderSide(color: Colors.grey)),
      width: 200,
      fontFamily: 'serif',
    ),
    "a": Style(
      textDecoration: TextDecoration.none,
      backgroundColor: Colors.deepPurple,
    ),
  },
),
Clone this wiki locally