{"id":19600635,"url":"https://github.com/stanwood/framework-arch-android","last_synced_at":"2026-04-18T07:36:26.194Z","repository":{"id":92390487,"uuid":"156602065","full_name":"stanwood/framework-arch-android","owner":"stanwood","description":"Libraries containing homegrown architecture related utility classes","archived":false,"fork":false,"pushed_at":"2020-02-03T00:15:46.000Z","size":464,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":3,"default_branch":"develop","last_synced_at":"2025-07-05T18:11:19.066Z","etag":null,"topics":["android","dagger2","dagger2-android"],"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/stanwood.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2018-11-07T20:08:39.000Z","updated_at":"2025-03-30T00:45:10.000Z","dependencies_parsed_at":"2023-05-17T02:30:41.754Z","dependency_job_id":null,"html_url":"https://github.com/stanwood/framework-arch-android","commit_stats":null,"previous_names":[],"tags_count":8,"template":false,"template_full_name":null,"purl":"pkg:github/stanwood/framework-arch-android","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stanwood%2Fframework-arch-android","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stanwood%2Fframework-arch-android/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stanwood%2Fframework-arch-android/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stanwood%2Fframework-arch-android/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/stanwood","download_url":"https://codeload.github.com/stanwood/framework-arch-android/tar.gz/refs/heads/develop","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/stanwood%2Fframework-arch-android/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31961347,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-18T00:39:45.007Z","status":"online","status_checked_at":"2026-04-18T02:00:07.018Z","response_time":103,"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","dagger2","dagger2-android"],"created_at":"2024-11-11T09:15:44.360Z","updated_at":"2026-04-18T07:36:26.167Z","avatar_url":"https://github.com/stanwood.png","language":"Kotlin","readme":"[![Release](https://jitpack.io/v/stanwood/framework-arch-android.svg?style=flat-square)](https://jitpack.io/#stanwood/framework-arch-android)\n[![API](https://img.shields.io/badge/API-16%2B-blue.svg?style=flat)](https://android-arsenal.com/api?level=16)\n\n# stanwood Architecture Utilities (Android)\n\nA set of libraries containing homegrown architecture related utility classes as well as stanwood's general architecture guidelines.\n\n## Import\n\nThe stanwood Architecture Utilities are hosted on JitPack. Therefore you can simply import them by adding\n\n```groovy\nallprojects {\n    repositories {\n        ...\n        maven { url \"https://jitpack.io\" }\n    }\n}\n```\n\nto your project's `build.gradle`.\n\nThen add this to you app's `build.gradle`:\n\n```groovy\ndependencies {\n    implementation 'com.github.stanwood.framework-arch-android:\u003cmodule\u003e:\u003cinsert latest version here\u003e' // aar versions available as well\n}\n```\n\n## Usage\n\nIn general we recommend usage of the [stanwood Android templates](https://plugins.jetbrains.com/plugin/11954-stanwood-android-templates) IntelliJ plugin. It provides easy to use templates for all the concepts described below and more. Install the plugin and find the templates in the `New...` context menu in the `stanwood` folder.\n\nThe templates include comments and TODOs. Fix all TODOs and you should have a nicely running app in no time.\n\nThe plugin adds the libraries in this repository to your dependencies for you when needed.\n\nFor detailed usage of the various libraries in this repository please refer to the README's of the respective libraries. You can find them at the root of the library folders, e.g. _di/README.md_.\n\nFind a sample app showing off more (also advanced concepts like loading state, error handling, paging etc.) in the _app_ folder.\n\n## Kotlin\n\nAll our apps are written in Kotlin and that's what the IntelliJ plugin and the libraries target. We don't offer any official Java support although many classes in the libraries might work just fine when used in combination with Java as well.\n\n## Dependencies\n\nThe libraries contain a number of dependencies on third party libraries we use in all our apps. There is nothing out of the ordinary to find here. In general we recommend to stick with the versions provided by this library and not override them in your app. We will make sure to keep those dependencies updated within a reasonable timeframe and after having tested them with the components provided in this library.\n\nThe main third party libraries we use in our architecture aside of Google's usual Android libraries are (in no special order):\n\n- [Google/Dagger](https://github.com/google/dagger) (specifically the Android flavour) for DI\n- [ReactiveX/RxJava](https://github.com/ReactiveX/RxJava) for handling data streams\n- [NYTimes/Store](https://github.com/NYTimes/Store) and its Kotlin/Android specific flavours for simple network data retrieval and offline persistence\n- [Android Architecture Components](https://developer.android.com/topic/libraries/architecture/) for data loading, data streaming within the View layer, navigation and general persistence\n\n## Libraries\n\nThis repository offers the following libraries:\n\n### core\nThe core library contains the most important basic components of our architecture such as `ViewModel` (don't confuse it with Google's `ViewModel`!), `ViewDataProvider`, `Resource` as well as some RxJava 2 helper classes. All other libraries depend on this library so that you rarely need to pull this in manually.\n\n### di\nThis library provides a set of dagger compatible factories for `ViewModel`s and `ViewDataProvider`s as well as Android specific scopes and modules.\n\n### nav\nThe nav library provides helper classes for simplified navigation handling. This is mainly used by the IntelliJ plugin.\n\n## Packaging\n\nWe package our classes as follows (WIP):\n\n**TODO: Update screenshot**\n\n![Packages](https://imgur.com/PVvcczQ.png)\n\nGeneric (app-wide) components belong into the root folder packages while those for specific features are to be moved into the specific feature folders.\n\nInteractors are *always* located in the *interactor* package in the app root package. Similarly Repositories are *always* located in the *repositories* package. You never find any of those within a feature package.\n\n## General architecture\n\n![Architecture](https://i.imgur.com/MxrQdaP.jpg)\n\nThe overall architecture consists of three layers:\n\n1. **Data layer**: where the data is fetched from some source (i.e. network, database, file system). This is where the *Repository* classes reside\n2. **Domain layer**: where data from the data layer is brought into a form suitable for the app - this is mainly done by single-purpose *Interactors* which often fetch data from multiple Repositories\n3. **View layer**: where domain data is presented to the user (after optional conversion by means of the ViewModel), here we find *ViewDataProvider*s, *ViewModel*s and our usual Android suspects like Fragments and Activities.\n\nMost dependencies are resolved by means of the dagger DI framework (don't worry, the plugin generates most of the modules and components for you). Usually data flows by means of RxJava 2 streams between the layers.\n\n### Data layer\n\nThe data layer is the central source for data not originating from within the app. It is our interface to the outside world.\n\nRepositories are the main components of the data layer. They take care of fetching and sending data from/to outside data sources.\n\nAs the data layer is the bottom-most layer repositories don't know anything about the layers above. They are usually contacted directly by Interactors from the domain layers.\n\nInstead of sending raw source data to the interactors, Repositories *map* data to *Domain Objects*. This helps at abstracting the actual sources from the rest of the app and allows for relatively simple replacements or changes of sources.\n\nThey also take care of transparently persisting data, e.g. for offline cases. In case of network sources we use the Store library for that. The network data itself is fetched via retrofit.\n\nWhen a DB is needed we prefer *Room* for its reactive interface which integrates very well in our reactive way of coding.\n\nA simple repository implementation might look like this (the IntelliJ plugin will aid you in creating similar classes within seconds):\n\n```kotlin\nclass MhwRepository @Inject constructor(private val api: MhwApi, fileSystem: FileSystem) {\n    companion object {\n        private val allArmor = BarCode(\"Armor\", \"all\")\n    }\n\n    private val sourcePersister = SourcePersisterFactory.create(fileSystem, 60, TimeUnit.MINUTES)\n    private val memoryPolicy =\n        MemoryPolicy.builder().setExpireAfterWrite(30).setExpireAfterTimeUnit(TimeUnit.MINUTES)\n            .build()\n\n    private val armorStore by lazy {\n        SerializationParserFactory.createSourceParser(MhwArmor.serializer().list)\n            .fetchFrom { api.fetchArmor() }\n            .open()\n    }\n\n    fun fetchArmorById(id: Long): Single\u003cArmor\u003e =\n        armorStore.get(allArmor).map { src -\u003e\n            src.first { it.id == id }\n                .mapToArmor()\n        }\n\n    private fun \u003cT\u003e Parser\u003cBufferedSource, T\u003e.fetchFrom(fetcher: (BarCode) -\u003e Single\u003cBufferedSource\u003e) =\n        StoreBuilder.parsedWithKey\u003cBarCode, BufferedSource, T\u003e()\n            .fetcher(fetcher)\n            .persister(sourcePersister)\n            .refreshOnStale()\n            .memoryPolicy(memoryPolicy)\n            .parser(this)\n}\n```\n\n## Domain Layer\n\nThe domain layer is where our business rules are defined - usually in the form of Interactors.\n\nInteractors usually fetch data from multiple Repositories, merge that data and return it as more complex Domain Models. Often they also implement business rules like: \"only return data here when the user is logged in, otherwise throw an error\" or \"only enable the feature when the user has completed an IAP\".\n\nEven if an Interactor just fetches data straight from one Repository don't feel tempted to skip implementation of that Interactor and directly access the repository from the above View layer. You will loose the benefit of having all business rules defined in an encapsulated way.\n\nInteractors are usually quite tightly scoped and thus very reusable. It is not uncommon for ViewDataProviders to access multiple interactors to get the data needed by its ViewModel.\n\nAn Interactor can be as simple as the following, but it can get much more complex when user handling, dynamic feature flags and merging of data from different sources (usually repositories) are involved:\n\n```kotlin\nclass GetArmorInteractor @Inject constructor(private val repository: MhwRepository) {\n\n    fun getArmor() = repository.fetchArmorSets()\n}\n```\n\n## View layer\n\nThe View layer is the topmost layer in our Architecture. As such it is the user facing layer. The View layer is where most of our features are sitting.\n\nThe interface between the View layer and the Domain layer is defined by ViewDataProviders.\n\nViewDataProviders are regular Android ViewModels. As such they remain in place even across configuration changes. However, their sole purpose is to fetch (and possibly post) data, not to handle any UI logic.\n\nThis is done by the ViewModel (again, not the Android ViewModel!) in collaboration with the Fragment.\n\nEvery ViewModel has access to a ViewDataProvider. The ViewModel subscribes to an Observable supplied by the ViewDataProvider to receive data from the lower layers.\n\nThe ViewDataProvider is also where you would implement refresh handling and where data from multiple Interactors is merged. Thus the ViewDataProvider decides *which streams are provided to the ViewModel depending on what the ViewModel asks for*.\n\nA simple ViewDataProvider might look like this:\n\n```kotlin\nclass ArmorDataProviderImpl @Inject constructor(\n    private val armorInteractor: GetArmorInteractor,\n    private val exceptionMapper: ExceptionMessageMapper\n) : ViewDataProvider(), ArmorDataProvider {\n    private var disposable: CompositeDisposable = CompositeDisposable()\n    private val retrySubject = PublishSubject.create\u003cUnit\u003e()\n\n    override val data =\n        retrySubject\n            .startWith(Unit)\n            .switchMap { Observable.concat(Observable.just(Resource.Loading()), mappedArmorSets) }\n            .replay(1)\n            .autoConnect(1) { disposable += it }\n\n    private val mappedArmorSets\n        get() = armorInteractor.getArmor()\n            .map { res -\u003e\n                res.associateBy(\n                    { ArmorItem.SetViewModel(it.id, \"${it.name} (${it.rank})\") },\n                    { armor -\u003e\n                        armor.pieces.map {\n                            ArmorItem.ArmorViewModel(it.id, it.name, it.image, it.type)\n                        }\n                    })\n            }\n            .compose(ResourceTransformer.fromSingle(exceptionMapper))\n            .toObservable()\n\n    override fun retry() {\n        retrySubject.onNext(Unit)\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        disposable.dispose()\n    }\n}\n```\n\nAgain the plugin will aid you in defining the necessary interfaces and the class itself.\n\nWhen the ViewModel receives data from the ViewDataProvider it may map it again to data objects suitable for whatever UI the data shall be presented in (e.g. by applying filters). The mapped objects can in turn be ViewModels (e.g. when presenting data in a RecyclerView, we usually suffix those with _Item_) or just data classes. In both cases data is bound to the UI by means of `android.databinding.Observable*` properties. \n\nFor better performance and control of what happens we usually avoid having our ViewModels implement `BaseObservable`, better use the `notifyPropertyChanged()` methods to inform the UI of changes.\n\nIt is also fine to use `LiveData` to forward data to the UI, but keep in mind that this also leads to less control over what happens when.\n\nNote, that ViewModels usually don't know anything about the UI itself (so they don't have references to Views etc.). Use data binding instead and provide only View *data* to the ViewModel (e.g. by using the generated binding in the Fragment, writing binding adapters should only rarely be necessary then).\n\n*Contrary to the Android ViewModel, our ViewModel can easily have access to the Fragment or Activity context, just inject it and don't worry about leaks.*\n\n```kotlin\nclass ArmorsViewModel @Inject constructor(private val dataProvider: ArmorDataProvider) : ViewModel {\n    private val navigation = PublishSubject.create\u003cNavigationTarget\u003e()\n    val navigator = navigation.firstElement()!!\n\n    val items =\n        dataProvider.data\n            .filter { it.data != null }\n            .map { it.data!! }\n            .observeOn(AndroidSchedulers.mainThread())!!\n\n    fun retry() {\n        dataProvider.retry()\n    }\n\n    val status = dataProvider.data\n        .compose(ResourceStatusTransformer.fromObservable())\n        .observeOn(AndroidSchedulers.mainThread())!!\n}\n```\n\nThe last part of the chain is the Fragment that injects the ViewModel. This is likely the most boring part.\n\nThe Fragment subscribes to the data stream provided by the ViewModel (usually in `onCreate()`). If the Fragment hosts a RecyclerView it might then forward the data to the Adapter once it arrives.\n\n```kotlin\nclass ArmorsFragment : Fragment(), HasSupportFragmentInjector {\n\n    @Inject\n    internal lateinit var viewModelFactory: ViewModelFactory\u003cArmorsViewModel\u003e\n    private var viewModel: ArmorsViewModel? = null\n    @Inject\n    internal lateinit var androidInjector: DispatchingAndroidInjector\u003cFragment\u003e\n    @Inject\n    internal lateinit var dataBindingComponent: DataBindingComponent\n    private var binding: FragmentArmorBinding? = null\n    private var rcvAdapter: ArmorsAdapter? = null\n\n    override fun supportFragmentInjector() = androidInjector\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        AndroidSupportInjection.inject(this)\n        super.onCreate(savedInstanceState)\n        viewModel = viewModelFactory.create(ArmorsViewModel::class.java)\n    }\n\n    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?) =\n        DataBindingUtil.inflate\u003cFragmentArmorBinding\u003e(inflater, R.layout.fragment_armor, container, false, dataBindingComponent)\n            .apply {\n                binding = this\n                retryCallback = View.OnClickListener { viewModel?.retry() }\n            }.root\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        view.requestApplyInsets()\n        binding?.apply {\n            rcv.apply {\n                setHasFixedSize(true)\n                layoutManager = LinearLayoutManager(context)\n            }\n            lifecycleOwner = viewLifecycleOwner\n        }\n        rcvAdapter = ArmorsAdapter(LayoutInflater.from(context), dataBindingComponent) { viewModel?.itemClicked(it) }\n        viewModel?.apply {\n            items.subscribeBy(viewLifecycleOwner, onNext = {\n                binding?.rcv?.apply {\n                    rcvAdapter?.apply {\n                        if (adapter == null) {\n                            adapter = this\n                        }\n                        submitList(it)\n                    }\n                }\n            })\n            status.subscribeBy(viewLifecycleOwner, onNext = {\n                binding?.status = it\n            })\n            navigator.subscribeBy(\n                viewLifecycleOwner,\n                onSuccess = { findNavController().navigate(it.navDirections, it.navOptions) })\n        }\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        viewModel?.destroy()\n    }\n}\n```\n\n## A word on `Resource`s\n\nThe Resource class is a sealed class wrapping objects returned by asynchronous operations (usually in streams). We use it within the View layer to ease status handling (there also is a pure `ResourceStatus` class which only propagates status without any data), so make sure to always wrap your data objects in Resources first thing when preparing stream data in a ViewDataProvider. Various transformers in the core library will help you with mapping RxJava data streams into RxJava Resource/ResourceStatus streams (check out the `ResourceTransformer`/`ResourceStatusTransformer` object classes for details).\n\nA Resource can have three states: Success, Failed and Loading. Depending on the state the Resource will contain the data itself (**Success**), a message and an optionally a Throwable and data (**Failed**, the data might originate from other merged sources) or no data (**Loading**).\n\nThe sealed class concept will help you to easily react to state changes in your UI.\n\nUsually your ViewModel will split the Resource data stream coming from the Interactors into two streams: a pure data stream and a ResourceStatus stream. This makes reacting on changes in your UI even simpler.\n\n```kotlin\nval items =\n    dataProvider.data\n        .filter { it.data != null }\n        .map { it.data!! }\n        .observeOn(AndroidSchedulers.mainThread())!!\n\nval status = dataProvider.data\n    .compose(ResourceStatusTransformer.fromObservable())\n    .observeOn(AndroidSchedulers.mainThread())!!\n```\n\nState and Resource listening can happen in either your XML or in the Fragment. A Fragment might do the following in `onViewCreated()`:\n\n```kotlin\nviewModel.items.subscribeBy(viewLifecycleOwner, onNext = {\n    binding?.rcv?.apply {\n        rcvAdapter?.apply {\n            if (adapter == null) {\n                adapter = this\n            }\n            submitList(it)\n        }\n    }\n})\nviewModel.status.subscribeBy(viewLifecycleOwner, onNext = {\n    binding?.status = it\n})\n```\n\n## Contribute\n\nThis project follows the [Android Kotlin Code Style](https://android.github.io/kotlin-guides/style.html)\nfor all Kotlin classes (exception: line length = 140).\n\nThe project ships with all necessary IDE settings and checks enabled. Pre-commit we run ktlint to check for adherence. Usually running `\n./gradlew ktlintFormat` will fix all errors reported by ktlint.\n\nOur CI runs those checks as well when you create your PR.\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstanwood%2Fframework-arch-android","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstanwood%2Fframework-arch-android","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstanwood%2Fframework-arch-android/lists"}