{"id":22050154,"url":"https://github.com/icapps/androidarchitecture","last_synced_at":"2026-04-29T22:01:31.468Z","repository":{"id":54581856,"uuid":"135125148","full_name":"icapps/androidarchitecture","owner":"icapps","description":"Base library containing architecture components for android apps","archived":false,"fork":false,"pushed_at":"2021-02-09T12:55:38.000Z","size":196,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-03-23T15:15:37.870Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/icapps.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-05-28T07:27:30.000Z","updated_at":"2021-11-09T12:35:06.000Z","dependencies_parsed_at":"2022-08-13T20:30:41.041Z","dependency_job_id":null,"html_url":"https://github.com/icapps/androidarchitecture","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/icapps/androidarchitecture","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icapps%2Fandroidarchitecture","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icapps%2Fandroidarchitecture/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icapps%2Fandroidarchitecture/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icapps%2Fandroidarchitecture/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/icapps","download_url":"https://codeload.github.com/icapps/androidarchitecture/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/icapps%2Fandroidarchitecture/sbom","scorecard":{"id":479690,"data":{"date":"2025-08-11","repo":{"name":"github.com/icapps/androidarchitecture","commit":"3fe769cb62c791325b9fbb36e78f3851023323af"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.6,"checks":[{"name":"Binary-Artifacts","score":9,"reason":"binaries present in source code","details":["Warn: binary detected: gradle/wrapper/gradle-wrapper.jar:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Code-Review","score":5,"reason":"Found 7/13 approved changesets -- score normalized to 5","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: Apache License 2.0: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 25 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-19T16:17:35.432Z","repository_id":54581856,"created_at":"2025-08-19T16:17:35.432Z","updated_at":"2025-08-19T16:17:35.432Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32445555,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-29T20:22:27.477Z","status":"ssl_error","status_checked_at":"2026-04-29T20:22:26.507Z","response_time":110,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.6:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":[],"created_at":"2024-11-30T14:18:02.770Z","updated_at":"2026-04-29T22:01:31.451Z","avatar_url":"https://github.com/icapps.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"# icapps Android Architecture Components\n\n[ ![Download](https://api.bintray.com/packages/icapps/maven/icapps-android-architecture/images/download.svg) ](https://bintray.com/icapps/maven/icapps-android-architecture/_latestVersion)\n\nLibrary containing architecture components for android apps. The components in this library should be loosely coupled and\nmost of the dependencies being to external libraries referenced in the code should be added manually to the consuming project's \nbuild.gradle file.\n\n**Note**: Prior to version 0.5.0, add '-x' to the version name for a version of the library that uses androidx dependencies. Starting with version 0.5.0, androidx dependencies are used by default.\n\n\n## Setup\n```\n// Include dependency to base library\nimplementation \"com.icapps.android:architecture:${archComponentsVersion}\"\n\n// Include used library component dependencies.\n// In this case we use retrofit and leakcanary helpers from the architecture library\nimplementation \"com.squareup.retrofit2:retrofit:${retrofitVersion}\"\nimplementation \"com.squareup.leakcanary:leakcanary-android:${leakCanaryVersion}\"\n\n```\n\n## Components\n\n### ObservableFuture\n\n#### Introduction\n\nAn `ObservableFuture` represents an operation that will deliver a result / multiple results at some point in the future. They are designed so that they can easily be observed on Android, keeping in mind the threading challenges of the platform. The API is designed to be as fluent as possible, while still being pretty concise.\n\nThe result of ObservableFutures can be posted on different threads:\n\n- On the main thread by using `future observe onMain`.\n- On the caller thread by using `future observe onCaller`\n- Using a lifecycle object, which will observe the ObservableFuture on the main thread, and cancel the ObservableFuture once the lifecycle hits `Lifecycle.Event.ON_STOP`. You can do this by using `future observe lifecycle`\n\nA future has two main callbacks: `onSuccess` and `onFailure`. Each callback can only be attached once! Callbacks can and will be called more than once if multiple results are posted.\n\nExceptions thrown in `onSuccess` will be caught and propagated to `onFailure`. Warning: exceptions thrown in `onFailure` are not caught and will crash you application. Be sure to wrap dangerous method calls in `onFailure` in a try catch.\n\n#### Cancelling\n\nYou can cancel an `ObservableFuture` by calling `future.cancel()`. This will ensure that any callbacks will be cleared from memory and will not be called.\n\nExample 1. In this example the callbacks will be executed on the main thread and the future will cancel itself when the lifecycle \nenters the `STOPPED` state\n```kotlin\nval coffeeFuture = coffeeMachine.makeCoffee() onSuccess { coffee -\u003e\n    // Lambda function reiving the value in case the future completes successfully\n    ...\n} onFailure { throwable -\u003e\n    // Lambda function reiving the error in case the future completes with an exception\n    ...\n} observe lifecycle\n```\n\nExample 2. In this example the callbacks will be executed on the thread that is posting the result (success or failure).\n```kotlin\nval coffeeFuture = coffeeMachine.makeCoffee() onSuccess { coffee -\u003e\n    ...\n} onFailure {\n    ...\n} observe onCaller\n\noverride fun onStop() {\n    coffeeFuture.cancel()\n}\n```\n\nExample 3. In this example the callbacks will be executed on the main thread.\n```kotlin\nval coffeeFuture = coffeeMachine.makeCoffee() onSuccess { coffee -\u003e\n    ...\n} onFailure {\n    ...\n} observe onMain\n\noverride fun onStop() {\n    coffeeFuture.cancel()\n}\n```\n\n#### Executing synchronously\n\n`ObservableFuture` has a method called `execute()` that blocks the calling thread until the result of the future is ready to be delivered.\n*WARNING:* This method is generally dangerous as it has subtle pitfalls.\n\nExcept for `RetrofitObservableFuture` this method will use a latch to wait for the results that are delivered using direct observe. If the underlying future cannot be executed for whatever reason (eg thread could not be started, ...),\nthis method will block. Also, since this is being executed in the context of the future in the first place, special care should be taken with regards to reentrant locks (eg: synchronized(...), ...) as the thread calling [execute] will not be the same thread that is doing the actual executing.\n\nExample 4. Executing a future synchronously\n```kotlin\nval future = coffeeMachine.makeCoffee() // returns an ObservableFuture\nval coffee = future.execute(10000) // Will block the current thread for 10 seconds, and return coffee (or throw the throwable from onFailure)\n```\n\n#### Creating Futures\n\nThere are a few implementations of the `(Mutable)ObservableFuture`-interface. The simplest one being `ConcreteMutableObservableFuture`.\n\nIt allows you to pass results (either success or failure) to the future with the `onResult(T)` and `onFailure(throwable)` methods. The result will be posted on the correct thread for you (depending on which thread the future is observed)\n\nExample 5. Creating a `ConcreteMutableObservableFuture` (If you do something similar, make sure to unsubscribe from the timer at some point!)\n```kotlin\nval timeFuture = ConcreteMutableObservableFuture\u003cLong\u003e()\nsomeTimer.addTickListener(intervalMs = 1000) { timeFuture.onResult(System.currentTimeMillis()) }\nreturn future\n```\n\nA shortcut for creating a future of something that runs on a (background) thread is `onBackground { }`. It runs the supplied lambda on a new thread (you can pass an `Executor` if you want) and posts the result of the lambda to the `ConcreteMutableObservableFuture` that it returns.\nAn alternative also exists for making sure something is not running on the main thread: `offMain { }`. It achieves the same thing as `onBackground { }`, but will not start a new thread if the current thread is not the main thread.\n\nExample 6. Creating a future with `onBackground`\n```\nclass CoffeeMachine @Inject constructor(heater: WaterHeater, grinder: CoffeeGrinder, pump: Pump) {\n\n    fun makeCoffee(): ObservableFuture\u003cCoffee\u003e {\n        return onBackground {\n            val warmWater = heater.heat(temp = 100)\n            val groundCoffee = grinder.grind(amount = 42)\n            pump.pumpCoffee(warmWater, groundCoffee)\n        }\n    }\n\n}\n\n```\n\nThere are also some shortcuts for directly returning a future with a certain result:\n\n- `ObservableFuture.withData(t)` or `t.asObservable()`: returns an `Observable\u003cT\u003e` which will immediately call `onSuccess` with the provided data\n- `ObservableFuture.withError\u003cT\u003e(throwable)` returns an `ObservableFuture\u003cT\u003e` which will immediately call `onFailure` with the provided throwable\n\nOur library also contains extensions for creating futures of Retrofit calls: `Call.wrapToFuture()`.\n\nExample 7: Creating a future of a Retrofit `Call`\n```kotlin\nfun getChannels(): ObservableFuture\u003cList\u003cChannel\u003e\u003e {\n    return retrofitChannelService.getChannels().wrapToFuture()    \n}\n```\n\n#### Peeking\n\n`ObservableFuture`s also have another result/failure callback, which we call peeking. You can add a callback which will also be called on success and/or failure. You can either listen only for success: `peek {}` or for both success and failure: `peekBoth { success, failure -\u003e ... }`\n\nPeeking is most easily explained with a practical example:\n\nExample 8. Using `peek`\n\n```kotlin\nclass ChannelRepository(val channelService: ChannelService) {\n\n    private val memCache = mutableListOf\u003cChannel\u003e()\n\n    fun getChannels(): ObservableFuture\u003cChannel\u003e {\n        synchronized(this) {\n            if(memCache.isNotEmpty()) return memCache.asObservable()\n        }\n\n        return channelService.getChannels().wrapToFuture() peek { channels -\u003e\n            synchronized(this) {\n                memCache.clear()\n                memCache.putAll(channels)\n            }\n        }\n    }\n\n}\n```\nIn this example, we keep a memory cache of channels. When there is no cache available and the channels call succeeds, the memCache is updated.\n\n#### Combining Futures\n\n`ObservableFuture`s can be combined in multiple ways:\n\n- `ObservableFuture.of(a, b, ...)`  n futures will be executed asynchronously. Once both futures have reached `onSuccess`, the result (a `Pair\u003cfirstT, secondT\u003e`) will be posted to onSuccess.\n\nExample 9. `ObservableFuture.of`\n```kotlin\nObservableFuture.of(sessionRepository.getSession(), channelRepository.getChannels()) onSuccess { (session, channels) -\u003e\n    ...\n} observe lifecycle\n```\n\n- `future andThen { future2 }` : The 2 futures will be executed after one another. Once the first future has reached `onSuccess`, the next `ObservableFuture` will be created with the result from the previous. The result of the second future will be posted to `onSuccess`.\n\nExample 10. usage of `andThen`\n```kotlin\nsessionRepository.getSession() andThen { session -\u003e\n    session -\u003e channelRepository.getChannels(session.profileId)\n} onSuccess { channels -\u003e\n    ...\n} observe lifecycle\n```\n\n- `future andThenAlso { future2 }` : The 2 futures that will be executed after one another. Once the first future has reached `onSuccess`, the next `ObservableFuture` will be created with the result from the previous. The result of both futures (a `Pair\u003cfirstT, secondT\u003e`) will be posted to `onSuccess`.\n\nExample 11. usage of `andThenAlso`\n```kotlin\nsessionRepository.getSession() andThenAlso { session -\u003e\n    session -\u003e channelRepository.getChannels(session.profileId)\n} onSuccess { (session, channels) -\u003e\n    ...\n} observe lifecycle\n```\n\n#### AsyncMemoizer\nAs an alternative to `BatchingObservableFuture`, you can use `AsyncMemoizer`. This class is designed to execute exactly once and provides access to the value when the computation has finished.\n\nThis class can be used to easily create a network cache that ensures multiple calls are not running at the same time.\n\nExample 12. usage of `AsyncMemoizer`\n```kotlin\n//Future will get notified with results from the future created by runOnce\nval future = memoizer.runOnce { /* code that creates the future, will execute EXACTLY ONCE */ }\n\n//Easily access the future of the memoizer, will get notified with the results of the future created by runOnce\nval otherFuture = memoizer.future\n```\n\n### ViewModels, Repositories, Dagger setup\n\n#### BaseRepository\n\n`BaseRepository` is an abstract base class for repositories. It mostly provides helper methods for transforming retrofit `Call`s into ObservableFutures (`makeCall()`).\n\n#### BaseViewModel\n\n`BaseViewModel` is an abstract base class for ViewModels. It extends the `ViewModel` class from Google's Architecture Components.\n\nThe only thing that `BaseViewModel` does over a `ViewModel` is providing `saveInstanceState`, `restoreInstanceState` methods and a var which allows you to see whether viewModels are fresh instances or recreated. If you use our `ViewModelLifecycleController`, state saving and restoring for these viewmodels is handled for you.\n\n#### ViewModelLifecycleController and Dagger setup\n\n`ViewModelLifecycleController` is an injectable wrapper class around `ViewModelProviders.of`. It handles the creation of viewModels for activities/fragments and handles the saving and restoring of instance state into the viewmodels.\n\nFor injecting ViewModels with dagger, the classes `ViewModelKey` and `ViewModelFactory` are included. For a clear dagger setup example, check out our project template: https://github.com/icapps/android-template-kotlin-viewmodel\n\n### Other extensions and utils\n\nOther extensions and utilities can be found in the `com.icapps.architecture.utils` package:\n\n- `UIExtensions.kt` contains many shortcuts for inflating views, converting dp/px units and loading resources\n- `LifecycleExt.kt` contains a shortcut for adding a stop observer to a `Lifecycle`\n- `ObservableExt.kt` contains utils for observing `ObservableField\u003cT\u003e`s, `ObservableInt`s, `ObservableBoolean`s and `ObservableList`s with `lifecycle`\n- `Cached` is a class that allows you to easily set up variables that clear themselves after a certain amount of time. Example usage: `var cachedValue: T? by Cached(CACHE_VALIDITY_MS)`","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficapps%2Fandroidarchitecture","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ficapps%2Fandroidarchitecture","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ficapps%2Fandroidarchitecture/lists"}