Showing preview only (441K chars total). Download the full file or copy to clipboard to get everything.
Repository: arkivanov/Essenty
Branch: master
Commit: 703543ddf558
Files: 137
Total size: 391.7 KB
Directory structure:
gitextract_dhrtofoi/
├── .editorconfig
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ ├── build.yml
│ └── publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── back-handler/
│ ├── .gitignore
│ ├── api/
│ │ ├── android/
│ │ │ └── back-handler.api
│ │ ├── back-handler.klib.api
│ │ └── jvm/
│ │ └── back-handler.api
│ ├── build.gradle.kts
│ └── src/
│ ├── androidMain/
│ │ ├── AndroidManifest.xml
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── backhandler/
│ │ └── AndroidBackHandler.kt
│ ├── androidUnitTest/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── backhandler/
│ │ ├── AndroidBackHandlerTest.kt
│ │ ├── AndroidBackHandlerWithLifecycleTest.kt
│ │ └── OnBackPressedCallbackAdapterTest.kt
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── backhandler/
│ │ ├── BackCallback.kt
│ │ ├── BackDispatcher.kt
│ │ ├── BackEvent.kt
│ │ ├── BackHandler.kt
│ │ ├── BackHandlerOwner.kt
│ │ ├── DefaultBackDispatcher.kt
│ │ └── Utils.kt
│ └── commonTest/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── backhandler/
│ └── DefaultBackDispatcherTest.kt
├── build.gradle.kts
├── deps.versions.toml
├── detekt.yml
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── instance-keeper/
│ ├── .gitignore
│ ├── api/
│ │ ├── android/
│ │ │ └── instance-keeper.api
│ │ ├── instance-keeper.klib.api
│ │ └── jvm/
│ │ └── instance-keeper.api
│ ├── build.gradle.kts
│ └── src/
│ ├── androidMain/
│ │ ├── AndroidManifest.xml
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── instancekeeper/
│ │ └── AndroidExt.kt
│ ├── androidUnitTest/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── instancekeeper/
│ │ └── AndroidInstanceKeeperTest.kt
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── instancekeeper/
│ │ ├── DefaultInstanceKeeperDispatcher.kt
│ │ ├── ExperimentalInstanceKeeperApi.kt
│ │ ├── InstanceKeeper.kt
│ │ ├── InstanceKeeperDispatcher.kt
│ │ ├── InstanceKeeperExt.kt
│ │ └── InstanceKeeperOwner.kt
│ └── commonTest/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── instancekeeper/
│ ├── DefaultInstanceKeeperDispatcherTest.kt
│ └── InstanceKeeperExtTest.kt
├── lifecycle/
│ ├── .gitignore
│ ├── api/
│ │ ├── android/
│ │ │ └── lifecycle.api
│ │ ├── jvm/
│ │ │ └── lifecycle.api
│ │ └── lifecycle.klib.api
│ ├── build.gradle.kts
│ └── src/
│ ├── androidMain/
│ │ ├── AndroidManifest.xml
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── lifecycle/
│ │ └── AndroidExt.kt
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── lifecycle/
│ │ ├── Lifecycle.kt
│ │ ├── LifecycleExt.kt
│ │ ├── LifecycleOwner.kt
│ │ ├── LifecycleRegistry.kt
│ │ ├── LifecycleRegistryExt.kt
│ │ └── LifecycleRegistryImpl.kt
│ ├── commonTest/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── lifecycle/
│ │ ├── LifecycleExtTest.kt
│ │ └── LifecycleRegistryTest.kt
│ ├── itvosMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── lifecycle/
│ │ └── ApplicationLifecycle.kt
│ └── itvosTest/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── lifecycle/
│ ├── ApplicationLifecyclePlatformTest.kt
│ └── ApplicationLifecycleTest.kt
├── lifecycle-coroutines/
│ ├── .gitignore
│ ├── api/
│ │ ├── android/
│ │ │ └── lifecycle-coroutines.api
│ │ ├── jvm/
│ │ │ └── lifecycle-coroutines.api
│ │ └── lifecycle-coroutines.klib.api
│ ├── build.gradle.kts
│ └── src/
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── lifecycle/
│ │ └── coroutines/
│ │ ├── CoroutineScopeWithLifecycle.kt
│ │ ├── DispatchersExt.kt
│ │ ├── FlowWithLifecycle.kt
│ │ └── RepeatOnLifecycle.kt
│ └── commonTest/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── lifecycle/
│ └── coroutines/
│ ├── CoroutineScopeWithLifecycleTest.kt
│ ├── DispatchersExtTest.kt
│ └── LifecycleCoroutinesExtTest.kt
├── lifecycle-reaktive/
│ ├── .gitignore
│ ├── api/
│ │ ├── android/
│ │ │ └── lifecycle-reaktive.api
│ │ ├── jvm/
│ │ │ └── lifecycle-reaktive.api
│ │ └── lifecycle-reaktive.klib.api
│ ├── build.gradle.kts
│ └── src/
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── lifecycle/
│ │ └── reaktive/
│ │ └── DisposableWithLifecycle.kt
│ └── commonTest/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── lifecycle/
│ └── reaktive/
│ └── DisposableWithLifecycleTest.kt
├── settings.gradle.kts
├── state-keeper/
│ ├── .gitignore
│ ├── api/
│ │ ├── android/
│ │ │ └── state-keeper.api
│ │ ├── jvm/
│ │ │ └── state-keeper.api
│ │ └── state-keeper.klib.api
│ ├── build.gradle.kts
│ └── src/
│ ├── androidMain/
│ │ ├── AndroidManifest.xml
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── statekeeper/
│ │ ├── AndroidExt.kt
│ │ ├── BundleExt.kt
│ │ └── PersistableBundleExt.kt
│ ├── androidUnitTest/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── statekeeper/
│ │ ├── AndroidStateKeeperTest.kt
│ │ ├── BundleExtTest.kt
│ │ └── TestUtils.android.kt
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── statekeeper/
│ │ ├── DefaultStateKeeperDispatcher.kt
│ │ ├── ExperimentalStateKeeperApi.kt
│ │ ├── PolymorphicSerializer.kt
│ │ ├── SerializableContainer.kt
│ │ ├── StateKeeper.kt
│ │ ├── StateKeeperDispatcher.kt
│ │ ├── StateKeeperExt.kt
│ │ ├── StateKeeperOwner.kt
│ │ ├── Utils.kt
│ │ └── base64/
│ │ ├── Decoder.kt
│ │ ├── Dictionaries.kt
│ │ ├── Encoder.kt
│ │ └── README.md
│ ├── commonTest/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── statekeeper/
│ │ ├── CodingTest.kt
│ │ ├── DefaultStateKeeperDispatcherTest.kt
│ │ ├── PolymorphicSerializerTest.kt
│ │ ├── SerializableContainerTest.kt
│ │ ├── SerializableData.kt
│ │ ├── StateKeeperExtTest.kt
│ │ ├── TestUtils.kt
│ │ └── base64/
│ │ ├── Base64ImplTest.kt
│ │ └── README.md
│ ├── javaMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── statekeeper/
│ │ └── Utils.java.kt
│ ├── jsTest/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── statekeeper/
│ │ └── DefaultStateKeeperDispatcherJsTest.kt
│ └── nonJavaMain/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── statekeeper/
│ └── Utils.kt
├── state-keeper-benchmarks/
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ └── res/
│ │ └── AndroidManifest.xml
│ └── test/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── statekeeper/
│ └── benchmarks/
│ └── Benchmarks.kt
├── tools/
│ └── check-publication/
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src/
│ ├── androidMain/
│ │ └── AndroidManifest.xml
│ └── commonMain/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── tools/
│ └── checkpublication/
│ └── Dummy.kt
└── utils-internal/
├── .gitignore
├── build.gradle.kts
└── src/
├── androidMain/
│ └── AndroidManifest.xml
└── commonMain/
└── kotlin/
└── com/
└── arkivanov/
└── essenty/
└── utils/
└── internal/
├── ExperimentalEssentyApi.kt
└── InternalEssentyApi.kt
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
root = true
[*]
insert_final_newline = true
[{*.kt, *.kts}]
max_line_length = 140
ij_kotlin_packages_to_use_import_on_demand = ^
ij_continuation_indent_size = 4
ij_kotlin_align_in_columns_case_branch = false
ij_kotlin_align_multiline_binary_operation = false
ij_kotlin_align_multiline_extends_list = false
ij_kotlin_align_multiline_method_parentheses = false
ij_kotlin_align_multiline_parameters = true
ij_kotlin_align_multiline_parameters_in_calls = false
ij_kotlin_allow_trailing_comma = false
ij_kotlin_allow_trailing_comma_on_call_site = false
ij_kotlin_assignment_wrap = normal
ij_kotlin_blank_lines_after_class_header = 0
ij_kotlin_blank_lines_around_block_when_branches = 0
ij_kotlin_blank_lines_before_declaration_with_comment_or_annotation_on_separate_line = 1
ij_kotlin_block_comment_at_first_column = true
ij_kotlin_call_parameters_new_line_after_left_paren = true
ij_kotlin_call_parameters_right_paren_on_new_line = true
ij_kotlin_call_parameters_wrap = on_every_item
ij_kotlin_catch_on_new_line = false
ij_kotlin_class_annotation_wrap = split_into_lines
ij_kotlin_code_style_defaults = KOTLIN_OFFICIAL
ij_kotlin_continuation_indent_for_chained_calls = false
ij_kotlin_continuation_indent_for_expression_bodies = false
ij_kotlin_continuation_indent_in_argument_lists = false
ij_kotlin_continuation_indent_in_elvis = false
ij_kotlin_continuation_indent_in_if_conditions = false
ij_kotlin_continuation_indent_in_parameter_lists = false
ij_kotlin_continuation_indent_in_supertype_lists = false
ij_kotlin_else_on_new_line = false
ij_kotlin_enum_constants_wrap = off
ij_kotlin_extends_list_wrap = normal
ij_kotlin_field_annotation_wrap = split_into_lines
ij_kotlin_finally_on_new_line = false
ij_kotlin_if_rparen_on_new_line = true
ij_kotlin_import_nested_classes = false
ij_kotlin_imports_layout = *, java.**, javax.**, kotlin.**, ^
ij_kotlin_insert_whitespaces_in_simple_one_line_method = true
ij_kotlin_keep_blank_lines_before_right_brace = 2
ij_kotlin_keep_blank_lines_in_code = 2
ij_kotlin_keep_blank_lines_in_declarations = 2
ij_kotlin_keep_first_column_comment = true
ij_kotlin_keep_indents_on_empty_lines = false
ij_kotlin_keep_line_breaks = true
ij_kotlin_lbrace_on_next_line = false
ij_kotlin_line_comment_add_space = false
ij_kotlin_line_comment_at_first_column = true
ij_kotlin_method_annotation_wrap = split_into_lines
ij_kotlin_method_call_chain_wrap = normal
ij_kotlin_method_parameters_new_line_after_left_paren = true
ij_kotlin_method_parameters_right_paren_on_new_line = true
ij_kotlin_method_parameters_wrap = on_every_item
ij_kotlin_name_count_to_use_star_import = 2147483647
ij_kotlin_name_count_to_use_star_import_for_members = 2147483647
ij_kotlin_parameter_annotation_wrap = off
ij_kotlin_space_after_comma = true
ij_kotlin_space_after_extend_colon = true
ij_kotlin_space_after_type_colon = true
ij_kotlin_space_before_catch_parentheses = true
ij_kotlin_space_before_comma = false
ij_kotlin_space_before_extend_colon = true
ij_kotlin_space_before_for_parentheses = true
ij_kotlin_space_before_if_parentheses = true
ij_kotlin_space_before_lambda_arrow = true
ij_kotlin_space_before_type_colon = false
ij_kotlin_space_before_when_parentheses = true
ij_kotlin_space_before_while_parentheses = true
ij_kotlin_spaces_around_additive_operators = true
ij_kotlin_spaces_around_assignment_operators = true
ij_kotlin_spaces_around_equality_operators = true
ij_kotlin_spaces_around_function_type_arrow = true
ij_kotlin_spaces_around_logical_operators = true
ij_kotlin_spaces_around_multiplicative_operators = true
ij_kotlin_spaces_around_range = false
ij_kotlin_spaces_around_relational_operators = true
ij_kotlin_spaces_around_unary_operator = false
ij_kotlin_spaces_around_when_arrow = true
ij_kotlin_variable_annotation_wrap = off
ij_kotlin_while_on_new_line = false
ij_kotlin_wrap_elvis_expressions = 1
ij_kotlin_wrap_expression_body_functions = 1
ij_kotlin_wrap_first_method_in_call_chain = false
================================================
FILE: .github/FUNDING.yml
================================================
# These are supported funding model platforms
github: arkivanov
custom: ["https://www.buymeacoffee.com/arkivanov"]
================================================
FILE: .github/workflows/build.yml
================================================
name: Build
on:
pull_request:
paths-ignore:
- 'docs/**'
jobs:
linux-build:
name: Build on Linux
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Java
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 17
- name: Update dependencies
run: sudo apt-get update
- name: Install dependencies
run: sudo apt-get install nodejs chromium-browser
- name: Build
uses: gradle/gradle-build-action@v2
with:
arguments: build -Dsplit_targets
macos-build:
name: Build on macOS
runs-on: macos-14
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Java
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 17
- name: Build project
uses: gradle/gradle-build-action@v2
with:
arguments: build -Dsplit_targets
================================================
FILE: .github/workflows/publish.yml
================================================
name: Publish
on:
workflow_dispatch:
jobs:
create-staging-repository:
runs-on: ubuntu-latest
name: Create staging repository
outputs:
repository_id: ${{ steps.create.outputs.repository_id }}
steps:
- id: create
uses: nexus-actions/create-nexus-staging-repo@v1.3.0
with:
username: arkivanov
password: ${{ secrets.SONATYPE_PASSWORD }}
staging_profile_id: ${{ secrets.SONATYPE_STAGING_PROFILE_ID }}
description: Created by GitHub Actions
base_url: https://s01.oss.sonatype.org/service/local/
publish:
name: Publish
runs-on: macos-14
needs: create-staging-repository
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Install Java
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 17
- name: Publish
env:
SONATYPE_REPOSITORY_ID: ${{ needs.create-staging-repository.outputs.repository_id }}
SONATYPE_USER_NAME: ${{ secrets.SONATYPE_USER_NAME }}
SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }}
SIGNING_KEY: ${{ secrets.SIGNING_KEY }}
SIGNING_PASSWORD: ${{ secrets.SIGNING_PASSWORD }}
run: ./gradlew publish
close-staging-repository:
name: Close staging repository
runs-on: ubuntu-latest
needs: [ create-staging-repository, publish ]
steps:
- name: Close staging repository
uses: nexus-actions/release-nexus-staging-repo@v1.1
with:
username: arkivanov
password: ${{ secrets.SONATYPE_PASSWORD }}
staging_repository_id: ${{ needs.create-staging-repository.outputs.repository_id }}
base_url: https://s01.oss.sonatype.org/service/local/
close_only: 'true'
check-publication:
name: Check publication
runs-on: macos-14
needs: close-staging-repository
steps:
- name: Checkout
uses: actions/checkout@v1
- name: Install Java
uses: actions/setup-java@v3
with:
distribution: 'zulu'
java-version: 17
- name: Check publication
run: ./gradlew kotlinUpgradeYarnLock :tools:check-publication:build -Pcheck_publication
================================================
FILE: .gitignore
================================================
*.iml
.gradle
local.properties
.idea
/build
.DS_Store
.kotlin
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
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
http://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.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: README.md
================================================
[](https://search.maven.org/search?q=g:com.arkivanov.essenty)
[](http://www.apache.org/licenses/LICENSE-2.0)
[](https://twitter.com/arkann1985)
# Essenty
The most essential libraries for Kotlin Multiplatform development.
Supported targets:
- `android`
- `jvm`
- `js`
- `wasmJs`
- `ios`
- `watchos`
- `tvos`
- `macos`
- `linuxX64`
## Lifecycle
When writing Kotlin Multiplatform (common) code we often need to handle lifecycle events of a screen. For example, to stop background operations when the screen is destroyed, or to reload some data when the screen is activated. Essenty provides the `Lifecycle` API to help with lifecycle handling in the common code. It is very similar to [Android Activity lifecycle](https://developer.android.com/guide/components/activities/activity-lifecycle).
### Setup
Groovy:
```groovy
// Add the dependency, typically under the commonMain source set
implementation "com.arkivanov.essenty:lifecycle:<essenty_version>"
```
Kotlin:
```kotlin
// Add the dependency, typically under the commonMain source set
implementation("com.arkivanov.essenty:lifecycle:<essenty_version>")
```
### Lifecycle state transitions
<img src="docs/media/LifecycleStates.png" width="512">
### Content
The main [Lifecycle](https://github.com/arkivanov/Essenty/blob/master/lifecycle/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/Lifecycle.kt) interface provides ability to observe the lifecycle state changes. There are also handy [extension functions](https://github.com/arkivanov/Essenty/blob/master/lifecycle/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/LifecycleExt.kt) for convenience.
The [LifecycleRegistry](https://github.com/arkivanov/Essenty/blob/master/lifecycle/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/LifecycleRegistry.kt) interface extends both the `Lifecycle` and the `Lifecycle.Callbacks` at the same time. It can be used to manually control the lifecycle, for example in tests. You can also find some useful [extension functions](https://github.com/arkivanov/Essenty/blob/master/lifecycle/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/LifecycleRegistryExt.kt).
The [LifecycleOwner](https://github.com/arkivanov/Essenty/blob/master/lifecycle/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/LifecycleOwner.kt) just holds the `Lifecyle`. It may be implemented by an arbitrary class, to provide convenient API.
#### Android extensions
From Android, the `Lifecycle` can be obtained by using special functions, can be found [here](https://github.com/arkivanov/Essenty/blob/master/lifecycle/src/androidMain/kotlin/com/arkivanov/essenty/lifecycle/AndroidExt.kt).
#### iOS and tvOS extensions
There is [ApplicationLifecycle](https://github.com/arkivanov/Essenty/blob/master/lifecycle/src/itvosMain/kotlin/com/arkivanov/essenty/lifecycle/ApplicationLifecycle.kt) available for `ios` and `tvos` targets. It follows the `UIApplication` lifecycle notifications.
> ⚠️ Since this implementation subscribes to `UIApplication` global lifecycle events, the instance and all its registered callbacks (and whatever they capture) will stay in memory until the application is destroyed or until `ApplicationLifecycle#destroy` method is called. It's ok to use it in a global scope like `UIApplicationDelegate`, but it may cause memory leaks when used in a narrower scope like `UIViewController` if it gets destroyed earlier. Use the `destroy` method to destroy the lifecycle manually and prevent memory leaks.
#### Reaktive extensions
There are some useful `Lifecycle` extensions for Reaktive.
- Automatic management of `Disposable` and `DisposableScope` by `Lifecycle`, can be found [here](https://github.com/arkivanov/Essenty/blob/master/lifecycle-reaktive/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/reaktive/DisposableWithLifecycle.kt).
#### Coroutines extensions
There are some useful `Lifecycle` extensions for Coroutines.
- Automatic management of `CoroutineScope` by `Lifecycle`, can be found [here](https://github.com/arkivanov/Essenty/blob/master/lifecycle-coroutines/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/coroutines/CoroutineScopeWithLifecycle.kt)
- `Flow.withLifecycle(Lifecycle): Flow` - can be found [here](https://github.com/arkivanov/Essenty/blob/master/lifecycle-coroutines/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/coroutines/FlowWithLifecycle.kt).
- `Lifecycle.repeatOnLifecycle(block)` - can be found [here](https://github.com/arkivanov/Essenty/blob/master/lifecycle-coroutines/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/coroutines/RepeatOnLifecycle.kt).
### Usage example
#### Observing the Lifecyle
The lifecycle can be observed using its `subscribe`/`unsubscribe` methods:
```kotlin
import com.arkivanov.essenty.lifecycle.Lifecycle
class SomeLogic(lifecycle: Lifecycle) {
init {
lifecycle.subscribe(
object : Lifecycle.Callbacks {
override fun onCreate() {
// Handle lifecycle created
}
// onStart, onResume, onPause, onStop are also available
override fun onDestroy() {
// Handle lifecycle destroyed
}
}
)
}
}
```
Or using the extension functions:
```kotlin
import com.arkivanov.essenty.lifecycle.Lifecycle
import com.arkivanov.essenty.lifecycle.doOnCreate
import com.arkivanov.essenty.lifecycle.doOnDestroy
import com.arkivanov.essenty.lifecycle.subscribe
class SomeLogic(lifecycle: Lifecycle) {
init {
lifecycle.subscribe(
onCreate = { /* Handle lifecycle created */ },
// onStart, onResume, onPause, onStop are also available
onDestroy = { /* Handle lifecycle destroyed */ }
)
lifecycle.doOnCreate {
// Handle lifecycle created
}
// doOnStart, doOnResume, doOnPause, doOnStop are also available
lifecycle.doOnDestroy {
// Handle lifecycle destroyed
}
}
}
```
#### Using the LifecycleRegistry manually
A default implementation of the `LifecycleRegisty` interface can be instantiated using the corresponding builder function:
```kotlin
import com.arkivanov.essenty.lifecycle.LifecycleRegistry
import com.arkivanov.essenty.lifecycle.resume
import com.arkivanov.essenty.lifecycle.destroy
val lifecycleRegistry = LifecycleRegistry()
val someLogic = SomeLogic(lifecycleRegistry)
lifecycleRegistry.resume()
// At some point later
lifecycleRegistry.destroy()
```
## StateKeeper
When writing common code targeting Android, it might be required to preserve some data over process death or Android configuration changes. For this purpose, Essenty provides the `StateKeeper` API, which is inspired by the AndroidX [SavedStateHandle](https://developer.android.com/reference/androidx/lifecycle/SavedStateHandle).
### Setup
Groovy:
```groovy
// Add the dependency, typically under the commonMain source set
implementation "com.arkivanov.essenty:state-keeper:<essenty_version>"
```
Kotlin:
```kotlin
// Add the dependency, typically under the commonMain source set
implementation("com.arkivanov.essenty:state-keeper:<essenty_version>")
```
### Content
The main [StateKeeper](https://github.com/arkivanov/Essenty/blob/master/state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/StateKeeper.kt) interface provides ability to register/unregister state suppliers, and also to consume any previously saved state. You can also find some handy [extension functions](https://github.com/arkivanov/Essenty/blob/master/state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/StateKeeperExt.kt). You can also find some handy [extension functions](https://github.com/arkivanov/Essenty/blob/master/state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/StateKeeperExt.kt).
The [StateKeeperDispatcher](https://github.com/arkivanov/Essenty/blob/master/state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/StateKeeperDispatcher.kt) interface extends `StateKeeper` and allows state saving, by calling all registered state providers.
The [StateKeeperOwner](https://github.com/arkivanov/Essenty/blob/master/state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/StateKeeperOwner.kt) interface is just a holder of `StateKeeper`. It may be implemented by an arbitrary class, to provide convenient API.
#### Android extensions
From Android side, `StateKeeper` can be obtained by using special functions, can be found [here](https://github.com/arkivanov/Essenty/blob/master/state-keeper/src/androidMain/kotlin/com/arkivanov/essenty/statekeeper/AndroidExt.kt).
There are also some handy [extension functions](https://github.com/arkivanov/Essenty/blob/master/state-keeper/src/androidMain/kotlin/com/arkivanov/essenty/statekeeper/BundleExt.kt) for serializing/deserializing `KSerializable` objects to/from [Bundle](https://developer.android.com/reference/android/os/Bundle):
- `fun <T : Any> Bundle.putSerializable(key: String?, value: T?, strategy: SerializationStrategy<T>)`
- `fun <T : Any> Bundle.getSerializable(key: String?, strategy: DeserializationStrategy<T>): T?`
- `fun Bundle.putSerializableContainer(key: String?, value: SerializableContainer?)`
- `fun Bundle.getSerializableContainer(key: String?): SerializableContainer?`
Similar extensions are also available for [PersistableBundle](https://developer.android.com/reference/android/os/PersistableBundle).
### Usage example
#### Using StateKeeper
> ⚠️ Make sure you [setup](https://github.com/Kotlin/kotlinx.serialization#setup) `kotlinx-serialization` properly.
```kotlin
import com.arkivanov.essenty.statekeeper.StateKeeper
import kotlinx.serialization.Serializable
class SomeLogic(stateKeeper: StateKeeper) {
// Use the saved State if any, otherwise create a new State
private var state: State = stateKeeper.consume(key = "SAVED_STATE", strategy = State.serializer()) ?: State()
init {
// Register the State supplier
stateKeeper.register(key = "SAVED_STATE", strategy = State.serializer()) { state }
}
@Serializable
private class State(
val someValue: Int = 0
)
}
```
#### Saveable properties (experimental since version `2.2.0-alpha01`)
```kotlin
import com.arkivanov.essenty.statekeeper.StateKeeper
import com.arkivanov.essenty.statekeeper.saveable
import kotlinx.serialization.Serializable
class SomeLogic(stateKeeper: StateKeeper) {
private var state: State by stateKeeper.saveable(serializer = State.serializer(), init = ::State)
@Serializable
private class State(val someValue: Int = 0)
}
```
#### Saveable state holders (experimental since version `2.2.0-alpha01`)
```kotlin
import com.arkivanov.essenty.statekeeper.StateKeeper
import com.arkivanov.essenty.statekeeper.saveable
import kotlinx.serialization.Serializable
class SomeLogic(stateKeeper: StateKeeper) {
private val viewModel by stateKeeper.saveable(serializer = State.serializer(), state = ViewModel::state) { savedState ->
ViewModel(state = savedState ?: State())
}
private class ViewModel(var state: State)
@Serializable
private class State(val someValue: Int = 0)
}
```
##### Polymorphic serialization (experimental)
Sometimes it might be necessary to serialize an interface or an abstract class that you don't own but have implemented. For this purpose Essenty provides `polymorphicSerializer` function that can be used to create custom polymorphic serializers for unowned base types.
For example a third-party library may have the following interface.
```kotlin
interface Filter {
// Omitted code
}
```
Then we can have multiple implementations of `Filter`.
```kotlin
@Serializable
class TextFilter(val text: String) : Filter { /* Omitted code */ }
@Serializable
class RatingFilter(val stars: Int) : Filter { /* Omitted code */ }
```
Now we can create a polymorphic serializer for `Filter` as follows. It can be used to save and restore `Filter` directly via StateKeeper, or to have `Filter` as part of another `Serializable` class.
```kotlin
import com.arkivanov.essenty.statekeeper.polymorphicSerializer
import com.slack.circuit.runtime.screen.Screen
import kotlinx.serialization.KSerializer
import kotlinx.serialization.Serializable
import kotlinx.serialization.modules.SerializersModule
import kotlinx.serialization.modules.polymorphic
object FilterSerializer : KSerializer<Filter> by polymorphicSerializer(
SerializersModule {
polymorphic(Filter::class) {
subclass(TextFilter::class, TextFilter.serializer())
subclass(RatingFilter::class, RatingFilter.serializer())
}
}
)
```
#### Using the StateKeeperDispatcher manually
On Android, the `StateKeeper` obtained via one of the extensions described above automatically saves and restores the state. On other platforms (if needed) the state can be saved and restored manually. A default implementation of `StateKeeperDisptacher` interface can be instantiated using the corresponding builder function. The state can be encoded as a JSON string and saved using the corresponding platform-specific API.
```kotlin
import com.arkivanov.essenty.statekeeper.SerializableContainer
import com.arkivanov.essenty.statekeeper.StateKeeper
import com.arkivanov.essenty.statekeeper.StateKeeperDispatcher
val stateKeeperDispatcher = StateKeeperDispatcher(/*Previously saved state, or null*/)
val someLogic = SomeLogic(stateKeeperDispatcher)
// At some point later when it's time to save the state
val savedState: SerializableContainer = stateKeeperDispatcher.save()
// The returned SerializableContainer can now be saved using the corresponding platform-specific API
```
## InstanceKeeper
When writing common code targetting Android, it might be required to retain objects over Android configuration changes. This use case is covered by the `InstanceKeeper` API, which is similar to the AndroidX [ViewModel](https://developer.android.com/topic/libraries/architecture/viewmodel).
### Setup
Groovy:
```groovy
// Add the dependency, typically under the commonMain source set
implementation "com.arkivanov.essenty:instance-keeper:<essenty_version>"
```
Kotlin:
```kotlin
// Add the dependency, typically under the commonMain source set
implementation("com.arkivanov.essenty:instance-keeper:<essenty_version>")
```
### Content
The main [InstanceKeeper](https://github.com/arkivanov/Essenty/blob/master/instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeper.kt) interface is responsible for storing object instances, represented by the [InstanceKeeper.Instance] interface. Instances of the `InstanceKeeper.Instance` interface survive Android Configuration changes, the `InstanceKeeper.Instance.onDestroy()` method is called when `InstanceKeeper` goes out of scope (e.g. the screen is finished). You can also find some handy [extension functions](https://github.com/arkivanov/Essenty/blob/master/instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeperExt.kt).
The [InstanceKeeperDispatcher](https://github.com/arkivanov/Essenty/blob/master/instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeperDispatcher.kt) interface extends `InstanceKeeper` and adds ability to destroy all registered instances.
The [InstanceKeeperOwner](https://github.com/arkivanov/Essenty/blob/master/instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeperOwner.kt) interface is just a holder of `InstanceKeeper`. It may be implemented by an arbitrary class, to provide convenient API.
#### Android extensions
From Android side, `InstanceKeeper` can be obtained by using special functions, can be found [here](https://github.com/arkivanov/Essenty/blob/master/instance-keeper/src/androidMain/kotlin/com/arkivanov/essenty/instancekeeper/AndroidExt.kt).
### Usage example
#### Using the InstanceKeeper
```kotlin
import com.arkivanov.essenty.instancekeeper.InstanceKeeper
import com.arkivanov.essenty.instancekeeper.getOrCreate
class SomeLogic(instanceKeeper: InstanceKeeper) {
// Get the existing instance or create a new one
private val viewModel = instanceKeeper.getOrCreate { ViewModel() }
}
/*
* Survives Android configuration changes.
* ⚠️ Pay attention to not leak any dependencies.
*/
class ViewModel : InstanceKeeper.Instance {
override fun onDestroy() {
// Called when the screen is finished
}
}
```
##### Alternative way (experimental since version 2.2.0-alpha01, stable since 2.2.0)
```kotlin
class SomeLogic(instanceKeeperOwner: InstanceKeeperOwner) : InstanceKeeperOwner by instanceKeeperOwner {
// Get the existing instance or create a new one
private val viewModel = retainedInstance { ViewModel() }
}
```
#### Using the InstanceKeeperDispatcher manually
A default implementation of the `InstanceKeeperDispatcher` interface can be instantiated using the corresponding builder function:
```kotlin
import com.arkivanov.essenty.instancekeeper.InstanceKeeper
import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher
// Create a new instance of InstanceKeeperDispatcher, or reuse an existing one
val instanceKeeperDispatcher = InstanceKeeperDispatcher()
val someLogic = SomeLogic(instanceKeeperDispatcher)
// At some point later
instanceKeeperDispatcher.destroy()
```
## BackHandler
The `BackHandler` API provides ability to handle back button clicks (e.g. the Android device's back button), in common code. This API is similar to AndroidX [OnBackPressedDispatcher](https://developer.android.com/reference/androidx/activity/OnBackPressedDispatcher).
### Setup
Groovy:
```groovy
// Add the dependency, typically under the commonMain source set
implementation "com.arkivanov.essenty:back-handler:<essenty_version>"
```
Kotlin:
```kotlin
// Add the dependency, typically under the commonMain source set
implementation("com.arkivanov.essenty:back-handler:<essenty_version>")
```
### Content
The [BackHandler](https://github.com/arkivanov/Essenty/blob/master/back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackHandler.kt) interface provides ability to register and unregister back button callbacks. When the device's back button is pressed, all registered callbacks are called in reverse order, the first enabled callback is called and the iteration finishes.
> Starting from `v1.2.x`, when the device's back button is pressed, all registered callbacks are sorted in ascending order first by priority and then by index, the last enabled callback is called.
[BackCallback](https://github.com/arkivanov/Essenty/blob/master/back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackCallback.kt) allows handling back events, including predictive back gestures.
The [BackDispatcher](https://github.com/arkivanov/Essenty/blob/master/back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackDispatcher.kt) interface extends `BackHandler` and is responsible for triggering the registered callbacks. The `BackDispatcher.back()` method triggers all registered callbacks in reverse order, and returns `true` if an enabled callback was called, and `false` if no enabled callback was found.
#### Android extensions
From Android side, `BackHandler` can be obtained by using special functions, can be found [here](https://github.com/arkivanov/Essenty/blob/master/back-handler/src/androidMain/kotlin/com/arkivanov/essenty/backhandler/AndroidBackHandler.kt).
### Predictive Back Gesture
Both `BackHandler` and `BackDispatcher` bring the new [Android Predictive Back Gesture](https://developer.android.com/guide/navigation/custom-back/predictive-back-gesture) to Kotlin Multiplatform.
#### Predictive Back Gesture on Android
On Android, the predictive back gesture only works starting with Android T. On Android T, it works only between Activities, if enabled in the system settings. Starting with Android U, the predictive back gesture also works between application's screens inside an Activity. In the latter case, back gesture events can be handled using `BackCallback`.
#### Predictive Back Gesture on other platforms
On all other platforms, predictive back gestures can be dispatched manually via `BackDispatcher`. This can be done e.g. by adding an overlay on top of the UI and handling touch events manually.
### Usage example
#### Using the BackHandler
```kotlin
import com.arkivanov.essenty.backhandler.BackHandler
class SomeLogic(backHandler: BackHandler) {
private val callback = BackCallback {
// Called when the back button is pressed
}
init {
backHandler.register(callback)
// Disable the callback when needed
callback.isEnabled = false
}
}
```
#### Using the BackDispatcher manually
A default implementation of the `BackDispatcher` interface can be instantiated using the corresponding builder function:
```kotlin
import com.arkivanov.essenty.backhandler.BackDispatcher
val backDispatcher = BackDispatcher()
val someLogic = SomeLogic(backDispatcher)
if (!backDispatcher.back()) {
// The back pressed event was not handled
}
```
## Author
Twitter: [@arkann1985](https://twitter.com/arkann1985)
If you like this project you can always <a href="https://www.buymeacoffee.com/arkivanov" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-blue.png" alt="Buy Me A Coffee" height=32></a> ;-)
================================================
FILE: back-handler/.gitignore
================================================
/build
================================================
FILE: back-handler/api/android/back-handler.api
================================================
public final class com/arkivanov/essenty/backhandler/AndroidBackHandlerKt {
public static final fun BackHandler (Landroidx/activity/OnBackPressedDispatcher;)Lcom/arkivanov/essenty/backhandler/BackHandler;
public static final fun BackHandler (Landroidx/activity/OnBackPressedDispatcher;Landroidx/lifecycle/LifecycleOwner;)Lcom/arkivanov/essenty/backhandler/BackHandler;
public static final fun backHandler (Landroidx/activity/OnBackPressedDispatcherOwner;)Lcom/arkivanov/essenty/backhandler/BackHandler;
public static final fun connectOnBackPressedCallback (Lcom/arkivanov/essenty/backhandler/BackDispatcher;)Landroidx/activity/OnBackPressedCallback;
}
public abstract class com/arkivanov/essenty/backhandler/BackCallback {
public static final field Companion Lcom/arkivanov/essenty/backhandler/BackCallback$Companion;
public static final field PRIORITY_DEFAULT I
public static final field PRIORITY_MAX I
public static final field PRIORITY_MIN I
public fun <init> ()V
public fun <init> (ZI)V
public synthetic fun <init> (ZIILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun addEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V
public final fun getPriority ()I
public final fun isEnabled ()Z
public abstract fun onBack ()V
public fun onBackCancelled ()V
public fun onBackProgressed (Lcom/arkivanov/essenty/backhandler/BackEvent;)V
public fun onBackStarted (Lcom/arkivanov/essenty/backhandler/BackEvent;)V
public final fun removeEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V
public final fun setEnabled (Z)V
public final fun setPriority (I)V
}
public final class com/arkivanov/essenty/backhandler/BackCallback$Companion {
}
public final class com/arkivanov/essenty/backhandler/BackCallbackKt {
public static final fun BackCallback (ZILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)Lcom/arkivanov/essenty/backhandler/BackCallback;
public static synthetic fun BackCallback$default (ZILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/arkivanov/essenty/backhandler/BackCallback;
}
public abstract interface class com/arkivanov/essenty/backhandler/BackDispatcher : com/arkivanov/essenty/backhandler/BackHandler {
public abstract fun addEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V
public abstract fun back ()Z
public abstract fun cancelPredictiveBack ()V
public abstract fun isEnabled ()Z
public abstract fun progressPredictiveBack (Lcom/arkivanov/essenty/backhandler/BackEvent;)V
public abstract fun removeEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V
public abstract fun startPredictiveBack (Lcom/arkivanov/essenty/backhandler/BackEvent;)Z
}
public final class com/arkivanov/essenty/backhandler/BackDispatcherKt {
public static final fun BackDispatcher ()Lcom/arkivanov/essenty/backhandler/BackDispatcher;
}
public final class com/arkivanov/essenty/backhandler/BackEvent {
public fun <init> ()V
public fun <init> (FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FF)V
public synthetic fun <init> (FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FFILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()F
public final fun component2 ()Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public final fun component3 ()F
public final fun component4 ()F
public final fun copy (FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FF)Lcom/arkivanov/essenty/backhandler/BackEvent;
public static synthetic fun copy$default (Lcom/arkivanov/essenty/backhandler/BackEvent;FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FFILjava/lang/Object;)Lcom/arkivanov/essenty/backhandler/BackEvent;
public fun equals (Ljava/lang/Object;)Z
public final fun getProgress ()F
public final fun getSwipeEdge ()Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public final fun getTouchX ()F
public final fun getTouchY ()F
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class com/arkivanov/essenty/backhandler/BackEvent$SwipeEdge : java/lang/Enum {
public static final field LEFT Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public static final field RIGHT Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public static final field UNKNOWN Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public static fun values ()[Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
}
public abstract interface class com/arkivanov/essenty/backhandler/BackHandler {
public abstract fun isRegistered (Lcom/arkivanov/essenty/backhandler/BackCallback;)Z
public abstract fun register (Lcom/arkivanov/essenty/backhandler/BackCallback;)V
public abstract fun unregister (Lcom/arkivanov/essenty/backhandler/BackCallback;)V
}
public abstract interface class com/arkivanov/essenty/backhandler/BackHandlerOwner {
public abstract fun getBackHandler ()Lcom/arkivanov/essenty/backhandler/BackHandler;
}
================================================
FILE: back-handler/api/back-handler.klib.api
================================================
// Klib ABI Dump
// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
// - Show declarations: true
// Library unique name: <com.arkivanov.essenty:back-handler>
abstract interface com.arkivanov.essenty.backhandler/BackDispatcher : com.arkivanov.essenty.backhandler/BackHandler { // com.arkivanov.essenty.backhandler/BackDispatcher|null[0]
abstract val isEnabled // com.arkivanov.essenty.backhandler/BackDispatcher.isEnabled|{}isEnabled[0]
abstract fun <get-isEnabled>(): kotlin/Boolean // com.arkivanov.essenty.backhandler/BackDispatcher.isEnabled.<get-isEnabled>|<get-isEnabled>(){}[0]
abstract fun addEnabledChangedListener(kotlin/Function1<kotlin/Boolean, kotlin/Unit>) // com.arkivanov.essenty.backhandler/BackDispatcher.addEnabledChangedListener|addEnabledChangedListener(kotlin.Function1<kotlin.Boolean,kotlin.Unit>){}[0]
abstract fun back(): kotlin/Boolean // com.arkivanov.essenty.backhandler/BackDispatcher.back|back(){}[0]
abstract fun cancelPredictiveBack() // com.arkivanov.essenty.backhandler/BackDispatcher.cancelPredictiveBack|cancelPredictiveBack(){}[0]
abstract fun progressPredictiveBack(com.arkivanov.essenty.backhandler/BackEvent) // com.arkivanov.essenty.backhandler/BackDispatcher.progressPredictiveBack|progressPredictiveBack(com.arkivanov.essenty.backhandler.BackEvent){}[0]
abstract fun removeEnabledChangedListener(kotlin/Function1<kotlin/Boolean, kotlin/Unit>) // com.arkivanov.essenty.backhandler/BackDispatcher.removeEnabledChangedListener|removeEnabledChangedListener(kotlin.Function1<kotlin.Boolean,kotlin.Unit>){}[0]
abstract fun startPredictiveBack(com.arkivanov.essenty.backhandler/BackEvent): kotlin/Boolean // com.arkivanov.essenty.backhandler/BackDispatcher.startPredictiveBack|startPredictiveBack(com.arkivanov.essenty.backhandler.BackEvent){}[0]
}
abstract interface com.arkivanov.essenty.backhandler/BackHandler { // com.arkivanov.essenty.backhandler/BackHandler|null[0]
abstract fun isRegistered(com.arkivanov.essenty.backhandler/BackCallback): kotlin/Boolean // com.arkivanov.essenty.backhandler/BackHandler.isRegistered|isRegistered(com.arkivanov.essenty.backhandler.BackCallback){}[0]
abstract fun register(com.arkivanov.essenty.backhandler/BackCallback) // com.arkivanov.essenty.backhandler/BackHandler.register|register(com.arkivanov.essenty.backhandler.BackCallback){}[0]
abstract fun unregister(com.arkivanov.essenty.backhandler/BackCallback) // com.arkivanov.essenty.backhandler/BackHandler.unregister|unregister(com.arkivanov.essenty.backhandler.BackCallback){}[0]
}
abstract interface com.arkivanov.essenty.backhandler/BackHandlerOwner { // com.arkivanov.essenty.backhandler/BackHandlerOwner|null[0]
abstract val backHandler // com.arkivanov.essenty.backhandler/BackHandlerOwner.backHandler|{}backHandler[0]
abstract fun <get-backHandler>(): com.arkivanov.essenty.backhandler/BackHandler // com.arkivanov.essenty.backhandler/BackHandlerOwner.backHandler.<get-backHandler>|<get-backHandler>(){}[0]
}
abstract class com.arkivanov.essenty.backhandler/BackCallback { // com.arkivanov.essenty.backhandler/BackCallback|null[0]
constructor <init>(kotlin/Boolean = ..., kotlin/Int = ...) // com.arkivanov.essenty.backhandler/BackCallback.<init>|<init>(kotlin.Boolean;kotlin.Int){}[0]
final var isEnabled // com.arkivanov.essenty.backhandler/BackCallback.isEnabled|{}isEnabled[0]
final fun <get-isEnabled>(): kotlin/Boolean // com.arkivanov.essenty.backhandler/BackCallback.isEnabled.<get-isEnabled>|<get-isEnabled>(){}[0]
final fun <set-isEnabled>(kotlin/Boolean) // com.arkivanov.essenty.backhandler/BackCallback.isEnabled.<set-isEnabled>|<set-isEnabled>(kotlin.Boolean){}[0]
final var priority // com.arkivanov.essenty.backhandler/BackCallback.priority|{}priority[0]
final fun <get-priority>(): kotlin/Int // com.arkivanov.essenty.backhandler/BackCallback.priority.<get-priority>|<get-priority>(){}[0]
final fun <set-priority>(kotlin/Int) // com.arkivanov.essenty.backhandler/BackCallback.priority.<set-priority>|<set-priority>(kotlin.Int){}[0]
abstract fun onBack() // com.arkivanov.essenty.backhandler/BackCallback.onBack|onBack(){}[0]
final fun addEnabledChangedListener(kotlin/Function1<kotlin/Boolean, kotlin/Unit>) // com.arkivanov.essenty.backhandler/BackCallback.addEnabledChangedListener|addEnabledChangedListener(kotlin.Function1<kotlin.Boolean,kotlin.Unit>){}[0]
final fun removeEnabledChangedListener(kotlin/Function1<kotlin/Boolean, kotlin/Unit>) // com.arkivanov.essenty.backhandler/BackCallback.removeEnabledChangedListener|removeEnabledChangedListener(kotlin.Function1<kotlin.Boolean,kotlin.Unit>){}[0]
open fun onBackCancelled() // com.arkivanov.essenty.backhandler/BackCallback.onBackCancelled|onBackCancelled(){}[0]
open fun onBackProgressed(com.arkivanov.essenty.backhandler/BackEvent) // com.arkivanov.essenty.backhandler/BackCallback.onBackProgressed|onBackProgressed(com.arkivanov.essenty.backhandler.BackEvent){}[0]
open fun onBackStarted(com.arkivanov.essenty.backhandler/BackEvent) // com.arkivanov.essenty.backhandler/BackCallback.onBackStarted|onBackStarted(com.arkivanov.essenty.backhandler.BackEvent){}[0]
final object Companion { // com.arkivanov.essenty.backhandler/BackCallback.Companion|null[0]
final const val PRIORITY_DEFAULT // com.arkivanov.essenty.backhandler/BackCallback.Companion.PRIORITY_DEFAULT|{}PRIORITY_DEFAULT[0]
final fun <get-PRIORITY_DEFAULT>(): kotlin/Int // com.arkivanov.essenty.backhandler/BackCallback.Companion.PRIORITY_DEFAULT.<get-PRIORITY_DEFAULT>|<get-PRIORITY_DEFAULT>(){}[0]
final const val PRIORITY_MAX // com.arkivanov.essenty.backhandler/BackCallback.Companion.PRIORITY_MAX|{}PRIORITY_MAX[0]
final fun <get-PRIORITY_MAX>(): kotlin/Int // com.arkivanov.essenty.backhandler/BackCallback.Companion.PRIORITY_MAX.<get-PRIORITY_MAX>|<get-PRIORITY_MAX>(){}[0]
final const val PRIORITY_MIN // com.arkivanov.essenty.backhandler/BackCallback.Companion.PRIORITY_MIN|{}PRIORITY_MIN[0]
final fun <get-PRIORITY_MIN>(): kotlin/Int // com.arkivanov.essenty.backhandler/BackCallback.Companion.PRIORITY_MIN.<get-PRIORITY_MIN>|<get-PRIORITY_MIN>(){}[0]
}
}
final class com.arkivanov.essenty.backhandler/BackEvent { // com.arkivanov.essenty.backhandler/BackEvent|null[0]
constructor <init>(kotlin/Float = ..., com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge = ..., kotlin/Float = ..., kotlin/Float = ...) // com.arkivanov.essenty.backhandler/BackEvent.<init>|<init>(kotlin.Float;com.arkivanov.essenty.backhandler.BackEvent.SwipeEdge;kotlin.Float;kotlin.Float){}[0]
final val progress // com.arkivanov.essenty.backhandler/BackEvent.progress|{}progress[0]
final fun <get-progress>(): kotlin/Float // com.arkivanov.essenty.backhandler/BackEvent.progress.<get-progress>|<get-progress>(){}[0]
final val swipeEdge // com.arkivanov.essenty.backhandler/BackEvent.swipeEdge|{}swipeEdge[0]
final fun <get-swipeEdge>(): com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge // com.arkivanov.essenty.backhandler/BackEvent.swipeEdge.<get-swipeEdge>|<get-swipeEdge>(){}[0]
final val touchX // com.arkivanov.essenty.backhandler/BackEvent.touchX|{}touchX[0]
final fun <get-touchX>(): kotlin/Float // com.arkivanov.essenty.backhandler/BackEvent.touchX.<get-touchX>|<get-touchX>(){}[0]
final val touchY // com.arkivanov.essenty.backhandler/BackEvent.touchY|{}touchY[0]
final fun <get-touchY>(): kotlin/Float // com.arkivanov.essenty.backhandler/BackEvent.touchY.<get-touchY>|<get-touchY>(){}[0]
final fun component1(): kotlin/Float // com.arkivanov.essenty.backhandler/BackEvent.component1|component1(){}[0]
final fun component2(): com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge // com.arkivanov.essenty.backhandler/BackEvent.component2|component2(){}[0]
final fun component3(): kotlin/Float // com.arkivanov.essenty.backhandler/BackEvent.component3|component3(){}[0]
final fun component4(): kotlin/Float // com.arkivanov.essenty.backhandler/BackEvent.component4|component4(){}[0]
final fun copy(kotlin/Float = ..., com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge = ..., kotlin/Float = ..., kotlin/Float = ...): com.arkivanov.essenty.backhandler/BackEvent // com.arkivanov.essenty.backhandler/BackEvent.copy|copy(kotlin.Float;com.arkivanov.essenty.backhandler.BackEvent.SwipeEdge;kotlin.Float;kotlin.Float){}[0]
final fun equals(kotlin/Any?): kotlin/Boolean // com.arkivanov.essenty.backhandler/BackEvent.equals|equals(kotlin.Any?){}[0]
final fun hashCode(): kotlin/Int // com.arkivanov.essenty.backhandler/BackEvent.hashCode|hashCode(){}[0]
final fun toString(): kotlin/String // com.arkivanov.essenty.backhandler/BackEvent.toString|toString(){}[0]
final enum class SwipeEdge : kotlin/Enum<com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge> { // com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge|null[0]
enum entry LEFT // com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge.LEFT|null[0]
enum entry RIGHT // com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge.RIGHT|null[0]
enum entry UNKNOWN // com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge.UNKNOWN|null[0]
final val entries // com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge.entries|#static{}entries[0]
final fun <get-entries>(): kotlin.enums/EnumEntries<com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge> // com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge.entries.<get-entries>|<get-entries>#static(){}[0]
final fun valueOf(kotlin/String): com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge // com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge.valueOf|valueOf#static(kotlin.String){}[0]
final fun values(): kotlin/Array<com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge> // com.arkivanov.essenty.backhandler/BackEvent.SwipeEdge.values|values#static(){}[0]
}
}
final fun com.arkivanov.essenty.backhandler/BackCallback(kotlin/Boolean = ..., kotlin/Int = ..., kotlin/Function1<com.arkivanov.essenty.backhandler/BackEvent, kotlin/Unit>? = ..., kotlin/Function1<com.arkivanov.essenty.backhandler/BackEvent, kotlin/Unit>? = ..., kotlin/Function0<kotlin/Unit>? = ..., kotlin/Function0<kotlin/Unit>): com.arkivanov.essenty.backhandler/BackCallback // com.arkivanov.essenty.backhandler/BackCallback|BackCallback(kotlin.Boolean;kotlin.Int;kotlin.Function1<com.arkivanov.essenty.backhandler.BackEvent,kotlin.Unit>?;kotlin.Function1<com.arkivanov.essenty.backhandler.BackEvent,kotlin.Unit>?;kotlin.Function0<kotlin.Unit>?;kotlin.Function0<kotlin.Unit>){}[0]
final fun com.arkivanov.essenty.backhandler/BackDispatcher(): com.arkivanov.essenty.backhandler/BackDispatcher // com.arkivanov.essenty.backhandler/BackDispatcher|BackDispatcher(){}[0]
================================================
FILE: back-handler/api/jvm/back-handler.api
================================================
public abstract class com/arkivanov/essenty/backhandler/BackCallback {
public static final field Companion Lcom/arkivanov/essenty/backhandler/BackCallback$Companion;
public static final field PRIORITY_DEFAULT I
public static final field PRIORITY_MAX I
public static final field PRIORITY_MIN I
public fun <init> ()V
public fun <init> (ZI)V
public synthetic fun <init> (ZIILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun addEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V
public final fun getPriority ()I
public final fun isEnabled ()Z
public abstract fun onBack ()V
public fun onBackCancelled ()V
public fun onBackProgressed (Lcom/arkivanov/essenty/backhandler/BackEvent;)V
public fun onBackStarted (Lcom/arkivanov/essenty/backhandler/BackEvent;)V
public final fun removeEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V
public final fun setEnabled (Z)V
public final fun setPriority (I)V
}
public final class com/arkivanov/essenty/backhandler/BackCallback$Companion {
}
public final class com/arkivanov/essenty/backhandler/BackCallbackKt {
public static final fun BackCallback (ZILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;)Lcom/arkivanov/essenty/backhandler/BackCallback;
public static synthetic fun BackCallback$default (ZILkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lcom/arkivanov/essenty/backhandler/BackCallback;
}
public abstract interface class com/arkivanov/essenty/backhandler/BackDispatcher : com/arkivanov/essenty/backhandler/BackHandler {
public abstract fun addEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V
public abstract fun back ()Z
public abstract fun cancelPredictiveBack ()V
public abstract fun isEnabled ()Z
public abstract fun progressPredictiveBack (Lcom/arkivanov/essenty/backhandler/BackEvent;)V
public abstract fun removeEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V
public abstract fun startPredictiveBack (Lcom/arkivanov/essenty/backhandler/BackEvent;)Z
}
public final class com/arkivanov/essenty/backhandler/BackDispatcherKt {
public static final fun BackDispatcher ()Lcom/arkivanov/essenty/backhandler/BackDispatcher;
}
public final class com/arkivanov/essenty/backhandler/BackEvent {
public fun <init> ()V
public fun <init> (FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FF)V
public synthetic fun <init> (FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FFILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()F
public final fun component2 ()Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public final fun component3 ()F
public final fun component4 ()F
public final fun copy (FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FF)Lcom/arkivanov/essenty/backhandler/BackEvent;
public static synthetic fun copy$default (Lcom/arkivanov/essenty/backhandler/BackEvent;FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FFILjava/lang/Object;)Lcom/arkivanov/essenty/backhandler/BackEvent;
public fun equals (Ljava/lang/Object;)Z
public final fun getProgress ()F
public final fun getSwipeEdge ()Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public final fun getTouchX ()F
public final fun getTouchY ()F
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class com/arkivanov/essenty/backhandler/BackEvent$SwipeEdge : java/lang/Enum {
public static final field LEFT Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public static final field RIGHT Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public static final field UNKNOWN Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public static fun values ()[Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
}
public abstract interface class com/arkivanov/essenty/backhandler/BackHandler {
public abstract fun isRegistered (Lcom/arkivanov/essenty/backhandler/BackCallback;)Z
public abstract fun register (Lcom/arkivanov/essenty/backhandler/BackCallback;)V
public abstract fun unregister (Lcom/arkivanov/essenty/backhandler/BackCallback;)V
}
public abstract interface class com/arkivanov/essenty/backhandler/BackHandlerOwner {
public abstract fun getBackHandler ()Lcom/arkivanov/essenty/backhandler/BackHandler;
}
================================================
FILE: back-handler/build.gradle.kts
================================================
import com.arkivanov.gradle.bundle
import com.arkivanov.gradle.setupBinaryCompatibilityValidator
import com.arkivanov.gradle.setupMultiplatform
import com.arkivanov.gradle.setupPublication
import com.arkivanov.gradle.setupSourceSets
plugins {
id("kotlin-multiplatform")
id("com.android.library")
id("com.arkivanov.gradle.setup")
}
setupMultiplatform()
setupPublication()
setupBinaryCompatibilityValidator()
android {
namespace = "com.arkivanov.essenty.backhandler"
}
kotlin {
setupSourceSets {
val android by bundle()
common.main.dependencies {
implementation(project(":utils-internal"))
}
android.main.dependencies {
implementation(deps.androidx.activity.activityKtx)
}
}
}
================================================
FILE: back-handler/src/androidMain/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest/>
================================================
FILE: back-handler/src/androidMain/kotlin/com/arkivanov/essenty/backhandler/AndroidBackHandler.kt
================================================
package com.arkivanov.essenty.backhandler
import androidx.activity.BackEventCompat
import androidx.activity.OnBackPressedCallback
import androidx.activity.OnBackPressedDispatcher
import androidx.activity.OnBackPressedDispatcherOwner
import androidx.lifecycle.LifecycleOwner
/**
* Creates a new instance of [BackHandler] and attaches it to the provided AndroidX [OnBackPressedDispatcher].
*/
fun BackHandler(onBackPressedDispatcher: OnBackPressedDispatcher): BackHandler =
BackDispatcher().also { dispatcher ->
onBackPressedDispatcher.addCallback(dispatcher.connectOnBackPressedCallback())
}
/**
* Creates a new instance of [BackHandler] and attaches it to the provided AndroidX [OnBackPressedDispatcher]
* only when the [LifecycleOwner]'s Lifecycle is [STARTED][androidx.lifecycle.Lifecycle.State.STARTED].
*/
fun BackHandler(
onBackPressedDispatcher: OnBackPressedDispatcher,
lifecycleOwner: LifecycleOwner,
): BackHandler =
BackDispatcher().also { dispatcher ->
onBackPressedDispatcher.addCallback(lifecycleOwner, dispatcher.connectOnBackPressedCallback())
}
/**
* Creates a new instance of [BackHandler] and attaches it to the AndroidX [OnBackPressedDispatcher].
*/
fun OnBackPressedDispatcherOwner.backHandler(): BackHandler =
BackHandler(onBackPressedDispatcher = onBackPressedDispatcher)
/**
* Creates a new instance of [OnBackPressedCallback] and connects it with this [BackDispatcher].
* All events from the returned [OnBackPressedCallback] are forwarded to this [BackDispatcher].
* The enabled state from this [BackDispatcher] is forwarded to the returned [OnBackPressedCallback].
*/
fun BackDispatcher.connectOnBackPressedCallback(): OnBackPressedCallback =
OnBackPressedCallbackAdapter(dispatcher = this)
private class OnBackPressedCallbackAdapter(
private val dispatcher: BackDispatcher,
) : OnBackPressedCallback(enabled = dispatcher.isEnabled) {
init {
dispatcher.addEnabledChangedListener { isEnabled = it }
}
override fun handleOnBackPressed() {
dispatcher.back()
}
override fun handleOnBackStarted(backEvent: BackEventCompat) {
dispatcher.startPredictiveBack(backEvent.toEssentyBackEvent())
}
override fun handleOnBackProgressed(backEvent: BackEventCompat) {
dispatcher.progressPredictiveBack(backEvent.toEssentyBackEvent())
}
override fun handleOnBackCancelled() {
dispatcher.cancelPredictiveBack()
}
private fun BackEventCompat.toEssentyBackEvent(): BackEvent =
BackEvent(
progress = progress,
swipeEdge = when (swipeEdge) {
BackEventCompat.EDGE_LEFT -> BackEvent.SwipeEdge.LEFT
BackEventCompat.EDGE_RIGHT -> BackEvent.SwipeEdge.RIGHT
else -> BackEvent.SwipeEdge.UNKNOWN
},
touchX = touchX,
touchY = touchY,
)
}
================================================
FILE: back-handler/src/androidUnitTest/kotlin/com/arkivanov/essenty/backhandler/AndroidBackHandlerTest.kt
================================================
package com.arkivanov.essenty.backhandler
import androidx.activity.BackEventCompat
import androidx.activity.BackEventCompat.Companion.EDGE_LEFT
import androidx.activity.BackEventCompat.Companion.EDGE_RIGHT
import androidx.activity.OnBackPressedDispatcher
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@Suppress("TestFunctionName")
class AndroidBackHandlerTest {
private val dispatcher = OnBackPressedDispatcher()
private val handler = BackHandler(onBackPressedDispatcher = dispatcher)
@Test
fun WHEN_created_THEN_hasEnabledCallbacks_false() {
assertFalse(dispatcher.hasEnabledCallbacks())
}
@Test
fun WHEN_enabled_callback_registered_THEN_hasEnabledCallbacks_true() {
handler.register(callback(isEnabled = true))
assertTrue(dispatcher.hasEnabledCallbacks())
}
@Test
fun WHEN_disabled_callback_registered_THEN_hasEnabledCallbacks_false() {
handler.register(callback(isEnabled = false))
assertFalse(dispatcher.hasEnabledCallbacks())
}
@Test
fun WHEN_multiple_callbacks_registered_and_one_enabled_THEN_hasEnabledCallbacks_true() {
handler.register(callback(isEnabled = false))
handler.register(callback(isEnabled = true))
handler.register(callback(isEnabled = false))
assertTrue(dispatcher.hasEnabledCallbacks())
}
@Test
fun GIVEN_multiple_disabled_callbacks_WHEN_one_callback_enabled_THEN_hasEnabledCallbacks_true() {
val callback2 = callback(isEnabled = false)
handler.register(callback(isEnabled = false))
handler.register(callback2)
handler.register(callback(isEnabled = false))
callback2.isEnabled = true
assertTrue(dispatcher.hasEnabledCallbacks())
}
@Test
fun GIVEN_multiple_enabled_callbacks_WHEN_all_callbacks_disabled_except_one_THEN_hasEnabledCallbacks_true() {
val callbacks = listOf(callback(isEnabled = true), callback(isEnabled = true), callback(isEnabled = true))
callbacks.forEach(handler::register)
callbacks.drop(1).forEach { it.isEnabled = false }
assertTrue(dispatcher.hasEnabledCallbacks())
}
@Test
fun GIVEN_multiple_enabled_callbacks_WHEN_all_callbacks_disabled_THEN_hasEnabledCallbacks_false() {
val callbacks = listOf(callback(isEnabled = true), callback(isEnabled = true), callback(isEnabled = true))
callbacks.forEach(handler::register)
callbacks.forEach { it.isEnabled = false }
assertFalse(dispatcher.hasEnabledCallbacks())
}
@Test
fun GIVEN_multiple_enabled_callbacks_WHEN_all_callbacks_removed_THEN_hasEnabledCallbacks_false() {
val callbacks = listOf(callback(isEnabled = true), callback(isEnabled = true), callback(isEnabled = true))
callbacks.forEach(handler::register)
callbacks.forEach(handler::unregister)
assertFalse(dispatcher.hasEnabledCallbacks())
}
@Test
fun GIVEN_multiple_enabled_callbacks_WHEN_all_callbacks_removed_except_one_THEN_hasEnabledCallbacks_true() {
val callbacks = listOf(callback(isEnabled = true), callback(isEnabled = true), callback(isEnabled = true))
callbacks.forEach(handler::register)
callbacks.drop(1).forEach(handler::unregister)
assertTrue(dispatcher.hasEnabledCallbacks())
}
@Test
fun GIVEN_all_callbacks_disabled_WHEN_onBackPressed_THEN_callbacks_not_called() {
var isCalled = false
repeat(3) {
handler.register(callback(isEnabled = false) { isCalled = true })
}
dispatcher.onBackPressed()
assertFalse(isCalled)
}
@Test
fun GIVEN_all_callbacks_enabled_WHEN_onBackPressed_THEN_only_last_callback_called() {
val called = MutableList(3) { false }
repeat(called.size) { index ->
handler.register(callback(isEnabled = true) { called[index] = true })
}
dispatcher.onBackPressed()
assertContentEquals(listOf(false, false, true), called)
}
@Test
fun GIVEN_only_one_callback_enabled_WHEN_onBackPressed_THEN_only_enabled_callback_called() {
val called = MutableList(3) { false }
repeat(called.size) { index ->
handler.register(callback(isEnabled = index == 0) { called[index] = true })
}
dispatcher.onBackPressed()
assertContentEquals(listOf(true, false, false), called)
}
@Test
fun GIVEN_multiple_enabled_callbacks_registered_and_all_callbacks_removed_except_one_WHEN_onBackPressed_THEN_callback_called() {
val called = MutableList(3) { false }
val callbacks = List(called.size) { index -> callback(isEnabled = true) { called[index] = true } }
callbacks.forEach(handler::register)
callbacks.drop(1).forEach(handler::unregister)
dispatcher.onBackPressed()
assertContentEquals(listOf(true, false, false), called)
}
@Test
fun GIVEN_enabled_callbacks_registered_with_priorities_WHEN_onBackPressed_THEN_last_callback_with_higher_priority_called() {
val list = ArrayList<Int>()
handler.register(callback(isEnabled = true, priority = 0) { list += 1 })
handler.register(callback(isEnabled = true, priority = 1) { list += 2 })
handler.register(callback(isEnabled = true, priority = 2) { list += 3 })
handler.register(callback(isEnabled = true, priority = 1) { list += 4 })
handler.register(callback(isEnabled = true, priority = 2) { list += 5 })
handler.register(callback(isEnabled = true, priority = 0) { list += 6 })
handler.register(callback(isEnabled = true, priority = 1) { list += 7 })
handler.register(callback(isEnabled = true, priority = 0) { list += 8 })
dispatcher.onBackPressed()
assertContentEquals(listOf(5), list)
}
@Test
fun GIVEN_enabled_callbacks_registered_with_priorities_WHEN_priority_changed_and_onBackPressed_THEN_last_callback_with_higher_priority_called() {
val list = ArrayList<Int>()
handler.register(callback(isEnabled = true, priority = 0) { list += 1 })
val callback = callback(isEnabled = true, priority = 1) { list += 2 }
handler.register(callback)
handler.register(callback(isEnabled = true, priority = 2) { list += 3 })
callback.priority = 3
dispatcher.onBackPressed()
assertContentEquals(listOf(2), list)
}
@Test
fun WHEN_progress_with_back_THEN_callbacks_called() {
val receivedEvents = ArrayList<Any?>()
handler.register(
BackCallback(
onBackStarted = { receivedEvents += it },
onBackProgressed = { receivedEvents += it },
onBackCancelled = { receivedEvents += "Cancel" },
onBack = { receivedEvents += "Back" },
)
)
dispatcher.dispatchOnBackStarted(BackEventCompat(progress = 0.1F, swipeEdge = EDGE_LEFT, touchX = 1F, touchY = 2F))
dispatcher.dispatchOnBackProgressed(BackEventCompat(progress = 0.2F, swipeEdge = EDGE_RIGHT, touchX = 2F, touchY = 3F))
dispatcher.dispatchOnBackProgressed(BackEventCompat(progress = 0.3F, swipeEdge = EDGE_LEFT, touchX = 3F, touchY = 4F))
dispatcher.onBackPressed()
assertContentEquals(
listOf(
BackEvent(progress = 0.1F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 1F, touchY = 2F),
BackEvent(progress = 0.2F, swipeEdge = BackEvent.SwipeEdge.RIGHT, touchX = 2F, touchY = 3F),
BackEvent(progress = 0.3F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 3F, touchY = 4F),
"Back",
),
receivedEvents,
)
}
@Test
fun WHEN_progress_with_cancel_THEN_callbacks_called() {
val receivedEvents = ArrayList<Any?>()
handler.register(
BackCallback(
onBackStarted = { receivedEvents += it },
onBackProgressed = { receivedEvents += it },
onBackCancelled = { receivedEvents += "Cancel" },
onBack = { receivedEvents += "Back" },
)
)
dispatcher.dispatchOnBackStarted(BackEventCompat(progress = 0.1F, swipeEdge = EDGE_LEFT, touchX = 1F, touchY = 2F))
dispatcher.dispatchOnBackProgressed(BackEventCompat(progress = 0.2F, swipeEdge = EDGE_RIGHT, touchX = 2F, touchY = 3F))
dispatcher.dispatchOnBackProgressed(BackEventCompat(progress = 0.3F, swipeEdge = EDGE_LEFT, touchX = 3F, touchY = 4F))
dispatcher.dispatchOnBackCancelled()
assertContentEquals(
listOf(
BackEvent(progress = 0.1F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 1F, touchY = 2F),
BackEvent(progress = 0.2F, swipeEdge = BackEvent.SwipeEdge.RIGHT, touchX = 2F, touchY = 3F),
BackEvent(progress = 0.3F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 3F, touchY = 4F),
"Cancel",
),
receivedEvents,
)
}
private fun callback(
isEnabled: Boolean = true,
priority: Int = BackCallback.PRIORITY_DEFAULT,
onBack: () -> Unit = {},
): BackCallback =
BackCallback(
isEnabled = isEnabled,
priority = priority,
onBack = onBack,
)
}
================================================
FILE: back-handler/src/androidUnitTest/kotlin/com/arkivanov/essenty/backhandler/AndroidBackHandlerWithLifecycleTest.kt
================================================
package com.arkivanov.essenty.backhandler
import androidx.activity.OnBackPressedDispatcher
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import kotlin.test.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@Suppress("TestFunctionName")
class AndroidBackHandlerWithLifecycleTest {
private val dispatcher = OnBackPressedDispatcher()
private val lifecycleOwner = LifecycleOwnerImpl()
@Test
fun GIVEN_lifecycle_created_WHEN_handler_created_THEN_hasEnabledCallbacks_returns_false() {
lifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
val handler = handler()
handler.register(callback())
assertFalse(dispatcher.hasEnabledCallbacks())
}
@Test
fun GIVEN_lifecycle_started_WHEN_handler_created_THEN_hasEnabledCallbacks_returns_true() {
lifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
val handler = handler()
handler.register(callback())
assertTrue(dispatcher.hasEnabledCallbacks())
}
@Test
fun GIVEN_handler_created_WHEN_lifecycle_started_THEN_hasEnabledCallbacks_returns_true() {
val handler = handler()
handler.register(callback())
lifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
assertTrue(dispatcher.hasEnabledCallbacks())
}
@Test
fun GIVEN_lifecycle_started_WHEN_lifecycle_stopped_THEN_hasEnabledCallbacks_returns_false() {
lifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_START)
val handler = handler()
handler.register(callback())
lifecycleOwner.lifecycle.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
assertFalse(dispatcher.hasEnabledCallbacks())
}
private fun handler(): BackHandler =
BackHandler(
onBackPressedDispatcher = dispatcher,
lifecycleOwner = lifecycleOwner,
)
private fun callback(): BackCallback =
BackCallback(isEnabled = true, onBack = {})
private class LifecycleOwnerImpl : LifecycleOwner {
override val lifecycle: LifecycleRegistry = LifecycleRegistry.createUnsafe(this)
}
}
================================================
FILE: back-handler/src/androidUnitTest/kotlin/com/arkivanov/essenty/backhandler/OnBackPressedCallbackAdapterTest.kt
================================================
package com.arkivanov.essenty.backhandler
import androidx.activity.BackEventCompat
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@Suppress("TestFunctionName")
class OnBackPressedCallbackAdapterTest {
private val dispatcher = BackDispatcher()
@Test
fun WHEN_connected_with_empty_dispatcher_THEN_disabled() {
val callback = dispatcher.connectOnBackPressedCallback()
assertFalse(callback.isEnabled)
}
@Test
fun WHEN_connected_with_not_empty_disabled_dispatcher_THEN_disabled() {
dispatcher.register(BackCallback(isEnabled = false, onBack = {}))
val callback = dispatcher.connectOnBackPressedCallback()
assertFalse(callback.isEnabled)
}
@Test
fun WHEN_connected_with_not_empty_enabled_dispatcher_THEN_enabled() {
dispatcher.register(BackCallback(isEnabled = true, onBack = {}))
val callback = dispatcher.connectOnBackPressedCallback()
assertTrue(callback.isEnabled)
}
@Test
fun GIVEN_connected_with_empty_dispatcher_WHEN_disabled_callback_registered_in_dispatcher_THEN_disabled() {
val callback = dispatcher.connectOnBackPressedCallback()
dispatcher.register(BackCallback(isEnabled = false, onBack = {}))
assertFalse(callback.isEnabled)
}
@Test
fun GIVEN_connected_with_empty_dispatcher_WHEN_enabled_callback_registered_in_dispatcher_THEN_enabled() {
val callback = dispatcher.connectOnBackPressedCallback()
dispatcher.register(BackCallback(isEnabled = true, onBack = {}))
assertTrue(callback.isEnabled)
}
@Test
fun GIVEN_connected_with_not_empty_disabled_dispatcher_WHEN_disabled_callback_registered_in_dispatcher_THEN_disabled() {
dispatcher.register(BackCallback(isEnabled = false, onBack = {}))
val callback = dispatcher.connectOnBackPressedCallback()
dispatcher.register(BackCallback(isEnabled = false, onBack = {}))
assertFalse(callback.isEnabled)
}
@Test
fun GIVEN_connected_with_not_empty_disabled_dispatcher_WHEN_enabled_callback_registered_in_dispatcher_THEN_enabled() {
dispatcher.register(BackCallback(isEnabled = false, onBack = {}))
val callback = dispatcher.connectOnBackPressedCallback()
dispatcher.register(BackCallback(isEnabled = true, onBack = {}))
assertTrue(callback.isEnabled)
}
@Test
fun GIVEN_connected_with_not_empty_enabled_dispatcher_WHEN_disabled_callback_registered_in_dispatcher_THEN_enabled() {
dispatcher.register(BackCallback(isEnabled = true, onBack = {}))
val callback = dispatcher.connectOnBackPressedCallback()
dispatcher.register(BackCallback(isEnabled = false, onBack = {}))
assertTrue(callback.isEnabled)
}
@Test
fun GIVEN_connected_with_not_empty_enabled_dispatcher_WHEN_enabled_callback_registered_in_dispatcher_THEN_enabled() {
dispatcher.register(BackCallback(isEnabled = true, onBack = {}))
val callback = dispatcher.connectOnBackPressedCallback()
dispatcher.register(BackCallback(isEnabled = true, onBack = {}))
assertTrue(callback.isEnabled)
}
@Test
fun GIVEN_connected_with_not_empty_disabled_dispatcher_WHEN_all_callbacks_unregistered_from_dispatcher_THEN_disabled() {
val essentyCallback = BackCallback(isEnabled = false, onBack = {})
dispatcher.register(essentyCallback)
val callback = dispatcher.connectOnBackPressedCallback()
dispatcher.unregister(essentyCallback)
assertFalse(callback.isEnabled)
}
@Test
fun GIVEN_connected_with_not_empty_enabled_dispatcher_WHEN_not_all_callbacks_unregistered_from_dispatcher_THEN_enabled() {
val essentyCallback = BackCallback(isEnabled = true, onBack = {})
dispatcher.register(essentyCallback)
dispatcher.register(BackCallback(isEnabled = true, onBack = {}))
val callback = dispatcher.connectOnBackPressedCallback()
dispatcher.unregister(essentyCallback)
assertTrue(callback.isEnabled)
}
@Test
fun GIVEN_connected_with_not_empty_enabled_dispatcher_WHEN_all_callbacks_unregistered_from_dispatcher_THEN_disabled() {
val essentyCallback = BackCallback(isEnabled = true, onBack = {})
dispatcher.register(essentyCallback)
val callback = dispatcher.connectOnBackPressedCallback()
dispatcher.unregister(essentyCallback)
assertFalse(callback.isEnabled)
}
@Test
fun WHEN_progress_with_back_THEN_forwards() {
val callback = dispatcher.connectOnBackPressedCallback()
val receivedEvents = ArrayList<Any?>()
dispatcher.register(
BackCallback(
onBackStarted = { receivedEvents += it },
onBackProgressed = { receivedEvents += it },
onBackCancelled = { receivedEvents += "Cancel" },
onBack = { receivedEvents += "Back" },
)
)
callback.handleOnBackStarted(BackEventCompat(progress = 0.1F, swipeEdge = BackEventCompat.EDGE_LEFT, touchX = 1F, touchY = 2F))
callback.handleOnBackProgressed(BackEventCompat(progress = 0.2F, swipeEdge = BackEventCompat.EDGE_RIGHT, touchX = 2F, touchY = 3F))
callback.handleOnBackProgressed(BackEventCompat(progress = 0.3F, swipeEdge = BackEventCompat.EDGE_LEFT, touchX = 3F, touchY = 4F))
callback.handleOnBackPressed()
assertContentEquals(
listOf(
BackEvent(progress = 0.1F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 1F, touchY = 2F),
BackEvent(progress = 0.2F, swipeEdge = BackEvent.SwipeEdge.RIGHT, touchX = 2F, touchY = 3F),
BackEvent(progress = 0.3F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 3F, touchY = 4F),
"Back",
),
receivedEvents,
)
}
@Test
fun WHEN_progress_with_cancel_THEN_forwards() {
val callback = dispatcher.connectOnBackPressedCallback()
val receivedEvents = ArrayList<Any?>()
dispatcher.register(
BackCallback(
onBackStarted = { receivedEvents += it },
onBackProgressed = { receivedEvents += it },
onBackCancelled = { receivedEvents += "Cancel" },
onBack = { receivedEvents += "Back" },
)
)
callback.handleOnBackStarted(BackEventCompat(progress = 0.1F, swipeEdge = BackEventCompat.EDGE_LEFT, touchX = 1F, touchY = 2F))
callback.handleOnBackProgressed(BackEventCompat(progress = 0.2F, swipeEdge = BackEventCompat.EDGE_RIGHT, touchX = 2F, touchY = 3F))
callback.handleOnBackProgressed(BackEventCompat(progress = 0.3F, swipeEdge = BackEventCompat.EDGE_LEFT, touchX = 3F, touchY = 4F))
callback.handleOnBackCancelled()
assertContentEquals(
listOf(
BackEvent(progress = 0.1F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 1F, touchY = 2F),
BackEvent(progress = 0.2F, swipeEdge = BackEvent.SwipeEdge.RIGHT, touchX = 2F, touchY = 3F),
BackEvent(progress = 0.3F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 3F, touchY = 4F),
"Cancel",
),
receivedEvents,
)
}
}
================================================
FILE: back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackCallback.kt
================================================
package com.arkivanov.essenty.backhandler
import kotlin.properties.Delegates
/**
* A callback for back button handling.
*
* @param isEnabled the initial enabled state of the callback.
* @param priority determines the order of callback execution.
* When calling, callbacks are sorted in ascending order first by priority and then by index,
* the last enabled callback gets called.
*/
abstract class BackCallback(
isEnabled: Boolean = true,
var priority: Int = PRIORITY_DEFAULT,
) {
private var enabledListeners = emptySet<(Boolean) -> Unit>()
/**
* Controls the enabled state of the callback.
*/
var isEnabled: Boolean by Delegates.observable(isEnabled) { _, _, newValue ->
enabledListeners.forEach { it(newValue) }
}
/**
* Registers the specified [listener] to be called when the enabled state of the callback changes.
*/
fun addEnabledChangedListener(listener: (isEnabled: Boolean) -> Unit) {
this.enabledListeners += listener
}
/**
* Unregisters the specified [listener].
*/
fun removeEnabledChangedListener(listener: (isEnabled: Boolean) -> Unit) {
this.enabledListeners -= listener
}
/**
* Called when the back button is pressed, or the predictive back gesture is finished.
*/
abstract fun onBack()
/**
* Called when the predictive back gesture starts.
*/
open fun onBackStarted(backEvent: BackEvent) {
}
/**
* Called on every progress of the predictive back gesture.
*/
open fun onBackProgressed(backEvent: BackEvent) {
}
/**
* Called when the predictive back gesture is cancelled.
*/
open fun onBackCancelled() {
}
companion object {
const val PRIORITY_DEFAULT: Int = 0
const val PRIORITY_MIN: Int = Int.MIN_VALUE
const val PRIORITY_MAX: Int = Int.MAX_VALUE
}
}
fun BackCallback(
isEnabled: Boolean = true,
priority: Int = 0,
onBackStarted: ((BackEvent) -> Unit)? = null,
onBackProgressed: ((BackEvent) -> Unit)? = null,
onBackCancelled: (() -> Unit)? = null,
onBack: () -> Unit,
): BackCallback =
object : BackCallback(isEnabled = isEnabled, priority = priority) {
override fun onBackStarted(backEvent: BackEvent) {
onBackStarted?.invoke(backEvent)
}
override fun onBackProgressed(backEvent: BackEvent) {
onBackProgressed?.invoke(backEvent)
}
override fun onBackCancelled() {
onBackCancelled?.invoke()
}
override fun onBack() {
onBack.invoke()
}
}
================================================
FILE: back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackDispatcher.kt
================================================
package com.arkivanov.essenty.backhandler
import kotlin.js.JsName
/**
* Provides a way to manually trigger back button handlers.
*/
interface BackDispatcher : BackHandler {
/**
* Returns `true` if there is at least one enabled handler, `false` otherwise.
*/
val isEnabled: Boolean
/**
* Adds the provided [listener], which will be called every time the enabled state of
* this [BackDispatcher] changes.
*/
fun addEnabledChangedListener(listener: (isEnabled: Boolean) -> Unit)
/**
* Removes the provided enabled state changed [listener].
*/
fun removeEnabledChangedListener(listener: (isEnabled: Boolean) -> Unit)
/**
* If no predictive back gesture is currently in progress, finds the last enabled
* callback with the highest priority and calls [BackCallback.onBack].
*
* If the predictive back gesture is currently in progress, calls [BackCallback.onBack] on
* the previously selected callback.
*
* @return `true` if any callback was triggered, `false` otherwise.
*/
fun back(): Boolean
/**
* Starts handling the predictive back gesture. Picks one of the enabled callback (if any)
* that will be handling the gesture and calls [BackCallback.onBackStarted].
*
* @return `true` if any callback was triggered, `false` otherwise.
*/
fun startPredictiveBack(backEvent: BackEvent): Boolean
/**
* Calls [BackCallback.onBackProgressed] on the previously selected callback.
*/
fun progressPredictiveBack(backEvent: BackEvent)
/**
* Calls [BackCallback.onBackCancelled] on the previously selected callback.
*/
fun cancelPredictiveBack()
}
/**
* Creates and returns a default implementation of [BackDispatcher].
*/
@JsName("backDispatcher")
fun BackDispatcher(): BackDispatcher =
DefaultBackDispatcher()
================================================
FILE: back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackEvent.kt
================================================
package com.arkivanov.essenty.backhandler
/**
* Represents an event of the predictive back gesture.
*
* @param progress progress factor of the back gesture, must be between 0 and 1.
* @param swipeEdge Indicates which edge the gesture is being performed from.
* @param touchX absolute X location of the touch point of this event.
* @param touchY absolute Y location of the touch point of this event.
*/
data class BackEvent(
val progress: Float = 0F,
val swipeEdge: SwipeEdge = SwipeEdge.UNKNOWN,
val touchX: Float = 0F,
val touchY: Float = 0F,
) {
init {
require(progress in 0F..1F) { "The 'progress' argument must be between 0 and 1 (both inclusive)" }
}
enum class SwipeEdge {
UNKNOWN,
LEFT,
RIGHT,
}
}
================================================
FILE: back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackHandler.kt
================================================
package com.arkivanov.essenty.backhandler
/**
* A handler for back button presses.
*/
interface BackHandler {
/**
* Checks whether the provided [BackCallback] is registered or not.
*/
fun isRegistered(callback: BackCallback): Boolean
/**
* Registers the specified [callback] to be called when the back button is invoked.
*/
fun register(callback: BackCallback)
/**
* Unregisters the specified [callback].
*/
fun unregister(callback: BackCallback)
}
================================================
FILE: back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackHandlerOwner.kt
================================================
package com.arkivanov.essenty.backhandler
/**
* Represents a holder of [BackHandler]. It may be implemented by an arbitrary class, to provide convenient API.
*/
interface BackHandlerOwner {
val backHandler: BackHandler
}
================================================
FILE: back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/DefaultBackDispatcher.kt
================================================
package com.arkivanov.essenty.backhandler
internal class DefaultBackDispatcher : BackDispatcher {
private var set = emptySet<BackCallback>()
private var progressData: ProgressData? = null
override val isEnabled: Boolean get() = set.any(BackCallback::isEnabled)
private var enabledChangedListeners = emptySet<(Boolean) -> Unit>()
private var hasEnabledCallback: Boolean = false
private val onCallbackEnabledChanged: (Boolean) -> Unit = { onCallbackEnabledChanged() }
private fun onCallbackEnabledChanged() {
val hasEnabledCallback = isEnabled
if (this.hasEnabledCallback != hasEnabledCallback) {
this.hasEnabledCallback = hasEnabledCallback
enabledChangedListeners.forEach { it.invoke(hasEnabledCallback) }
}
}
override fun isRegistered(callback: BackCallback): Boolean =
callback in set
override fun register(callback: BackCallback) {
check(callback !in set) { "Callback is already registered" }
this.set += callback
callback.addEnabledChangedListener(onCallbackEnabledChanged)
onCallbackEnabledChanged()
}
override fun unregister(callback: BackCallback) {
check(callback in set) { "Callback is not registered" }
this.set -= callback
callback.removeEnabledChangedListener(onCallbackEnabledChanged)
if (callback == progressData?.callback) {
progressData?.callback = null
callback.onBackCancelled()
}
onCallbackEnabledChanged()
}
override fun addEnabledChangedListener(listener: (isEnabled: Boolean) -> Unit) {
enabledChangedListeners += listener
}
override fun removeEnabledChangedListener(listener: (isEnabled: Boolean) -> Unit) {
enabledChangedListeners -= listener
}
override fun back(): Boolean {
val callback = progressData?.callback ?: set.findMostImportant()
progressData = null
callback?.onBack()
return callback != null
}
override fun startPredictiveBack(backEvent: BackEvent): Boolean {
val callback = set.findMostImportant() ?: return false
progressData = ProgressData(startEvent = backEvent, callback = callback)
callback.onBackStarted(backEvent)
return true
}
override fun progressPredictiveBack(backEvent: BackEvent) {
val progressData = progressData ?: return
if (progressData.callback == null) {
progressData.callback = set.findMostImportant()
progressData.callback?.onBackStarted(progressData.startEvent)
}
progressData.callback?.onBackProgressed(backEvent)
}
override fun cancelPredictiveBack() {
progressData?.callback?.onBackCancelled()
progressData = null
}
private class ProgressData(
val startEvent: BackEvent,
var callback: BackCallback?,
)
}
================================================
FILE: back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/Utils.kt
================================================
package com.arkivanov.essenty.backhandler
internal fun Iterable<BackCallback>.findMostImportant(): BackCallback? =
sortedBy(BackCallback::priority).lastOrNull(BackCallback::isEnabled)
================================================
FILE: back-handler/src/commonTest/kotlin/com/arkivanov/essenty/backhandler/DefaultBackDispatcherTest.kt
================================================
package com.arkivanov.essenty.backhandler
import kotlin.test.Test
import kotlin.test.assertContentEquals
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@Suppress("TestFunctionName")
class DefaultBackDispatcherTest {
private val dispatcher = DefaultBackDispatcher()
@Test
fun WHEN_created_THEN_disabled() {
assertFalse(dispatcher.isEnabled)
}
@Test
fun WHEN_enabled_callback_registered_THEN_enabled() {
dispatcher.register(callback(isEnabled = true))
assertTrue(dispatcher.isEnabled)
}
@Test
fun WHEN_two_enabled_callbacks_registered_THEN_enabled() {
dispatcher.register(callback(isEnabled = true))
dispatcher.register(callback(isEnabled = true))
assertTrue(dispatcher.isEnabled)
}
@Test
fun WHEN_two_callbacks_registered_one_disabled_THEN_enabled() {
dispatcher.register(callback(isEnabled = false))
dispatcher.register(callback(isEnabled = true))
assertTrue(dispatcher.isEnabled)
}
@Test
fun GIVEN_two_enabled_callbacks_registered_WHEN_one_callback_disabled_THEN_enabled() {
val callback1 = callback(isEnabled = true)
dispatcher.register(callback1)
dispatcher.register(callback(isEnabled = true))
callback1.isEnabled = false
assertTrue(dispatcher.isEnabled)
}
@Test
fun GIVEN_two_enabled_callbacks_registered_WHEN_all_callbacks_disabled_THEN_disabled() {
val callback1 = callback(isEnabled = true)
val callback2 = callback(isEnabled = true)
dispatcher.register(callback1)
dispatcher.register(callback2)
callback1.isEnabled = false
callback2.isEnabled = false
assertFalse(dispatcher.isEnabled)
}
@Test
fun WHEN_two_disabled_callbacks_registered_THEN_disabled() {
dispatcher.register(callback(isEnabled = false))
dispatcher.register(callback(isEnabled = false))
assertFalse(dispatcher.isEnabled)
}
@Test
fun GIVEN_enabled_callback_registered_WHEN_callback_unregistered_THEN_disabled() {
val callback = callback(isEnabled = true)
dispatcher.register(callback)
dispatcher.unregister(callback)
assertFalse(dispatcher.isEnabled)
}
@Test
fun GIVEN_two_enabled_callbacks_registered_WHEN_one_callback_unregistered_THEN_enabled() {
val callback1 = callback(isEnabled = true)
dispatcher.register(callback1)
dispatcher.register(callback(isEnabled = true))
dispatcher.unregister(callback1)
assertTrue(dispatcher.isEnabled)
}
@Test
fun GIVEN_two_enabled_callbacks_registered_WHEN_all_callback_unregistered_THEN_disabled() {
val callback1 = callback(isEnabled = true)
val callback2 = callback(isEnabled = true)
dispatcher.register(callback1)
dispatcher.register(callback2)
dispatcher.unregister(callback1)
dispatcher.unregister(callback2)
assertFalse(dispatcher.isEnabled)
}
@Test
fun GIVEN_enabled_callbacks_registered_WHEN_back_THEN_last_callback_called() {
val list = ArrayList<Int>()
dispatcher.register(callback(isEnabled = true) { list += 1 })
dispatcher.register(callback(isEnabled = true) { list += 2 })
dispatcher.back()
assertContentEquals(listOf(2), list)
}
@Test
fun GIVEN_enabled_callbacks_registered_with_priorities_WHEN_back_THEN_last_callback_with_higher_priority_called() {
val list = ArrayList<Int>()
dispatcher.register(callback(isEnabled = true, priority = 0) { list += 1 })
dispatcher.register(callback(isEnabled = true, priority = 1) { list += 2 })
dispatcher.register(callback(isEnabled = true, priority = 2) { list += 3 })
dispatcher.register(callback(isEnabled = true, priority = 1) { list += 4 })
dispatcher.register(callback(isEnabled = true, priority = 2) { list += 5 })
dispatcher.register(callback(isEnabled = true, priority = 0) { list += 6 })
dispatcher.register(callback(isEnabled = true, priority = 1) { list += 7 })
dispatcher.register(callback(isEnabled = true, priority = 0) { list += 8 })
dispatcher.back()
assertContentEquals(listOf(5), list)
}
@Test
fun GIVEN_enabled_callbacks_registered_with_priorities_WHEN_priority_changed_and_back_THEN_last_callback_with_higher_priority_called() {
val list = ArrayList<Int>()
dispatcher.register(callback(isEnabled = true, priority = 0) { list += 1 })
val callback = callback(isEnabled = true, priority = 1) { list += 2 }
dispatcher.register(callback)
dispatcher.register(callback(isEnabled = true, priority = 2) { list += 3 })
callback.priority = 3
dispatcher.back()
assertContentEquals(listOf(2), list)
}
@Test
fun GIVEN_callbacks_not_registered_WHEN_back_THEN_returned_false() {
val result = dispatcher.back()
assertFalse(result)
}
@Test
fun GIVEN_enabled_callback_registered_WHEN_back_THEN_returned_true() {
dispatcher.register(callback(isEnabled = true))
val result = dispatcher.back()
assertTrue(result)
}
@Test
fun GIVEN_enabled_callbacks_registered_and_then_some_disabled_WHEN_back_THEN_last_enabled_callback_called() {
val list = ArrayList<Int>()
val callback2 = callback(isEnabled = true) { list += 2 }
val callback4 = callback(isEnabled = true) { list += 4 }
dispatcher.register(callback(isEnabled = true) { list += 1 })
dispatcher.register(callback2)
dispatcher.register(callback(isEnabled = true) { list += 3 })
dispatcher.register(callback4)
callback2.isEnabled = false
callback4.isEnabled = false
dispatcher.back()
assertContentEquals(listOf(3), list)
}
@Test
fun GIVEN_some_disabled_callbacks_registered_WHEN_back_THEN_returned_true() {
val callback2 = callback(isEnabled = true)
val callback4 = callback(isEnabled = true)
dispatcher.register(callback(isEnabled = true))
dispatcher.register(callback2)
dispatcher.register(callback(isEnabled = true))
dispatcher.register(callback4)
callback2.isEnabled = false
callback4.isEnabled = false
val result = dispatcher.back()
assertTrue(result)
}
@Test
fun GIVEN_some_disabled_callbacks_registered_WHEN_back_THEN_last_enabled_callback_called() {
val list = ArrayList<Int>()
dispatcher.register(callback(isEnabled = true) { list += 1 })
dispatcher.register(callback(isEnabled = false) { list += 2 })
dispatcher.register(callback(isEnabled = true) { list += 3 })
dispatcher.register(callback(isEnabled = false) { list += 4 })
dispatcher.back()
assertContentEquals(listOf(3), list)
}
@Test
fun GIVEN_callbacks_registered_and_some_disabled_WHEN_back_THEN_returned_true() {
dispatcher.register(callback(isEnabled = true))
dispatcher.register(callback(isEnabled = false))
dispatcher.register(callback(isEnabled = true))
dispatcher.register(callback(isEnabled = false))
val result = dispatcher.back()
assertTrue(result)
}
@Test
fun GIVEN_enabled_callbacks_registered_and_then_all_disabled_WHEN_back_THEN_returned_false() {
val callback1 = callback(isEnabled = true)
val callback2 = callback(isEnabled = true)
dispatcher.register(callback1)
dispatcher.register(callback2)
callback1.isEnabled = false
callback2.isEnabled = false
val result = dispatcher.back()
assertFalse(result)
}
@Test
fun GIVEN_disabled_callbacks_registered_WHEN_back_THEN_returned_false() {
dispatcher.register(callback = callback(isEnabled = false))
dispatcher.register(callback = callback(isEnabled = false))
val result = dispatcher.back()
assertFalse(result)
}
@Test
fun GIVEN_callback_not_registered_WHEN_startPredictiveBack_THEN_returns_false() {
val result = dispatcher.startPredictiveBack(BackEvent())
assertFalse(result)
}
@Test
fun GIVEN_disabled_callback_registered_WHEN_startPredictiveBack_THEN_returns_false() {
dispatcher.register(callback = callback(isEnabled = false))
val result = dispatcher.startPredictiveBack(BackEvent())
assertFalse(result)
}
@Test
fun GIVEN_enabled_callback_registered_WHEN_startPredictiveBack_THEN_returns_true() {
dispatcher.register(callback = callback(isEnabled = true))
val result = dispatcher.startPredictiveBack(BackEvent())
assertTrue(result)
}
@Test
fun WHEN_progress_with_back_THEN_callbacks_called() {
val startEvent = BackEvent(progress = 0.1F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 1F, touchY = 2F)
val progressEvent1 = BackEvent(progress = 0.2F, swipeEdge = BackEvent.SwipeEdge.RIGHT, touchX = 2F, touchY = 3F)
val progressEvent2 = BackEvent(progress = 0.3F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 3F, touchY = 4F)
val callback = LoggingCallback()
dispatcher.register(callback)
dispatcher.startPredictiveBack(startEvent)
dispatcher.progressPredictiveBack(progressEvent1)
dispatcher.progressPredictiveBack(progressEvent2)
dispatcher.back()
callback.assertEvents(
"start" to startEvent,
"progress" to progressEvent1,
"progress" to progressEvent2,
"back" to null,
)
}
@Test
fun WHEN_progress_with_cancel_THEN_callbacks_called() {
val startEvent = BackEvent(progress = 0.1F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 1F, touchY = 2F)
val progressEvent1 = BackEvent(progress = 0.2F, swipeEdge = BackEvent.SwipeEdge.RIGHT, touchX = 2F, touchY = 3F)
val progressEvent2 = BackEvent(progress = 0.3F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 3F, touchY = 4F)
val callback = LoggingCallback()
dispatcher.register(callback)
dispatcher.startPredictiveBack(startEvent)
dispatcher.progressPredictiveBack(progressEvent1)
dispatcher.progressPredictiveBack(progressEvent2)
dispatcher.cancelPredictiveBack()
callback.assertEvents(
"start" to startEvent,
"progress" to progressEvent1,
"progress" to progressEvent2,
"cancel" to null,
)
}
@Test
fun GIVEN_callback_registered_and_gesture_started_WHEN_unregister_THEN_callback_cancelled() {
val callback = LoggingCallback()
dispatcher.register(callback)
dispatcher.startPredictiveBack(BackEvent())
callback.clear()
dispatcher.unregister(callback)
callback.assertEvents("cancel" to null)
}
@Test
fun GIVEN_two_callbacks_registered_and_gesture_started_and_progress_callback_unregistered_WHEN_progress_THEN_another_callback_started_and_progressed() {
val callback1 = LoggingCallback()
val callback2 = LoggingCallback()
dispatcher.register(callback1)
dispatcher.register(callback2)
dispatcher.startPredictiveBack(BackEvent(progress = 0F))
dispatcher.progressPredictiveBack(BackEvent(progress = 0.1F))
dispatcher.unregister(callback2)
dispatcher.progressPredictiveBack(BackEvent(progress = 0.2F))
callback1.assertEvents("start" to BackEvent(progress = 0F), "progress" to BackEvent(progress = 0.2F))
}
@Test
fun GIVEN_two_callbacks_registered_and_gesture_started_and_progress_callback_unregistered_WHEN_back_THEN_another_callback_back() {
val callback1 = LoggingCallback()
val callback2 = LoggingCallback()
dispatcher.register(callback1)
dispatcher.register(callback2)
dispatcher.startPredictiveBack(BackEvent(progress = 0F))
dispatcher.progressPredictiveBack(BackEvent(progress = 0.1F))
dispatcher.unregister(callback2)
dispatcher.back()
callback1.assertEvents("back" to null)
}
@Test
fun GIVEN_two_callbacks_registered_and_gesture_started_and_progress_callback_unregistered_WHEN_cancel_THEN_another_callback_not_called() {
val callback1 = LoggingCallback()
val callback2 = LoggingCallback()
dispatcher.register(callback1)
dispatcher.register(callback2)
dispatcher.startPredictiveBack(BackEvent(progress = 0F))
dispatcher.progressPredictiveBack(BackEvent(progress = 0.1F))
dispatcher.unregister(callback2)
dispatcher.cancelPredictiveBack()
callback1.assertEvents()
}
@Test
fun WHEN_another_callback_registered_while_in_progress_THEN_old_callback_called() {
val startEvent = BackEvent(progress = 0.1F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 1F, touchY = 2F)
val progressEvent1 = BackEvent(progress = 0.2F, swipeEdge = BackEvent.SwipeEdge.RIGHT, touchX = 2F, touchY = 3F)
val progressEvent2 = BackEvent(progress = 0.3F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 3F, touchY = 4F)
val callback = LoggingCallback()
dispatcher.register(callback)
dispatcher.startPredictiveBack(startEvent)
dispatcher.progressPredictiveBack(progressEvent1)
dispatcher.register(LoggingCallback())
dispatcher.progressPredictiveBack(progressEvent2)
dispatcher.cancelPredictiveBack()
callback.assertEvents(
"start" to startEvent,
"progress" to progressEvent1,
"progress" to progressEvent2,
"cancel" to null,
)
}
@Test
fun WHEN_another_callback_registered_while_in_progress_THEN_new_callback_not_called() {
val startEvent = BackEvent(progress = 0.1F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 1F, touchY = 2F)
val progressEvent1 = BackEvent(progress = 0.2F, swipeEdge = BackEvent.SwipeEdge.RIGHT, touchX = 2F, touchY = 3F)
val progressEvent2 = BackEvent(progress = 0.3F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 3F, touchY = 4F)
val callback = LoggingCallback()
dispatcher.register(LoggingCallback())
dispatcher.startPredictiveBack(startEvent)
dispatcher.progressPredictiveBack(progressEvent1)
dispatcher.register(callback)
dispatcher.progressPredictiveBack(progressEvent2)
dispatcher.cancelPredictiveBack()
callback.assertEvents()
}
@Test
fun GIVEN_EnabledChanged_listener_added_WHEN_enabled_callback_registered_THEN_listener_called_with_true() {
val events = ArrayList<Boolean>()
dispatcher.addEnabledChangedListener { events += it }
dispatcher.register(callback(isEnabled = true, onBack = {}))
assertContentEquals(listOf(true), events)
}
@Test
fun GIVEN_EnabledChanged_listener_added_WHEN_disabled_callback_registered_THEN_listener_not_called() {
val events = ArrayList<Boolean>()
dispatcher.addEnabledChangedListener { events += it }
dispatcher.register(callback(isEnabled = false, onBack = {}))
assertContentEquals(emptyList(), events)
}
@Test
fun GIVEN_enabled_callback_registered_and_EnabledChanged_listener_added_WHEN_callback_unregistered_THEN_listener_called_with_false() {
val events = ArrayList<Boolean>()
val callback = callback(isEnabled = true, onBack = {})
dispatcher.register(callback)
dispatcher.addEnabledChangedListener { events += it }
dispatcher.unregister(callback)
assertContentEquals(listOf(false), events)
}
@Test
fun GIVEN_disabled_callback_registered_and_EnabledChanged_listener_added_WHEN_callback_unregistered_THEN_listener_not_called() {
val events = ArrayList<Boolean>()
val callback = callback(isEnabled = false, onBack = {})
dispatcher.register(callback)
dispatcher.addEnabledChangedListener { events += it }
dispatcher.unregister(callback)
assertContentEquals(emptyList(), events)
}
@Test
fun GIVEN_disabled_callback_registered_and_EnabledChanged_listener_added_WHEN_callback_enabled_THEN_listener_called_with_true() {
val events = ArrayList<Boolean>()
val callback = callback(isEnabled = false, onBack = {})
dispatcher.register(callback)
dispatcher.addEnabledChangedListener { events += it }
callback.isEnabled = true
assertContentEquals(listOf(true), events)
}
@Test
fun GIVEN_enabled_callback_registered_and_EnabledChanged_listener_added_WHEN_callback_disabled_THEN_listener_called_with_false() {
val events = ArrayList<Boolean>()
val callback = callback(isEnabled = true, onBack = {})
dispatcher.register(callback)
dispatcher.addEnabledChangedListener { events += it }
callback.isEnabled = false
assertContentEquals(listOf(false), events)
}
@Test
fun GIVEN_two_disabled_callback_registered_and_EnabledChanged_listener_added_WHEN_one_callback_enabled_THEN_listener_called_with_true() {
val events = ArrayList<Boolean>()
val callback1 = callback(isEnabled = false, onBack = {})
dispatcher.register(callback1)
dispatcher.register(callback(isEnabled = false, onBack = {}))
dispatcher.addEnabledChangedListener { events += it }
callback1.isEnabled = true
assertContentEquals(listOf(true), events)
}
@Test
fun GIVEN_two_enabled_callback_registered_and_EnabledChanged_listener_added_WHEN_one_callback_disabled_THEN_listener_not_called() {
val events = ArrayList<Boolean>()
val callback1 = callback(isEnabled = true, onBack = {})
dispatcher.register(callback1)
dispatcher.register(callback(isEnabled = true, onBack = {}))
dispatcher.addEnabledChangedListener { events += it }
callback1.isEnabled = false
assertContentEquals(emptyList(), events)
}
@Test
fun GIVEN_one_disabled_and_one_enabled_callback_registered_and_EnabledChanged_listener_added_WHEN_callback_enabled_THEN_listener_not_called() {
val events = ArrayList<Boolean>()
val callback1 = callback(isEnabled = false, onBack = {})
dispatcher.register(callback1)
dispatcher.register(BackCallback(isEnabled = true, onBack = {}))
dispatcher.addEnabledChangedListener { events += it }
callback1.isEnabled = true
assertContentEquals(emptyList(), events)
}
@Test
fun GIVEN_one_disabled_and_one_enabled_callback_registered_and_EnabledChanged_listener_added_WHEN_callback_disabled_THEN_listener_called_with_false() {
val events = ArrayList<Boolean>()
val callback1 = callback(isEnabled = true, onBack = {})
dispatcher.register(callback1)
dispatcher.register(BackCallback(isEnabled = false, onBack = {}))
dispatcher.addEnabledChangedListener { events += it }
callback1.isEnabled = false
assertContentEquals(listOf(false), events)
}
@Test
fun GIVEN_one_disabled_and_one_enabled_callback_registered_and_EnabledChanged_listener_added_WHEN_enabled_callback_unregistered_THEN_listener_called_with_false() {
val events = ArrayList<Boolean>()
val callback1 = callback(isEnabled = true, onBack = {})
dispatcher.register(callback1)
dispatcher.register(BackCallback(isEnabled = false, onBack = {}))
dispatcher.addEnabledChangedListener { events += it }
dispatcher.unregister(callback1)
assertContentEquals(listOf(false), events)
}
@Test
fun GIVEN_one_disabled_and_one_enabled_callback_registered_and_EnabledChanged_listener_added_WHEN_disabled_callback_unregistered_THEN_listener_not_called() {
val events = ArrayList<Boolean>()
val callback1 = callback(isEnabled = false, onBack = {})
dispatcher.register(callback1)
dispatcher.register(BackCallback(isEnabled = true, onBack = {}))
dispatcher.addEnabledChangedListener { events += it }
dispatcher.unregister(callback1)
assertContentEquals(emptyList(), events)
}
@Test
fun GIVEN_EnabledChanged_listener_added_and_disabled_callback_registered_WHEN_callback_enabled_THEN_listener_called_with_true() {
val events = ArrayList<Boolean>()
dispatcher.addEnabledChangedListener { events += it }
val callback = callback(isEnabled = false, onBack = {})
dispatcher.register(callback)
events.clear()
callback.isEnabled = true
assertContentEquals(listOf(true), events)
}
@Test
fun GIVEN_EnabledChanged_listener_added_and_enabled_callback_registered_WHEN_callback_disabled_THEN_listener_called_with_false() {
val events = ArrayList<Boolean>()
dispatcher.addEnabledChangedListener { events += it }
val callback = callback(isEnabled = true, onBack = {})
dispatcher.register(callback)
events.clear()
callback.isEnabled = false
assertContentEquals(listOf(false), events)
}
@Test
fun GIVEN_EnabledChanged_listener_added_and_two_disabled_callback_registered_WHEN_one_callback_enabled_THEN_listener_called_with_true() {
val events = ArrayList<Boolean>()
dispatcher.addEnabledChangedListener { events += it }
val callback1 = callback(isEnabled = false, onBack = {})
dispatcher.register(callback1)
dispatcher.register(callback(isEnabled = false, onBack = {}))
events.clear()
callback1.isEnabled = true
assertContentEquals(listOf(true), events)
}
@Test
fun GIVEN_EnabledChanged_listener_added_and_two_enabled_callback_registered_WHEN_one_callback_disabled_THEN_listener_not_called() {
val events = ArrayList<Boolean>()
dispatcher.addEnabledChangedListener { events += it }
val callback1 = callback(isEnabled = true, onBack = {})
dispatcher.register(callback1)
dispatcher.register(callback(isEnabled = true, onBack = {}))
events.clear()
callback1.isEnabled = false
assertContentEquals(emptyList(), events)
}
@Test
fun GIVEN_EnabledChanged_listener_added_and_one_disabled_and_one_enabled_callback_registered_WHEN_callback_enabled_THEN_listener_not_called() {
val events = ArrayList<Boolean>()
dispatcher.addEnabledChangedListener { events += it }
val callback1 = callback(isEnabled = false, onBack = {})
dispatcher.register(callback1)
dispatcher.register(BackCallback(isEnabled = true, onBack = {}))
events.clear()
callback1.isEnabled = true
assertContentEquals(emptyList(), events)
}
@Test
fun GIVEN_EnabledChanged_listener_added_and_one_disabled_and_one_enabled_callback_registered_WHEN_callback_disabled_THEN_listener_called_with_false() {
val events = ArrayList<Boolean>()
dispatcher.addEnabledChangedListener { events += it }
val callback1 = callback(isEnabled = true, onBack = {})
dispatcher.register(callback1)
dispatcher.register(BackCallback(isEnabled = false, onBack = {}))
events.clear()
callback1.isEnabled = false
assertContentEquals(listOf(false), events)
}
@Test
fun GIVEN_EnabledChanged_listener_added_and_one_disabled_and_one_enabled_callback_registered_WHEN_enabled_callback_unregistered_THEN_listener_called_with_false() {
val events = ArrayList<Boolean>()
dispatcher.addEnabledChangedListener { events += it }
val callback1 = callback(isEnabled = true, onBack = {})
dispatcher.register(callback1)
dispatcher.register(BackCallback(isEnabled = false, onBack = {}))
events.clear()
dispatcher.unregister(callback1)
assertContentEquals(listOf(false), events)
}
@Test
fun GIVEN_EnabledChanged_listener_added_and_one_disabled_and_one_enabled_callback_registered_WHEN_disabled_callback_unregistered_THEN_listener_not_called() {
val events = ArrayList<Boolean>()
dispatcher.addEnabledChangedListener { events += it }
val callback1 = callback(isEnabled = false, onBack = {})
dispatcher.register(callback1)
dispatcher.register(BackCallback(isEnabled = true, onBack = {}))
events.clear()
dispatcher.unregister(callback1)
assertContentEquals(emptyList(), events)
}
@Test
fun GIVEN_callback_not_registered_WHEN_isRegistered_THEN_returns_false() {
val isRegistered = dispatcher.isRegistered(callback())
assertFalse(isRegistered)
}
@Test
fun GIVEN_callback_registered_WHEN_isRegistered_for_another_callback_THEN_returns_false() {
dispatcher.register(callback())
val isRegistered = dispatcher.isRegistered(callback())
assertFalse(isRegistered)
}
@Test
fun GIVEN_callback_registered_WHEN_isRegistered_for_same_callback_THEN_returns_true() {
val callback = callback()
dispatcher.register(callback)
val isRegistered = dispatcher.isRegistered(callback)
assertTrue(isRegistered)
}
private fun callback(
isEnabled: Boolean = true,
priority: Int = 0,
onBack: () -> Unit = {},
): BackCallback =
BackCallback(isEnabled = isEnabled, priority = priority, onBack = onBack)
private class LoggingCallback : BackCallback() {
private val events: MutableList<Pair<String, BackEvent?>> = ArrayList()
override fun onBackStarted(backEvent: BackEvent) {
events += "start" to backEvent
}
override fun onBackProgressed(backEvent: BackEvent) {
events += "progress" to backEvent
}
override fun onBack() {
events += "back" to null
}
override fun onBackCancelled() {
events += "cancel" to null
}
fun clear() {
events.clear()
}
fun assertEvents(vararg events: Pair<String, BackEvent?>) {
assertContentEquals(events.toList(), this.events)
}
}
}
================================================
FILE: build.gradle.kts
================================================
import com.arkivanov.gradle.AndroidConfig
import com.arkivanov.gradle.BinaryCompatibilityValidatorConfig
import com.arkivanov.gradle.PublicationConfig
import com.arkivanov.gradle.ensureUnreachableTasksDisabled
import com.arkivanov.gradle.iosCompat
import com.arkivanov.gradle.macosCompat
import com.arkivanov.gradle.setupDefaults
import com.arkivanov.gradle.setupDetekt
import com.arkivanov.gradle.tvosCompat
import com.arkivanov.gradle.watchosCompat
import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
// Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
repositories {
gradlePluginPortal()
mavenCentral()
google()
maven("https://maven.pkg.jetbrains.space/public/p/compose/dev")
}
dependencies {
classpath(deps.kotlin.kotlinGradlePlug)
classpath(deps.android.gradle)
classpath(deps.kotlinx.binaryCompatibilityValidator)
classpath(deps.detekt.gradleDetektPlug)
classpath(deps.jetbrains.kotlin.serializationGradlePlug)
}
}
plugins {
id("com.arkivanov.gradle.setup")
}
setupDefaults(
multiplatformConfigurator = {
androidTarget()
jvm()
js {
browser()
nodejs()
}
wasmJs {
browser()
}
linuxX64()
iosCompat()
watchosCompat()
tvosCompat()
macosCompat()
},
androidConfig = AndroidConfig(
minSdkVersion = 15,
compileSdkVersion = 34,
targetSdkVersion = 34,
),
publicationConfig = PublicationConfig(
group = "com.arkivanov.essenty",
version = deps.versions.essenty.get(),
projectName = "Essenty",
projectDescription = "Essential libraries for Kotlin Multiplatform",
projectUrl = "https://github.com/arkivanov/Essenty",
scmUrl = "scm:git:git://github.com/arkivanov/Essenty.git",
licenseName = "The Apache License, Version 2.0",
licenseUrl = "http://www.apache.org/licenses/LICENSE-2.0.txt",
developerId = "arkivanov",
developerName = "Arkadii Ivanov",
developerEmail = "arkann1985@gmail.com",
signingKey = System.getenv("SIGNING_KEY"),
signingPassword = System.getenv("SIGNING_PASSWORD"),
repositoryUrl = "https://s01.oss.sonatype.org/service/local/staging/deployByRepositoryId/${System.getenv("SONATYPE_REPOSITORY_ID")}",
repositoryUserName = System.getenv("SONATYPE_USER_NAME"),
repositoryPassword = System.getenv("SONATYPE_PASSWORD"),
),
binaryCompatibilityValidatorConfig = BinaryCompatibilityValidatorConfig(klib = true),
)
setupDetekt()
ensureUnreachableTasksDisabled()
allprojects {
repositories {
mavenCentral()
google()
}
afterEvaluate {
extensions.findByType<KotlinMultiplatformExtension>()?.apply {
sourceSets {
all {
languageSettings.optIn("com.arkivanov.essenty.utils.internal.InternalEssentyApi")
}
}
}
}
}
================================================
FILE: deps.versions.toml
================================================
[versions]
essenty = "2.5.0"
kotlin = "2.1.0"
kotlinxBinaryCompatibilityValidator = "0.16.3"
kotlinxCoroutines = "1.9.0"
detektGradlePlugin = "1.23.6"
junit = "4.13.2"
androidGradle = "8.0.2"
androidxLifecycle = "2.6.2"
androidxSavedstate = "1.2.1"
androidxActivity = "1.8.1"
jetbrainsKotlinxSerialization = "1.6.3"
robolectric = "4.9.1"
reaktive = "2.1.0"
[libraries]
kotlin-kotlinGradlePlug = { group = "org.jetbrains.kotlin", name = "kotlin-gradle-plugin", version.ref = "kotlin" }
kotlinx-binaryCompatibilityValidator = { group = "org.jetbrains.kotlinx", name = "binary-compatibility-validator", version.ref = "kotlinxBinaryCompatibilityValidator" }
kotlinx-coroutinesCore = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-core", version.ref = "kotlinxCoroutines" }
kotlinx-coroutinesTest = { group = "org.jetbrains.kotlinx", name = "kotlinx-coroutines-test", version.ref = "kotlinxCoroutines" }
detekt-gradleDetektPlug = { group = "io.gitlab.arturbosch.detekt", name = "detekt-gradle-plugin", version.ref = "detektGradlePlugin" }
android-gradle = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradle" }
androidx-lifecycle-lifecycleCommonJava8 = { group = "androidx.lifecycle", name = "lifecycle-common-java8", version.ref = "androidxLifecycle" }
androidx-lifecycle-lifecycleRuntime = { group = "androidx.lifecycle", name = "lifecycle-runtime", version.ref = "androidxLifecycle" }
androidx-lifecycle-lifecycleViewmodelKtx = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-ktx", version.ref = "androidxLifecycle" }
androidx-savedstate-savedstateKtx = { group = "androidx.savedstate", name = "savedstate-ktx", version.ref = "androidxSavedstate" }
androidx-activity-activityKtx = { group = "androidx.activity", name = "activity-ktx", version.ref = "androidxActivity" }
jetbrains-kotlin-serializationGradlePlug = { group = "org.jetbrains.kotlin", name = "kotlin-serialization", version.ref = "kotlin" }
jetbrains-kotlinx-kotlinxSerializationCore = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-core", version.ref = "jetbrainsKotlinxSerialization" }
jetbrains-kotlinx-kotlinxSerializationJson = { group = "org.jetbrains.kotlinx", name = "kotlinx-serialization-json", version.ref = "jetbrainsKotlinxSerialization" }
robolectric-robolectric = { group = "org.robolectric", name = "robolectric", version.ref = "robolectric" }
reaktive-reaktive = { group = "com.badoo.reaktive", name = "reaktive", version.ref = "reaktive" }
================================================
FILE: detekt.yml
================================================
complexity:
CyclomaticComplexMethod:
threshold: 15
ignoreSingleWhenExpression: true
ignoreSimpleWhenEntries: true
ignoreNestingFunctions: false
CognitiveComplexMethod:
active: true
threshold: 15
LongParameterList:
active: false
TooManyFunctions:
active: false
exceptions:
PrintStackTrace:
active: false
TooGenericExceptionCaught:
active: false
naming:
FunctionNaming:
excludes: ['**/test/**', '**/*Test/**']
ignoreAnnotated: [ 'Composable' ]
MemberNameEqualsClassName:
active: false
style:
ForbiddenComment:
values: ['FIXME:', 'STOPSHIP:']
MagicNumber:
active: false
MaxLineLength:
maxLineLength: 140
excludes: ['**/test/**', '**/*Test/**']
ReturnCount:
active: false
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-all.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: gradle.properties
================================================
kotlin.code.style=official
org.gradle.jvmargs=-Xmx2g
org.gradle.parallel=true
org.gradle.caching=true
systemProp.org.gradle.internal.publish.checksums.insecure=true
android.useAndroidX=true
android.enableJetifier=true
kotlin.mpp.androidSourceSetLayoutVersion=2
kotlin.mpp.applyDefaultHierarchyTemplate=false
# For compatibility with Kotlin 1.9.0
android.experimental.lint.version=8.1.0
================================================
FILE: gradlew
================================================
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# 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.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Stop when "xargs" is not available.
if ! command -v xargs >/dev/null 2>&1
then
die "xargs is not available"
fi
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if %ERRORLEVEL% equ 0 goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
set EXIT_CODE=%ERRORLEVEL%
if %EXIT_CODE% equ 0 set EXIT_CODE=1
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
exit /b %EXIT_CODE%
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: instance-keeper/.gitignore
================================================
/build
================================================
FILE: instance-keeper/api/android/instance-keeper.api
================================================
public final class com/arkivanov/essenty/instancekeeper/AndroidExtKt {
public static final fun InstanceKeeper (Landroidx/lifecycle/ViewModelStore;Z)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;
public static synthetic fun InstanceKeeper$default (Landroidx/lifecycle/ViewModelStore;ZILjava/lang/Object;)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;
public static final fun instanceKeeper (Landroidx/lifecycle/ViewModelStoreOwner;Z)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;
public static synthetic fun instanceKeeper$default (Landroidx/lifecycle/ViewModelStoreOwner;ZILjava/lang/Object;)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;
}
public abstract interface annotation class com/arkivanov/essenty/instancekeeper/ExperimentalInstanceKeeperApi : java/lang/annotation/Annotation {
}
public abstract interface class com/arkivanov/essenty/instancekeeper/InstanceKeeper {
public abstract fun get (Ljava/lang/Object;)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;
public abstract fun put (Ljava/lang/Object;Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;)V
public abstract fun remove (Ljava/lang/Object;)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;
}
public abstract interface class com/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance {
public abstract fun onDestroy ()V
}
public final class com/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance$DefaultImpls {
public static fun onDestroy (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;)V
}
public final class com/arkivanov/essenty/instancekeeper/InstanceKeeper$SimpleInstance : com/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance {
public fun <init> (Ljava/lang/Object;)V
public final fun getInstance ()Ljava/lang/Object;
public fun onDestroy ()V
}
public abstract interface class com/arkivanov/essenty/instancekeeper/InstanceKeeperDispatcher : com/arkivanov/essenty/instancekeeper/InstanceKeeper {
public abstract fun destroy ()V
}
public final class com/arkivanov/essenty/instancekeeper/InstanceKeeperDispatcherKt {
public static final fun InstanceKeeperDispatcher ()Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperDispatcher;
}
public final class com/arkivanov/essenty/instancekeeper/InstanceKeeperExtKt {
public static final fun getOrCreate (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;
public static final fun getOrCreateCloseable (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/AutoCloseable;
public static final fun getOrCreateSimple (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
public static final fun retainedCloseable (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/AutoCloseable;
public static final fun retainedInstance (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;
public static final fun retainedSimpleInstance (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
public static final fun retainingCloseable (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/PropertyDelegateProvider;
public static final fun retainingCloseable (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/PropertyDelegateProvider;
public static synthetic fun retainingCloseable$default (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider;
public static synthetic fun retainingCloseable$default (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider;
public static final fun retainingInstance (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/PropertyDelegateProvider;
public static final fun retainingInstance (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/PropertyDelegateProvider;
public static synthetic fun retainingInstance$default (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider;
public static synthetic fun retainingInstance$default (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider;
public static final fun retainingSimpleInstance (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/PropertyDelegateProvider;
public static final fun retainingSimpleInstance (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/PropertyDelegateProvider;
public static synthetic fun retainingSimpleInstance$default (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider;
public static synthetic fun retainingSimpleInstance$default (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider;
}
public abstract interface class com/arkivanov/essenty/instancekeeper/InstanceKeeperOwner {
public abstract fun getInstanceKeeper ()Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;
}
================================================
FILE: instance-keeper/api/instance-keeper.klib.api
================================================
// Klib ABI Dump
// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxX64, macosArm64, macosX64, tvosArm64, tvosSimulatorArm64, tvosX64, wasmJs, watchosArm32, watchosArm64, watchosSimulatorArm64, watchosX64]
// Rendering settings:
// - Signature version: 2
// - Show manifest properties: true
// - Show declarations: true
// Library unique name: <com.arkivanov.essenty:instance-keeper>
open annotation class com.arkivanov.essenty.instancekeeper/ExperimentalInstanceKeeperApi : kotlin/Annotation { // com.arkivanov.essenty.instancekeeper/ExperimentalInstanceKeeperApi|null[0]
constructor <init>() // com.arkivanov.essenty.instancekeeper/ExperimentalInstanceKeeperApi.<init>|<init>(){}[0]
}
abstract interface com.arkivanov.essenty.instancekeeper/InstanceKeeper { // com.arkivanov.essenty.instancekeeper/InstanceKeeper|null[0]
abstract fun get(kotlin/Any): com.arkivanov.essenty.instancekeeper/InstanceKeeper.Instance? // com.arkivanov.essenty.instancekeeper/InstanceKeeper.get|get(kotlin.Any){}[0]
abstract fun put(kotlin/Any, com.arkivanov.essenty.instancekeeper/InstanceKeeper.Instance) // com.arkivanov.essenty.instancekeeper/InstanceKeeper.put|put(kotlin.Any;com.arkivanov.essenty.instancekeeper.InstanceKeeper.Instance){}[0]
abstract fun remove(kotlin/Any): com.arkivanov.essenty.instancekeeper/InstanceKeeper.Instance? // com.arkivanov.essenty.instancekeeper/InstanceKeeper.remove|remove(kotlin.Any){}[0]
abstract interface Instance { // com.arkivanov.essenty.instancekeeper/InstanceKeeper.Instance|null[0]
open fun onDestroy() // com.arkivanov.essenty.instancekeeper/InstanceKeeper.Instance.onDestroy|onDestroy(){}[0]
}
final class <#A1: out kotlin/Any?> SimpleInstance : com.arkivanov.essenty.instancekeeper/InstanceKeeper.Instance { // com.arkivanov.essenty.instancekeeper/InstanceKeeper.SimpleInstance|null[0]
constructor <init>(#A1) // com.arkivanov.essenty.instancekeeper/InstanceKeeper.SimpleInstance.<init>|<init>(1:0){}[0]
final val instance // com.arkivanov.essenty.instancekeeper/InstanceKeeper.SimpleInstance.instance|{}instance[0]
final fun <get-instance>(): #A1 // com.arkivanov.essenty.instancekeeper/InstanceKeeper.SimpleInstance.instance.<get-instance>|<get-instance>(){}[0]
}
}
abstract interface com.arkivanov.essenty.instancekeeper/InstanceKeeperDispatcher : com.arkivanov.essenty.instancekeeper/InstanceKeeper { // com.arkivanov.essenty.instancekeeper/InstanceKeeperDispatcher|null[0]
abstract fun destroy() // com.arkivanov.essenty.instancekeeper/InstanceKeeperDispatcher.destroy|destroy(){}[0]
}
abstract interface com.arkivanov.essenty.instancekeeper/InstanceKeeperOwner { // com.arkivanov.essenty.instancekeeper/InstanceKeeperOwner|null[0]
abstract val instanceKeeper // com.arkivanov.essenty.instancekeeper/InstanceKeeperOwner.instanceKeeper|{}instanceKeeper[0]
abstract fun <get-instanceKeeper>(): com.arkivanov.essenty.instancekeeper/InstanceKeeper // com.arkivanov.essenty.instancekeeper/InstanceKeeperOwner.instanceKeeper.<get-instanceKeeper>|<get-instanceKeeper>(){}[0]
}
final fun com.arkivanov.essenty.instancekeeper/InstanceKeeperDispatcher(): com.arkivanov.essenty.instancekeeper/InstanceKeeperDispatcher // com.arkivanov.essenty.instancekeeper/InstanceKeeperDispatcher|InstanceKeeperDispatcher(){}[0]
final inline fun <#A: com.arkivanov.essenty.instancekeeper/InstanceKeeper.Instance> (com.arkivanov.essenty.instancekeeper/InstanceKeeper).com.arkivanov.essenty.instancekeeper/getOrCreate(kotlin/Any, kotlin/Function0<#A>): #A // com.arkivanov.essenty.instancekeeper/getOrCreate|getOrCreate@com.arkivanov.essenty.instancekeeper.InstanceKeeper(kotlin.Any;kotlin.Function0<0:0>){0§<com.arkivanov.essenty.instancekeeper.InstanceKeeper.Instance>}[0]
final inline fun <#A: com.arkivanov.essenty.instancekeeper/InstanceKeeper.Instance> (com.arkivanov.essenty.instancekeeper/InstanceKeeper).com.arkivanov.essenty.instancekeeper/retainingInstance(kotlin/Any? = ..., crossinline kotlin/Function0<#A>): kotlin.properties/PropertyDelegateProvider<kotlin/Any?, kotlin.properties/ReadOnlyProperty<kotlin/Any?, #A>> // com.arkivanov.essenty.instancekeeper/retainingInstance|retainingInstance@com.arkivanov.essenty.instancekeeper.InstanceKeeper(kotlin.Any?;kotlin.Function0<0:0>){0§<com.arkivanov.essenty.instancekeeper.InstanceKeeper.Instance>}[0]
final inline fun <#A: com.arkivanov.essenty.instancekeeper/InstanceKeeper.Instance> (com.arkivanov.essenty.instancekeeper/InstanceKeeperOwner).com.arkivanov.essenty.instancekeeper/retainedInstance(kotlin/Any, kotlin/Function0<#A>): #A // com.arkivanov.essenty.instancekeeper/retainedInstance|retainedInstance@com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner(kotlin.Any;kotlin.Function0<0:0>){0§<com.arkivanov.essenty.instancekeeper.InstanceKeeper.Instance>}[0]
final inline fun <#A: com.arkivanov.essenty.instancekeeper/InstanceKeeper.Instance> (com.arkivanov.essenty.instancekeeper/InstanceKeeperOwner).com.arkivanov.essenty.instancekeeper/retainingInstance(kotlin/Any? = ..., crossinline kotlin/Function0<#A>): kotlin.properties/PropertyDelegateProvider<kotlin/Any?, kotlin.properties/ReadOnlyProperty<kotlin/Any?, #A>> // com.arkivanov.essenty.instancekeeper/retainingInstance|retainingInstance@com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner(kotlin.Any?;kotlin.Function0<0:0>){0§<com.arkivanov.essenty.instancekeeper.InstanceKeeper.Instance>}[0]
final inline fun <#A: kotlin/Any?> (com.arkivanov.essenty.instancekeeper/InstanceKeeper).com.arkivanov.essenty.instancekeeper/getOrCreateSimple(kotlin/Any, kotlin/Function0<#A>): #A // com.arkivanov.essenty.instancekeeper/getOrCreateSimple|getOrCreateSimple@com.arkivanov.essenty.instancekeeper.InstanceKeeper(kotlin.Any;kotlin.Function0<0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (com.arkivanov.essenty.instancekeeper/InstanceKeeper).com.arkivanov.essenty.instancekeeper/retainingSimpleInstance(kotlin/Any? = ..., crossinline kotlin/Function0<#A>): kotlin.properties/PropertyDelegateProvider<kotlin/Any?, kotlin.properties/ReadOnlyProperty<kotlin/Any?, #A>> // com.arkivanov.essenty.instancekeeper/retainingSimpleInstance|retainingSimpleInstance@com.arkivanov.essenty.instancekeeper.InstanceKeeper(kotlin.Any?;kotlin.Function0<0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (com.arkivanov.essenty.instancekeeper/InstanceKeeperOwner).com.arkivanov.essenty.instancekeeper/retainedSimpleInstance(kotlin/Any, kotlin/Function0<#A>): #A // com.arkivanov.essenty.instancekeeper/retainedSimpleInstance|retainedSimpleInstance@com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner(kotlin.Any;kotlin.Function0<0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (com.arkivanov.essenty.instancekeeper/InstanceKeeperOwner).com.arkivanov.essenty.instancekeeper/retainingSimpleInstance(kotlin/Any? = ..., crossinline kotlin/Function0<#A>): kotlin.properties/PropertyDelegateProvider<kotlin/Any?, kotlin.properties/ReadOnlyProperty<kotlin/Any?, #A>> // com.arkivanov.essenty.instancekeeper/retainingSimpleInstance|retainingSimpleInstance@com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner(kotlin.Any?;kotlin.Function0<0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/AutoCloseable> (com.arkivanov.essenty.instancekeeper/InstanceKeeper).com.arkivanov.essenty.instancekeeper/getOrCreateCloseable(kotlin/Any, kotlin/Function0<#A>): #A // com.arkivanov.essenty.instancekeeper/getOrCreateCloseable|getOrCreateCloseable@com.arkivanov.essenty.instancekeeper.InstanceKeeper(kotlin.Any;kotlin.Function0<0:0>){0§<kotlin.AutoCloseable>}[0]
final inline fun <#A: kotlin/AutoCloseable> (com.arkivanov.essenty.instancekeeper/InstanceKeeper).com.arkivanov.essenty.instancekeeper/retainingCloseable(kotlin/Any? = ..., crossinline kotlin/Function0<#A>): kotlin.properties/PropertyDelegateProvider<kotlin/Any?, kotlin.properties/ReadOnlyProperty<kotlin/Any?, #A>> // com.arkivanov.essenty.instancekeeper/retainingCloseable|retainingCloseable@com.arkivanov.essenty.instancekeeper.InstanceKeeper(kotlin.Any?;kotlin.Function0<0:0>){0§<kotlin.AutoCloseable>}[0]
final inline fun <#A: kotlin/AutoCloseable> (com.arkivanov.essenty.instancekeeper/InstanceKeeperOwner).com.arkivanov.essenty.instancekeeper/retainedCloseable(kotlin/Any, kotlin/Function0<#A>): #A // com.arkivanov.essenty.instancekeeper/retainedCloseable|retainedCloseable@com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner(kotlin.Any;kotlin.Function0<0:0>){0§<kotlin.AutoCloseable>}[0]
final inline fun <#A: kotlin/AutoCloseable> (com.arkivanov.essenty.instancekeeper/InstanceKeeperOwner).com.arkivanov.essenty.instancekeeper/retainingCloseable(kotlin/Any? = ..., crossinline kotlin/Function0<#A>): kotlin.properties/PropertyDelegateProvider<kotlin/Any?, kotlin.properties/ReadOnlyProperty<kotlin/Any?, #A>> // com.arkivanov.essenty.instancekeeper/retainingCloseable|retainingCloseable@com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner(kotlin.Any?;kotlin.Function0<0:0>){0§<kotlin.AutoCloseable>}[0]
final inline fun <#A: reified com.arkivanov.essenty.instancekeeper/InstanceKeeper.Instance> (com.arkivanov.essenty.instancekeeper/InstanceKeeper).com.arkivanov.essenty.instancekeeper/getOrCreate(kotlin/Function0<#A>): #A // com.arkivanov.essenty.instancekeeper/getOrCreate|getOrCreate@com.arkivanov.essenty.instancekeeper.InstanceKeeper(kotlin.Function0<0:0>){0§<com.arkivanov.essenty.instancekeeper.InstanceKeeper.Instance>}[0]
final inline fun <#A: reified com.arkivanov.essenty.instancekeeper/InstanceKeeper.Instance> (com.arkivanov.essenty.instancekeeper/InstanceKeeperOwner).com.arkivanov.essenty.instancekeeper/retainedInstance(kotlin/Function0<#A>): #A // com.arkivanov.essenty.instancekeeper/retainedInstance|retainedInstance@com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner(kotlin.Function0<0:0>){0§<com.arkivanov.essenty.instancekeeper.InstanceKeeper.Instance>}[0]
final inline fun <#A: reified kotlin/Any?> (com.arkivanov.essenty.instancekeeper/InstanceKeeper).com.arkivanov.essenty.instancekeeper/getOrCreateSimple(kotlin/Function0<#A>): #A // com.arkivanov.essenty.instancekeeper/getOrCreateSimple|getOrCreateSimple@com.arkivanov.essenty.instancekeeper.InstanceKeeper(kotlin.Function0<0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A: reified kotlin/Any?> (com.arkivanov.essenty.instancekeeper/InstanceKeeperOwner).com.arkivanov.essenty.instancekeeper/retainedSimpleInstance(kotlin/Function0<#A>): #A // com.arkivanov.essenty.instancekeeper/retainedSimpleInstance|retainedSimpleInstance@com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner(kotlin.Function0<0:0>){0§<kotlin.Any?>}[0]
final inline fun <#A: reified kotlin/AutoCloseable> (com.arkivanov.essenty.instancekeeper/InstanceKeeper).com.arkivanov.essenty.instancekeeper/getOrCreateCloseable(kotlin/Function0<#A>): #A // com.arkivanov.essenty.instancekeeper/getOrCreateCloseable|getOrCreateCloseable@com.arkivanov.essenty.instancekeeper.InstanceKeeper(kotlin.Function0<0:0>){0§<kotlin.AutoCloseable>}[0]
final inline fun <#A: reified kotlin/AutoCloseable> (com.arkivanov.essenty.instancekeeper/InstanceKeeperOwner).com.arkivanov.essenty.instancekeeper/retainedCloseable(kotlin/Function0<#A>): #A // com.arkivanov.essenty.instancekeeper/retainedCloseable|retainedCloseable@com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner(kotlin.Function0<0:0>){0§<kotlin.AutoCloseable>}[0]
================================================
FILE: instance-keeper/api/jvm/instance-keeper.api
================================================
public abstract interface annotation class com/arkivanov/essenty/instancekeeper/ExperimentalInstanceKeeperApi : java/lang/annotation/Annotation {
}
public abstract interface class com/arkivanov/essenty/instancekeeper/InstanceKeeper {
public abstract fun get (Ljava/lang/Object;)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;
public abstract fun put (Ljava/lang/Object;Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;)V
public abstract fun remove (Ljava/lang/Object;)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;
}
public abstract interface class com/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance {
public abstract fun onDestroy ()V
}
public final class com/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance$DefaultImpls {
public static fun onDestroy (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;)V
}
public final class com/arkivanov/essenty/instancekeeper/InstanceKeeper$SimpleInstance : com/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance {
public fun <init> (Ljava/lang/Object;)V
public final fun getInstance ()Ljava/lang/Object;
public fun onDestroy ()V
}
public abstract interface class com/arkivanov/essenty/instancekeeper/InstanceKeeperDispatcher : com/arkivanov/essenty/instancekeeper/InstanceKeeper {
public abstract fun destroy ()V
}
public final class com/arkivanov/essenty/instancekeeper/InstanceKeeperDispatcherKt {
public static final fun InstanceKeeperDispatcher ()Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperDispatcher;
}
public final class com/arkivanov/essenty/instancekeeper/InstanceKeeperExtKt {
public static final fun getOrCreate (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;
public static final fun getOrCreateCloseable (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/AutoCloseable;
public static final fun getOrCreateSimple (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
public static final fun retainedCloseable (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/AutoCloseable;
public static final fun retainedInstance (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper$Instance;
public static final fun retainedSimpleInstance (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Ljava/lang/Object;
public static final fun retainingCloseable (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/PropertyDelegateProvider;
public static final fun retainingCloseable (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/PropertyDelegateProvider;
public static synthetic fun retainingCloseable$default (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider;
public static synthetic fun retainingCloseable$default (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider;
public static final fun retainingInstance (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/PropertyDelegateProvider;
public static final fun retainingInstance (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/PropertyDelegateProvider;
public static synthetic fun retainingInstance$default (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider;
public static synthetic fun retainingInstance$default (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider;
public static final fun retainingSimpleInstance (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/PropertyDelegateProvider;
public static final fun retainingSimpleInstance (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;)Lkotlin/properties/PropertyDelegateProvider;
public static synthetic fun retainingSimpleInstance$default (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider;
public static synthetic fun retainingSimpleInstance$default (Lcom/arkivanov/essenty/instancekeeper/InstanceKeeperOwner;Ljava/lang/Object;Lkotlin/jvm/functions/Function0;ILjava/lang/Object;)Lkotlin/properties/PropertyDelegateProvider;
}
public abstract interface class com/arkivanov/essenty/instancekeeper/InstanceKeeperOwner {
public abstract fun getInstanceKeeper ()Lcom/arkivanov/essenty/instancekeeper/InstanceKeeper;
}
================================================
FILE: instance-keeper/build.gradle.kts
================================================
import com.arkivanov.gradle.bundle
import com.arkivanov.gradle.setupBinaryCompatibilityValidator
import com.arkivanov.gradle.setupMultiplatform
import com.arkivanov.gradle.setupPublication
import com.arkivanov.gradle.setupSourceSets
plugins {
id("kotlin-multiplatform")
id("com.android.library")
id("com.arkivanov.gradle.setup")
}
setupMultiplatform()
setupPublication()
setupBinaryCompatibilityValidator()
android {
namespace = "com.arkivanov.essenty.instancekeeper"
}
kotlin {
setupSourceSets {
val android by bundle()
common.main.dependencies {
implementation(project(":utils-internal"))
}
android.main.dependencies {
implementation(deps.androidx.lifecycle.lifecycleViewmodelKtx)
}
}
}
================================================
FILE: instance-keeper/src/androidMain/AndroidManifest.xml
================================================
<?xml version="1.0" encoding="utf-8"?>
<manifest/>
================================================
FILE: instance-keeper/src/androidMain/kotlin/com/arkivanov/essenty/instancekeeper/AndroidExt.kt
================================================
package com.arkivanov.essenty.instancekeeper
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.get
/**
* Creates a new instance of [InstanceKeeper] and attaches it to the provided AndroidX [ViewModelStore].
*
* @param discardRetainedInstances a flag indicating whether any previously retained instances should be
* discarded and destroyed or not, default value is `false`.
*/
fun InstanceKeeper(
viewModelStore: ViewModelStore,
discardRetainedInstances: Boolean = false,
): InstanceKeeper =
ViewModelProvider(
viewModelStore,
object : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel> create(modelClass: Class<T>): T = InstanceKeeperViewModel() as T
}
)
.get<InstanceKeeperViewModel>()
.apply {
if (discardRetainedInstances) {
recreate()
}
}
.instanceKeeperDispatcher
/**
* Creates a new instance of [InstanceKeeper] and attaches it to the AndroidX [ViewModelStore].
*
* @param discardRetainedInstances a flag indicating whether any previously retained instances should be
* discarded and destroyed or not, default value is `false`.
*/
fun ViewModelStoreOwner.instanceKeeper(discardRetainedInstances: Boolean = false): InstanceKeeper =
InstanceKeeper(viewModelStore = viewModelStore, discardRetainedInstances = discardRetainedInstances)
internal class InstanceKeeperViewModel : ViewModel() {
var instanceKeeperDispatcher: InstanceKeeperDispatcher = InstanceKeeperDispatcher()
private set
override fun onCleared() {
instanceKeeperDispatcher.destroy()
}
fun recreate() {
instanceKeeperDispatcher.destroy()
instanceKeeperDispatcher = InstanceKeeperDispatcher()
}
}
================================================
FILE: instance-keeper/src/androidUnitTest/kotlin/com/arkivanov/essenty/instancekeeper/AndroidInstanceKeeperTest.kt
================================================
package com.arkivanov.essenty.instancekeeper
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import kotlin.test.Test
import kotlin.test.assertNotSame
import kotlin.test.assertSame
import kotlin.test.assertTrue
@Suppress("TestFunctionName")
class AndroidInstanceKeeperTest {
@Test
fun retains_instances() {
val owner = TestOwner()
var instanceKeeper = owner.instanceKeeper()
val instance1 = instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
instanceKeeper = owner.instanceKeeper()
val instance2 = instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
assertSame(instance1, instance2)
}
@Test
fun GIVEN_discardRetainedInstances_is_true_on_restore_THEN_instances_not_retained() {
val owner = TestOwner()
var instanceKeeper = owner.instanceKeeper()
val instance1 = instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
instanceKeeper = owner.instanceKeeper(discardRetainedInstances = true)
val instance2 = instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
assertNotSame(instance1, instance2)
}
@Test
fun GIVEN_discardRetainedInstances_is_true_on_restore_THEN_old_instances_destroyed() {
val owner = TestOwner()
val instanceKeeper = owner.instanceKeeper()
val instance1 = instanceKeeper.getOrCreate(key = "key", factory = ::TestInstance)
owner.instanceKeeper(discardRetainedInstances = true)
assertTrue(instance1.isDestroyed)
}
private class TestOwner : ViewModelStoreOwner {
override val viewModelStore: ViewModelStore = ViewModelStore()
}
private class TestInstance : InstanceKeeper.Instance {
var isDestroyed: Boolean = false
override fun onDestroy() {
isDestroyed = true
}
}
}
================================================
FILE: instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/DefaultInstanceKeeperDispatcher.kt
================================================
package com.arkivanov.essenty.instancekeeper
import com.arkivanov.essenty.instancekeeper.InstanceKeeper.Instance
internal class DefaultInstanceKeeperDispatcher : InstanceKeeperDispatcher {
private val map = HashMap<Any, Instance>()
private var isDestroyed = false
override fun get(key: Any): Instance? =
map[key]
override fun put(key: Any, instance: Instance) {
check(key !in map) { "Another instance is already associated with the key: $key" }
map[key] = instance
if (isDestroyed) {
instance.onDestroy()
}
}
override fun remove(key: Any): Instance? =
map.remove(key)
override fun destroy() {
if (!isDestroyed) {
isDestroyed = true
map.values.toList().forEach(Instance::onDestroy)
}
}
}
================================================
FILE: instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/ExperimentalInstanceKeeperApi.kt
================================================
package com.arkivanov.essenty.instancekeeper
/**
* Marks experimental API in Essenty. An experimental API can be changed or removed at any time.
*/
@RequiresOptIn(level = RequiresOptIn.Level.WARNING)
@Retention(AnnotationRetention.BINARY)
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
annotation class ExperimentalInstanceKeeperApi
================================================
FILE: instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeper.kt
================================================
package com.arkivanov.essenty.instancekeeper
/**
* A generic keyed store of [Instance] objects. Instances are destroyed at the end of the
* [InstanceKeeper]'s scope, which is typically tied to the scope of a back stack entry.
* E.g. instances are retained over Android configuration changes, and destroyed when the
* corresponding back stack entry is popped.
*/
interface InstanceKeeper {
/**
* Returns an instance with the given [key], or `null` if no instance with the given key exists.
*/
fun get(key: Any): Instance?
/**
* Stores the given [instance] with the given [key]. Throws [IllegalStateException] if another
* instance is already registered with the given [key].
*/
fun put(key: Any, instance: Instance)
/**
* Removes an instance with the given [key]. This does not destroy the instance.
*/
fun remove(key: Any): Instance?
/**
* Represents a destroyable instance.
*/
interface Instance {
/**
* Called at the end of the [InstanceKeeper]'s scope.
*/
fun onDestroy() {}
}
/**
* Are simple [Instance] wrapper for cases when destroying is not required.
*/
class SimpleInstance<out T>(val instance: T) : Instance
}
================================================
FILE: instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeperDispatcher.kt
================================================
package com.arkivanov.essenty.instancekeeper
import kotlin.js.JsName
/**
* Represents a destroyable [InstanceKeeper].
*/
interface InstanceKeeperDispatcher : InstanceKeeper {
/**
* Destroys all existing instances. Instances are not cleared, so that they can be
* accessed later. Any new instances will be immediately destroyed.
*/
fun destroy()
}
/**
* Creates a default implementation of [InstanceKeeperDispatcher].
*/
@JsName("instanceKeeperDispatcher")
fun InstanceKeeperDispatcher(): InstanceKeeperDispatcher = DefaultInstanceKeeperDispatcher()
================================================
FILE: instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeperExt.kt
================================================
package com.arkivanov.essenty.instancekeeper
import com.arkivanov.essenty.instancekeeper.InstanceKeeper.SimpleInstance
import kotlin.properties.PropertyDelegateProvider
import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.typeOf
/**
* Returns a previously stored [InstanceKeeper.Instance] of type [T] with the given key,
* or creates and stores a new one if it doesn't exist.
*/
inline fun <T : InstanceKeeper.Instance> InstanceKeeper.getOrCreate(key: Any, factory: () -> T): T {
@Suppress("UNCHECKED_CAST") // Assuming the type per key is always the same
var instance: T? = get(key) as T?
if (instance == null) {
instance = factory()
put(key, instance)
}
return instance
}
/**
* Returns a previously stored [InstanceKeeper.Instance] of type [T],
* or creates and stores a new one if it doesn't exist. Uses `typeOf<T>()` as key.
*
* Deprecated. Using `getOrCreate` without the `key` parameter may crash when [T]
* refers to a type with type (generic) parameters (e.g. a `StateFlow>`) and R8 Full Mode enabled.
* See [KT-42913](https://youtrack.jetbrains.com/issue/KT-42913) for more information.
*/
@Deprecated(
message = "Using getOrCreate without the key parameter is unsafe. Please use the other variant.",
replaceWith = ReplaceWith(
expression = "getOrCreate(key = , factory = factory)",
)
)
inline fun <reified T : InstanceKeeper.Instance> InstanceKeeper.getOrCreate(factory: () -> T): T =
getOrCreate(key = typeOf<T>(), factory = factory)
/**
* Returns a previously stored [AutoCloseable] instance of type [T] with the given [key],
* or creates and stores a new one if it doesn't exist.
*
* @param key a key to store and retrieve the instance.
* @param factory a function creating a new instance of type [T].
*/
inline fun <T : AutoCloseable> InstanceKeeper.getOrCreateCloseable(key: Any, factory: () -> T): T =
getOrCreate(key = key) {
val instance = factory()
object : InstanceKeeper.Instance {
val instance: T = instance
override fun onDestroy() {
instance.close()
}
}
}.instance
/**
* Returns a previously stored [AutoCloseable] instance of type [T],
* or creates and stores a new one if it doesn't exist. Uses `typeOf<T>()` as key.
*
* Deprecated. Using `getOrCreateCloseable` without the `key` parameter may crash when [T]
* refers to a type with type (generic) parameters (e.g. a `StateFlow>`) and R8 Full Mode enabled.
* See [KT-42913](https://youtrack.jetbrains.com/issue/KT-42913) for more information.
*
* @param factory a function creating a new instance of type [T].
*/
@Deprecated(
message = "Using getOrCreateCloseable without the key parameter is unsafe. Please use the other variant.",
replaceWith = ReplaceWith(
expression = "getOrCreateCloseable(key = , factory = factory)",
)
)
inline fun <reified T : AutoCloseable> InstanceKeeper.getOrCreateCloseable(factory: () -> T): T =
getOrCreateCloseable(key = typeOf<T>(), factory = factory)
/**
* A convenience function for [InstanceKeeper.getOrCreate].
*/
inline fun <T : InstanceKeeper.Instance> InstanceKeeperOwner.retainedInstance(key: Any, factory: () -> T): T =
instanceKeeper.getOrCreate(key = key, factory = factory)
/**
* A convenience function for [InstanceKeeper.getOrCreate].
*
* Deprecated. Using `retainedInstance` without the `key` parameter may crash when [T]
* refers to a type with type (generic) parameters (e.g. a `StateFlow>`) and R8 Full Mode enabled.
* See [KT-42913](https://youtrack.jetbrains.com/issue/KT-42913) for more information.
*/
@Deprecated(
message = "Using retainedInstance without the key parameter is unsafe. Please use the other variant.",
replaceWith = ReplaceWith(
expression = "retainedInstance(key = , factory = factory)",
)
)
inline fun <reified T : InstanceKeeper.Instance> InstanceKeeperOwner.retainedInstance(factory: () -> T): T =
instanceKeeper.getOrCreate(key = typeOf<T>(), factory = factory)
/**
* A convenience function for [InstanceKeeper.getOrCreateCloseable].
*/
inline fun <T : AutoCloseable> InstanceKeeperOwner.retainedCloseable(key: Any, factory: () -> T): T =
instanceKeeper.getOrCreateCloseable(key = key, factory = factory)
/**
* A convenience function for [InstanceKeeper.getOrCreateCloseable].
*
* Deprecated. Using `retainedCloseable` without the `key` parameter may crash when [T]
* refers to a type with type (generic) parameters (e.g. a `StateFlow>`) and R8 Full Mode enabled.
* See [KT-42913](https://youtrack.jetbrains.com/issue/KT-42913) for more information.
*/
@Deprecated(
message = "Using retainedCloseable without the key parameter is unsafe. Please use the other variant.",
replaceWith = ReplaceWith(
expression = "retainedCloseable(key = , factory = factory)",
)
)
inline fun <reified T : AutoCloseable> InstanceKeeperOwner.retainedCloseable(factory: () -> T): T =
instanceKeeper.getOrCreateCloseable(key = typeOf<T>(), factory = factory)
/**
* Returns a previously stored instance of type [T] with the given key,
* or creates and stores a new one if it doesn't exist.
*
* This overload is for simple cases when instance destroying is not required.
*/
inline fun <T> InstanceKeeper.getOrCreateSimple(key: Any, factory: () -> T): T =
getOrCreate(key = key) { SimpleInstance(factory()) }
.instance
/**
* Returns a previously stored instance of type [T],
* or creates and stores a new one if it doesn't exist. Uses `typeOf<T>()` as key.
*
* This overload is for simple cases when instance destroying is not required.
*
* Deprecated. Using `getOrCreateSimple` without the `key` parameter may crash when [T]
* refers to a type with type (generic) parameters (e.g. a `StateFlow>`) and R8 Full Mode enabled.
* See [KT-42913](https://youtrack.jetbrains.com/issue/KT-42913) for more information.
*/
@Deprecated(
message = "Using getOrCreateSimple without the key parameter is unsafe. Please use the other variant.",
replaceWith = ReplaceWith(
expression = "getOrCreateSimple(key = , factory = factory)",
)
)
inline fun <reified T> InstanceKeeper.getOrCreateSimple(factory: () -> T): T =
getOrCreateSimple(key = typeOf<T>(), factory = factory)
/**
* A convenience function for [InstanceKeeper.getOrCreateSimple].
*/
inline fun <T> InstanceKeeperOwner.retainedSimpleInstance(key: Any, factory: () -> T): T =
instanceKeeper.getOrCreateSimple(key = key, factory = factory)
/**
* A convenience function for [InstanceKeeper.getOrCreateSimple].
*
* Deprecated. Using `retainedSimpleInstance` without the `key` parameter may crash when [T]
* refers to a type with type (generic) parameters (e.g. a `StateFlow>`) and R8 Full Mode enabled.
* See [KT-42913](https://youtrack.jetbrains.com/issue/KT-42913) for more information.
*/
@Deprecated(
message = "Using retainedSimpleInstance without the key parameter is unsafe. Please use the other variant.",
replaceWith = ReplaceWith(
expression = "retainedSimpleInstance(key = , factory = factory)",
)
)
inline fun <reified T> InstanceKeeperOwner.retainedSimpleInstance(factory: () -> T): T =
instanceKeeper.getOrCreateSimple(key = typeOf<T>(), factory = factory)
/**
* Helper function for creating a
* [delegated property]((https://kotlinlang.org/docs/delegated-properties.html)) whose value is
* automatically retained using [InstanceKeeper].
*
* Example:
*
* ```
* import com.arkivanov.essenty.instancekeeper.InstanceKeeper
* import com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner
* import com.arkivanov.essenty.instancekeeper.retainingInstance
*
* class SomeLogic(override val instanceKeeper: InstanceKeeper) : InstanceKeeperOwner {
* private val viewModel by retainingInstance { RetainedViewModel() }
*
* private class RetainedViewModel : InstanceKeeper.Instance {
* // ...
* }
* }
* ```
*
* @param key an optional key for storing and retrieving the instance. If not provided, then the
* property name is used as a key.
* @param factory a function creating a new instance of type [T].
* @param T a type of the property, extends [InstanceKeeper.Instance].
* @return [PropertyDelegateProvider] of type [T], typically used to define a delegated property.
* @see getOrCreate
*/
@ExperimentalInstanceKeeperApi
inline fun <T : InstanceKeeper.Instance> InstanceKeeper.retainingInstance(
key: Any? = null,
crossinline factory: () -> T,
): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>> =
PropertyDelegateProvider { _, property ->
val instance = getOrCreate(key = key ?: "RETAINING_INSTANCE_${property.name}", factory = factory)
ReadOnlyProperty { _, _ -> instance }
}
/**
* Helper function for creating a
* [delegated property]((https://kotlinlang.org/docs/delegated-properties.html)) whose value is
* automatically retained using [InstanceKeeper].
*
* Example:
*
* ```
* import com.arkivanov.essenty.instancekeeper.InstanceKeeper
* import com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner
* import com.arkivanov.essenty.instancekeeper.retainingInstance
*
* class SomeLogic(override val instanceKeeper: InstanceKeeper) : InstanceKeeperOwner {
* private val viewModel by retainingInstance { RetainedViewModel() }
*
* private class RetainedViewModel : InstanceKeeper.Instance {
* // ...
* }
* }
* ```
*
* @param key an optional key for storing and retrieving the instance. If not provided, then the
* property name is used as a key.
* @param factory a function creating a new instance of type [T].
* @param T a type of the property, extends [InstanceKeeper.Instance].
* @return [PropertyDelegateProvider] of type [T], typically used to define a delegated property.
* @see getOrCreate
*/
@ExperimentalInstanceKeeperApi
inline fun <T : InstanceKeeper.Instance> InstanceKeeperOwner.retainingInstance(
key: Any? = null,
crossinline factory: () -> T,
): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>> =
instanceKeeper.retainingInstance(key = key, factory = factory)
/**
* Helper function for creating a
* [delegated property]((https://kotlinlang.org/docs/delegated-properties.html)) whose value is
* automatically retained using [InstanceKeeper].
*
* This overload is for simple cases when instance destroying is not required.
*
* Example:
*
* ```
* import com.arkivanov.essenty.instancekeeper.InstanceKeeper
* import com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner
* import com.arkivanov.essenty.instancekeeper.retainingSimpleInstance
*
* class SomeLogic(override val instanceKeeper: InstanceKeeper) : InstanceKeeperOwner {
* private val viewModel by retainingSimpleInstance { RetainedClass() }
*
* private class RetainedClass {
* // ...
* }
* }
* ```
*
* @param key an optional key for storing and retrieving the instance. If not provided, then the
* property name is used as a key.
* @param factory a function creating a new instance of type [T].
* @param T a type of the property.
* @return [PropertyDelegateProvider] of type [T], typically used to define a delegated property.
* @see getOrCreateSimple
*/
@ExperimentalInstanceKeeperApi
inline fun <T> InstanceKeeper.retainingSimpleInstance(
key: Any? = null,
crossinline factory: () -> T,
): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>> =
PropertyDelegateProvider { _, property ->
val instance = getOrCreateSimple(key = key ?: "RETAINING_SIMPLE_INSTANCE_${property.name}", factory = factory)
ReadOnlyProperty { _, _ -> instance }
}
/**
* Helper function for creating a
* [delegated property]((https://kotlinlang.org/docs/delegated-properties.html)) whose value is
* automatically retained using [InstanceKeeper].
*
* This overload is for simple cases when instance destroying is not required.
*
* Example:
*
* ```
* import com.arkivanov.essenty.instancekeeper.InstanceKeeper
* import com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner
* import com.arkivanov.essenty.instancekeeper.retainingSimpleInstance
*
* class SomeLogic(override val instanceKeeper: InstanceKeeper) : InstanceKeeperOwner {
* private val viewModel by retainingSimpleInstance { RetainedClass() }
*
* private class RetainedClass {
* // ...
* }
* }
* ```
*
* @param key an optional key for storing and retrieving the instance. If not provided, then the
* property name is used as a key.
* @param factory a function creating a new instance of type [T].
* @param T a type of the property.
* @return [PropertyDelegateProvider] of type [T], typically used to define a delegated property.
* @see getOrCreateSimple
*/
@ExperimentalInstanceKeeperApi
inline fun <T> InstanceKeeperOwner.retainingSimpleInstance(
key: Any? = null,
crossinline factory: () -> T,
): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>> =
instanceKeeper.retainingSimpleInstance(key = key, factory = factory)
/**
* Helper function for creating a
* [delegated property]((https://kotlinlang.org/docs/delegated-properties.html)) whose value is
* automatically retained using [InstanceKeeper].
*
* This overload is for simple cases when instance destroying is not required.
*
* Example:
*
* ```
* import com.arkivanov.essenty.instancekeeper.InstanceKeeper
* import com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner
* import com.arkivanov.essenty.instancekeeper.retainingSimpleInstance
*
* class SomeLogic(override val instanceKeeper: InstanceKeeper) : InstanceKeeperOwner {
* private val viewModel by retainingSimpleInstance { RetainedViewModel() }
*
* private class RetainedViewModel : AutoCloseable {
* // ...
* }
* }
* ```
*
* @param key an optional key for storing and retrieving the instance. If not provided, then the
* property name is used as a key.
* @param factory a function creating a new instance of type [T].
* @param T a type of the property, extends [AutoCloseable].
* @return [PropertyDelegateProvider] of type [T], typically used to define a delegated property.
* @see getOrCreateCloseable
*/
@ExperimentalInstanceKeeperApi
inline fun <T : AutoCloseable> InstanceKeeper.retainingCloseable(
key: Any? = null,
crossinline factory: () -> T,
): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>> =
PropertyDelegateProvider { _, property ->
val instance = getOrCreateCloseable(key = key ?: "RETAINING_CLOSEABLE_${property.name}", factory = factory)
ReadOnlyProperty { _, _ -> instance }
}
/**
* Helper function for creating a
* [delegated property]((https://kotlinlang.org/docs/delegated-properties.html)) whose value is
* automatically retained using [InstanceKeeper].
*
* This overload is for simple cases when instance destroying is not required.
*
* Example:
*
* ```
* import com.arkivanov.essenty.instancekeeper.InstanceKeeper
* import com.arkivanov.essenty.instancekeeper.InstanceKeeperOwner
* import com.arkivanov.essenty.instancekeeper.retainingSimpleInstance
*
* class SomeLogic(override val instanceKeeper: InstanceKeeper) : InstanceKeeperOwner {
* private val viewModel by retainingSimpleInstance { RetainedViewModel() }
*
* private class RetainedViewModel : AutoCloseable {
* // ...
* }
* }
* ```
*
* @param key an optional key for storing and retrieving the instance. If not provided, then the
* property name is used as a key.
* @param factory a function creating a new instance of type [T].
* @param T a type of the property, extends [AutoCloseable].
* @return [PropertyDelegateProvider] of type [T], typically used to define a delegated property.
* @see getOrCreateCloseable
*/
@ExperimentalInstanceKeeperApi
inline fun <T : AutoCloseable> InstanceKeeperOwner.retainingCloseable(
key: Any? = null,
crossinline factory: () -> T,
): PropertyDelegateProvider<Any?, ReadOnlyProperty<Any?, T>> =
instanceKeeper.retainingCloseable(key = key, factory = factory)
================================================
FILE: instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeperOwner.kt
================================================
package com.arkivanov.essenty.instancekeeper
/**
* Represents a holder of [InstanceKeeper].
*/
interface InstanceKeeperOwner {
val instanceKeeper: InstanceKeeper
}
================================================
FILE: instance-keeper/src/commonTest/kotlin/com/arkivanov/essenty/instancekeeper/DefaultInstanceKeeperDispatcherTest.kt
================================================
package com.arkivanov.essenty.instancekeeper
import kotlin.test.Test
import kotlin.test.assertFails
import kotlin.test.assertFalse
import kotlin.test.assertNull
import kotlin.test.assertSame
import kotlin.test.assertTrue
@Suppress("TestFunctionName")
class DefaultInstanceKeeperDispatcherTest {
@Test
fun GIVEN_no_instance_put_WHEN_get_THEN_returns_null() {
val dispatcher = DefaultInstanceKeeperDispatcher()
dispatcher.put(key = "key1", instance = TestInstance())
val returnedInstance = dispatcher.get(key = "key2")
assertNull(returnedInstance)
}
@Test
fun GIVEN_instance_put_WHEN_get_THEN_returns_instance() {
val dispatcher = DefaultInstanceKeeperDispatcher()
val instance1 = TestInstance()
val instance2 = TestInstance()
dispatcher.put(key = "key1", instance = instance1)
dispatcher.put(key = "key2", instance = instance2)
val returnedInstance1 = dispatcher.get(key = "key1")
val returnedInstance2 = dispatcher.get(key = "key2")
assertSame(instance1, returnedInstance1)
assertSame(instance2, returnedInstance2)
}
@Test
fun GIVEN_instance_put_WHEN_put_with_same_key_THEN_throws_exception() {
val dispatcher = DefaultInstanceKeeperDispatcher()
dispatcher.put(key = "key", instance = TestInstance())
assertFails {
dispatcher.put(key = "key", instance = TestInstance())
}
}
@Test
fun GIVEN_instance_with_same_key_put_twice_WHEN_get_THEN_returns_original_instance() {
val dispatcher = DefaultInstanceKeeperDispatcher()
val instance = TestInstance()
dispatcher.put(key = "key", instance = instance)
try {
dispatcher.put(key = "key", instance = TestInstance())
} catch (ignored: Exception) {
}
val returnedInstance = dispatcher.get(key = "key")
assertSame(instance, returnedInstance)
}
@Test
fun GIVEN_instances_put_WHEN_destroy_THEN_instances_destroyed() {
val dispatcher = DefaultInstanceKeeperDispatcher()
val instance1 = TestInstance()
val instance2 = TestInstance()
dispatcher.put(key = "key1", instance = instance1)
dispatcher.put(key = "key2", instance = instance2)
dispatcher.destroy()
assertTrue(instance1.isDestroyed)
assertTrue(instance2.isDestroyed)
}
@Test
fun GIVEN_instance_put_and_destroyed_WHEN_get_THEN_returns_instance() {
val dispatcher = DefaultInstanceKeeperDispatcher()
val instance = TestInstance()
dispatcher.put(key = "key", instance = instance)
dispatcher.destroy()
val returnedInstance = dispatcher.get(key = "key")
assertSame(instance, returnedInstance)
}
@Test
fun GIVEN_destroyed_WHEN_put_THEN_does_not_throw() {
val dispatcher = DefaultInstanceKeeperDispatcher()
val instance = TestInstance()
dispatcher.destroy()
dispatcher.put(key = "key", instance = instance)
}
@Test
fun GIVEN_destroyed_and_put_WHEN_get_THEN_returns_instance() {
val dispatcher = DefaultInstanceKeeperDispatcher()
val instance = TestInstance()
dispatcher.destroy()
dispatcher.put(key = "key", instance = instance)
val returnedInstance = dispatcher.get(key = "key")
assertSame(instance, returnedInstance)
}
@Test
fun GIVEN_not_empty_and_destroyed_WHEN_destroy_THEN_instance_is_not_destroyed_second_time() {
val dispatcher = DefaultInstanceKeeperDispatcher()
val instance = TestInstance()
dispatcher.put(key = "key", instance = instance)
dispatcher.destroy()
instance.isDestroyed = false
dispatcher.destroy()
assertFalse(instance.isDestroyed)
}
@Test
fun GEVEN_instance_not_put_WHEN_remove_THEN_returns_null() {
val dispatcher = DefaultInstanceKeeperDispatcher()
val returnedInstance = dispatcher.remove(key = "key")
assertNull(returnedInstance)
}
@Test
fun GEVEN_instance_put_WHEN_remove_THEN_returns_instance() {
val instance = TestInstance()
val dispatcher = DefaultInstanceKeeperDispatcher()
dispatcher.put("key", instance)
val returnedInstance = dispatcher.remove(key = "key")
assertSame(instance, returnedInstance)
}
@Test
fun GEVEN_instance_removed_WHEN_remove_THEN_returns_null() {
val instance = TestInstance()
val dispatcher = DefaultInstanceKeeperDispatcher()
dispatcher.put("key", instance)
dispatcher.remove("key")
val returnedInstance = dispatcher.remove(key = "key")
assertNull(returnedInstance)
}
@Test
fun GIVEN_instance_put_and_destroyed_WHEN_remove_THEN_returns_instance() {
val dispatcher = DefaultInstanceKeeperDispatcher()
val instance = TestInstance()
dispatcher.put(key = "key", instance = instance)
dispatcher.destroy()
val returnedInstance = dispatcher.remove(key = "key")
assertSame(instance, returnedInstance)
}
@Test
fun GIVEN_instance_put_and_destroyed_and_removed_WHEN_remove_THEN_returns_null() {
val dispatcher = DefaultInstanceKeeperDispatcher()
val instance = TestInstance()
dispatcher.put(key = "key", instance = instance)
dispatcher.destroy()
dispatcher.remove(key = "key")
val returnedInstance = dispatcher.remove(key = "key")
assertNull(returnedInstance)
}
@Test
fun GIVEN_instance_put_WHEN_destroy_and_instance_calls_get_in_onDestroy_THEN_returns_null() {
val dispatcher = DefaultInstanceKeeperDispatcher()
var returnedInstance: Any? = Any()
dispatcher.put(key = "key", instance = TestInstance(onDestroy = { returnedInstance = null }))
dispatcher.destroy()
assertNull(returnedInstance)
}
@Test
fun GIVEN_instances_put_WHEN_destroy_and_instance_calls_remove_in_onDestroy_THEN_does_not_throw() {
val dispatcher = DefaultInstanceKeeperDispatcher()
dispatcher.put(key = "key1", instance = TestInstance(onDestroy = { dispatcher.remove(key = "key1") }))
dispatcher.put(key = "key2", instance = TestInstance(onDestroy = { dispatcher.remove(key = "key2") }))
dispatcher.destroy()
}
private class TestInstance(
private val onDestroy: () -> Unit = {},
) : InstanceKeeper.Instance {
var isDestroyed: Boolean = false
override fun onDestroy() {
isDestroyed = true
onDestroy.invoke()
}
}
}
================================================
FILE: instance-keeper/src/commonTest/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeperExtTest.kt
================================================
package com.arkivanov.essenty.instancekeeper
import kotlin.test.Test
import kotlin.test.assertNotSame
import kotlin.test.assertSame
gitextract_dhrtofoi/
├── .editorconfig
├── .github/
│ ├── FUNDING.yml
│ └── workflows/
│ ├── build.yml
│ └── publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── back-handler/
│ ├── .gitignore
│ ├── api/
│ │ ├── android/
│ │ │ └── back-handler.api
│ │ ├── back-handler.klib.api
│ │ └── jvm/
│ │ └── back-handler.api
│ ├── build.gradle.kts
│ └── src/
│ ├── androidMain/
│ │ ├── AndroidManifest.xml
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── backhandler/
│ │ └── AndroidBackHandler.kt
│ ├── androidUnitTest/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── backhandler/
│ │ ├── AndroidBackHandlerTest.kt
│ │ ├── AndroidBackHandlerWithLifecycleTest.kt
│ │ └── OnBackPressedCallbackAdapterTest.kt
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── backhandler/
│ │ ├── BackCallback.kt
│ │ ├── BackDispatcher.kt
│ │ ├── BackEvent.kt
│ │ ├── BackHandler.kt
│ │ ├── BackHandlerOwner.kt
│ │ ├── DefaultBackDispatcher.kt
│ │ └── Utils.kt
│ └── commonTest/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── backhandler/
│ └── DefaultBackDispatcherTest.kt
├── build.gradle.kts
├── deps.versions.toml
├── detekt.yml
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradle.properties
├── gradlew
├── gradlew.bat
├── instance-keeper/
│ ├── .gitignore
│ ├── api/
│ │ ├── android/
│ │ │ └── instance-keeper.api
│ │ ├── instance-keeper.klib.api
│ │ └── jvm/
│ │ └── instance-keeper.api
│ ├── build.gradle.kts
│ └── src/
│ ├── androidMain/
│ │ ├── AndroidManifest.xml
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── instancekeeper/
│ │ └── AndroidExt.kt
│ ├── androidUnitTest/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── instancekeeper/
│ │ └── AndroidInstanceKeeperTest.kt
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── instancekeeper/
│ │ ├── DefaultInstanceKeeperDispatcher.kt
│ │ ├── ExperimentalInstanceKeeperApi.kt
│ │ ├── InstanceKeeper.kt
│ │ ├── InstanceKeeperDispatcher.kt
│ │ ├── InstanceKeeperExt.kt
│ │ └── InstanceKeeperOwner.kt
│ └── commonTest/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── instancekeeper/
│ ├── DefaultInstanceKeeperDispatcherTest.kt
│ └── InstanceKeeperExtTest.kt
├── lifecycle/
│ ├── .gitignore
│ ├── api/
│ │ ├── android/
│ │ │ └── lifecycle.api
│ │ ├── jvm/
│ │ │ └── lifecycle.api
│ │ └── lifecycle.klib.api
│ ├── build.gradle.kts
│ └── src/
│ ├── androidMain/
│ │ ├── AndroidManifest.xml
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── lifecycle/
│ │ └── AndroidExt.kt
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── lifecycle/
│ │ ├── Lifecycle.kt
│ │ ├── LifecycleExt.kt
│ │ ├── LifecycleOwner.kt
│ │ ├── LifecycleRegistry.kt
│ │ ├── LifecycleRegistryExt.kt
│ │ └── LifecycleRegistryImpl.kt
│ ├── commonTest/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── lifecycle/
│ │ ├── LifecycleExtTest.kt
│ │ └── LifecycleRegistryTest.kt
│ ├── itvosMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── lifecycle/
│ │ └── ApplicationLifecycle.kt
│ └── itvosTest/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── lifecycle/
│ ├── ApplicationLifecyclePlatformTest.kt
│ └── ApplicationLifecycleTest.kt
├── lifecycle-coroutines/
│ ├── .gitignore
│ ├── api/
│ │ ├── android/
│ │ │ └── lifecycle-coroutines.api
│ │ ├── jvm/
│ │ │ └── lifecycle-coroutines.api
│ │ └── lifecycle-coroutines.klib.api
│ ├── build.gradle.kts
│ └── src/
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── lifecycle/
│ │ └── coroutines/
│ │ ├── CoroutineScopeWithLifecycle.kt
│ │ ├── DispatchersExt.kt
│ │ ├── FlowWithLifecycle.kt
│ │ └── RepeatOnLifecycle.kt
│ └── commonTest/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── lifecycle/
│ └── coroutines/
│ ├── CoroutineScopeWithLifecycleTest.kt
│ ├── DispatchersExtTest.kt
│ └── LifecycleCoroutinesExtTest.kt
├── lifecycle-reaktive/
│ ├── .gitignore
│ ├── api/
│ │ ├── android/
│ │ │ └── lifecycle-reaktive.api
│ │ ├── jvm/
│ │ │ └── lifecycle-reaktive.api
│ │ └── lifecycle-reaktive.klib.api
│ ├── build.gradle.kts
│ └── src/
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── lifecycle/
│ │ └── reaktive/
│ │ └── DisposableWithLifecycle.kt
│ └── commonTest/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── lifecycle/
│ └── reaktive/
│ └── DisposableWithLifecycleTest.kt
├── settings.gradle.kts
├── state-keeper/
│ ├── .gitignore
│ ├── api/
│ │ ├── android/
│ │ │ └── state-keeper.api
│ │ ├── jvm/
│ │ │ └── state-keeper.api
│ │ └── state-keeper.klib.api
│ ├── build.gradle.kts
│ └── src/
│ ├── androidMain/
│ │ ├── AndroidManifest.xml
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── statekeeper/
│ │ ├── AndroidExt.kt
│ │ ├── BundleExt.kt
│ │ └── PersistableBundleExt.kt
│ ├── androidUnitTest/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── statekeeper/
│ │ ├── AndroidStateKeeperTest.kt
│ │ ├── BundleExtTest.kt
│ │ └── TestUtils.android.kt
│ ├── commonMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── statekeeper/
│ │ ├── DefaultStateKeeperDispatcher.kt
│ │ ├── ExperimentalStateKeeperApi.kt
│ │ ├── PolymorphicSerializer.kt
│ │ ├── SerializableContainer.kt
│ │ ├── StateKeeper.kt
│ │ ├── StateKeeperDispatcher.kt
│ │ ├── StateKeeperExt.kt
│ │ ├── StateKeeperOwner.kt
│ │ ├── Utils.kt
│ │ └── base64/
│ │ ├── Decoder.kt
│ │ ├── Dictionaries.kt
│ │ ├── Encoder.kt
│ │ └── README.md
│ ├── commonTest/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── statekeeper/
│ │ ├── CodingTest.kt
│ │ ├── DefaultStateKeeperDispatcherTest.kt
│ │ ├── PolymorphicSerializerTest.kt
│ │ ├── SerializableContainerTest.kt
│ │ ├── SerializableData.kt
│ │ ├── StateKeeperExtTest.kt
│ │ ├── TestUtils.kt
│ │ └── base64/
│ │ ├── Base64ImplTest.kt
│ │ └── README.md
│ ├── javaMain/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── statekeeper/
│ │ └── Utils.java.kt
│ ├── jsTest/
│ │ └── kotlin/
│ │ └── com/
│ │ └── arkivanov/
│ │ └── essenty/
│ │ └── statekeeper/
│ │ └── DefaultStateKeeperDispatcherJsTest.kt
│ └── nonJavaMain/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── statekeeper/
│ └── Utils.kt
├── state-keeper-benchmarks/
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src/
│ ├── main/
│ │ └── res/
│ │ └── AndroidManifest.xml
│ └── test/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── statekeeper/
│ └── benchmarks/
│ └── Benchmarks.kt
├── tools/
│ └── check-publication/
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src/
│ ├── androidMain/
│ │ └── AndroidManifest.xml
│ └── commonMain/
│ └── kotlin/
│ └── com/
│ └── arkivanov/
│ └── essenty/
│ └── tools/
│ └── checkpublication/
│ └── Dummy.kt
└── utils-internal/
├── .gitignore
├── build.gradle.kts
└── src/
├── androidMain/
│ └── AndroidManifest.xml
└── commonMain/
└── kotlin/
└── com/
└── arkivanov/
└── essenty/
└── utils/
└── internal/
├── ExperimentalEssentyApi.kt
└── InternalEssentyApi.kt
Condensed preview — 137 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (426K chars).
[
{
"path": ".editorconfig",
"chars": 3924,
"preview": "root = true\n\n[*]\ninsert_final_newline = true\n\n[{*.kt, *.kts}]\nmax_line_length = 140\nij_kotlin_packages_to_use_import_on_"
},
{
"path": ".github/FUNDING.yml",
"chars": 116,
"preview": "# These are supported funding model platforms\n\ngithub: arkivanov\ncustom: [\"https://www.buymeacoffee.com/arkivanov\"]\n"
},
{
"path": ".github/workflows/build.yml",
"chars": 1021,
"preview": "name: Build\n\non:\n pull_request:\n paths-ignore:\n - 'docs/**'\n\njobs:\n linux-build:\n name: Build on Linux\n "
},
{
"path": ".github/workflows/publish.yml",
"chars": 2249,
"preview": "name: Publish\n\non:\n workflow_dispatch:\n\njobs:\n create-staging-repository:\n runs-on: ubuntu-latest\n name: Create "
},
{
"path": ".gitignore",
"chars": 62,
"preview": "*.iml\n.gradle\nlocal.properties\n.idea\n/build\n.DS_Store\n.kotlin\n"
},
{
"path": "LICENSE",
"chars": 11357,
"preview": " Apache License\n Version 2.0, January 2004\n "
},
{
"path": "README.md",
"chars": 21743,
"preview": "[](https://search.mav"
},
{
"path": "back-handler/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "back-handler/api/android/back-handler.api",
"chars": 5241,
"preview": "public final class com/arkivanov/essenty/backhandler/AndroidBackHandlerKt {\n\tpublic static final fun BackHandler (Landro"
},
{
"path": "back-handler/api/back-handler.klib.api",
"chars": 11115,
"preview": "// Klib ABI Dump\n// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxX64, macosArm64, macosX64, tvosArm64, tvosSi"
},
{
"path": "back-handler/api/jvm/back-handler.api",
"chars": 4584,
"preview": "public abstract class com/arkivanov/essenty/backhandler/BackCallback {\n\tpublic static final field Companion Lcom/arkivan"
},
{
"path": "back-handler/build.gradle.kts",
"chars": 769,
"preview": "import com.arkivanov.gradle.bundle\nimport com.arkivanov.gradle.setupBinaryCompatibilityValidator\nimport com.arkivanov.gr"
},
{
"path": "back-handler/src/androidMain/AndroidManifest.xml",
"chars": 51,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest/>\n"
},
{
"path": "back-handler/src/androidMain/kotlin/com/arkivanov/essenty/backhandler/AndroidBackHandler.kt",
"chars": 2914,
"preview": "package com.arkivanov.essenty.backhandler\n\nimport androidx.activity.BackEventCompat\nimport androidx.activity.OnBackPress"
},
{
"path": "back-handler/src/androidUnitTest/kotlin/com/arkivanov/essenty/backhandler/AndroidBackHandlerTest.kt",
"chars": 9436,
"preview": "package com.arkivanov.essenty.backhandler\n\nimport androidx.activity.BackEventCompat\nimport androidx.activity.BackEventCo"
},
{
"path": "back-handler/src/androidUnitTest/kotlin/com/arkivanov/essenty/backhandler/AndroidBackHandlerWithLifecycleTest.kt",
"chars": 2251,
"preview": "package com.arkivanov.essenty.backhandler\n\nimport androidx.activity.OnBackPressedDispatcher\nimport androidx.lifecycle.Li"
},
{
"path": "back-handler/src/androidUnitTest/kotlin/com/arkivanov/essenty/backhandler/OnBackPressedCallbackAdapterTest.kt",
"chars": 7384,
"preview": "package com.arkivanov.essenty.backhandler\n\nimport androidx.activity.BackEventCompat\nimport kotlin.test.Test\nimport kotli"
},
{
"path": "back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackCallback.kt",
"chars": 2638,
"preview": "package com.arkivanov.essenty.backhandler\n\nimport kotlin.properties.Delegates\n\n/**\n * A callback for back button handlin"
},
{
"path": "back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackDispatcher.kt",
"chars": 1888,
"preview": "package com.arkivanov.essenty.backhandler\n\nimport kotlin.js.JsName\n\n/**\n * Provides a way to manually trigger back butto"
},
{
"path": "back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackEvent.kt",
"chars": 779,
"preview": "package com.arkivanov.essenty.backhandler\n\n/**\n * Represents an event of the predictive back gesture.\n *\n * @param progr"
},
{
"path": "back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackHandler.kt",
"chars": 509,
"preview": "package com.arkivanov.essenty.backhandler\n\n/**\n * A handler for back button presses.\n */\ninterface BackHandler {\n\n /*"
},
{
"path": "back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackHandlerOwner.kt",
"chars": 229,
"preview": "package com.arkivanov.essenty.backhandler\n\n/**\n * Represents a holder of [BackHandler]. It may be implemented by an arbi"
},
{
"path": "back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/DefaultBackDispatcher.kt",
"chars": 2923,
"preview": "package com.arkivanov.essenty.backhandler\n\ninternal class DefaultBackDispatcher : BackDispatcher {\n\n private var set "
},
{
"path": "back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/Utils.kt",
"chars": 189,
"preview": "package com.arkivanov.essenty.backhandler\n\ninternal fun Iterable<BackCallback>.findMostImportant(): BackCallback? =\n "
},
{
"path": "back-handler/src/commonTest/kotlin/com/arkivanov/essenty/backhandler/DefaultBackDispatcherTest.kt",
"chars": 26373,
"preview": "package com.arkivanov.essenty.backhandler\n\nimport kotlin.test.Test\nimport kotlin.test.assertContentEquals\nimport kotlin."
},
{
"path": "build.gradle.kts",
"chars": 3114,
"preview": "import com.arkivanov.gradle.AndroidConfig\nimport com.arkivanov.gradle.BinaryCompatibilityValidatorConfig\nimport com.arki"
},
{
"path": "deps.versions.toml",
"chars": 2504,
"preview": "[versions]\n\nessenty = \"2.5.0\"\nkotlin = \"2.1.0\"\nkotlinxBinaryCompatibilityValidator = \"0.16.3\"\nkotlinxCoroutines = \"1.9.0"
},
{
"path": "detekt.yml",
"chars": 767,
"preview": "complexity:\n CyclomaticComplexMethod:\n threshold: 15\n ignoreSingleWhenExpression: true\n ignoreSimpleWhenEntrie"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 252,
"preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
},
{
"path": "gradle.properties",
"chars": 387,
"preview": "kotlin.code.style=official\norg.gradle.jvmargs=-Xmx2g\norg.gradle.parallel=true\norg.gradle.caching=true\nsystemProp.org.gra"
},
{
"path": "gradlew",
"chars": 8504,
"preview": "#!/bin/sh\n\n#\n# Copyright © 2015-2021 the original authors.\n#\n# Licensed under the Apache License, Version 2.0 (the \"Lice"
},
{
"path": "gradlew.bat",
"chars": 2868,
"preview": "@rem\r\n@rem Copyright 2015 the original author or authors.\r\n@rem\r\n@rem Licensed under the Apache License, Version 2.0 (th"
},
{
"path": "instance-keeper/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "instance-keeper/api/android/instance-keeper.api",
"chars": 6178,
"preview": "public final class com/arkivanov/essenty/instancekeeper/AndroidExtKt {\n\tpublic static final fun InstanceKeeper (Landroid"
},
{
"path": "instance-keeper/api/instance-keeper.klib.api",
"chars": 11392,
"preview": "// Klib ABI Dump\n// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxX64, macosArm64, macosX64, tvosArm64, tvosSi"
},
{
"path": "instance-keeper/api/jvm/instance-keeper.api",
"chars": 5504,
"preview": "public abstract interface annotation class com/arkivanov/essenty/instancekeeper/ExperimentalInstanceKeeperApi : java/lan"
},
{
"path": "instance-keeper/build.gradle.kts",
"chars": 783,
"preview": "import com.arkivanov.gradle.bundle\nimport com.arkivanov.gradle.setupBinaryCompatibilityValidator\nimport com.arkivanov.gr"
},
{
"path": "instance-keeper/src/androidMain/AndroidManifest.xml",
"chars": 51,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest/>\n"
},
{
"path": "instance-keeper/src/androidMain/kotlin/com/arkivanov/essenty/instancekeeper/AndroidExt.kt",
"chars": 1948,
"preview": "package com.arkivanov.essenty.instancekeeper\n\nimport androidx.lifecycle.ViewModel\nimport androidx.lifecycle.ViewModelPro"
},
{
"path": "instance-keeper/src/androidUnitTest/kotlin/com/arkivanov/essenty/instancekeeper/AndroidInstanceKeeperTest.kt",
"chars": 1917,
"preview": "package com.arkivanov.essenty.instancekeeper\n\nimport androidx.lifecycle.ViewModelStore\nimport androidx.lifecycle.ViewMod"
},
{
"path": "instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/DefaultInstanceKeeperDispatcher.kt",
"chars": 828,
"preview": "package com.arkivanov.essenty.instancekeeper\n\nimport com.arkivanov.essenty.instancekeeper.InstanceKeeper.Instance\n\ninter"
},
{
"path": "instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/ExperimentalInstanceKeeperApi.kt",
"chars": 348,
"preview": "package com.arkivanov.essenty.instancekeeper\n\n/**\n * Marks experimental API in Essenty. An experimental API can be chang"
},
{
"path": "instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeper.kt",
"chars": 1265,
"preview": "package com.arkivanov.essenty.instancekeeper\n\n/**\n * A generic keyed store of [Instance] objects. Instances are destroye"
},
{
"path": "instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeperDispatcher.kt",
"chars": 580,
"preview": "package com.arkivanov.essenty.instancekeeper\n\nimport kotlin.js.JsName\n\n/**\n * Represents a destroyable [InstanceKeeper]."
},
{
"path": "instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeperExt.kt",
"chars": 16126,
"preview": "package com.arkivanov.essenty.instancekeeper\n\nimport com.arkivanov.essenty.instancekeeper.InstanceKeeper.SimpleInstance\n"
},
{
"path": "instance-keeper/src/commonMain/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeperOwner.kt",
"chars": 172,
"preview": "package com.arkivanov.essenty.instancekeeper\n\n/**\n * Represents a holder of [InstanceKeeper].\n */\ninterface InstanceKeep"
},
{
"path": "instance-keeper/src/commonTest/kotlin/com/arkivanov/essenty/instancekeeper/DefaultInstanceKeeperDispatcherTest.kt",
"chars": 6683,
"preview": "package com.arkivanov.essenty.instancekeeper\n\nimport kotlin.test.Test\nimport kotlin.test.assertFails\nimport kotlin.test."
},
{
"path": "instance-keeper/src/commonTest/kotlin/com/arkivanov/essenty/instancekeeper/InstanceKeeperExtTest.kt",
"chars": 4217,
"preview": "package com.arkivanov.essenty.instancekeeper\n\nimport kotlin.test.Test\nimport kotlin.test.assertNotSame\nimport kotlin.tes"
},
{
"path": "lifecycle/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "lifecycle/api/android/lifecycle.api",
"chars": 7493,
"preview": "public final class com/arkivanov/essenty/lifecycle/AndroidExtKt {\n\tpublic static final fun asEssentyLifecycle (Landroidx"
},
{
"path": "lifecycle/api/jvm/lifecycle.api",
"chars": 7181,
"preview": "public abstract interface class com/arkivanov/essenty/lifecycle/Lifecycle {\n\tpublic abstract fun getState ()Lcom/arkivan"
},
{
"path": "lifecycle/api/lifecycle.klib.api",
"chars": 11195,
"preview": "// Klib ABI Dump\n// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxX64, macosArm64, macosX64, tvosArm64, tvosSi"
},
{
"path": "lifecycle/build.gradle.kts",
"chars": 989,
"preview": "import com.arkivanov.gradle.bundle\nimport com.arkivanov.gradle.dependsOn\nimport com.arkivanov.gradle.setupBinaryCompatib"
},
{
"path": "lifecycle/src/androidMain/AndroidManifest.xml",
"chars": 52,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest />\n"
},
{
"path": "lifecycle/src/androidMain/kotlin/com/arkivanov/essenty/lifecycle/AndroidExt.kt",
"chars": 2573,
"preview": "package com.arkivanov.essenty.lifecycle\n\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.Li"
},
{
"path": "lifecycle/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/Lifecycle.kt",
"chars": 1271,
"preview": "package com.arkivanov.essenty.lifecycle\n\n/**\n * A holder of [Lifecycle.State] that can be observed for changes.\n *\n * Po"
},
{
"path": "lifecycle/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/LifecycleExt.kt",
"chars": 5133,
"preview": "package com.arkivanov.essenty.lifecycle\n\n/**\n * A convenience method for [Lifecycle.subscribe].\n */\nfun Lifecycle.subscr"
},
{
"path": "lifecycle/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/LifecycleOwner.kt",
"chars": 147,
"preview": "package com.arkivanov.essenty.lifecycle\n\n/**\n * Represents a holder of [Lifecycle].\n */\ninterface LifecycleOwner {\n\n "
},
{
"path": "lifecycle/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/LifecycleRegistry.kt",
"chars": 686,
"preview": "package com.arkivanov.essenty.lifecycle\n\nimport kotlin.js.JsName\n\n/**\n * Represents [Lifecycle] and [Lifecycle.Callbacks"
},
{
"path": "lifecycle/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/LifecycleRegistryExt.kt",
"chars": 1788,
"preview": "package com.arkivanov.essenty.lifecycle\n\n/**\n * Drives the state of the [Lifecycle] forward to [Lifecycle.State.CREATED]"
},
{
"path": "lifecycle/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/LifecycleRegistryImpl.kt",
"chars": 1994,
"preview": "package com.arkivanov.essenty.lifecycle\n\nimport com.arkivanov.essenty.lifecycle.Lifecycle.Callbacks\nimport com.arkivanov"
},
{
"path": "lifecycle/src/commonTest/kotlin/com/arkivanov/essenty/lifecycle/LifecycleExtTest.kt",
"chars": 5493,
"preview": "package com.arkivanov.essenty.lifecycle\n\nimport com.arkivanov.essenty.lifecycle.Lifecycle.Callbacks\nimport com.arkivanov"
},
{
"path": "lifecycle/src/commonTest/kotlin/com/arkivanov/essenty/lifecycle/LifecycleRegistryTest.kt",
"chars": 3826,
"preview": "package com.arkivanov.essenty.lifecycle\n\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\n@Suppress(\"TestFunctio"
},
{
"path": "lifecycle/src/itvosMain/kotlin/com/arkivanov/essenty/lifecycle/ApplicationLifecycle.kt",
"chars": 4592,
"preview": "package com.arkivanov.essenty.lifecycle\n\nimport platform.Foundation.NSNotification\nimport platform.Foundation.NSNotifica"
},
{
"path": "lifecycle/src/itvosTest/kotlin/com/arkivanov/essenty/lifecycle/ApplicationLifecyclePlatformTest.kt",
"chars": 1294,
"preview": "package com.arkivanov.essenty.lifecycle\n\nimport platform.Foundation.NSNotificationCenter\nimport platform.UIKit.UIApplica"
},
{
"path": "lifecycle/src/itvosTest/kotlin/com/arkivanov/essenty/lifecycle/ApplicationLifecycleTest.kt",
"chars": 9789,
"preview": "package com.arkivanov.essenty.lifecycle\n\nimport platform.Foundation.NSNotification\nimport platform.Foundation.NSNotifica"
},
{
"path": "lifecycle-coroutines/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "lifecycle-coroutines/api/android/lifecycle-coroutines.api",
"chars": 2867,
"preview": "public final class com/arkivanov/essenty/lifecycle/coroutines/CoroutineScopeWithLifecycleKt {\n\tpublic static final fun c"
},
{
"path": "lifecycle-coroutines/api/jvm/lifecycle-coroutines.api",
"chars": 2867,
"preview": "public final class com/arkivanov/essenty/lifecycle/coroutines/CoroutineScopeWithLifecycleKt {\n\tpublic static final fun c"
},
{
"path": "lifecycle-coroutines/api/lifecycle-coroutines.klib.api",
"chars": 3409,
"preview": "// Klib ABI Dump\n// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxX64, macosArm64, macosX64, tvosArm64, tvosSi"
},
{
"path": "lifecycle-coroutines/build.gradle.kts",
"chars": 753,
"preview": "import com.arkivanov.gradle.setupBinaryCompatibilityValidator\nimport com.arkivanov.gradle.setupMultiplatform\nimport com."
},
{
"path": "lifecycle-coroutines/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/coroutines/CoroutineScopeWithLifecycle.kt",
"chars": 1264,
"preview": "package com.arkivanov.essenty.lifecycle.coroutines\n\nimport com.arkivanov.essenty.lifecycle.Lifecycle\nimport com.arkivano"
},
{
"path": "lifecycle-coroutines/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/coroutines/DispatchersExt.kt",
"chars": 587,
"preview": "package com.arkivanov.essenty.lifecycle.coroutines\n\nimport kotlinx.coroutines.MainCoroutineDispatcher\nimport kotlin.conc"
},
{
"path": "lifecycle-coroutines/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/coroutines/FlowWithLifecycle.kt",
"chars": 1798,
"preview": "package com.arkivanov.essenty.lifecycle.coroutines\n\nimport com.arkivanov.essenty.lifecycle.Lifecycle\nimport kotlinx.coro"
},
{
"path": "lifecycle-coroutines/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/coroutines/RepeatOnLifecycle.kt",
"chars": 5202,
"preview": "package com.arkivanov.essenty.lifecycle.coroutines\n\nimport com.arkivanov.essenty.lifecycle.Lifecycle\nimport com.arkivano"
},
{
"path": "lifecycle-coroutines/src/commonTest/kotlin/com/arkivanov/essenty/lifecycle/coroutines/CoroutineScopeWithLifecycleTest.kt",
"chars": 1380,
"preview": "package com.arkivanov.essenty.lifecycle.coroutines\n\nimport com.arkivanov.essenty.lifecycle.LifecycleRegistry\nimport com."
},
{
"path": "lifecycle-coroutines/src/commonTest/kotlin/com/arkivanov/essenty/lifecycle/coroutines/DispatchersExtTest.kt",
"chars": 1383,
"preview": "package com.arkivanov.essenty.lifecycle.coroutines\n\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Expe"
},
{
"path": "lifecycle-coroutines/src/commonTest/kotlin/com/arkivanov/essenty/lifecycle/coroutines/LifecycleCoroutinesExtTest.kt",
"chars": 5473,
"preview": "package com.arkivanov.essenty.lifecycle.coroutines\n\nimport com.arkivanov.essenty.lifecycle.Lifecycle\nimport com.arkivano"
},
{
"path": "lifecycle-reaktive/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "lifecycle-reaktive/api/android/lifecycle-reaktive.api",
"chars": 405,
"preview": "public final class com/arkivanov/essenty/lifecycle/reaktive/DisposableWithLifecycleKt {\n\tpublic static final fun disposa"
},
{
"path": "lifecycle-reaktive/api/jvm/lifecycle-reaktive.api",
"chars": 405,
"preview": "public final class com/arkivanov/essenty/lifecycle/reaktive/DisposableWithLifecycleKt {\n\tpublic static final fun disposa"
},
{
"path": "lifecycle-reaktive/api/lifecycle-reaktive.klib.api",
"chars": 1029,
"preview": "// Klib ABI Dump\n// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxX64, macosArm64, macosX64, tvosArm64, tvosSi"
},
{
"path": "lifecycle-reaktive/build.gradle.kts",
"chars": 644,
"preview": "import com.arkivanov.gradle.setupBinaryCompatibilityValidator\nimport com.arkivanov.gradle.setupMultiplatform\nimport com."
},
{
"path": "lifecycle-reaktive/src/commonMain/kotlin/com/arkivanov/essenty/lifecycle/reaktive/DisposableWithLifecycle.kt",
"chars": 803,
"preview": "package com.arkivanov.essenty.lifecycle.reaktive\n\nimport com.arkivanov.essenty.lifecycle.Lifecycle\nimport com.arkivanov."
},
{
"path": "lifecycle-reaktive/src/commonTest/kotlin/com/arkivanov/essenty/lifecycle/reaktive/DisposableWithLifecycleTest.kt",
"chars": 1250,
"preview": "package com.arkivanov.essenty.lifecycle.reaktive\n\nimport com.arkivanov.essenty.lifecycle.LifecycleRegistry\nimport com.ar"
},
{
"path": "settings.gradle.kts",
"chars": 936,
"preview": "dependencyResolutionManagement {\n versionCatalogs {\n create(\"deps\") {\n from(files(\"deps.versions.to"
},
{
"path": "state-keeper/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "state-keeper/api/android/state-keeper.api",
"chars": 7721,
"preview": "public final class com/arkivanov/essenty/statekeeper/AndroidExtKt {\n\tpublic static final fun StateKeeper (Landroidx/save"
},
{
"path": "state-keeper/api/jvm/state-keeper.api",
"chars": 4787,
"preview": "public abstract interface annotation class com/arkivanov/essenty/statekeeper/ExperimentalStateKeeperApi : java/lang/anno"
},
{
"path": "state-keeper/api/state-keeper.klib.api",
"chars": 7651,
"preview": "// Klib ABI Dump\n// Targets: [iosArm64, iosSimulatorArm64, iosX64, js, linuxX64, macosArm64, macosX64, tvosArm64, tvosSi"
},
{
"path": "state-keeper/build.gradle.kts",
"chars": 1393,
"preview": "import com.arkivanov.gradle.bundle\nimport com.arkivanov.gradle.dependsOn\nimport com.arkivanov.gradle.setupBinaryCompatib"
},
{
"path": "state-keeper/src/androidMain/AndroidManifest.xml",
"chars": 51,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest/>\n"
},
{
"path": "state-keeper/src/androidMain/kotlin/com/arkivanov/essenty/statekeeper/AndroidExt.kt",
"chars": 3905,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport android.os.Bundle\nimport androidx.savedstate.SavedStateRegistry\nimport"
},
{
"path": "state-keeper/src/androidMain/kotlin/com/arkivanov/essenty/statekeeper/BundleExt.kt",
"chars": 2761,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport android.os.Bundle\nimport android.os.Parcel\nimport android.os.Parcelabl"
},
{
"path": "state-keeper/src/androidMain/kotlin/com/arkivanov/essenty/statekeeper/PersistableBundleExt.kt",
"chars": 2512,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.Persistabl"
},
{
"path": "state-keeper/src/androidUnitTest/kotlin/com/arkivanov/essenty/statekeeper/AndroidStateKeeperTest.kt",
"chars": 4008,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport android.os.Bundle\nimport android.os.Parcel\nimport androidx.lifecycle.L"
},
{
"path": "state-keeper/src/androidUnitTest/kotlin/com/arkivanov/essenty/statekeeper/BundleExtTest.kt",
"chars": 1786,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport android.os.Bundle\nimport kotlinx.serialization.Serializable\nimport org"
},
{
"path": "state-keeper/src/androidUnitTest/kotlin/com/arkivanov/essenty/statekeeper/TestUtils.android.kt",
"chars": 430,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport android.os.Bundle\nimport android.os.Parcel\n\ninternal fun Bundle.parcel"
},
{
"path": "state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/DefaultStateKeeperDispatcher.kt",
"chars": 2033,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlinx.serialization.DeserializationStrategy\nimport kotlinx.serializa"
},
{
"path": "state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/ExperimentalStateKeeperApi.kt",
"chars": 342,
"preview": "package com.arkivanov.essenty.statekeeper\n\n/**\n * Marks experimental API in Essenty. An experimental API can be changed "
},
{
"path": "state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/PolymorphicSerializer.kt",
"chars": 3846,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlinx.serialization.ExperimentalSerializationApi\nimport kotlinx.seri"
},
{
"path": "state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/SerializableContainer.kt",
"chars": 3516,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport com.arkivanov.essenty.statekeeper.base64.base64ToByteArray\nimport com."
},
{
"path": "state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/StateKeeper.kt",
"chars": 1299,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlinx.serialization.DeserializationStrategy\nimport kotlinx.serializa"
},
{
"path": "state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/StateKeeperDispatcher.kt",
"chars": 587,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlin.js.JsName\n\n/**\n * Represents a savable [StateKeeper].\n */\ninter"
},
{
"path": "state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/StateKeeperExt.kt",
"chars": 7272,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlinx.serialization.KSerializer\nimport kotlinx.serialization.Seriali"
},
{
"path": "state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/StateKeeperOwner.kt",
"chars": 157,
"preview": "package com.arkivanov.essenty.statekeeper\n\n/**\n * Represents a holder of [StateKeeper].\n */\ninterface StateKeeperOwner {"
},
{
"path": "state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/Utils.kt",
"chars": 447,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlinx.serialization.DeserializationStrategy\nimport kotlinx.serializa"
},
{
"path": "state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/base64/Decoder.kt",
"chars": 1855,
"preview": "package com.arkivanov.essenty.statekeeper.base64\n\ninternal fun String.base64ToByteArray(): ByteArray =\n decode(this)\n"
},
{
"path": "state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/base64/Dictionaries.kt",
"chars": 274,
"preview": "package com.arkivanov.essenty.statekeeper.base64\n\ninternal val dictionary: CharArray = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdef"
},
{
"path": "state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/base64/Encoder.kt",
"chars": 1429,
"preview": "package com.arkivanov.essenty.statekeeper.base64\n\ninternal fun ByteArray.toBase64(): String =\n encode(this)\n\ninternal"
},
{
"path": "state-keeper/src/commonMain/kotlin/com/arkivanov/essenty/statekeeper/base64/README.md",
"chars": 249,
"preview": "The content of this package was copied from https://github.com/cy6erGn0m/kotlinx.serialization/tree/cy/base64/formats/ba"
},
{
"path": "state-keeper/src/commonTest/kotlin/com/arkivanov/essenty/statekeeper/CodingTest.kt",
"chars": 365,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\nclass CodingTest {\n\n"
},
{
"path": "state-keeper/src/commonTest/kotlin/com/arkivanov/essenty/statekeeper/DefaultStateKeeperDispatcherTest.kt",
"chars": 4149,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlinx.serialization.Serializable\nimport kotlin.test.Test\nimport kotl"
},
{
"path": "state-keeper/src/commonTest/kotlin/com/arkivanov/essenty/statekeeper/PolymorphicSerializerTest.kt",
"chars": 1371,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlinx.serialization.ExperimentalSerializationApi\nimport kotlinx.seri"
},
{
"path": "state-keeper/src/commonTest/kotlin/com/arkivanov/essenty/statekeeper/SerializableContainerTest.kt",
"chars": 6047,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlinx.serialization.Serializable\nimport kotlin.test.Test\nimport kotl"
},
{
"path": "state-keeper/src/commonTest/kotlin/com/arkivanov/essenty/statekeeper/SerializableData.kt",
"chars": 19690,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlinx.serialization.KSerializer\nimport kotlinx.serialization.Seriali"
},
{
"path": "state-keeper/src/commonTest/kotlin/com/arkivanov/essenty/statekeeper/StateKeeperExtTest.kt",
"chars": 3660,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlinx.serialization.builtins.nullable\nimport kotlinx.serialization.b"
},
{
"path": "state-keeper/src/commonTest/kotlin/com/arkivanov/essenty/statekeeper/TestUtils.kt",
"chars": 399,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlinx.serialization.KSerializer\n\ninternal fun <T : Any> T.serializeA"
},
{
"path": "state-keeper/src/commonTest/kotlin/com/arkivanov/essenty/statekeeper/base64/Base64ImplTest.kt",
"chars": 1620,
"preview": "package com.arkivanov.essenty.statekeeper.base64\n\nimport kotlin.test.Test\nimport kotlin.test.assertEquals\n\nclass Base64I"
},
{
"path": "state-keeper/src/commonTest/kotlin/com/arkivanov/essenty/statekeeper/base64/README.md",
"chars": 244,
"preview": "The content of this package was copied from https://github.com/cy6erGn0m/kotlinx.serialization/tree/cy/base64/formats/ba"
},
{
"path": "state-keeper/src/javaMain/kotlin/com/arkivanov/essenty/statekeeper/Utils.java.kt",
"chars": 1390,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlinx.serialization.DeserializationStrategy\nimport kotlinx.serializa"
},
{
"path": "state-keeper/src/jsTest/kotlin/com/arkivanov/essenty/statekeeper/DefaultStateKeeperDispatcherJsTest.kt",
"chars": 551,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlin.test.Test\nimport kotlin.test.assertTrue\n\n@Suppress(\"TestFunctio"
},
{
"path": "state-keeper/src/nonJavaMain/kotlin/com/arkivanov/essenty/statekeeper/Utils.kt",
"chars": 496,
"preview": "package com.arkivanov.essenty.statekeeper\n\nimport kotlinx.serialization.DeserializationStrategy\nimport kotlinx.serializa"
},
{
"path": "state-keeper-benchmarks/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "state-keeper-benchmarks/build.gradle.kts",
"chars": 593,
"preview": "import com.arkivanov.gradle.setupAndroidLibrary\n\nplugins {\n id(\"kotlin-android\")\n id(\"com.android.library\")\n id"
},
{
"path": "state-keeper-benchmarks/src/main/res/AndroidManifest.xml",
"chars": 51,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest/>\n"
},
{
"path": "state-keeper-benchmarks/src/test/kotlin/com/arkivanov/essenty/statekeeper/benchmarks/Benchmarks.kt",
"chars": 3701,
"preview": "@file:Suppress(\"INVISIBLE_MEMBER\", \"INVISIBLE_REFERENCE\")\n\npackage com.arkivanov.essenty.statekeeper.benchmarks\n\nimport "
},
{
"path": "tools/check-publication/.gitignore",
"chars": 45,
"preview": "*.iml\n.gradle\n/local.properties\n.idea\n/build\n"
},
{
"path": "tools/check-publication/build.gradle.kts",
"chars": 1114,
"preview": "import com.arkivanov.gradle.setupMultiplatform\nimport com.arkivanov.gradle.setupSourceSets\n\nplugins {\n id(\"kotlin-mul"
},
{
"path": "tools/check-publication/src/androidMain/AndroidManifest.xml",
"chars": 51,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest/>\n"
},
{
"path": "tools/check-publication/src/commonMain/kotlin/com/arkivanov/essenty/tools/checkpublication/Dummy.kt",
"chars": 83,
"preview": "package com.arkivanov.essenty.tools.checkpublication\n\nfun dummy() {\n // no-op\n}\n"
},
{
"path": "utils-internal/.gitignore",
"chars": 7,
"preview": "/build\n"
},
{
"path": "utils-internal/build.gradle.kts",
"chars": 312,
"preview": "import com.arkivanov.gradle.setupMultiplatform\nimport com.arkivanov.gradle.setupPublication\n\nplugins {\n id(\"kotlin-mu"
},
{
"path": "utils-internal/src/androidMain/AndroidManifest.xml",
"chars": 51,
"preview": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest/>\n"
},
{
"path": "utils-internal/src/commonMain/kotlin/com/arkivanov/essenty/utils/internal/ExperimentalEssentyApi.kt",
"chars": 341,
"preview": "package com.arkivanov.essenty.utils.internal\n\n/**\n * Marks experimental API in Essenty. An experimental API can be chang"
},
{
"path": "utils-internal/src/commonMain/kotlin/com/arkivanov/essenty/utils/internal/InternalEssentyApi.kt",
"chars": 313,
"preview": "package com.arkivanov.essenty.utils.internal\n\n@RequiresOptIn(message = \"This API is internal, please don't use it.\", lev"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the arkivanov/Essenty GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 137 files (391.7 KB), approximately 100.4k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.