Headless @Composable hooks that drive UI logic. Inspired by React.
π Now supports Kotlin/Compose Multiplatform! Run on Android, Desktop, and more!
π’ Looking for other hooks? In v2.0, we focused on KMP support and kept only the
querymodule. For other hooks likeuseState,useEffect,useContext,useReducer,useToggle, anduseConnectionStatus, check out ComposeHooks - a comprehensive collection of Compose hooks!
Desktop App:
./gradlew :app:runAndroid App:
./gradlew :app:assembleDebug
./gradlew :app:installDebugRun Tests:
# All tests
./gradlew test
# Library tests only
./gradlew :query:test
# App tests only
./gradlew :app:testuseQuery- Type-safe async data fetching with caching and loading/error/success statesuseMutation- Async mutations with callback handling- Type-safe Keys - Strongly-typed query keys using data classes
- QueryClient - Centralized cache management with automatic deduplication
- Cache Invalidation - Type-safe cache invalidation patterns
Platforms supported: Android, Desktop (JVM), ready for iOS/Web
Add to your libs.versions.toml:
[versions]
useCompose = "2.0.0" # Use latest version
[libraries]
useCompose-query = { module = "com.github.pavi2410.useCompose:query", version.ref = "useCompose" }Add to your project's build.gradle.kts (project level):
allprojects {
repositories {
maven { url = uri("https://jitpack.io") }
}
}Add to your module's build.gradle.kts:
For Kotlin Multiplatform:
kotlin {
sourceSets {
commonMain.dependencies {
implementation(libs.useCompose.query)
}
}
}For Android:
dependencies {
implementation(libs.useCompose.query)
}First, create a QueryClient and wrap your app with QueryClientProvider:
@Composable
fun App() {
QueryClientProvider(client = remember { QueryClient() }) {
// Your app content
MyAppContent()
}
}Create data classes that implement the Key interface:
data class UserKey(val userId: Long) : Key
data class PostsKey(val userId: Long, val page: Int = 1) : Key
data class RepoKey(val owner: String, val name: String) : Key
// For singleton keys without parameters, use data object
data object AllUsersKey : Key
data object AppConfigKey : KeyUse the useQuery hook with your type-safe keys:
@Composable
fun UserProfile(userId: Long) {
val queryState by useQuery(
key = UserKey(userId),
queryFn = {
// Your async operation
fetchUserFromApi(userId)
}
)
when (val state = queryState.dataState) {
is DataState.Pending -> Text("Loading...")
is DataState.Error -> Text("Error: ${state.message}")
is DataState.Success -> Text("User: ${state.data.name}")
}
}- π Type Safety: Query keys are strongly-typed data classes, preventing typos and enabling IDE support
- π Automatic Caching: Queries are cached automatically and shared across components
- β»οΈ Smart Invalidation: Type-safe cache invalidation with
invalidateQuery()andinvalidateQueriesOfType<T>() - β‘ Request Deduplication: Multiple components requesting the same data share a single network request
- π― Compose Integration: Built specifically for Jetpack Compose with reactive state updates
val queryClient = useQueryClient()
// Invalidate a specific query
queryClient.invalidateQuery(UserKey(123))// Invalidate all user queries
queryClient.invalidateQueriesOfType<UserKey>()data class RepoKey(val repoPath: String) : Key
@Composable
fun GitHubRepoExample() {
val queryState by useQuery(
key = RepoKey("pavi2410/useCompose"),
queryFn = {
// Your HTTP client call
httpClient.get("https://api.github.com/repos/pavi2410/useCompose")
.body<RepoData>()
}
)
when (val state = queryState.dataState) {
is DataState.Pending -> Text("Loading repository...")
is DataState.Error -> Text("Error: ${state.message}")
is DataState.Success -> {
val repo = state.data
Column {
Text("Name: ${repo.full_name}")
Text("Stars: ${repo.stargazers_count}")
}
}
}
}@Composable
fun PostsAndCommentsExample(postId: Int) {
// Each query is cached independently
val postsQuery by useQuery(
key = PostsListKey(),
queryFn = { fetchAllPosts() }
)
val postDetailQuery by useQuery(
key = PostDetailKey(postId),
queryFn = { fetchPost(postId) }
)
// UI renders both queries...
}The foundation of type-safe queries. Implement this interface on data classes:
interface Key
// Examples:
data class UserKey(val userId: Long) : Key
data class PostKey(val postId: String) : Key
object AllPostsKey : Key // For singleton keysThe main hook for data fetching with caching:
@Composable
fun <T> useQuery(
key: Key,
queryFn: suspend CoroutineScope.() -> T,
options: QueryOptions = QueryOptions.Default
): State<QueryState<T>>Parameters:
key- Type-safe key for caching and identificationqueryFn- Suspend function that fetches the dataoptions- Configuration options (e.g.,enabled)
The state returned by useQuery:
data class QueryState<T>(
val fetchStatus: FetchStatus, // Idle, Fetching
val dataState: DataState<T> // Pending, Error, Success
)
sealed interface DataState<out T> {
object Pending : DataState<Nothing>
data class Error(val message: String) : DataState<Nothing>
data class Success<T>(val data: T) : DataState<T>
}Central cache management:
class QueryClient {
suspend fun <T> getQuery(key: Key, queryFn: suspend CoroutineScope.() -> T): CacheEntry<T>
suspend fun invalidateQuery(key: Key)
suspend inline fun <reified T : Key> invalidateQueriesOfType()
suspend fun clear()
}Compose provider for dependency injection:
@Composable
fun QueryClientProvider(
client: QueryClient,
content: @Composable () -> Unit
)
@Composable
fun useQueryClient(): QueryClientThis library now supports Kotlin Multiplatform! Help us extend it with more hooks and platform support (iOS, Web, etc.).