{"id":13605653,"url":"https://github.com/GetStream/butterfly","last_synced_at":"2025-04-12T05:34:08.188Z","repository":{"id":40271684,"uuid":"451401439","full_name":"GetStream/butterfly","owner":"GetStream","description":"🦋  Butterfly helps you to build adaptive and responsive UIs for Android with Jetpack WindowManager.","archived":false,"fork":false,"pushed_at":"2022-07-01T02:44:01.000Z","size":104324,"stargazers_count":215,"open_issues_count":3,"forks_count":10,"subscribers_count":5,"default_branch":"develop","last_synced_at":"2024-08-02T19:38:04.181Z","etag":null,"topics":["android","dual-screen","foldable","foldable-devices","jetpack","jetpack-compose","windowmanager"],"latest_commit_sha":null,"homepage":"https://getstream.github.io/butterfly/","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/GetStream.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":".github/CODEOWNERS","security":null,"support":null}},"created_at":"2022-01-24T09:37:27.000Z","updated_at":"2024-06-27T13:01:20.000Z","dependencies_parsed_at":"2022-08-09T15:58:39.852Z","dependency_job_id":null,"html_url":"https://github.com/GetStream/butterfly","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GetStream%2Fbutterfly","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GetStream%2Fbutterfly/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GetStream%2Fbutterfly/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/GetStream%2Fbutterfly/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/GetStream","download_url":"https://codeload.github.com/GetStream/butterfly/tar.gz/refs/heads/develop","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":223497972,"owners_count":17155232,"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","dual-screen","foldable","foldable-devices","jetpack","jetpack-compose","windowmanager"],"created_at":"2024-08-01T19:01:01.192Z","updated_at":"2024-11-07T10:31:00.322Z","avatar_url":"https://github.com/GetStream.png","language":"Kotlin","funding_links":[],"categories":["Kotlin"],"sub_categories":[],"readme":"![Butterfly](https://user-images.githubusercontent.com/24237865/150674436-d2713bf0-da35-4b75-8629-5c83d8500cd7.png)\u003cbr\u003e\u003cbr\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://devlibrary.withgoogle.com/products/android/repos/GetStream-butterfly\"\u003e\u003cimg alt=\"Google\" src=\"https://skydoves.github.io/badges/google-devlib.svg\"/\u003e\u003c/a\u003e\u003cbr\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://github.com/GetStream/butterfly/actions/workflows/android.yml\"\u003e\u003cimg alt=\"Build Status\" src=\"https://github.com/GetStream/butterfly/actions/workflows/android.yml/badge.svg\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://us12.campaign-archive.com/?u=f39692e245b94f7fb693b6d82\u0026id=fff08b5e14\"\u003e\u003cimg alt=\"Kotlin Weekly\" src=\"https://skydoves.github.io/badges/kotlin-weekly2.svg\"/\u003e\u003c/a\u003e\n  \u003ca href=\"https://androidweekly.net/issues/issue-503\"\u003e\u003cimg alt=\"Android Weekly\" src=\"https://skydoves.github.io/badges/android-weekly.svg\"/\u003e\u003c/a\u003e \n  \u003ca href=\"https://getstream.github.io/butterfly/\"\u003e\u003cimg alt=\"Dokka\" src=\"preview/dokka-butterfly.svg\"/\u003e\u003c/a\u003e\n\u003c/p\u003e\u003cbr\u003e\n\n\u003cp align=\"center\"\u003e\n🦋 Butterfly helps you to build adaptive and responsive UIs for Android with Jetpack WindowManager. \u003cbr\u003e\nAlso, it supports useful functions for Jetpack Compose and LiveData integration.\n\n\u003c/p\u003e\u003cbr\u003e\n\n## Preview\n\u003cp align=\"center\"\u003e\n\u003cimg src=\"/preview/preview0.png\" /\u003e\n\u003c/p\u003e\n\n\u003cdetails\u003e\n    \u003csummary\u003e🌗 See dark theme\u003c/summary\u003e\n    \n### Dark Theme\n\u003cimg src=\"/preview/preview1.png\" /\u003e\n    \n\u003c/details\u003e\n\n## Demo Project\n\nThe demo project was built with the [Stream Chat SDK for Jetpack Compose](https://getstream.io/chat/sdk/compose/).\nIt would be helpful to understand the demo project if you check out the links below:\n\n- [Compose Chat Messaging Tutorial](https://getstream.io/chat/compose/tutorial/)\n- [Introduction to dual-screen devices](https://docs.microsoft.com/en-us/dual-screen/introduction)\n- [Unbundling the WindowManager](https://medium.com/androiddevelopers/unbundling-the-windowmanager-fa060adb3ce9)\n\n\u003ca href=\"https://getstream.io/tutorials/android-chat/?utm_source=Github\u0026utm_campaign=Devrel_oss\u0026utm_medium=sketchbook\"\u003e\u003cimg src=\"https://user-images.githubusercontent.com/24237865/138428440-b92e5fb7-89f8-41aa-96b1-71a5486c5849.png\" align=\"right\" width=\"13%\"/\u003e\u003c/a\u003e\n\n## Contribution 💙\n\nButterfly is maintained by __[Stream](https://getstream.io/)__.\nIf you’re interested in adding powerful In-App Messaging to your app, check out the __[Stream Chat SDK for Android](https://getstream.io/tutorials/android-chat/?utm_source=Github\u0026utm_campaign=Devrel_oss\u0026utm_medium=butterfly)__!\nAlso, anyone can contribute to improving code, docs, or something following our [Contributing Guideline](/CONTRIBUTING.md).\n\n## Download\n[![Maven Central](https://img.shields.io/maven-central/v/io.getstream/butterfly.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.getstream%22%20AND%20a:%22butterfly%22)\n\n### Gradle\nAdd the codes below to your **root** `build.gradle` file (not your module build.gradle file).\n```gradle\nallprojects {\n    repositories {\n        mavenCentral()\n    }\n}\n```\n\nNext, add the dependency below to your **module**'s `build.gradle` file.\n\n```gradle\ndependencies {\n    implementation \"io.getstream:butterfly:1.0.2\"\n    implementation \"org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1\"\n}\n```\n\n\u003e **Note**: Butterfly includes [Jetpack WindowManager](https://developer.android.com/jetpack/androidx/releases/window) to compute window internally. So if you're using WindowManager in your project, please make sure your project uses the same version or exclude the dependency to adapt yours.\n\n### SNAPSHOT \n\n\u003cdetails\u003e\n \u003csummary\u003eSee how to import the snapshot\u003c/summary\u003e\n\n### Including the SNAPSHOT\nSnapshots of the current development version of Butterfly are available, which track [the latest versions](https://oss.sonatype.org/content/repositories/snapshots/io/getstream/butterfly/).\n\nTo import snapshot versions on your project, add the code snippet below on your gradle file.\n```Gradle\nrepositories {\n   maven { url 'https://oss.sonatype.org/content/repositories/snapshots/' }\n}\n```\n\nNext, add the below dependency to your **module**'s `build.gradle` file.\n```gradle\ndependencies {\n    implementation \"io.getstream:butterfly:1.0.3-SNAPSHOT\"\n}\n```\n\n\u003c/details\u003e\n\n## Set up Foldable Emulator\n\n\u003ca href=\"https://docs.microsoft.com/en-us/dual-screen/introduction\"\u003e\n   \u003cimg src=\"/preview/instruction4.png\" /\u003e\n\u003c/a\u003e\n\nIf you don't have foldable devices or emulators, you can set up a foldable emulator environment following the below instruction:\n\n👉 Check out the [Set up Foldable Emulator (Surface Duo 2)](/INSTRUCTION_FOLDABLE.md)\n\n## Usage\n\nButterfly uses [Jetpack WindowManager](https://developer.android.com/jetpack/androidx/releases/window?gclid=Cj0KCQiAuP-OBhDqARIsAD4XHpeYC6wgI0kSXjzRVoTdamCppreFdJdNsYr0xPIuQdqq-tzjzSM5-hEaAifuEALw_wcB),\nso it would be helpful to understand if you have background knowledge of the **WindowManager** APIs.\n\n### WindowSize\n\nWindowSize represents breakpoints, which are the screen size at which a layout will adapt to best fit content and conform to responsive layout requirements. \nButterfly follows three breakpoints by [Material Design](https://m3.material.io/foundations/adaptive-design/large-screens/overview).\n\n- **WindowSize.Compact**: Most phones in portrait mode. (0-599 dp range)\n- **WindowSize.Medium**: Most foldables and tablets in portrait mode. (600-839 dp range)\n- **WindowSize.Expanded**: Most tablets in landscape mode. (840+ dp range)\n\nYou can get an instance of the `WindowSize` class with `getWindowSize()` method on your **Activity** or **Fragment** as following below:\n\n```kotlin\nval windowSize: WindowSize = getWindowSize()\nwhen (windowSize) {\n    is WindowSize.Compact -\u003e // the window size is compact.\n    is WindowSize.Medium -\u003e // the window size is medium.\n    is WindowSize.Expanded -\u003e // the window size is expanded.\n}\n```\n\n### GlobalWindowSize\n\nYou can customize the pre-defined breakpoints, which used to `getWindowSize()` with `GlobalWindowSize` object class as following below:\n\n```kotlin\nGlobalWindowSize.compactWindowDpSize = 600\nGlobalWindowSize.mediumWindowDpSize = 840\n```\n\nAlso, you can fully customize a factory function of the `WindowSize` class as following below:\n\n```kotlin\nGlobalWindowSize.windowSizeFactory = { windowPixelSize -\u003e\n    when {\n        windowPixelSize.width \u003c 0 -\u003e throw IllegalArgumentException(\"Can't be negative\")\n        windowPixelSize.width \u003c 600.dp2Px() -\u003e WindowSize.Compact(windowPixelSize)\n        windowPixelSize.width \u003c 840.dp2Px() -\u003e WindowSize.Medium(windowPixelSize)\n        else -\u003e WindowSize.Expanded(windowPixelSize)\n    }\n}\n```\n\n## Posture\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://medium.com/androiddevelopers/unbundling-the-windowmanager-fa060adb3ce9\"\u003e\n  \u003cimg src=\"/preview/posture.png\" /\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n\u003e Fold state: `FLAT` and `HALF-OPENED` from [Google](https://medium.com/androiddevelopers/unbundling-the-windowmanager-fa060adb3ce9).\n\nPosture class represents device postures in the flexible display or a hinge between two physical display panels.\n\n- **Posture.TableTop** - Device posture is in tabletop mode (half open with the hinge horizontal).\n- **Posture.Book** - Device posture is in book mode (half open with the hinge vertical).\n- **Posture.Normal** - Device posture is in normal mode.\n\nYou can observe the posture as a Kotlin Flow on you **Activity** or **Fragment** as following below:\n\n```kotlin\nlifecycleScope.launch(Dispatchers.Main) {\n    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {\n        postureFlow.collect { posture -\u003e\n            when (posture) {\n                Posture.Normal -\u003e // posture is Normal\n                is Posture.TableTop -\u003e // posture is TableTop\n                is Posture.Book -\u003e // posture is Book\n            }\n        }\n        windowLayoutInfo.collect(::onWindowLayoutInfoUpdated)\n    }\n}\n```\n\u003e **Note**: Make sure your project includes [Coroutines](https://developer.android.com/kotlin/coroutines) and `androidx.lifecycle:lifecycle-runtime-ktx:2.4.0` dependencies.\n\n## WindowLayoutInfo\n\n[WindowLayoutInfo](https://developer.android.com/reference/androidx/window/layout/WindowLayoutInfo) contains the list of [DisplayFeature](https://developer.android.com/reference/androidx/window/layout/DisplayFeature)-s\nlocated within the window. You can observe the `WindowLayoutInfo` as following below:\n\n```kotlin\nlifecycleScope.launch(Dispatchers.Main) {\n    lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {\n        windowLayoutInfo.collect { windowLayoutInfo -\u003e\n            // something stuff            \n        }\n    }\n}\n```\n\nYou can also get the following information from the `WindowLayoutInfo` as in the following extensions:\n\n```kotlin\nval isSeparating: Boolean = WindowLayoutInfo.isSeparating\nval occlusionType: FoldingFeature.OcclusionType = WindowLayoutInfo.occlusionType \nval orientation: FoldingFeature.Orientation = WindowLayoutInfo.orientation\nval state: FoldingFeature.State = WindowLayoutInfo.state\n```\n\n## FoldingFeature\n\n[FoldingFeature](https://developer.android.com/reference/androidx/window/layout/FoldingFeature) that describes a fold in the flexible display or a hinge between two physical display panels.\nYou can utilize the extensions below to check folding states and device postures:\n\n```kotlin\nval foldingFeature = windowLayoutInfo.displayFeatures.findFoldingFeature()\nval posture = foldingFeature?.toPosture()\nval isTableTopPosture = foldingFeature?.isTableTopPosture\nval isBookPosture = foldingFeature?.isBookPosture\nval isHalfOpened = foldingFeature?.isHalfOpened\nval isFlat = foldingFeature?.isFlat\nval isVertical = foldingFeature?.isVertical\nval isHorizontal = foldingFeature?.isHorizontal\n```\n\n## Hinge Size\n\nIf your foldable device is separated by a hinge, you can get a hinge size with the extensions below:\n\n```kotlin\nval hingePxSize: Int = foldingFeature.hingePxSize\nval hingeDpSize: Int = foldingFeature.hingeDpSize\nval hingeWidthPxSize: Int = foldingFeature.hingeWidthPxSize\nval hingeHeightPxSize: Int = foldingFeature.hingeHeightPxSize\nval hingeWidthDpSize: Int = foldingFeature.hingeWidthDpSize\nval hingeHeightDpSize: Int = foldingFeature.hingeHeightDpSize\n```\n\n## WindowInfoActivity\n\nButterfly supports `WindowInfoActivity`, which tracks window configurations and update the [WindowLayoutInfo](https://developer.android.com/reference/androidx/window/layout/WindowLayoutInfo).\nIt has a default `windowSize` property and `onWindowLayoutInfoUpdated` abstract method as in the example below:\n\n```kotlin\nclass MainActivity : WindowInfoActivity() {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        // windowSize property will be initialized lazily.\n        when (windowSize) {\n            is WindowSize.Compact -\u003e \n            ...\n        }\n    }\n\n    override fun onWindowLayoutInfoUpdated(windowLayoutInfo: WindowLayoutInfo) {\n        val foldingFeature = windowLayoutInfo.displayFeatures.findFoldingFeature() ?: return\n        when (val posture = foldingFeature.toPosture()) {\n            Posture.Normal -\u003e Log.d(tag, \"[Posture.Normal] ${posture.size}\")\n            is Posture.TableTop -\u003e Log.d(tag, \"[Posture.TableTop] ${posture.size}\")\n            ...\n        }\n    }\n}\n```\n\nThe pre-defined `windowSize` property will be initialized lazily and the `onWindowLayoutInfoUpdated` will be updated\nwhen the `WindowLayoutInfo` configuration changed. As the same concept, you can extend `WindowInfoFragment` for your **Fragment**.\n\n\u003cimg align=\"right\" width=\"15%\" src=\"https://user-images.githubusercontent.com/24237865/149444862-961adb83-da2a-4179-9c27-37edb2f982f4.png\"\u003e\n\n## Butterfly for Jetpack Compose\n\n[![Maven Central](https://img.shields.io/maven-central/v/io.getstream/butterfly-compose.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.getstream%22%20AND%20a:%22butterfly-compose%22)\n\nButterfly supports Jetpack Compose to build adaptive and responsive UIs. First, add the dependency below to your **module's** `build.gradle` file.\n\n```gradle\ndependencies {\n    implementation \"io.getstream:butterfly-compose:1.0.1\"\n}\n```\n\n### WindowDpSize\n\nWindowDpSize represents breakpoints, which are the screen size at which a layout will adapt to best fit content and conform to responsive layout requirements. \nButterfly follows three breakpoints by [Material Design](https://m3.material.io/foundations/adaptive-design/large-screens/overview).\n\nYou can remember an instance of the `WindowDpSize` class with `rememberWindowDpSize()` method on your **Activity** or **Fragment** as following below:\n\n```kotlin\nval windowDpSize: WindowDpSize = rememberWindowDpSize()\nwhen (windowDpSize) {\n    is WindowSize.Compact -\u003e MainScreenRegular()\n    is WindowSize.Medium -\u003e MainScreenMedium()\n    is WindowSize.Expanded -\u003e MainScreenExpanded()\n}\n```\n\n\u003e **Note**: Likewise the `WindowSize`, you can also customize the pre-defined breakpoints, which used to `rememberWindowDpSize` with the [GlobalWindowSize](https://github.com/GetStream/butterfly#globalwindowsize) object class.\n\n## Posture\n\nYou can get a **State** of **Posture** to build adaptive and responsive UIs with the `postureState` extension on your **Activity** and **Fragment** as following below:\n\n```kotlin\nval postureState: State\u003cPosture\u003e = postureState\nwhen (postureState.value) {\n    Posture.Normal -\u003e // posture is Normal\n    is Posture.TableTop -\u003e // posture is TableTop\n    is Posture.Book -\u003e // posture is Book\n}\n```\n\n## WindowLayoutInfo\n\n[WindowLayoutInfo](https://developer.android.com/reference/androidx/window/layout/WindowLayoutInfo) contains the list of [DisplayFeature](https://developer.android.com/reference/androidx/window/layout/DisplayFeature)-s\nlocated within the window. You can get the **State** of the `WindowLayoutInfo` as following below:\n\n```kotlin\nval windowLayoutInfoState: State\u003cWindowLayoutInfo\u003e = windowLayoutInfoState\nval foldingFeature = windowLayoutInfoState.value.displayFeatures.findFoldingFeature()\n...\n```\n\n## CompositionLocal\n\nYou can pass instances of the `WindowDpSize` and `Posture` down through the Composition implicitly as following below:\n\n```kotlin\nCompositionLocalProvider(LocalWindowDpSize provides rememberWindowDpSize()) {\n    val windowDpSize = LocalWindowDpSize.current\n    ...\n}\n\nCompositionLocalProvider(LocalPosture provides postureState.value) {\n    val posture = LocalPosture.current\n    ...                \n}\n```\n\n## Hinge Size\n\nIf your foldable device is separated by a hinge, you can get a hinge size with the extensions below:\n\n```kotlin\nval hingeDp: Dp = FoldingFeature.hingeDp\nval hingeDpSize: DpSize = FoldingFeature.hingeDpSize\nval hingeWidthDp: Dp = FoldingFeature.hingeWidthDp\nval hingeHeightDp: Dp = FoldingFeature.hingeHeightDp\n```\n\n\u003cimg align=\"right\" width=\"15%\" src=\"https://user-images.githubusercontent.com/24237865/149445065-47c2506d-a738-4fb2-b4fb-eb6841b9e202.png\" /\u003e\n\n## Butterfly for LiveData Integration\n\n[![Maven Central](https://img.shields.io/maven-central/v/io.getstream/butterfly-livedata.svg?label=Maven%20Central)](https://search.maven.org/search?q=g:%22io.getstream%22%20AND%20a:%22butterfly-livedata%22)\n\nButterfly supports **LiveData** integration to let observing layout changes as **LiveData**. First, add the dependency below to your **module's** `build.gradle` file.\n\n```gradle\ndependencies {\n    implementation \"io.getstream:butterfly-livedata:1.0.1\"\n}\n```\n\nYou can observe **LiveData** of `Posture` and `WindowLayoutInfo` on your **Activity** and **Fragment** as in the following example below:\n\n```kotlin\npostureLiveData().observe(this) { posture -\u003e\n    // do something\n}\n\nwindowLayoutInfoLiveData().observe(this) { windowLayoutInfo -\u003e\n    // do something\n}\n```\n\n \u003ca href=\"https://getstream.io/tutorials/android-chat/?utm_source=Github\u0026utm_campaign=Devrel_oss\u0026utm_medium=butterfly\"\u003e\n\u003cimg src=\"https://user-images.githubusercontent.com/24237865/138428440-b92e5fb7-89f8-41aa-96b1-71a5486c5849.png\" align=\"right\" width=\"12%\"/\u003e\u003c/a\u003e\n\n## Find this library useful? ❤️\n\nSupport it by joining __[stargazers](https://github.com/getStream/butterfly/stargazers)__ for this repository. ⭐️ \u003cbr\u003e\nAlso, follow **[Stream](https://twitter.com/getstream_io)** on Twitter for our next creations!\n\n# License\n```xml\nCopyright 2022 Stream.IO, Inc. All Rights Reserved.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n   http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGetStream%2Fbutterfly","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FGetStream%2Fbutterfly","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FGetStream%2Fbutterfly/lists"}