Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/tutosrive/mcbfc/llms.txt

Use this file to discover all available pages before exploring further.

Rendering markdown into a single TextView is the simplest way to display formatted text on Android, but it has a hard ceiling: every node in the document is measured and drawn at once, which causes noticeable jank when the document is long. MarkwonAdapter solves this by mapping each top-level CommonMark AST node to its own RecyclerView item. Android can then recycle the views for nodes that scroll off screen, keeping memory usage flat and frame rate consistent regardless of document length.

How MarkwonAdapter works

When you call adapter.setMarkdown(markwon, text), Markwon parses the raw string into a CommonMark AST and walks its top-level nodes. For each node type it finds a registered Entry — a paired ViewHolder factory and binder — and uses that entry to create and populate a list item. Node types with no registered entry fall back to the default text Entry supplied via builderTextViewIsRoot(layoutId), which renders the node into a plain TextView. Because every node is its own list item, RecyclerView’s built-in view recycling handles the heavy lifting: a heading, a paragraph, a blockquote, and a fenced code block each get the right view type and are reused as the user scrolls.

The markdown wrapper class

The markdown class encapsulates all Markwon and RecyclerView wiring in a single, reusable object.
markdown.kt
package com.srm.markdown_render

import android.content.Context
import androidx.recyclerview.widget.DefaultItemAnimator
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.noties.markwon.Markwon
import io.noties.markwon.recycler.MarkwonAdapter
import org.commonmark.node.FencedCodeBlock

class markdown(val ctx: Context, view: RecyclerView) {
    private var mark: Markwon = Markwon.builder(ctx).build()
    private var adapter: MarkwonAdapter

    init {

        adapter = MarkwonAdapter
            .builderTextViewIsRoot(R.layout.adapter_default_entry)
            .include<FencedCodeBlock>(FencedCodeBlock::class.java, BlockCodeEntry(ctx))
            .build()
        view.layoutManager = LinearLayoutManager(ctx)
        view.itemAnimator = DefaultItemAnimator()
        view.adapter = adapter

    }

    fun sett(text: String) {
        this.adapter.setMarkdown(this.mark, text)
        this.adapter.notifyDataSetChanged()
    }
}
Here is what each part does:
  • Markwon.builder(ctx).build() — constructs the core Markwon instance. Additional plugins (images, syntax highlighting, tables, etc.) can be chained here before build().
  • MarkwonAdapter.builderTextViewIsRoot(R.layout.adapter_default_entry) — creates an MarkwonAdapter.Builder that uses adapter_default_entry.xml as the default layout. That layout is a single TextView whose root is the item view, so MarkwonAdapter can call Markwon.setParsedMarkdown directly on it for paragraphs, headings, and all other non-code nodes.
  • .include<FencedCodeBlock>(FencedCodeBlock::class.java, BlockCodeEntry(ctx)) — registers BlockCodeEntry as the Entry for every FencedCodeBlock node. When the adapter encounters a fenced code block in the AST it delegates creation and binding to this entry instead of the default TextView path.
  • LinearLayoutManager + DefaultItemAnimator — standard RecyclerView wiring. LinearLayoutManager stacks items vertically; DefaultItemAnimator provides the default add/remove animations.
  • sett(text) — the single public method callers use to display new markdown. It calls adapter.setMarkdown(mark, text) to re-parse and diff the node list, then notifyDataSetChanged() to tell the RecyclerView to rebind all visible items.

Setting up the RecyclerView in XML

The RecyclerView lives inside a LinearLayout in activity_main.xml. Its id recyclerMark is the reference point used in code to attach the adapter.
activity_main.xml
<androidx.recyclerview.widget.RecyclerView
    android:id="@+id/recyclerMark"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />
The full layout places an EditText above the RecyclerView so you can paste text into the field and verify the copy button works — a useful debug affordance that can be removed in production.

Binding markdown in an Activity

MainActivity.onCreate creates the markdown wrapper and immediately loads a test document from raw resources:
MainActivity.kt
val mark = markdown(this, findViewById(R.id.recyclerMark))
mark.sett(getRawTest())
getRawTest() opens res/raw/test_markdown as a buffered stream and returns its full contents as a String. You can replace this call with any string source — a network response, a local file, or a user-supplied input.
Calling notifyDataSetChanged() on every sett() call is perfectly fine for a demo or small documents, but it forces RecyclerView to rebind every visible item even when only one line changed. For production apps with large or frequently-updated documents, replace the notifyDataSetChanged() call with a DiffUtil.DiffResult dispatch so only the items that actually changed are rebound and animated.

Build docs developers (and LLMs) love