Rich Text renderer that parses Contentful Rich Text JSON object and returns a renderable Flutter widget

Overview

Contentful Rich Text Renderer for Flutter

Rich Text renderer that parses Contentful Rich Text field JSON output and produces a Flutter Widget tree that can be displayed in a Flutter app.

Installation

To use this plugin, add contentful_rich_text as a dependency in your pubspec.yaml file.

Usage

Note: The json examples are samples of what Contentful's API can return. The getContentfulJson method is just a placeholder for where your code would call a method to get your data from the Contentful API.

Simple document with paragraph, no inline marks (bold/italic/underline)

Sample JSON
{
  "nodeType": "document",
  "content": [
    {
      "nodeType": "paragraph",
      "content": [
        {
          "nodeType": "text",
          "value": "Hello world!",
          "marks": []
        }
      ]
    }
  ]
}
Sample Dart implementation
import 'package:contentful_rich_text/contentful_rich_text.dart';

// getting data from Contentful, must be implemented in your project
// document will be a dynamic variable JSON map matching the above JSON
var document = getContentfulJson(); 

class Demo extends StatelessWidget {

@override
  Widget build(BuildContext context) {
    return ContentfulRichText(document).documentToWidgetTree;
    // Example widget tree produced:
    // Paragraph(
    //   Text(text: 'Hello World'),
    // );
  }
}

Simple document with paragraph, with bold and italic inline marks

Sample JSON
{
  "nodeType": "document",
  "content": [
    {
      "nodeType": "paragraph",
      "content": [
        {
          "nodeType": "text",
          "value": "Hello",
          "marks": [{ "type": "bold" }],
        },
        {
          "nodeType": "text",
          "value": " world!",
          "marks": [{ "type": "italic" }]
        }
      ]
    }
  ]
}
Sample Dart implementation
import 'package:contentful_rich_text/contentful_rich_text.dart';

// getting data from Contentful, must be implemented in your project
// document will be a dynamic variable JSON map matching the above JSON
var document = getContentfulJson();

class Demo extends StatelessWidget {

@override
  Widget build(BuildContext context) {
    return ContentfulRichText(document).documentToWidgetTree;
    // Example widget tree produced:
    // Paragraph(
    //   Text(
    //     text: '',
    //     children: 
   
    [
   
    //       TextSpan(text: 'Hello', style: { fontWeight: FontWeight.bold }),
    //       TextSpan(text: ' world!', style: { fontStyle: FontStyle.italic }), 
    //     ],
    //   ),
    // );
  }
}

Simple document with paragraph, with bold and italic inline marks, using a custom renderer and mark

You can also pass custom renderers for both marks and nodes as an optional parameter like so:

Sample JSON
{
  "nodeType": "document",
  "data": {},
  "content": [
    {
      "nodeType": "paragraph",
      "data":{},
      "content": [
        {
          "nodeType": "text",
          "value": "Hello",
          "marks": [{ "type": "bold" }],
          "data": {}
        },
        {
          "nodeType": "text",
          "value": " world!",
          "marks": [{ "type": "italic" }],
          "data": {}
        }
      ]
    }
  ]
}
Sample Dart implementation
import 'package:contentful_rich_text/contentful_rich_text.dart';

// getting data from Contentful, must be implemented in your project
// document will be a dynamic variable JSON map matching the above JSON
var document = getContentfulJson();

var options = {
  renderMark: RenderMark defaultMarkRenderers = RenderMark({
    [MARKS.BOLD.value]: () => CustomBoldTextStyle, // returns TextStyle
  }),
  renderNode: RenderNode defaultNodeRenderers = RenderNode({
    [BLOCKS.PARAGRAPH.value]: (node, next) => CustomParagraph(next: next(node.content))
  })
}

class Demo extends StatelessWidget {

@override
  Widget build(BuildContext context) {
    return ContentfulRichText(document, options).documentToWidgetTree;
    // Example widget tree produced:
    // CustomParagraph(
    //   textStyle: CustomBoldTextStyle(),
    //   ...
    // );
  }
}

RenderNodes

RenderNodes are the various content nodes that Contentful sends for the Rich Text Widget. Blocks are block level items, and inlines are inline items.

Note: Not all of the nodes sent from Contentful are currently implemented. See below for the implementation status.

The renderNode keys should be the value of one of the following BLOCKS and INLINES properties as defined in contentful_rich_text/types:

  • BLOCKS

    • DOCUMENT
    • PARAGRAPH
    • HEADING_1
    • HEADING_2
    • HEADING_3
    • HEADING_4
    • HEADING_5
    • HEADING_6
    • UL_LIST // partially implemented, nested lists are not implemented yet
    • OL_LIST // partially implemented, nested lists are not implemented yet
    • LIST_ITEM
    • QUOTE // not implemented yet
    • HR
    • EMBEDDED_ENTRY // not implemented yet
    • EMBEDDED_ASSET // not implemented yet
  • INLINES

    • HYPERLINK
    • EMBEDDED_ENTRY (this is different from the BLOCKS.EMBEDDED_ENTRY) // not implemented yet
    • ENTRY_HYPERLINK // not implemented yet
    • ASSET_HYPERLINK // not implemented yet

RenderMarks

RenderMarks are the various text styles that can be applied to text inline.

Note: Not all of the marks sent from Contentful are currently implemented. See below for the implementation status.

The renderMark keys should be the value of one of the following MARKS properties as defined in contentful_rich_text/types:

  • BOLD
  • ITALIC
  • UNDERLINE
  • CODE // not implemented yet
Comments
  • Not sufficient space between paragraphs

    Not sufficient space between paragraphs

    Problem

    Text appears different on Contentful compared to Flutter. To be more precise, the space between the paragraphs/headings is missing.

    Contentful

    Screenshot 2020-05-12 at 11 50 28

    Flutter

    Screenshot 2020-05-12 at 12 43 52

    Workaround

    Do double line breaks after each paragraph/heading.

    opened by SeverinAlexB 8
  • Parser crashes on entry-hyperlinks

    Parser crashes on entry-hyperlinks

    The readme says inline hyperlinks to entries are not supported (entry-hyperlinks/ENTRY_HYPERLINK), however the parser even crashes when it encounters these. Is it possible to have the parser just ignore these as it does with embedded assets for example?

    Below is an example JSON generated by Contentful, my Dart file to parse the JSON (it contains the same JSON just encoded), and the log of the crash.

    {
      "nodeType": "document",
      "data": {},
      "content": [
        {
          "nodeType": "paragraph",
          "content": [
            {
              "nodeType": "text",
              "value": "text before",
              "marks": [],
              "data": {}
            }
          ],
          "data": {}
        },
        {
          "nodeType": "paragraph",
          "content": [
            {
              "nodeType": "text",
              "value": "",
              "marks": [],
              "data": {}
            },
            {
              "nodeType": "entry-hyperlink",
              "content": [
                {
                  "nodeType": "text",
                  "value": "my content link",
                  "marks": [],
                  "data": {}
                }
              ],
              "data": {
                "target": {
                  "sys": {
                    "id": "6eiOBFjSlQukBFMx4XtzE",
                    "type": "Link",
                    "linkType": "Entry"
                  }
                }
              }
            },
            {
              "nodeType": "text",
              "value": "",
              "marks": [],
              "data": {}
            }
          ],
          "data": {}
        },
        {
          "nodeType": "paragraph",
          "content": [
            {
              "nodeType": "text",
              "value": "text after",
              "marks": [],
              "data": {}
            }
          ],
          "data": {}
        }
      ]
    }
    
    
    
    import 'dart:convert';
    
    import 'package:contentful_rich_text/contentful_rich_text.dart';
    import 'package:flutter/material.dart';
    
    class LinkErrorScreen extends StatelessWidget {
      const LinkErrorScreen({Key key}) : super(key: key);
    
      @override
      Widget build(BuildContext context) {
        dynamic document = jsonDecode("{\r\n  \"nodeType\": \"document\",\r\n  \"data\": {},\r\n  \"content\": [\r\n    {\r\n      \"nodeType\": \"paragraph\",\r\n      \"content\": [\r\n        {\r\n          \"nodeType\": \"text\",\r\n          \"value\": \"text before\",\r\n          \"marks\": [],\r\n          \"data\": {}\r\n        }\r\n      ],\r\n      \"data\": {}\r\n    },\r\n    {\r\n      \"nodeType\": \"paragraph\",\r\n      \"content\": [\r\n        {\r\n          \"nodeType\": \"text\",\r\n          \"value\": \"\",\r\n          \"marks\": [],\r\n          \"data\": {}\r\n        },\r\n        {\r\n          \"nodeType\": \"entry-hyperlink\",\r\n          \"content\": [\r\n            {\r\n              \"nodeType\": \"text\",\r\n              \"value\": \"my content link\",\r\n              \"marks\": [],\r\n              \"data\": {}\r\n            }\r\n          ],\r\n          \"data\": {\r\n            \"target\": {\r\n              \"sys\": {\r\n                \"id\": \"6eiOBFjSlQukBFMx4XtzE\",\r\n                \"type\": \"Link\",\r\n                \"linkType\": \"Entry\"\r\n              }\r\n            }\r\n          }\r\n        },\r\n        {\r\n          \"nodeType\": \"text\",\r\n          \"value\": \"\",\r\n          \"marks\": [],\r\n          \"data\": {}\r\n        }\r\n      ],\r\n      \"data\": {}\r\n    },\r\n    {\r\n      \"nodeType\": \"paragraph\",\r\n      \"content\": [\r\n        {\r\n          \"nodeType\": \"text\",\r\n          \"value\": \"text after\",\r\n          \"marks\": [],\r\n          \"data\": {}\r\n        }\r\n      ],\r\n      \"data\": {}\r\n    }\r\n  ]\r\n}\r\n");
        return ContentfulRichText(document).documentToWidgetTree;
      }
    }
    
    ══╡ EXCEPTION CAUGHT BY WIDGETS LIBRARY ╞═══════════════════════════════════════════════════════════
    The following NoSuchMethodError was thrown building Paragraph(dirty):
    The method 'contains' was called on null.
    Receiver: null
    Tried calling: contains("&")
    The relevant error-causing widget was:
      LinkErrorScreen                                                                   lib/pages/home.dart
    When the exception was thrown, this was the stack:
    #0      Object.noSuchMethod  (dart:core-patch/object_patch.dart:51:5)
    #1      HtmlUnescapeBase.convert                                                    package:html_unescape/src/base.dart
    #2      ContentfulRichText._processInlineNode                                       package:contentful_rich_text/contentful_rich_text.dart
    #3      ContentfulRichText.nodeToWidget.<anonymous closure>.<anonymous closure>     package:contentful_rich_text/contentful_rich_text.dart
    #4      MappedListIterable.elementAt  (dart:_internal/iterable.dart:417:31)
    #5      ListIterator.moveNext  (dart:_internal/iterable.dart:343:26)
    #6      new List.from  (dart:core-patch/array_patch.dart:57:19)
    #7      ContentfulRichText.nodeToWidget.<anonymous closure>                         package:contentful_rich_text/contentful_rich_text.dart
    #8      Paragraph.build                                                             package:contentful_rich_text/widgets/paragraph.dart
    #9      StatelessElement.build                                                      package:flutter/…/widgets/framework.dart
    #10     ComponentElement.performRebuild                                             package:flutter/…/widgets/framework.dart
    #11     Element.rebuild                                                             package:flutter/…/widgets/framework.dart
    #12     ComponentElement._firstBuild                                                package:flutter/…/widgets/framework.dart
    #13     ComponentElement.mount                                                      package:flutter/…/widgets/framework.dart
    #14     Element.inflateWidget                                                       package:flutter/…/widgets/framework.dart
    #15     MultiChildRenderObjectElement.mount                                         package:flutter/…/widgets/framework.dart
    ...     Normal element mounting (45 frames)
    #60     Element.inflateWidget                                                       package:flutter/…/widgets/framework.dart
    #61     Element.updateChild                                                         package:flutter/…/widgets/framework.dart
    #62     SliverMultiBoxAdaptorElement.updateChild                                    package:flutter/…/widgets/sliver.dart
    #63     SliverMultiBoxAdaptorElement.createChild.<anonymous closure>                package:flutter/…/widgets/sliver.dart
    #64     BuildOwner.buildScope                                                       package:flutter/…/widgets/framework.dart
    #65     SliverMultiBoxAdaptorElement.createChild                                    package:flutter/…/widgets/sliver.dart
    #66     RenderSliverMultiBoxAdaptor._createOrObtainChild.<anonymous closure>        package:flutter/…/rendering/sliver_multi_box_adaptor.dart
    #67     RenderObject.invokeLayoutCallback.<anonymous closure>                       package:flutter/…/rendering/object.dart
    #68     PipelineOwner._enableMutationsToDirtySubtrees                               package:flutter/…/rendering/object.dart
    #69     RenderObject.invokeLayoutCallback                                           package:flutter/…/rendering/object.dart
    #70     RenderSliverMultiBoxAdaptor._createOrObtainChild                            package:flutter/…/rendering/sliver_multi_box_adaptor.dart
    #71     RenderSliverMultiBoxAdaptor.addInitialChild                                 package:flutter/…/rendering/sliver_multi_box_adaptor.dart
    #72     RenderSliverFixedExtentBoxAdaptor.performLayout                             package:flutter/…/rendering/sliver_fixed_extent_list.dart
    #73     RenderObject.layout                                                         package:flutter/…/rendering/object.dart
    #74     RenderSliverEdgeInsetsPadding.performLayout                                 package:flutter/…/rendering/sliver_padding.dart
    #75     _RenderSliverFractionalPadding.performLayout                                package:flutter/…/widgets/sliver_fill.dart
    #76     RenderObject.layout                                                         package:flutter/…/rendering/object.dart
    #77     RenderViewportBase.layoutChildSequence                                      package:flutter/…/rendering/viewport.dart
    #78     RenderViewport._attemptLayout                                               package:flutter/…/rendering/viewport.dart
    #79     RenderViewport.performLayout                                                package:flutter/…/rendering/viewport.dart
    #80     RenderObject._layoutWithoutResize                                           package:flutter/…/rendering/object.dart
    #81     PipelineOwner.flushLayout                                                   package:flutter/…/rendering/object.dart
    #82     RendererBinding.drawFrame                                                   package:flutter/…/rendering/binding.dart
    #83     WidgetsBinding.drawFrame                                                    package:flutter/…/widgets/binding.dart
    #84     RendererBinding._handlePersistentFrameCallback                              package:flutter/…/rendering/binding.dart
    #85     SchedulerBinding._invokeFrameCallback                                       package:flutter/…/scheduler/binding.dart
    #86     SchedulerBinding.handleDrawFrame                                            package:flutter/…/scheduler/binding.dart
    #87     SchedulerBinding._handleDrawFrame                                           package:flutter/…/scheduler/binding.dart
    #91     _invoke  (dart:ui/hooks.dart:253:10)
    #92     _drawFrame  (dart:ui/hooks.dart:211:3)
    (elided 3 frames from dart:async)
    ════════════════════════════════════════════════════════════════════════════════════════════════════
    
    ════════ Exception caught by widgets library ═══════════════════════════════════
    The following NoSuchMethodError was thrown building Paragraph(dirty):
    The method 'contains' was called on null.
    Receiver: null
    Tried calling: contains("&")
    
    The relevant error-causing widget was
        LinkErrorScreen                                                                 lib/pages/home.dart
    When the exception was thrown, this was the stack
    #0      Object.noSuchMethod  (dart:core-patch/object_patch.dart:51:5)
    #1      HtmlUnescapeBase.convert                                                    package:html_unescape/src/base.dart
    #2      ContentfulRichText._processInlineNode                                       package:contentful_rich_text/contentful_rich_text.dart
    #3      ContentfulRichText.nodeToWidget.<anonymous closure>.<anonymous closure>     package:contentful_rich_text/contentful_rich_text.dart
    #4      MappedListIterable.elementAt  (dart:_internal/iterable.dart:417:31)
    ...
    ════════════════════════════════════════════════════════════════════════════════
    
    opened by czaefferer 4
  • Support inline embedded entries

    Support inline embedded entries

    Overriding the INLINES.EMBEDDED_ENTRY render node currently doesn't call the supplied renderer function when an inline embedded entry is present in the rich text response. This change forwards inline embedded entry node types to the singleton renderer so a custom text span can be inserted into the rich text.

    This is my first contribution to the repo so please shout if I've missed something and thanks for the great package!

    opened by rekubot 3
  • Using tables

    Using tables

    Is it at all possible to use / hack tables into Flutter from contentful using JSON plugins like https://github.com/AnalogMemory/contentful-tables ?

    Looking at the documentation, I could pass a custom renderer for the BLOCKS.JSON.value nodeType - would that work?

    opened by FickleLife 3
  • hyperlink not clickable

    hyperlink not clickable

    body: InkWell( child: Column( children: <Widget>[ ContentfulRichText(someVar) ] ) )

    the text is displayed and everything is fine and the hyperlink is blue and underlined but when I click on it nothing happens

    more information requested 
    opened by Laestry 2
  • Click handler on hyperlink is not working

    Click handler on hyperlink is not working

    Hi, thank you for the awesome plugin.

    I have data coming from contentful and some part of the data passed to Hyperlink class looks like this: {nodeType: hyperlink, content: [{nodeType: text, value: This is a text, marks: [], data: {}}], data: {uri: https://www.domain.com}}

    ISSUE

    With current implementation on Hyperlink class,

    • When setting text in this line: it will always have empty value
    • Therefore the tap gesture recognizer in this line is not being "called", because its impossible to click on an empty text

    SOLUTION

    • Change the implementation on this line to be text: (node['content'][0])['value']
    • Remove the need to put children, therefore deleting line since its duplicating the above suggestion

    Do you have any other thoughts on this?

    more information requested 
    opened by paramadharmika 2
  • No support for Marks

    No support for Marks

    In the current version marks (bold, italic, underline) does not work.

    A quick fix is using this fork: https://github.com/HelloCore/contentful-rich-text-flutter

    bug 
    opened by aignerjo 1
  • Question about the style of the text

    Question about the style of the text

    Hello dear, thank you first for this amazing package. I want just to ask if we can modify the style of our text because I use the package and the color of the text is white and I don't know how to change it. thanks in advance.

    opened by hmney 1
  • support transfer to plain text

    support transfer to plain text

    Thanks for your library, it helps us a lot parsing the contentful data.

    Will this package support transfer contentful raw data to raw text like thisnpm package in the future?

    looking forward for your reply. Thanks 😸

    enhancement help wanted 
    opened by whisperrrr 1
  • Add Github Actions support for running tests

    Add Github Actions support for running tests

    Now that there are some tests (see #20) we should probably setup a way so they can run automatically.

    An example can be found here: https://damienaicheh.github.io/flutter/github/actions/2021/05/06/flutter-tests-github-actions-codecov-en.html

    enhancement help wanted good first issue 
    opened by localpcguy 0
  • Support additional tags

    Support additional tags

    Thank you for the library!

    Are you going to support tags: EMBEDDED_ENTRY // not implemented yet EMBEDDED_ASSET // not implemented yet EMBEDDED_ENTRY (this is different from the BLOCKS.EMBEDDED_ENTRY) // not implemented yet ENTRY_HYPERLINK // not implemented yet ASSET_HYPERLINK // not implemented yet ?

    Is new library update coming soon?

    enhancement help wanted 
    opened by sergey-regotun 2
Owner
Kumanu
Kumanu
Soft and gentle rich text editing for Flutter applications.

About Zefyr Soft and gentle rich text editing for Flutter applications. You are viewing early dev preview version of this package which is no longer a

Memspace 2.2k Jan 8, 2023
Soft and gentle rich text editing for Flutter applications

Soft and gentle rich text editing for Flutter applications. Zefyrka is a fork of Zefyr package with the following improvements: support Flutter 2.0 op

null 85 Dec 21, 2022
A powerful extended official text for Flutter, which supports Speical Text(Image,@somebody), Custom Background, Custom overFlow, Text Selection.

Extended official text to build special text like inline image or @somebody quickly,it also support custom background,custom over flow and custom selection toolbar and handles.

FlutterCandies 509 Jan 4, 2023
A Flutter package to parse text and make them into linkified text widget

?? Flutter Parsed text A Flutter package to parse text and extract parts using predefined types like url, phone and email and also supports Regex. Usa

Fayeed Pawaskar 213 Dec 27, 2022
Rich coin project of flutter

rich_coin flutter实现RichCoin项目。 Getting Started This project is a starting point for a Flutter application. A few resources to get you started if this

null 0 Nov 27, 2021
Create an AutoComplete TextField to search JSON data based on suggestions in Flutter.

Flutter Tutorial - AutoComplete TextField & AutoComplete Search Create an AutoComplete TextField to search JSON data based on suggestions in Flutter.

Johannes Milke 32 Oct 23, 2022
Flutter widget that automatically resizes text to fit perfectly within its bounds.

Flutter widget that automatically resizes text to fit perfectly within its bounds. Show some ❤️ and star the repo to support the project Resources: Do

Simon Leier 1.8k Jan 3, 2023
Arc Text Widget for Flutter

Flutter Arc Text Renders text along the arc. See demo. The story behind the plugin is here. Basic usage class MyApp extends StatelessWidget

Kirill Bubochkin 15 Oct 18, 2021
Text Editor in Flutter for Android and iOS to help free write WYSIWYG HTML code

Flutter Summernote Text Editor in Flutter for Android and iOS to help free write WYSIWYG HTML code based on Summernote 0.8.18 javascript wrapper. NOTI

Chandra Abdul Fattah 41 Sep 12, 2022
A masked text for Flutter.

flutter_masked_text Masked text input for flutter. Alert Hi guys! Unfortunately, I'm not developing mobile anymore. This repo will not receive updates

Ben-hur Santos Ott 264 Dec 21, 2022
A simple Flutter package that makes turning a FAB into a text field easy.

flutter_text_field_fab A simple Flutter widget that makes turning a FAB into a text field easy.

Haefele Software 4 Jan 18, 2022
A Tricky Solution for Implementing Inline-Image-In-Text Feature in Flutter.

A Tricky Solution for Implementing Inline-Image-In-Text Feature in Flutter.

Bytedance Inc. 646 Dec 29, 2022
A customizable code text field supporting syntax highlighting

CodeField A customizable code text field supporting syntax highlighting Live demo A live demo showcasing a few language / themes combinaisons Showcase

Bertrand 162 Dec 23, 2022
Statistics Dart package for easy and efficient data manipulation with many built-in functions and units.

statistics Statistics package for easy and efficient data manipulation with many built-in mathematical functions and units. Usage Numeric extension: i

Graciliano Monteiro Passos 13 Nov 7, 2022
A Flutter Package to render Mathematics, Physics and Chemistry Equations based on LaTeX

flutter_tex Contents About Demo Video Screenshots How to use? Android iOS Web Examples Quick Example TeXView Document TeXView Markdown TeXView Quiz Te

Shahzad Akram 219 Jan 5, 2023
Flutter textfield validation lets you validate different textform fields in your Flutter app

Flutter textfield validation lets you validate different textform fields in your Flutter app

World-Package 2 Sep 15, 2022
flutter 中文排版,支持分页上下对齐 两端对齐 翻页动画

text_composition flutter 中文排版 分页 上下对齐 两端对齐 多栏布局 弃用richText,使用Canvas,精确定位绘图位置,消除字体对排版影响 视频与截图 demo https://github.com/mabDc/text_composition/releases/t

西红柿大芝麻 50 Nov 3, 2022
Flutter Tutorial - PDF Viewer - Asset, File, Network & Firebase

Flutter Tutorial - PDF Viewer - Asset, File, Network & Firebase Use the Flutter PDF Viewer to download PDF documents and display them within your Flut

Johannes Milke 36 Dec 9, 2022
Flutter phone number input

phone_form_field Flutter phone input integrated with flutter internationalization Features Totally cross platform, this is a dart only package / depen

cedvdb 38 Dec 31, 2022