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.).