{"id":24801599,"url":"https://github.com/futuredapp/arkitekt","last_synced_at":"2026-01-11T18:00:18.566Z","repository":{"id":37800606,"uuid":"124508581","full_name":"futuredapp/arkitekt","owner":"futuredapp","description":"Arkitekt is a set of architectural tools based on Android Architecture Components, which gives you a solid base to implement the concise, testable and solid application.","archived":false,"fork":false,"pushed_at":"2024-11-07T16:42:30.000Z","size":1606,"stargazers_count":125,"open_issues_count":10,"forks_count":9,"subscribers_count":4,"default_branch":"5.x","last_synced_at":"2025-12-10T22:15:39.067Z","etag":null,"topics":["android","architecture-components","compose","databinding","jetpack","jetpack-compose","kotlin","mvvm","mvvm-architecture"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/futuredapp.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-03-09T08:05:39.000Z","updated_at":"2025-07-26T16:51:07.000Z","dependencies_parsed_at":"2022-06-23T14:25:36.159Z","dependency_job_id":"6bf6e2dd-2432-47b6-8322-12d23c4c3f48","html_url":"https://github.com/futuredapp/arkitekt","commit_stats":null,"previous_names":["thefuntasty/mvvm-android"],"tags_count":26,"template":false,"template_full_name":null,"purl":"pkg:github/futuredapp/arkitekt","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/futuredapp%2Farkitekt","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/futuredapp%2Farkitekt/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/futuredapp%2Farkitekt/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/futuredapp%2Farkitekt/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/futuredapp","download_url":"https://codeload.github.com/futuredapp/arkitekt/tar.gz/refs/heads/5.x","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/futuredapp%2Farkitekt/sbom","scorecard":{"id":414767,"data":{"date":"2025-08-11","repo":{"name":"github.com/futuredapp/arkitekt","commit":"c0dca94d984c8f7bda56ffe99b35fc93987cc1ed"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":4.8,"checks":[{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","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":"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":"Code-Review","score":7,"reason":"Found 6/8 approved changesets -- score normalized to 7","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":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/publish_release.yml:1","Warn: no topLevel permission defined: .github/workflows/publish_snapshot.yml:1","Warn: no topLevel permission defined: .github/workflows/pull_request.yml:1","Info: no jobLevel write permissions found"],"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":"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":"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":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish_release.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/futuredapp/arkitekt/publish_release.yml/5.x?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish_release.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/futuredapp/arkitekt/publish_release.yml/5.x?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish_snapshot.yml:14: update your workflow using https://app.stepsecurity.io/secureworkflow/futuredapp/arkitekt/publish_snapshot.yml/5.x?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/publish_snapshot.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/futuredapp/arkitekt/publish_snapshot.yml/5.x?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/publish_snapshot.yml:35: update your workflow using https://app.stepsecurity.io/secureworkflow/futuredapp/arkitekt/publish_snapshot.yml/5.x?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pull_request.yml:11: update your workflow using https://app.stepsecurity.io/secureworkflow/futuredapp/arkitekt/pull_request.yml/5.x?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/pull_request.yml:12: update your workflow using https://app.stepsecurity.io/secureworkflow/futuredapp/arkitekt/pull_request.yml/5.x?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/pull_request.yml:16: update your workflow using https://app.stepsecurity.io/secureworkflow/futuredapp/arkitekt/pull_request.yml/5.x?enable=pin","Warn: third-party GitHubAction not pinned by hash: .github/workflows/pull_request.yml:27: update your workflow using https://app.stepsecurity.io/secureworkflow/futuredapp/arkitekt/pull_request.yml/5.x?enable=pin","Info:   0 out of   6 GitHub-owned GitHubAction dependencies pinned","Info:   0 out of   3 third-party GitHubAction dependencies pinned"],"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":"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: LICENCE:0","Info: FSF or OSI recognized license: MIT License: LICENCE: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":"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":"Branch-Protection","score":-1,"reason":"internal error: error during GetBranch(4.x): error during branchesHandler.query: internal error: githubv4.Query: Resource not accessible by integration","details":null,"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":"Packaging","score":10,"reason":"packaging workflow detected","details":["Info: Project packages its releases by way of GitHub Actions.: .github/workflows/publish_snapshot.yml:8"],"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":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 30 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-18T23:33:05.674Z","repository_id":37800606,"created_at":"2025-08-18T23:33:05.674Z","updated_at":"2025-08-18T23:33:05.674Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":27801998,"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","status":"online","status_checked_at":"2025-12-18T02:00:09.725Z","response_time":55,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["android","architecture-components","compose","databinding","jetpack","jetpack-compose","kotlin","mvvm","mvvm-architecture"],"created_at":"2025-01-30T04:29:24.149Z","updated_at":"2026-01-11T18:00:18.293Z","avatar_url":"https://github.com/futuredapp.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cimg align=\"right\" src=\"extras/MMVM_Android.svg\"\u003e\n\n# Arkitekt\n\n[![Download](https://img.shields.io/maven-central/v/app.futured.arkitekt/core)](https://search.maven.org/search?q=app.futured.arkitekt)\n[![Build Status](https://github.com/futuredapp/arkitekt/workflows/Check%205.x/badge.svg)](https://github.com/futuredapp/arkitekt/actions)\n\nArkitekt is a set of architectural tools based on Android Architecture Components, which gives you a solid base to implement the concise, testable and solid application.\n\n# Installation\n\n```groovy\nandroid {\n    // AGP \u003c 4.0.0\n    dataBinding {\n        enabled = true\n    }\n    \n    // AGP \u003e= 4.0.0\n    buildFeatures {\n        dataBinding = true\n    }\n}\n\ndependencies {\n    implementation(\"app.futured.arkitekt:core:LatestVersion\")\n    implementation(\"app.futured.arkitekt:bindingadapters:LatestVersion\")\n    implementation(\"app.futured.arkitekt:dagger:LatestVersion\")\n    implementation(\"app.futured.arkitekt:cr-usecases:LatestVersion\")\n    implementation(\"app.futured.arkitekt:rx-usecases:LatestVersion\")\n    \n    // Testing\n    testImplementation(\"app.futured.arkitekt:core-test:LatestVersion\")\n    testImplementation(\"app.futured.arkitekt:rx-usecases-test:LatestVersion\")\n    testImplementation(\"app.futured.arkitekt:cr-usecases-test:LatestVersion\")\n}    \n```\n\n## Snapshot installation\n\nAdd new maven repo to your top level gradle file.\n\n```\nmaven { url \"https://oss.sonatype.org/content/repositories/snapshots\" }\n```\n\nSnapshots are grouped based on major version, so for version 5.x use:\n\n```groovy\nimplementation \"app.futured.arkitekt:arkitekt:5.X.X-SNAPSHOT\"\n```\n\n# Features\n\nArkitekt combines built-in support for Dagger 2 dependency injection, View DataBinding,\nViewModel and RxJava or Coroutines use cases. Architecture described here is used among wide variety\nof projects and it's production ready.\n\n![MVVM architecture](extras/architecture-diagram.png)\n\n# Usage\n\n## Table of contents\n\n1. [Getting started - Minimal project file hierarchy](#getting-started---minimal-project-file-hierarchy)\n2. [Use Cases](#use-cases)\n3. [Propagating data model changes into UI](#propagating-data-model-changes-into-ui)\n4. [Stores (Repositories)](#stores-repositories)\n\n## Getting started - Minimal project file hierarchy\nMinimal working project must contain files as presented in `example-minimal`\nmodule. File hierarchy might looks like this:\n```\nexample-minimal\n`-- src/main\n    |-- java/com/example\n    |   |-- injection  \n    |   |   |-- ActivityBuilderModule.kt\n    |   |   |-- ApplicationComponent.kt\n    |   |   `-- ApplicationModule.kt\n    |   |-- ui \n    |   |   |-- base/BaseActivity.kt\n    |   |   `-- main\n    |   |       |-- MainActivity.kt\n    |   |       |-- MainActivityModule.kt\n    |   |       |-- MainView.kt\n    |   |       |-- MainViewModel.kt\n    |   |       |-- MainViewModelFactory.kt\n    |   |       `-- MainViewState.kt\n    |   `-- App.kt \n    `-- res/layout/activity_main.xml  \n```\n\nKeep in mind this description focuses on architecture `.kt` files. Android related files like an \n`AndroidManifest.xml` are omitted. Let's describe individual files one by one:\n\n##### `ActivityBuilderModule.kt` \nFile contains Dagger module class that takes responsibility of proper injection\ninto Activities. This is the place where every Activity and its `ActivityModule` \nin project must be specified to make correct ViewModel injection work.\n \n```kotlin\n@Module\nabstract class ActivityBuilderModule {\n\n    @ContributesAndroidInjector(modules = [MainActivityModule::class])\n    abstract fun mainActivity(): MainActivity\n}\n``` \n\n##### `ApplicationComponent.kt`\n\nApplicationComponent interface combines your singleton Dagger modules and defines\nhow `DaggerApplicationComponent` should be generated.\n```kotlin\n@Singleton\n@Component(\n    modules = [\n        AndroidInjectionModule::class,\n        AndroidSupportInjectionModule::class,\n        ActivityBuilderModule::class,\n        ApplicationModule::class\n    ]\n)\ninterface ApplicationComponent : AndroidInjector\u003cApp\u003e {\n\n    @Component.Builder\n    interface Builder {\n\n        @BindsInstance\n        fun application(app: App): Builder\n\n        fun build(): ApplicationComponent\n    }\n}\n```\n\n##### `ApplicationModule.kt`\n\nApplication module definition. Your singleton scoped objects might\nbe specified here and injected wherever needed. Example implementation:\n```kotlin\n@Module\nclass ApplicationModule {\n\n    @Singleton\n    @Provides\n    fun moshi(): Moshi = Moshi.Builder().build()\n}\n```\n\n##### `BaseActivity.kt`\n\nAll of Activities in the project should inherit from this class to make DataBinding work properly.\nBe aware of fact BR class used in this class is generated when there is at least one layout file \nwith correctly defined data variables. Read more [here](#activity_mainxml).\n```kotlin\nabstract class BaseActivity\u003cVM : BaseViewModel\u003cVS\u003e, VS : ViewState, B : ViewDataBinding\u003e :\n    BaseDaggerBindingActivity\u003cVM, VS, B\u003e() {\n\n    override val brViewVariableId = BR.view\n    override val brViewModelVariableId = BR.viewModel\n    override val brViewStateVariableId = BR.viewState\n}\n```\n\n##### `MainActivity.kt`\n\nExample Activity implementation. `viewModelFactory` and `layoutResId` must be overridden in every\nActivity in order to make ViewModel injection and DataBinding work. `ActivityMainBinding` used\nin `BaseActivity` constructor is generated from related `activity_main.xml` layout file. Make sure this file\nexists and have root tag `\u003clayout\u003e` before you try to build your code. `ViewModel` can be\naccessed through derived `viewModel` field.\n```kotlin\nclass MainActivity : BaseActivity\u003cMainViewModel, MainViewState, ActivityMainBinding\u003e(), MainView {\n\n    @Inject override lateinit var viewModelFactory: MainViewModelFactory\n\n    override val layoutResId = R.layout.activity_main\n}\n```\n\n##### `MainActivityModule.kt`\n\n`MainActivity` scoped module. It becomes useful when you want to provide specific\nactivity related configuration e.g.:\n  \n```kotlin\n@Module\nabstract class MainActivityModule {\n\n    @Provides\n    fun provideUser(activity: MainActivity): User = \n            activity.intent.getParcelableExtra(\"user\")\n}\n```\n\n##### `MainView.kt`\n\nInterface representing actions executable on your Activity/Fragment. These actions\nmight be invoked directly from xml layout thanks to `view` data variable.  \n```kotlin\ninterface MainView : BaseView\n```\n\n##### `MainViewModel.kt`\n\nActivity/Fragment specific ViewModel implementation. You can choose between extending\n`BaseViewModel` or `BaseRxViewModel` with build-in support for RxJava based use cases.\n```kotlin\nclass MainViewModel @Inject constructor() : BaseViewModel\u003cMainViewState\u003e() {\n\n    override val viewState = MainViewState\n}\n```\n\n##### `MainViewModelFactory.kt`\n\nFactory responsible for `ViewModel` creation. It is injected in Activity/Fragment. \n```kotlin\nclass MainViewModelFactory @Inject constructor(\n    override val viewModelProvider: Provider\u003cMainViewModel\u003e\n) : BaseViewModelFactory\u003cMainViewModel\u003e() {\n    override val viewModelClass = MainViewModel::class\n}\n```\n\n##### `MainViewState.kt`\n\nState representation of an screen. Should contain set of `LiveData` fields observed\nby Activity/Fragment. State is stored in `ViewModel` thus survives screen rotation. \n```kotlin\nobject MainViewState : ViewState {\n    val user = DefaultValueLiveData\u003cUser\u003e(User.EMPTY)\n}\n```\n\n##### `activity_main.xml`\n\nLayout file containing proper DataBinding variables initialization. Make sure correct\ntypes are defined.\n```xml\n\u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\u003e\n\n    \u003cdata\u003e\n        \u003cvariable name=\"view\" type=\"app.futured.arkitekt.sample.ui.main.MainView\"/\u003e\n        \u003cvariable name=\"viewModel\" type=\"app.futured.arkitekt.sample.ui.main.MainViewModel\"/\u003e\n        \u003cvariable name=\"viewState\" type=\"app.futured.arkitekt.sample.ui.main.MainViewState\"/\u003e\n    \u003c/data\u003e\n\n    \u003cLinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:orientation=\"vertical\"\n            android:gravity=\"center\"\u003e\n\n    \u003c/LinearLayout\u003e\n\u003c/layout\u003e\n```\n\n## Use Cases\n\nModules `cr-usecases` and `rx-usecases` contains set of base classes useful for easy execution of\nbackground tasks based on Coroutines or RxJava streams respectively. In terms of Coroutines\ntwo base types are available - `UseCase` (single result use case) and `FlowUseCase` (multi result use case).\nRxJava base use cases match base Rx \"primitives\": `ObservableUseCase`, `SingleUseCase`, `FlowableUseCase`, `MaybeUseCase`\nand finally `CompletableUseCase`. \n\nFollowing example describes how to make an API call and how to deal with \nresult of this call. \n\n##### LoginUseCase.kt\n```kotlin\nclass LoginUseCase @Inject constructor(\n    private val apiManager: ApiManager // Retrofit Service\n) : SinglerUseCase\u003cLoginData, User\u003e() {\n\n    override fun prepare(args: LoginData): Single\u003cUser\u003e {\n        return apiManager.getUser(args)\n    }\n}\n\ndata class LoginData(val email: String, val password: String)\n```\n##### LoginViewState.kt\n```kotlin\nclass LoginViewState : ViewState {\n    // IN - values provided by UI\n    val email = DefaultValueLiveData(\"\")\n    val password = DefaultValueLiveData(\"\")\n\n    // OUT - Values observed by UI\n    val fullName = MutableLiveData\u003cString\u003e()\n    val isLoading = MutableLiveData\u003cBoolean\u003e()\n}\n```\n\n##### LoginViewModel.kt\n```kotlin\nclass LoginViewModel @Inject constructor(\n    private val loginUseCase: LoginUseCase // Inject UseCase\n) : BaseRxViewModel\u003cLoginViewState\u003e() {\n    override val viewState = LoginViewState()\n\n    fun logIn() = with(viewState) {\n        loginUseCase.execute(LoginData(email.value, email.password)) {\n            onStart {\n                isLoading.value = true\n            }\n            onSuccess {\n                isLoading.value = false\n                fullName.value = user.fullName // handle success \u0026 manipulate state\n            }\n            onError {\n                isLoading.value = false\n                // handle error\n            }\n        }\n    }\n}\n```\n\n### Synchronous execution of cr-usecase\n\nModule `cr-usecases` allows you to execute use cases synchronously. \n```kotlin\nfun onButtonClicked() = launchWithHandler {  \n    // ...\n    val data = useCase.execute().getOrDefault(\"Default\")  \n    // ...\n}\n```\n`execute` method returns a `Result` that can be either successful `Success` or failed `Error`.\n\n`launchWithHandler` launches a new coroutine encapsulated with a try-catch block. By default exception thrown in `launchWithHandler` is rethrown but it is possible to override this behavior with `defaultErrorHandler` or just log these exceptions in `logUnhandledException`.\n\n### Global error logger for handled errors in use-cases\n\nIn order to set an application-wide error logger for all handled errors in all use-cases, it is possible to set the following method in the `Application` class:\n\n```kotlin\nUseCaseErrorHandler.globalOnErrorLogger = { error -\u003e\n    CustomLogger.logError(error)\n}\n```\n\nThe `globalOnErrorLogger` callback in the `UseCaseErrorHandler` will be called for every error thrown in all use-cases that have defined onError receiver in the execute method.\n\nThe following execute method will trigger `globalOnErrorLogger`:\n\n```kotlin\nuseCase.execute {\n    ...\n    onError {\n        isLoading = false\n    }\n    ...\n}\n```\n\nThe following execute method won't trigger `globalOnErrorLogger` because onError is not defined and execute method will throw an unhandled exception.\n\n```kotlin\nuseCase.execute {}\n```\n\n## Propagating data model changes into UI\nThere are two main ways how to reflect data model changes in UI. Through `ViewState` observation\nor one-shot `Events`. \n\n### ViewState observation\n\nYou can observe state changes and reflect these changes in UI via DataBinding \nobservation directly in xml layout:\n\n ```xml\n \u003clayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\u003e\n \n     \u003cdata\u003e\n         \u003cvariable name=\"view\" type=\"app.futured.arkitekt.sample.ui.detail.DetailView\"/\u003e\n         \u003cvariable name=\"viewModel\" type=\"app.futured.arkitekt.sample.ui.detail.DetailViewModel\"/\u003e\n         \u003cvariable name=\"viewState\" type=\"app.futured.arkitekt.sample.ui.detail.DetailViewState\"/\u003e\n     \u003c/data\u003e\n     \n     \u003cTextView\n             android:layout_width=\"wrap_content\"\n             android:layout_height=\"wrap_content\"\n             android:text=\"@{viewState.myTextLiveData}\"/\u003e\n \u003c/layout\u003e\n```\n\n### Events\nEvents are one-shot messages sent from `ViewModel` to an Activity/Fragment. They\nare based on `LiveData` bus. Events are guaranteed to be delivered only once even when\nthere is screen rotation in progress. Basic event communication might look like this:\n\n##### `MainEvents.kt`\n```kotlin\nsealed class MainEvent : Event\u003cMainViewState\u003e()\n\nobject ShowDetailEvent : MainEvent()\n```\n\n##### `MainViewModel.kt`\n```kotlin\nclass MainViewModel @Inject constructor() : BaseViewModel\u003cMainViewState\u003e() {\n\n    override val viewState = MainViewState\n\n    fun onDetail() {\n        sendEvent(ShowDetailEvent)\n    }\n}\n```\n\n##### `MainActivity.kt`\n```kotlin\nclass MainActivity : BaseActivity\u003cMainViewModel, MainViewState, ActivityMainBinding\u003e(), MainView {\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n\n        observeEvent(ShowDetailEvent::class) { \n            startActivity(DetailActivity.getStartIntent(this)) \n        }\n    }\n}\n```\n\n## Stores (Repositories)\nAll our applications respect broadly known repository pattern. The main message this\npattern tells: Define `Store` (Repository) classes with single entity related business logic \neg. `UserStore`, `OrderStore`, `DeviceStore` etc. Let's see this principle on `UserStore` class\nfrom sample app:\n\n```kotlin\n@Singleton\nclass UserStore @Inject constructor() {\n    private val userRelay = BehaviorRelay.createDefault(User.EMPTY)\n\n    fun setUser(user: User) {\n        userRelay.accept(user)\n        // ... optionally persist user\n    }\n\n    fun getUser(): Observable\u003cUser\u003e {\n        return userRelay.hide()\n    }\n}\n```\n\nWith this approach only one class is responsible for `User` related data access. Besides \ncustom classes, Room library `Dao`s or for example Retrofit API interfaces might be \nperceived on the same domain level as stores. Thanks to use cases we can easily access, \nmanipulate and combine this kind of data on background threads. \n\n```kotlin\nclass GetUserFullNameObservabler @Inject constructor(\n    private val userStore: UserStore\n) : ObservablerUseCase\u003cString\u003e() {\n\n    override fun prepare(): Observable\u003cString\u003e {\n        return userStore.getUser()\n            .map { \"${it.firstName} ${it.lastName}\" }\n    }\n}\n```\n\nWe strictly respect this injection hierarchy:\n\n| Application Component | Injects |\n| --------- | --------------------- |\n| `Activity/Fragment` | `ViewModel` |\n| `ViewModel` | `ViewState`, `UseCase` |\n| `UseCase` | `Store` |\n| `Store` | `Dao`, `Persistence`, `ApiService` |\n\n## SavedStateHandle\n\nArkitekt also supports `SavedStateHandle` in `ViewModel`. To have access to `SavedStateHandle` instance you have to use `BaseSavedStateViewModelFactory` base class instead of `BaseViewModelFactory` in your ViewModelFactory implementation and provide `SavedStateRepositoryOwner` in your Activity/Fragment module if using Dagger.\n`SavedStateHandle` instance is part of `BaseViewModel` class so you can access it via `savedStateHandle` field. Beware that this field may be null if you don't use `BaseSavedStateViewModelFactory` as base class for your `ViewModelFactory` implementation.\n\n```kotlin\n@Module\nclass MainActivityModule {\n\n    @Provides\n    fun savedStateRegistryOwner(activity: MainActivity): SavedStateRegistryOwner = activity\n}\n```\n\n```kotlin\nclass MainViewModelFactory @Inject constructor(\n    savedStateRegistryOwner: SavedStateRegistryOwner,\n    override val viewModelProvider: Provider\u003cMainViewModel\u003e\n) : BaseSavedStateViewModelFactory\u003cMainViewModel\u003e(savedStateRegistryOwner) {\n    override val viewModelClass = MainViewModel::class\n}\n```\n## Testing\n\nIn order to create successful applications, it is highly encouraged to write tests for your application. But testing can be tricky sometimes so here are our best practices and utilities that will help you to achieve this goal with this library. \n\nSee [these tests](https://github.com/futuredapp/arkitekt/tree/5.x/example/src/) in `example` module for more detailed sample.\n\n### ViewModel testing\n\n[core-test](#Download) dependency contains utilities to help you with ViewModel testing.\n\n`ViewModelTest` that should be used as a base class for view model tests since it contains JUnit rules for dealing with a live data and with RxJava in tests.\n\nSee [these tests](https://github.com/futuredapp/arkitekt/tree/5.x/example/src/test/java/app/futured/arkitekt/sample/ui/) in `example` module for more detailed sample of view model testing.\n\n### Events testing\n\nThe [spy](https://github.com/mockk/mockk#spy) object should be used for an easy way of testing that expected events were sent to the view.\n\n```kotlin\nviewModel = spyk(SampleViewModel(mockViewState, ...), recordPrivateCalls = true)\n...\nverify { viewModel.sendEvent(ExpectedEvent) }\n```\n### Mocking of observeWithoutOwner \n\nWhen you are using `observeWithoutOwner` extensions then `everyObserveWithoutOwner` will be helpful for mocking of these methods.\n\nSo if a method in the view model looks somehow like this:\n```kotlin\nviewState.counter.observeWithoutOwner { value -\u003e\n    viewState.counterText.value = value.toString() \n}\n```\nthen it can be mocked with the following method:\n```kotlin\nval counterLambda = viewModel.everyObserveWithoutOwner { \n    viewState.counter\n}\n...\ncounterLambda.invoke(1) \n```\ninvoke(...) call will invoke a lambda argument passed to the `observeWithoutOwner` method in the tested method.\n\n\n### Mocking of Use Cases\n\nAdd [rx-usecase-test](#Download) or [cr-usecase-test](#Download) dependencies containing utilities to help you with mocking use cases in a view model.\n\nSince all 'execute' methods for [use cases](#use-cases) are implemented as extension functions, we created testing methods that will help you to easily mock them.\n\nSo if a method in the view model looks somehow like this:\n```kotlin\nfun onLoginClicked(name: String, password: String) {\n    loginUseCase.execute(LoginData(name, password)) {\n        onSuccess = { ... }\n    }\n}\n```\nthen it can be mocked with the following method:\n```kotlin\nmockLoginUseCase.mockExecute(args = ...) { Single.just(user) } // For RxJava Use Cases \nor\nmockLoginUseCase.mockExecute(args = ...) { user } // For Coroutines Use Cases\n```\nIn case that use case is using nullable arguments:\n```kotlin\nmockLoginUseCase.mockExecuteNullable(args = ...) { Single.just(user) } // For RxJava Use Cases\nor\nmockLoginUseCase.mockExecuteNullable(args = ...) { user } // For Coroutines Use Cases\n```\n\n### Activity and Fragment tests\n\n[core-test](#Download) dependency contains utilities to help you with espresso testing.\n\nIf you want to test Activities or Fragments then you have few possibilities. You can test them with the mocked implementation of a view model and view state, or you can test them with the real implementation of a view model and view state and with mocked use cases.\n\nSince Fragments and Activities from the dagger module are using AndroidInjection, we created utilities to deal with this.\n\nIn your tests, you can use `doAfterActivityInjection` and `doAfterFragmentInjection` to overwrite injected dependencies. These methods are called right after `AndroidInjection` and that allows overwriting of needed dependencies. In the following example, we are replacing the view model with the implementation that is using a view model with mocked dependencies and some random class with mocked implementation.  \n\n```kotlin\ndoAfterActivityInjection\u003cSampleActivity\u003e { activity -\u003e  \n    val provider = SampleViewModel(mockk(), SampleViewState()).asProvider()  \n    activity.viewModelFactory = SampleViewModelFactory(viewModelProvider)  \n    activity.someInjectedClass = mockk()  \n}\t\n```\nSee [these tests](https://github.com/futuredapp/arkitekt/tree/5.x/example/src/sharedTest/java/app/futured/arkitekt/sample/ui) in `example` module for more detailed samples of espresso test that can be executed as local unit tests or connected android tests.\n\n# License\nArkitekt is available under the MIT license. See the [LICENSE file](LICENCE) for more information.\n\nCreated with \u0026#x2764; at Futured. Inspired by [Alfonz library](https://github.com/petrnohejl/Alfonz).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffuturedapp%2Farkitekt","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ffuturedapp%2Farkitekt","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ffuturedapp%2Farkitekt/lists"}