{"id":18458803,"url":"https://github.com/agoda-com/boots","last_synced_at":"2025-04-08T05:34:38.759Z","repository":{"id":57716607,"uuid":"166779285","full_name":"agoda-com/boots","owner":"agoda-com","description":"Lightweight bootstrap library for your Kotlin, Java and Android apps","archived":false,"fork":false,"pushed_at":"2019-10-10T09:45:41.000Z","size":425,"stargazers_count":91,"open_issues_count":8,"forks_count":10,"subscribers_count":24,"default_branch":"master","last_synced_at":"2025-03-23T06:51:10.498Z","etag":null,"topics":["android","bootstrap","java","kotlin","library"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/agoda-com.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2019-01-21T08:48:46.000Z","updated_at":"2024-06-19T15:08:40.000Z","dependencies_parsed_at":"2022-08-23T21:10:45.542Z","dependency_job_id":null,"html_url":"https://github.com/agoda-com/boots","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agoda-com%2Fboots","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agoda-com%2Fboots/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agoda-com%2Fboots/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agoda-com%2Fboots/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/agoda-com","download_url":"https://codeload.github.com/agoda-com/boots/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247785919,"owners_count":20995641,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","bootstrap","java","kotlin","library"],"created_at":"2024-11-06T08:20:08.891Z","updated_at":"2025-04-08T05:34:33.740Z","avatar_url":"https://github.com/agoda-com.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Boots\n[![Bintray version](https://api.bintray.com/packages/agoda/maven/boots/images/download.svg)](https://bintray.com/agoda/maven/boots)\n[![Kotlin version badge](https://img.shields.io/badge/kotlin-1.3.50-blue.svg)](http://kotlinlang.org/)\n[![codecov](https://codecov.io/gh/agoda-com/boots/branch/master/graph/badge.svg)](https://codecov.io/gh/agoda-com/boots)\n\nLightweight and easy-to-use bootstrap library for Java | Kotlin | Android\n\n## The problem\nAt Agoda, our mobile app had a huge problem with modifying and executing the initialization logic.\nOur `Application` and `SplashActivity` classes were more that 2k lines of undocumented and uncommented code.\nEvery change in this classes could possibly lead to unexpected behaviour that could not be discovered by unit or ui testing.\n\nAnother problem we had is that when our app was restoring after being terminated by the system,\nwe had to manually stop current screen and launch our `SplashActivity` to ensure that all our\nsystems can operate normally.\n\nOn top of that, all this logic run mostly on the main thread, which made our TTI (time-to-interactable)\nas long as 4-6 seconds on a mid-range devices.\n\nWe had a strong feeling that we can change this.\n\n#### Goals we want to achieve\n- Simplicity\n- Speed\n- Safety\n- Extendability\n\n## Solution\nThat's how we implemented Boots, the lightweight and easy-to-use bootstrap library which you\ncan use for all of your projects running Java or Kotlin as well as Android apps.\n\nThe library allows you to easily decouple your initialization logic by splitting it\ninto `Bootable`s, which you can connect through simple dependency declarations.\nThen it's up to the library to do the rest for you.\n\n#### Capabilities\n- Automatic boot task resolving (dependencies, critical bootables)\n- Runtime SCC (strong connected component) check\n- Concurrent execution\n- Time measurements\n- Simple listener callbacks\n- Custom component implementations\n\n#### Usage\n\n##### Making a bootable\n`Bootable` is the base construction that is used in the library to decouple\nyour initialization logic. Most of the library's integration process is\ndefining your bootables so it is very important to understand it's capabilities.\n```kotlin\nabstract class Bootable {\n    abstract val key: Key.Single\n\n    open val dependencies: Key.Multiple = multiple()\n    open val isConcurrent: Boolean = true\n    open val isCritical: Boolean = false\n\n    @Throws(Throwable::class)\n    abstract fun boot()\n}\n```\nLets go through it's properties and see what they mean:\n\n- **key** is a unique identifier of your bootable (string id)\n- **dependencies** is a key that contains keys of bootables that your bootable depend on\n- **isConcurrent** is a flag that determines whether your bootable can be executed on a separate thread\n- **isCritical** is a flag that determines whether your bootable should be executed before any other non-critical.\n                 If any critical bootable fails to load, all tasks will be stopped.\n- **boot()** is a function of your bootable that gets invoked by the library.\n\nNow that you know what bootable consist of, you can create your own one.\nBut before that, you need to define a key:\n```kotlin\nobject Keys {\n    @JvmField val DEVICE_ID = single(\"device_id\")\n}\n```\nKeys were introduced to remove the necessity of having references on actual\nbootables when working with a library (especially in a modularized project).\nString could work as well, but keys are more flexible since they allow us\nto combine multiple identifiers as well as define filtering keys (`Critical` and `All`).\n\nNow it's time to define a bootable:\n```kotlin\nclass DeviceIdBootable(private val context: Context) : Bootable() {\n    override val key = Keys.DEVICE_ID\n    override val isCritical = true\n\n    override fun boot() {\n        val preferences = PreferenceManager.getDefaultSharedPreferences(context)\n\n        if (!preferences.contains(\"device_id\")) {\n            preferences\n                    .edit()\n                    .putString(\"device_id\", UUID.randomUUID().toString())\n                    .apply()\n        }\n    }\n}\n```\nOr you can use Kotlin DSL to define and add bootable at the same time:\n```kotlin\nBoots {\n    add(Keys.DO_SOMETHING, isCritical = true) {\n        doSomething()\n    }\n}\n```\nSo, you're ready to launch your boot sequence. You can do it using simple Kotlin DSL,\nor plain old Java way:\n```kotlin\nBoots {\n    // Adding instance of bootable to the system\n    add(DeviceIdBootable(applicationContext))\n\n    // Requesting to boot all bootables and saving listener instance\n    listener = boot(all()) {\n        onBoot = { /* do something */ }\n    }\n}\n```\n```java\nBoots.add(new DeviceIdBootable(getApplicationContext()));\n\nlistener = Boots.boot(all(), new Builder() {\n    @Override\n    public void onBoot(@NotNull Report report) {}\n\n    @Override\n    public void onFailure(@NotNull Report report) {}\n});\n```\n\n##### Checking state and observing\nTo get the current status of your bootable or list of bootables, you can generate the report:\n```kotlin\nBoots {\n    val report = report(all())\n}\n```\n```java\nReport report = Boots.report(all());\n```\nReport contains current status of bootable or common status of list of bootables and\ntime measurement information if the bootable has finished booting or failed.\n\nAlso you can listen for bootable events with the `subscribe()` functions:\n```kotlin\nBoots {\n    if (report(all()).status !is Booted) {\n        listener = subscribe(all()) {\n            onBoot = { /* do something */ }\n            onFailure = { /* do something */ }\n        }\n    }\n}\n```\n```java\nif (!(Boots.report(all()) instanceof Booted)) {\n    listener = Boots.subscribe(all(), new Builder() {\n        @Override\n        public void onBoot(@NotNull Report report) {}\n\n        @Override\n        public void onFailure(@NotNull Report report) {}\n    });\n}\n```\n**IMPORTANT**: default implementation of `Notifier` holds strong references\non the `Listener` instances. It is user's responsibility to unsubscribe if\nthe listener holds a reference on outer objects that need to be recycled.\nHolding weak references is not the best solution, since GC time is undetermined\nand invoking weak referenced listeners may lead to unexpected behaviour\n(`IllegalStateException`s in fragments, etc.).\n\n##### Customization\nLibrary provides ability to customize it's behaviour through providing\ncustom implementation of component interfaces. The components are:\n\n- **Executor**\n- **Notifier**\n- **Reporter**\n- **Sequencer**\n- **Logger**\n\nEach of them is responsible for a specific portion of library's behaviour and\ncan be overridden with custom logic. Let's take `RxAndroidExecutor`, for example,\nwhich uses RxJava under the hood to execute your bootables and also enables\ncontext switching, so that all callbacks and non-concurrent bootables can\nbe executed on Android's main thread:\n```kotlin\nBoots {\n    configure {\n        executor = RxAndroidExecutor(Schedulers.io())\n    }\n}\n```\n```java\nBoots.configure(new Configuration.Builder()\n        .setExecutor(new RxAndroidExecutor(Schedulers.io()))\n        .build());\n```\nAll custom implementations are provided via additional artifacts, so that\nyou can grab only core library with default implementation if that is fully\nsatisfies your needs. The list of available artifacts is at the **Setup** section.\n\n##### More\nFor any additional information please refer to library's documentation.\n\n#### Testing\nLibrary provides a testing artifact to help you easily mock your boot process.\n`mockito` and `mockito-kotlin` are used to provide you with this functionality.\nTo mock library's logic, you can use Kotlin DSL or Java style:\n```kotlin\nMocker {\n    mock(Keys.DEVICE_ID, booting())\n}\n```\n```java\nnew Mocker()\n        .mock(Keys.DEVICE_ID, booting())\n        .apply();\n```\nWhen you use `Mocker`, it automatically mocks all interactions as `Booted` by default\nand immediately triggers all added listeners.\n\n#### Setup\nMaven\n```xml\n\u003c!-- core library !--\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.agoda.boots\u003c/groupId\u003e\n  \u003cartifactId\u003ecore\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003ctype\u003epom\u003c/type\u003e\n\u003c/dependency\u003e\n\n\u003c!-- android logger !--\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.agoda.boots\u003c/groupId\u003e\n  \u003cartifactId\u003elogger-android\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003ctype\u003epom\u003c/type\u003e\n\u003c/dependency\u003e\n\n\u003c!-- rx android executor !--\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.agoda.boots\u003c/groupId\u003e\n  \u003cartifactId\u003eexecutor-rx-android\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003ctype\u003epom\u003c/type\u003e\n\u003c/dependency\u003e\n\n\u003c!-- coroutine executor !--\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.agoda.boots\u003c/groupId\u003e\n  \u003cartifactId\u003eexecutor-coroutine\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003ctype\u003epom\u003c/type\u003e\n\u003c/dependency\u003e\n\n\u003c!-- coroutine android executor !--\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.agoda.boots\u003c/groupId\u003e\n  \u003cartifactId\u003eexecutor-coroutine-android\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003ctype\u003epom\u003c/type\u003e\n\u003c/dependency\u003e\n\n\u003c!-- pure android executor !--\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.agoda.boots\u003c/groupId\u003e\n  \u003cartifactId\u003eexecutor-android\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003ctype\u003epom\u003c/type\u003e\n\u003c/dependency\u003e\n\n\u003c!-- test mocker !--\u003e\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.agoda.boots\u003c/groupId\u003e\n  \u003cartifactId\u003etest\u003c/artifactId\u003e\n  \u003cversion\u003eLATEST_VERSION\u003c/version\u003e\n  \u003ctype\u003epom\u003c/type\u003e\n\u003c/dependency\u003e\n```\nor Gradle:\n```groovy\nrepositories {\n    jcenter()\n}\n\ndependencies {\n    // core library\n    implementation 'com.agoda.boots:boots:LATEST_VERSION'\n\n    // pure android logger\n    implementation 'com.agoda.boots:logger-android:LATEST_VERSION'\n\n    // rx android executor\n    implementation 'com.agoda.boots:executor-rx-android:LATEST_VERSION'\n\n    // coroutine executor\n    implementation 'com.agoda.boots:executor-coroutine:LATEST_VERSION'\n\n    // coroutine android executor\n    implementation 'com.agoda.boots:executor-coroutine-android:LATEST_VERSION'\n\n    // pure android executor\n    implementation 'com.agoda.boots:executor-android:LATEST_VERSION'\n\n    // test mocker\n    testImplementation 'com.agoda.boots:test:LATEST_VERSION'\n}\n```\n\n#### Contribution Policy\n\nBoots is an open source project, and depends on its users to improve it. We are more than happy\nto find you interested in taking the project forward.\n\nKindly refer to the [Contribution Guidelines](https://github.com/agoda-com/boots/blob/master/CONTRIBUTING.md) for detailed information.\n\n#### Code of Conduct\n\nPlease refer to [Code of Conduct](https://github.com/agoda-com/boots/blob/master/CODE_OF_CONDUCT.md) document.\n\n#### License\n\nBoots is available under the [Apache License, Version 2.0](https://github.com/agoda-com/boots/blob/master/LICENSE).\n\n#### Thanks to\n\n* [Unlimity](https://github.com/unlimity) - **Ilya Lim**\n* [Сdsap](https://github.com/cdsap) - **Inaki Villar**\n* [Vacxe](https://github.com/vacxe) - **Konstantin Aksenov**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagoda-com%2Fboots","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fagoda-com%2Fboots","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagoda-com%2Fboots/lists"}