bonsai

414 stars
by adrielcafe

:deciduous_tree: A multiplatform tree view for Jetpack Compose

View on GitHub

Documentation

Maven metadata URL
Android API
kotlin
ktlint
License MIT

README image

Bonsai

A batteries-included Tree View for Jetpack Compose

README image

Features

Roadmap

  • iOS support
  • Draggable nodes
  • FileObserver (Android) and/or WatchService (JVM) integration

Import to your project

Add the desired dependencies to your module's build.gradle:

implementation "cafe.adriel.bonsai:bonsai-core:${latest-version}"
implementation "cafe.adriel.bonsai:bonsai-file-system:${latest-version}"
implementation "cafe.adriel.bonsai:bonsai-json:${latest-version}"

Current version: Maven metadata URL

Usage

Bonsai comes with a handy DSL for creating high-performance, customizable trees:

  1. Start by creating a new tree with Tree<T>{}
  2. Create nodes with Leaf<T>() and Branch<T>()
  3. Call Bonsai() to render the tree
@Composable
fun BonsaiExample() {
    val tree = Tree {
        Branch("Mammalia") {
            Branch("Carnivora") {
                Branch("Canidae") {
                    Branch("Canis") {
                        Leaf("Wolf", customIcon = { EmojiIcon("🐺") })
                        Leaf("Dog", customIcon = { EmojiIcon("🐶") })
                    }
                }
                Branch("Felidae") {
                    Branch("Felis") {
                        Leaf("Cat", customIcon = { EmojiIcon("🐱") })
                    }
                    Branch("Panthera") {
                        Leaf("Lion", customIcon = { EmojiIcon("🦁") })
                    }
                }
            }
        }
    }

    Bonsai(tree)
}

Output:

README image

Take a look at the sample app for working examples.

File System integration

Import cafe.adriel.bonsai:bonsai-file-system module to use it.

val tree = FileSystemTree(
    // Also works with java.nio.file.Path and okio.Path
    rootPath = File(path),
    // To show or not the root directory in the tree
    selfInclude = true
)

Bonsai(
    tree = tree,
    // Custom style
    style = FileSystemBonsaiStyle()
)

Output:

README image

JSON integration

Import cafe.adriel.bonsai:bonsai-json module to use it.

val tree = JsonTree(
    // Sample JSON from https://gateway.marvel.com/v1/public/characters
    json = responseJson
)

Bonsai(
    tree = tree,
    // Custom style
    style = JsonBonsaiStyle()
)

Output:

README image

Expanding & Collapsing

Easily control the expanded/collapsed state of your Tree:

  • toggleExpansion(node)
  • collapseRoot() / expandRoot()
  • collapseAll() / expandAll()
  • collapseFrom(depth) / expandUntil(depth)
  • collapseNode(node) / expandNode(node)

Selecting

Selected/Unselected state is also pretty simple to control:

  • selectedNodes
  • toggleSelection(node)
  • selectNode(node) / unselectNode(node)
  • clearSelection()

Click handling

Its also possible to set custom click behaviors for your Tree. Control single, double and long clicks by using the expand and select APIs.

Bonsai(
    tree = tree,
    onClick = { node ->
        tree.clearSelection()
        tree.toggleExpansion(node)
    },
    onDoubleClick = { node -> /* ... */ },
    onLongClick = { node -> /* ... */ }
)

Styling

Change your Tree appearance as you wish. Take a look at BonsaiStyle class for all available customizations.

Bonsai(
    tree = tree,
    style = BonsaiStyle(
        toggleIconRotationDegrees = 0f,
        toggleIcon = { node ->
            rememberVectorPainter(
                if (node is BranchNode && node.isExpanded) Icons.Outlined.UnfoldLess
                else Icons.Outlined.UnfoldMore
            )
        },
        nodeIconSize = 18.dp,
        nodeShape = CutCornerShape(percent = 20),
        nodeCollapsedIcon = { rememberVectorPainter(Icons.Outlined.Circle) },
        nodeExpandedIcon = { rememberVectorPainter(Icons.Outlined.Adjust) },
        nodeNameTextStyle = MaterialTheme.typography.overline
    )
)

Output:

Custom nodes

Need a deeper customization? You can set customIcons and customNames for each Leaf<T>() and Branch<T>() nodes.

Leaf(
    content = "Wolf", 
    customIcon = { EmojiIcon("🐺") }
)

Output:

Dispatch Newsletter
Be the first to discover new Compose libraries
Curated Insights
Digest in 5 minutes or less
Android Analysis
Entertaining takes on happenings
Insider Tips
From top Android developers
Hidden Gems
You won't find elsewhere
"
"Dispatch is a must read for Android devs today and my go-to for keeping up with all things Jetpack Compose. I eagerly await each issue, not just for the Compose gems honestly but for Vinay's unique insights, stories, and finds. The Dev Delight and Featured Dev sections are particular favorites of mine! He really should do a podcast."
Kaushik Gopal
Principal Engineer @ Instacart | Host of the Fragmented Podcast | GDE for Android
Join thousands of Android devs who look forward to Dispatch every week