Skip to content

Micoder-dev/LottiePullRefresh

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

2 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

LottiePullRefresh

A Compose Multiplatform pull-to-refresh that uses a Lottie animation as the refresh indicator. Targets Android and iOS, powered by Compottie for cross-platform Lottie rendering.

License Platform

This is a from-scratch, pure-Compose re-imagining of the classic view-based LottieSwipeRefreshLayout β€” no Android View system, no Accompanist. Just a NestedScrollConnection and a Lottie painter, so it runs unchanged on every Compose Multiplatform target.

Butterfly pull-to-refresh demo Β Β  Dot-spinner pull-to-refresh demo

Pull down β†’ the Lottie animation scrubs 0β†’1 with your finger β†’ release past the threshold β†’ it loops while refreshing.

Features

  • 🎯 Pure Compose β€” works on Android & iOS from a single commonMain implementation.
  • πŸͺ Lottie indicator β€” the animation scrubs live with your finger as you pull, then loops while refreshing.
  • 🧲 Sticky drag physics β€” the same resistance feel as the original view library.
  • 🧩 Composable-first API β€” hoistable state (LottiePullRefreshState), custom-indicator slot, and convenience overloads that take a LottieComposition or a raw Lottie JSON string.
  • 🎨 Customizable β€” threshold distance, indicator size, circular background, elevation, alignment.
  • πŸͺŸ Overlay or push-down β€” indicatorOverlay = true floats the indicator over the content; false pushes the content down so the indicator sits in the gap above it.

The Lottie scrubs 0β†’1 with the drag β€” it plays forward as you pull down and reverses as you pull back up (the progress is bound directly to the pull distance), then loops while refreshing. Use a vector Lottie (shapes only); image-backed or precomp/matte-heavy files won't render correctly in Compottie. The sample demonstrates several styles (a circular butterfly, a monitor-progress ring, a background-less dot spinner, and a push-down variant).

Install (JitPack)

1. Add the JitPack repository to your root settings.gradle.kts:

dependencyResolutionManagement {
    repositories {
        google()
        mavenCentral()
        maven { url = uri("https://jitpack.io") }   // <-- add this
    }
}

2. Add the dependency. In a Compose Multiplatform module, put it in commonMain:

kotlin {
    sourceSets {
        commonMain.dependencies {
            implementation("com.github.Micoder-dev.LottiePullRefresh:lottie-pullrefresh:0.1.0")
        }
    }
}

For an Android-only project it's the same coordinate in your dependencies { } block:

dependencies {
    implementation("com.github.Micoder-dev.LottiePullRefresh:lottie-pullrefresh:0.1.0")
}

iOS note. JitPack builds on Linux, which cannot compile Kotlin/Native Apple targets, so the JitPack artifact ships the Android variant. The full Android + iOS build is published from macOS to Maven Central under io.github.micoder:lottie-pullrefresh. For an iOS/KMP app, consume it from Maven Central; for Android, JitPack is all you need.

Compottie is exposed as an api dependency, so rememberLottieComposition, LottieCompositionSpec, etc. are available to you without an extra dependency.

Quick start

The simplest overload takes the raw Lottie JSON and handles everything for you:

@Composable
fun Feed(viewModel: FeedViewModel) {
    val isRefreshing by viewModel.isRefreshing.collectAsState()

    // Load the animation JSON however you like (compose resources, network, assets…).
    val json = /* "{ \"v\": \"5.7.4\", ... }" */

    LottiePullRefresh(
        isRefreshing = isRefreshing,
        onRefresh = { viewModel.refresh() },
        lottieJson = json,
    ) {
        LazyColumn(Modifier.fillMaxSize()) {
            items(viewModel.items) { ItemRow(it) }
        }
    }
}

With a pre-loaded LottieComposition

val composition by rememberLottieComposition {
    LottieCompositionSpec.JsonString(json)
}

LottiePullRefresh(
    isRefreshing = isRefreshing,
    onRefresh = { refresh() },
    composition = composition,
    refreshThreshold = 96.dp,
    indicatorSize = 56.dp,
) {
    // content
}

See Modes & styles for the overlay vs push-down modes and the background/size options.

Fully custom indicator

Hoist the state and drive your own indicator via the low-level overload:

val state = rememberLottiePullRefreshState(isRefreshing)

LottiePullRefresh(
    isRefreshing = isRefreshing,
    onRefresh = { refresh() },
    state = state,
    indicator = { s, triggerDistance ->
        LottieRefreshIndicator(
            state = s,
            refreshTriggerDistance = triggerDistance,
            composition = composition,
            background = false,          // draw the raw animation, no circular surface
            indicatorSize = 72.dp,
        )
    },
) {
    // content
}

LottiePullRefreshState exposes:

member description
isRefreshing whether a refresh is in progress
isSwipeInProgress whether the user is actively dragging
indicatorOffset current indicator offset in pixels (drive custom visuals off this)

Modes & styles

Overlay (default)

The indicator floats over the content. Swap in any vector Lottie and it scrubs 0β†’1 with the pull.

LottiePullRefresh(
    isRefreshing = isRefreshing,
    onRefresh = { refresh() },
    lottieJson = butterflyJson,
    indicatorSize = 48.dp,       // circular butterfly
    // indicatorOverlay = true   // (default) float over content
) {
    LazyColumn { items(rows) { RowItem(it) } }
}

Use indicatorBackground = false for a background-less indicator (e.g. the dot spinner), and bump indicatorSize for larger art.

Overlay dot-spinner demo

Push-down

Set indicatorOverlay = false and the content is pushed down by the pull distance, so the indicator sits in the gap above it (like the classic Material swipe-refresh).

LottiePullRefresh(
    isRefreshing = isRefreshing,
    onRefresh = { refresh() },
    lottieJson = butterflyJson,
    indicatorOverlay = false,     // push content down
    indicatorBackground = true,   // circular surface behind the animation
    refreshThreshold = 80.dp,
) {
    LazyColumn { items(rows) { RowItem(it) } }
}
Push-down mode demo

API surface

Declaration Purpose
LottiePullRefresh(isRefreshing, onRefresh, lottieJson, …) convenience β€” loads the JSON for you
LottiePullRefresh(isRefreshing, onRefresh, composition, …) pass a pre-built LottieComposition
LottiePullRefresh(isRefreshing, onRefresh, indicator, …) low-level β€” bring your own indicator
LottieRefreshIndicator(state, refreshTriggerDistance, composition, …) the default indicator
rememberLottiePullRefreshState(isRefreshing) remembers/updates the state

Project layout

LottiePullRefresh/
β”œβ”€β”€ lottie-pullrefresh/           # the library (Android + iOS)
β”‚   └── src/commonMain/kotlin/io/github/micoder/lottiepullrefresh/
β”‚       β”œβ”€β”€ LottiePullRefresh.kt                     # the container composable + overloads
β”‚       β”œβ”€β”€ LottieRefreshIndicator.kt               # the default Lottie indicator
β”‚       β”œβ”€β”€ LottiePullRefreshState.kt               # hoistable state
β”‚       └── LottiePullRefreshNestedScrollConnection.kt  # gesture/physics
└── sample/composeApp/            # Android + iOS sample app
    β”œβ”€β”€ src/commonMain/           # shared App() + bundled loader.json
    β”œβ”€β”€ src/androidMain/          # MainActivity
    └── src/iosMain/              # MainViewController()

Running the sample

Android

./gradlew :sample:composeApp:assembleDebug
# then install the APK from sample/composeApp/build/outputs/apk/debug/

iOS β€” the shared UI is exposed to Swift via MainViewController() in sample/composeApp/src/iosMain. Create an Xcode app that embeds the ComposeApp framework and host it in a SwiftUI UIViewControllerRepresentable:

struct ComposeView: UIViewControllerRepresentable {
    func makeUIViewController(context: Context) -> UIViewController {
        MainViewControllerKt.MainViewController()
    }
    func updateUIViewController(_ vc: UIViewController, context: Context) {}
}

iOS frameworks can only be compiled on macOS with Xcode; the Android artifacts build on any platform.

Toolchain

  • Kotlin 2.2.21 Β· Compose Multiplatform 1.11.1 Β· AGP 8.11.0 Β· Gradle 8.13
  • Compottie 2.2.4
  • Library minSdk 21 Β· sample minSdk 24 (Compose resources requirement)

License

Copyright 2026 Micoder-dev

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    https://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

Credits

About

πŸͺ Compose Multiplatform (Android & iOS) pull-to-refresh with a Lottie animation as the indicator β€” scrubs 0β†’1 live with your drag. Powered by Compottie.

Topics

Resources

License

Stars

1 star

Watchers

0 watching

Forks

Packages

 
 
 

Contributors

Languages