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.

BlockCodeEntry is the core customization point in MCBFC. It tells MarkwonAdapter how to create and bind a ViewHolder whenever it encounters a FencedCodeBlock node in the parsed CommonMark AST. Without it, fenced code blocks would fall through to the default TextView entry and lose the horizontal scrolling container and copy button that make code blocks genuinely usable on a narrow phone screen.

MarkwonAdapter.Entry contract

Every custom Entry must implement two methods:
  • createHolder(inflater, parent) — called once per new item view. Inflate your custom XML layout here and wrap the resulting root view in your Holder subclass. MarkwonAdapter caches holders via the standard RecyclerView recycling mechanism, so this method is only called as many times as needed to fill the screen plus a small buffer.
  • bindHolder(markwon, holder, node) — called every time an existing holder is bound to a (possibly different) FencedCodeBlock node. Use markwon.setParsedMarkdown(textView, markwon.render(node)) to convert the raw AST node into Android Spanned text and apply it to the TextView. This is also where you would update any other views in the holder (language labels, line counts, etc.).

BlockCodeEntry source

BlockCodeEntry.kt
package com.srm.markdown_render

import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.TextView
import android.widget.Toast
import io.noties.markwon.Markwon
import io.noties.markwon.recycler.MarkwonAdapter
import org.commonmark.node.FencedCodeBlock

class BlockCodeEntry(val ctx: Context) :
    MarkwonAdapter.Entry<FencedCodeBlock, BlockCodeEntry.Holder>() {
    override fun createHolder(p0: LayoutInflater, p1: ViewGroup): BlockCodeEntry.Holder {
        return Holder(
            p0.inflate(
                R.layout.fenced_code_view,
                p1,
                false
            ),
            ctx
        )
    }

    override fun bindHolder(
        p0: Markwon,
        p1: Holder,
        p2: FencedCodeBlock
    ) {
        p0.setParsedMarkdown(p1.commandText, p0.render(p2))
    }

    class Holder(itemView: View, val ctx: Context) : MarkwonAdapter.Holder(itemView) {
        var commandText: TextView = requireView<TextView>(R.id.command)
        var btnCopy: ImageButton = requireView<ImageButton>(R.id.copyCommand)

        init {
            btnCopy.setOnClickListener { v ->
                val text = commandText.text.trim()
                val clipBoard = ctx.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
                val textClip = ClipData.newPlainText("Command", text)
                clipBoard.setPrimaryClip(textClip)
                Toast.makeText(
                    ctx,
                    "Code copied to clipboard! ${text.subSequence(1..7)} ...",
                    Toast.LENGTH_LONG
                ).show()
            }
        }
    }
}

Creating the ViewHolder

createHolder inflates R.layout.fenced_code_view with attachToRoot = false (the third argument) and hands the resulting root view to BlockCodeEntry.Holder along with the Context needed for clipboard access.
fenced_code_view.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:background="@color/material_dynamic_neutral_variant10"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:paddingLeft="16dip"
    android:paddingRight="16dip"
    android:scrollbarStyle="outsideInset">

    <ImageButton
        android:id="@+id/copyCommand"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="end"
        android:background="@android:drawable/alert_dark_frame"
        android:elevation="18dp"
        android:src="?attr/actionModeCopyDrawable" />

    <HorizontalScrollView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:elevation="8dp">

        <TextView
            android:id="@+id/command"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:layout_gravity="center_vertical"
            android:elevation="-1dp"
            android:paddingStart="10dp"
            android:paddingTop="10dp"
            android:paddingEnd="70dp"
            android:paddingBottom="10dp"
            android:text="10dp10dp10dp10dp10dp10dp10dp10dp10dp10dp10dp10dp10dp10dp"
            android:textColor="@color/design_default_color_secondary" />
    </HorizontalScrollView>

</FrameLayout>
The layout uses a FrameLayout as its root so both the ImageButton and the HorizontalScrollView occupy the same space and can be layered independently via their elevation attributes. The horizontal scroll view lets long lines of code scroll left and right without wrapping, which is essential for readability on narrow screens.

Binding markdown to the ViewHolder

bindHolder receives the live Markwon instance (p0), the recycled Holder (p1), and the FencedCodeBlock AST node (p2). The single call:
p0.setParsedMarkdown(p1.commandText, p0.render(p2))
does two things in sequence:
  1. p0.render(p2) — walks the FencedCodeBlock node and produces a Spanned string that carries all Markwon style spans (monospace font, syntax-highlight colours if a highlight plugin is installed, etc.).
  2. p0.setParsedMarkdown(p1.commandText, …) — sets the Spanned text on commandText and triggers any Markwon post-processors that need a live view reference (image loaders, async tasks, etc.).

The Holder class

BlockCodeEntry.Holder extends MarkwonAdapter.Holder and holds two view references resolved at construction time:
  • commandText — the TextView nested inside the HorizontalScrollView. This is where the formatted code text is displayed.
  • btnCopy — the ImageButton floating above the scroll view. Its click listener is wired once in init and remains active for the lifetime of the holder, correctly picking up whatever code text has been bound most recently because it reads commandText.text at click time rather than capturing the text at bind time.
requireView<T>(id) is a helper method provided by MarkwonAdapter.Holder. Unlike findViewById, which returns null when the view is missing, requireView throws an IllegalStateException immediately during Holder construction if the requested id is not found. This fail-fast behaviour surfaces layout mismatches at development time rather than as a silent NullPointerException when a user taps the button.

Build docs developers (and LLMs) love