{"id":19336371,"url":"https://github.com/ahmad-hamwi/tabsync","last_synced_at":"2026-02-24T21:32:08.971Z","repository":{"id":45409111,"uuid":"398466672","full_name":"Ahmad-Hamwi/TabSync","owner":"Ahmad-Hamwi","description":"An Android lightweight synchronizer between (Views) RecyclerView \u0026 TabLayout, (Compose) LazColumn and TabRow.","archived":false,"fork":false,"pushed_at":"2023-10-29T13:49:10.000Z","size":254,"stargazers_count":160,"open_issues_count":0,"forks_count":14,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-31T20:12:05.466Z","etag":null,"topics":["android","lazy-list-state","lazy-lists","mediator","recyclerview","scrollable-tab-row","sync","synchronization","tablayout"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/Ahmad-Hamwi.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2021-08-21T04:37:56.000Z","updated_at":"2025-03-15T15:29:50.000Z","dependencies_parsed_at":"2023-02-11T12:15:19.806Z","dependency_job_id":"f3e70d16-614e-4b98-944a-2e7d95dcfc2a","html_url":"https://github.com/Ahmad-Hamwi/TabSync","commit_stats":{"total_commits":33,"total_committers":1,"mean_commits":33.0,"dds":0.0,"last_synced_commit":"597990285a658f2d7a0dad2b364f3a9af98fdb86"},"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ahmad-Hamwi%2FTabSync","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ahmad-Hamwi%2FTabSync/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ahmad-Hamwi%2FTabSync/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Ahmad-Hamwi%2FTabSync/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Ahmad-Hamwi","download_url":"https://codeload.github.com/Ahmad-Hamwi/TabSync/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253172160,"owners_count":21865472,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","lazy-list-state","lazy-lists","mediator","recyclerview","scrollable-tab-row","sync","synchronization","tablayout"],"created_at":"2024-11-10T03:10:47.579Z","updated_at":"2026-02-24T21:32:08.930Z","avatar_url":"https://github.com/Ahmad-Hamwi.png","language":"Kotlin","funding_links":["https://www.buymeacoffee.com/ahmadhamwi"],"categories":[],"sub_categories":[],"readme":"\u003ch1 align=\"center\"\u003eTabSync\u003c/h1\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://opensource.org/licenses/Apache-2.0\"\u003e\u003cimg alt=\"License\" src=\"https://img.shields.io/badge/License-Apache%202.0-blue.svg\"/\u003e\u003c/a\u003e\n    \u003ca href=\"https://android-arsenal.com/api?level=21\"\u003e\u003cimg alt=\"API\" src=\"https://img.shields.io/badge/API-21%2B-brightgreen.svg?style=flat\"/\u003e\u003c/a\u003e\n    \u003ca href=\"https://proandroiddev.com/synchronize-recyclerview-with-tablayout-3c5da4f3b18b\"\u003e\u003cimg alt=\"Medium\" src=\"https://skydoves.github.io/badges/Story-Medium.svg\"/\u003e\u003c/a\u003e\n    \u003ca href=\"https://proandroiddev.com/jetpack-compose-synchronize-lazyliststate-with-scrollabletabrow-22ff1f8577dc\"\u003e\u003cimg alt=\"Medium\" src=\"https://skydoves.github.io/badges/Story-Medium.svg\"/\u003e\u003c/a\u003e\n\n\u003cbr\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://www.buymeacoffee.com/ahmadhamwi\" target=\"_blank\"\u003e\u003cimg src=\"https://www.buymeacoffee.com/assets/img/custom_images/orange_img.png\" alt=\"Buy Me A Coffee\" /\u003e\n\u003c/p\u003e\n    \n\u003cp align=\"center\"\u003e\n    A lightweight synchronizer between Android's Tabs and Lists! Available for \n    \u003ca href=\"https://github.com/Ahmad-Hamwi/TabSync#setup-for-the-view-system\"\u003eViews\u003c/a\u003e \n    and \u003ca href=\"https://github.com/Ahmad-Hamwi/TabSync#setup-for-jetpack-compose\"\u003eJetpack Compose\u003c/a\u003e!\n\u003c/p\u003e\n\n## Behaviour expected ##\n\nAs you scroll through items, the corresponding tabs will be selected automatically, and vice-versa; selecting tabs by hand will correspond to a scroll of the list to the corresponding item.\n\n\n![Mediator attached](https://media.giphy.com/media/T1cDzfvY3KzQn7kp5d/giphy.gif)\n![Mediator attached with smooth scroll](https://media.giphy.com/media/MTS4wKN5EenEqgCPw7/giphy.gif)\n\n\n## Available on the View System \u0026 Jetpack Compose ##\n\n- On Android's [View](https://github.com/Ahmad-Hamwi/TabSync#setup-for-the-view-system), synchronization is made between TabLayout and RecyclerView\n- Android's [Jetpack Compose](https://github.com/Ahmad-Hamwi/TabSync#setup-for-jetpack-compose), the synchronization is made between any LazeListState-based Composable (e.g. LazyColumn) and an index-based Composable (e.g. ScrollableTabRow).\n\n## Articles ##\n- RecyclerView and TabLayout: Here's a [Medium Article](https://ahmad-hamwi.medium.com/synchronize-recyclerview-with-tablayout-3c5da4f3b18b) demonstrating the example in this repo step-by-step.\n- Jetpack compose: Here's the Jetpack Compose version [Medium Article](https://proandroiddev.com/jetpack-compose-synchronize-lazyliststate-with-scrollabletabrow-22ff1f8577dc) demonstrating the example in this repo step-by-step, and explaining how it's working.\n\n# Setup for the View system #\n\nGet the latest version via Maven Central:\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.ahmad-hamwi/tabsync/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.ahmad-hamwi/tabsync)\n\nAdd Maven Central repository to your root build.gradle at the end of repositories:\n\n```groovy\nallprojects {\n    repositories {\n        ...\n        mavenCentral()\n    }\n}\n```\n\nThen add the dependency\n\n```groovy\ndependencies {\n    implementation 'io.github.ahmad-hamwi:tabsync:1.0.1'\n}\n```\n\n## Usage ##\n\nHere is a non-comprehensive guide to TabSync:\n\nCreate a TabbedListMediator, and pass a RecyclerView, a TabLayout, and a list of RecyclerView's items indices that you wish to sync the tabs with.\n\n```kotlin\nval mediator = TabbedListMediator(\n    recyclerView,\n    tabLayout,\n    categories.indices.toList()\n)\n```\n\nMake sure that RecyclerView and the TabLayout are instantiated with their data (adapter with its\ndata for RecyclerView, and tabs for the TabLayout) and call the attach method. Note that the tabs'\ncount must not be less than the number of the passed indices.\n\n```kotlin\nmethod.attach()\n```\n\nTo make the RecycerView smooth scrolls when pressing on a tab, you can pass a smooth scroll\nflag to the mediator:\n\n```kotlin\nval mediator = TabbedListMediator(\n    recyclerView,\n    tabLayout,\n    categories.indices.toList(),\n    true\n)\n```\n\nTo stop syncing between the RecyclerView and the TabLayout, call the detach method:\n\n```kotlin\nmediator.detach()\n```\n\nIn case of refreshing the mediator (like data set has been changed), call the reAttach method:\n\n```kotlin\nmediator.reAttach()\n```\n\nTo get the smooth scroll flag, call the getter isSmoothScroll()\n\n```kotlin\nval isSmoothScrolling = mediator.isSmoothScroll()\n```\n\n\u003e **_NOTE:_**  You don't have to pass the full indices of the list provided as shown in the previous example. You may want to provide a couple of indices from your recyclerview's items to sync the tabs with:\n\u003e ```kotlin\n\u003e  val mediator = TabbedListMediator(\n\u003e    recyclerView,\n\u003e    tabLayout,\n\u003e    // Syncing the first tab with the item of index 0, the second one with the item of\n\u003e    // index 2, and the third with the item of index 4\n\u003e    mutableListOf(0, 2, 4) \n\u003e  )\n\u003e ```\n\n# Setup for Jetpack Compose #\n\nGet the latest version via Maven Central:\n\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.github.ahmad-hamwi/tabsync-compose/badge.svg)](https://maven-badges.herokuapp.com/maven-central/io.github.ahmad-hamwi/tabsync-compose)\n\nAdd Maven Central repository to your root build.gradle at the end of repositories:\n\n```groovy\nallprojects {\n    repositories {\n        ...\n        mavenCentral()\n    }\n}\n```\n\nThen add the dependency\n\n```groovy\ndependencies {\n    implementation 'io.github.ahmad-hamwi:tabsync-compose:1.0.1'\n}\n```\n\n## Usage ##\n\nHere is a non-comprehensive guide to TabSync Compose:\n\nCall ```lazyListTabSync``` composable, and pass the indices of your list that you wish to sync the tabs with, and assign the result to a destructuring declaration like so:\n\n```kotlin\n@Composable\nfun MyComposable(items: List\u003cItem\u003e) {\n\n    val (selectedTabIndex, setSelectedTabIndex, syncedListState) = lazyListTabSync(items.indices.toList())\n\n}\n```\n\nMake sure that whenever you call ```lazyListTabSync```, the indices and the list's data are ready (above we're passing the state as a parameter). \n\nNote that the tabs' count must not be less than the number of the passed indices.\n\n\n- The ```selectedTabIndex``` is the state of a ```ScrollableTabRow``` that we're going to sync with.\n- The ```setSelectedTabIndex``` is a function that we should call whenever the state of the ```ScrollableTabRow``` changes directly, for example by pressing the tab.\n\n\n``` kotlin\n    ScrollableTabRow(selectedTabIndex) {\n        categories.forEachIndexed { index, category -\u003e\n            Tab(\n                selected = index == selectedTabIndex,\n                onClick = { setSelectedTabIndex(index) },\n            )\n        }\n    }\n```\n\n- The ```listState``` is a ```LazyListState``` which should be attached to a scrollable that accepts it, for example a ```LazyColumn```.\n\n```kotlin\n    LazyColumn(state = syncedListState) {\n        ...\n    }\n```\n\n### Combined: ###\n\n(Full example can be found under [compose-app](https://github.com/Ahmad-Hamwi/TabSync/tree/master/compose-app))\n\n```kotlin\n@Composable\nfun MyComposable(categories: List\u003cCategory\u003e) {\n    val (selectedTabIndex, setSelectedTabIndex, syncedListState) = lazyListTabSync(categories.indices.toList())\n\n    Column {\n        ScrollableTabRow(selectedTabIndex) {\n            categories.forEachIndexed { index, category -\u003e\n                Tab(\n                    selected = index == selectedTabIndex,\n                    onClick = { setSelectedTabIndex(index) },\n                    ...\n                )\n            }\n        }\n\n        LazyColumn(state = syncedListState) {\n            ...\n        }\n    }\n}\n```\n\n\u003e **_NOTE:_**  You don't have to pass the full indices of the list provided as shown in the previous example. You may want to provide a couple of indices from your list's items to sync the tabs with:\n\u003e ```kotlin\n\u003e    // Syncing the first tab with the item of index 0, the second one with the item of\n\u003e    // index 2, and the third with the item of index 4\n\u003e    val (selectedTabIndex, setSelectedTabIndex, syncedListState) = lazyListTabSync(mutableListOf(0, 2, 4))\n\u003e ```\n\n### Additional arguments ###\n\n- By default, the list smooth scrolls when the selected tab changes, if you would like to stop the smooth scrolling, you can pass an optional ```smoothScroll``` flag:\n- ```tabsCount``` can be used to check for the viability of the synchronization with the tabs, the optimal case is that the number of tabs that we're syncing with is the same as the number of indices provided. (You cannot have a number of tabs lower than the number of indices provided, use ```tabsCount``` to make sure you're not doing that).\n- ```lazyListState``` can be used if you would like to provide your own state, the one coming from the destructuring declaration is going to be the same.\n\n```kotlin\n@Composable\nfun MyComposable(items: List\u003cItem\u003e) {\n\n    val (selectedTabIndex, setSelectedTabIndex, listState) = tabSyncMediator(\n        mutableListOf(0, 2, 4), //Mandatory. The indices of lazy list items to sync the tabs with\n        tabsCount = 3, //Optional. To check for viability of the synchronization with the tabs. Optimal when equals the count of syncedIndices.\n        lazyListState = rememberLazyListState(), //Optional. To provide your own LazyListState. Defaults to rememberLazyListState().\n        smoothScroll = true, // Optional. To make the auto scroll smooth or not when clicking tabs. Defaults to true\n    )\n\n}\n```\n\n# Contributing #\nThis library is made to help other developers out in their app developments, feel free to contribute by suggesting ideas and creating issues and PRs that would make this repository more helpful.\n\n# License\n\nCopyright (C) 2023 Ahmad Hamwi\n\nLicensed under the Apache License, Version 2.0\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahmad-hamwi%2Ftabsync","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fahmad-hamwi%2Ftabsync","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fahmad-hamwi%2Ftabsync/lists"}