We are committed to enabling the streaming rendering of Markdown content generated by large language models on the client side within AI-driven business applications.
This library is designed for native Android, iOS and HarmonyOS developers. Built on the open-source CommonMark parsing library, it supports core Markdown syntax and selected HTML tags, rendering them progressively within UI components. It exposes Markdown styling as a structured model, enabling customization and integration into your specific application contexts. For faster integration, refer to the sample code to preview the rendering effect based on input text.
- Support for markdown syntax: titles, paragraphs, ordered lists, unordered lists, tables, code blocks, mathematical formulas, inline code blocks, quotes, dividing lines, footnotes, links, and images.
- Support for HTML tags:
<s><sup><sub><mark><a><span><cite><del><font><img><u>, etc. - Streaming rendering and one-time full rendering modes.
- Customizable rendering styles for Markdown syntax.
- Adjustable streaming speed via custom parameters.
- Event support for clickable elements, including click handling, visibility callbacks, and rendering status updates, etc.
- Added some new extended HTML tags such as
<iconlink><icon>inAMHTMLTransformerclass.
The source code of this project is open-source. For information on how to download and run the project, please refer to the file INSTALL.
- AntMarkdown —— a standard markdown parser and rendering module based on commonMark.
- FluidMarkdown —— Component for streaming output.
- fluid-markdown —— Component for streaming output.
- markwon-xxx —— Syntax parsing and style rendering implementation based on the open-source markwon library.
- markdown —— the folder contains all the source code for the markdown component, such as syntax parsing, layout rendering, theme configuration, runtime services, etc.
- playground —— a demo to show markdown component feature list.
your/project/markdown/src/main/ets
├── engine // [folder] engine manages all services and plugins at runtime.
├── index.ets // [file] default exports.
├── markdown.ets // [file] markdown component compliant with the @ComponentV2 specification.
├── render // [folder] rendering-related logic based on the StyledString mechanism.
├── service // [folder] runtime service modules, such as syntax parsing, code highlighting, etc.
├── theme // [folder] style and theme-related logic based on the StyledString mechanism.
└── util // [folder] built-in utility api.- Native API:
AMXMarkdownWidget.his summary header file of the open API. The following is the general calling process of streaming rendering components.- Create a TextView instance.
- Generate default styles and set custom styles ( if needed )
- Begin streaming the Markdown content.
- Append data dynamically during rendering.
- Adjust list scrolling in response to content size changes in the TextView.
- Handle completion when rendering is finished.
AMXMarkdownTextView* contentTextView = [[AMXMarkdownTextView alloc] initWithFrame_ant_mark:CGRectMake(0, 0, screenWidht - 20 * 2, 1)];
// get default style config
AMXMarkdownStyleConfig* config = [AMXMarkdownStyleConfig defaultConfig];
// modify code block style for example
config.codeBlockConfig.backgroundColor = [UIColor greenColor];
// set the style with unique Id
[[AMXRenderService shared] setMarkdownStyleWithId:config styleId:@"demo"];
// begin print
[self.contentTextView startStreamingWithContent:@"testing data"];
// append content during printing
[self.contentTextView addStreamContent:@"**append test data**"];
// stop print when you need
[self.contentTextView stop];@interface StreamPreviewViewController ()<AMXMarkdownTextViewDelegate>
-(void)initUI
{
// set delegate
self.contentTextView.textViewDelegate = self;
}
// size change delegate
-(void)onSizeChange:(CGSize)size
{
// adjust size of AMXMarkdownTextView and container view
[self.contentTextView setFrame:CGRectMake(0, 0, self.contentTextView.frame.size.width, size.height)];
[self.containerView setContentSize:size];
CGPoint bottomOffset = CGPointMake(0, self.containerView.contentSize.height - self.containerView.bounds.size.height);
if (bottomOffset.y > 0) {
// scroll the container view to bottom
[self.containerView setContentOffset:bottomOffset animated:NO];
}
}- Sample Description
- The
StreamPreviewViewControllerclass is a sample page for previewing streaming output. - The
AIChatViewControllerclass demonstrates FluidMarkdown usage in simulated AI conversation scenarios. Note that the conversation data is statically defined and intended solely for rendering demonstration purposes.
- The
- Initialize by calling AFMInitializer.init(context, backgroundTaskHandler, imageHandler, logHandler) once globally. Except for context, all other parameters can be null.
- Create PrinterMarkDownTextView to display markdown content.
- Create MarkdownStyles to set render styles.
- Call PrinterMarkDownTextView.init() to bind MarkdownStyles and ElementClickEventCallback. Must be called, MarkdownStyles cannot be null.
- Set markdown content or call the print start method.
AFMInitializer.init(context, null, null, null);
// Create PrinterMarkDownTextView
PrinterMarkDownTextView markdownTextView = findViewById(R.id.markdown_view);
// Create MarkdownStyles,or you can also create custom styles with new MarkdownStyles()
MarkdownStyles styles = MarkdownStyles.getDefaultStyles();
// set style sample
styles.linkStyle().icon("https://you_image_url");
styles.setTitleStyle(0, TitleStyle.create(1.5f).icon(https://you_image_url));// Set title level 1 style
// bind MarkdownStyles and ElementClickEventCallback.
markdownTextView.init(styles, elementClickEventCallback);
// Set markdown content or you can call the startPrinting(content) to starting printing.
markdownTextView.setMarkdownText(markdown);- Sample Description
- MainActivity - Markdown normal mode sample.
- PrinterActivity - Streaming print sample.
- ListActivity - Streaming print list sample.
- Import and combine the Markdown component in your page
build()method. - Bind markdown-formatted content text via the
@Param contentparameter. - Handle various callback events inside the Markdown component to enhance the business interaction flow, such as
@Event onMarkdownAreaChange,@Event onMarkdownNodeClick, etc.
import { Markdown, EMarkdownMode } from 'fluid-markdown';
@ComponentV2
export struct MyComponent {
@Param content: string;
private scroller: Scroller = new Scroller();
build() {
Scroll(this.scroller) {
Markdown({
content: this.content,
mode: EMarkdownMode.Normal,
onMarkdownAreaChange: () => {},
onMarkdownNodeClick: data => {},
})
.margin(12)
}
.width('100%')
.height('100%')
.scrollable(ScrollDirection.Vertical)
.margin(12)
}
}- Enable streaming output mode by setting the
@Parammode parameter toEMarkdownMode.Typing. - Create a
MarkdownControllerinstance and bind it to the@Param controllerparameter to manage the streaming output process, such asupdate(),pause(),resume(), etc. - Note: Control methods of
MarkdownController, such asupdate(), can only be reliably executed within the@Event onMarkdownTypingReadycallback event. - Note: The
@Param contentparameter will be ignored in streaming output mode.
import {
Markdown, EMarkdownMode, MarkdownController, ETypingMode,
} from 'fluid-markdown';
@ComponentV2
export struct MyComponent {
private scroller: Scroller = new Scroller();
private markdownController: MarkdownController = new MarkdownController();
build() {
Scroll(this.scroller) {
Markdown({
controller: this.markdownController,
mode: EMarkdownMode.Typing,
onMarkdownTypingReady: () => {
this.markdownController.typing.update('Hello FluidMarkdown', ETypingMode.Begin);
},
})
.margin(12)
}
.width('100%')
.height('100%')
.scrollable(ScrollDirection.Vertical)
.margin(12)
}
}- Create a new Engine instance and bind it to the
@Param engineparameter. - Set
IThemeproperties for theme styling through thetheme servicewithin the Engine.
import { Markdown, EMarkdownMode, BaseEngine } from 'fluid-markdown';
@ComponentV2
export struct MyComponent {
@Param content: string;
private scroller: Scroller = new Scroller();
private engine: BaseEngine = new BaseEngine();
aboutToAppear() {
this.engine.theme!.theme!.document!.font!.fontColor = Color.Red;
}
build() {
Scroll(this.scroller) {
Markdown({
engine: this.engine,
content: this.content,
mode: EMarkdownMode.Normal,
onMarkdownAreaChange: () => {},
onMarkdownNodeClick: data => {},
})
.margin(12)
}
.width('100%')
.height('100%')
.scrollable(ScrollDirection.Vertical)
.margin(12)
}
}Build and run the Playground app to try out FluidMarkdown—have fun!
- Clickable elements within tables appear as plain text and are not interactive.
- Nested HTML tags within table cells are not supported.
- Tables on Android may overflow their container and do not support horizontal scrolling.
- LaTex capability on the HarmonyOS platform is under development and not yet available.
- The minimum required HarmonyOS API version is 15 or higher.
The FluidMarkdown team welcomes individual or team contributions. For more informations, please refer to the file CONTRIBUTING.
All source code is licensed under the Apache 2.0 license. For details, please refer to LICENSE.
We acknowledge the following open-source projects:
- noties/Markwon
- license: Apache-2.0
- indragiek/CocoaMarkdown
- license: MIT License
- commonmark/commonmark-spec
- license: MIT License
- https://github.com/max-lfeng/iosMath/
- license: MIT License
- https://github.com/mattt/Ono/
- license: MIT License
- markdown-it
- license: MIT License
- highlight.js
- license: BSD 3-Clause License
- csstree
- license: MIT License
- htmlparser2
- license: MIT License
- All developers who have contributed code to the Ant Markdown component
- Thanks to the following open source projects:



