{"id":21512029,"url":"https://github.com/strvcom/dundee","last_synced_at":"2025-04-09T18:21:59.207Z","repository":{"id":97727376,"uuid":"114254454","full_name":"strvcom/dundee","owner":"strvcom","description":"Android App Architecture Showcase","archived":false,"fork":false,"pushed_at":"2018-03-20T07:27:27.000Z","size":995,"stargazers_count":13,"open_issues_count":0,"forks_count":10,"subscribers_count":13,"default_branch":"master","last_synced_at":"2025-03-23T20:22:19.531Z","etag":null,"topics":["android","architecture","data-binding","mvvm","repository-pattern"],"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/strvcom.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":"2017-12-14T13:33:42.000Z","updated_at":"2022-09-03T13:06:35.000Z","dependencies_parsed_at":null,"dependency_job_id":"112ffe79-5be4-4a4f-844a-0d1b80f7c209","html_url":"https://github.com/strvcom/dundee","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strvcom%2Fdundee","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strvcom%2Fdundee/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strvcom%2Fdundee/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/strvcom%2Fdundee/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/strvcom","download_url":"https://codeload.github.com/strvcom/dundee/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248085636,"owners_count":21045191,"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","architecture","data-binding","mvvm","repository-pattern"],"created_at":"2024-11-23T22:25:37.733Z","updated_at":"2025-04-09T18:21:59.199Z","avatar_url":"https://github.com/strvcom.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"dundee\n==============\n[![CircleCI](https://circleci.com/gh/strvcom/dundee.svg?style=svg)](https://circleci.com/gh/strvcom/dundee)\n\n\u003cp align=\"center\"\u003e\n\t\u003cimg src=\"https://github.com/strvcom/dundee/raw/master/extras/device-2018-02-26-134516.png\" width=\"320\"/\u003e\n\t\u003cimg src=\"https://github.com/strvcom/dundee/raw/master/extras/device-2018-02-26-135801.png\" width=\"320\"/\u003e\n\u003c/p\n\n\nDundee is a showcase app written to demonstrate modern Android app architecture approaches. It displays current value of various cryptocurrencies fetched from different sources, \nstores your portfolio in the cloud within Firebase Firestore database and provides you with a detailed info about profit/loss for each of the currencies.\n\nThe app leverages MVVM architecture provided by Android Architecture Components. \nTogether with Kotlin language features and Android Data Binding framework there is a minimal amount of boilerplate code needed to create fully functional app. \nData is accessed through a repository pattern, network call results can be automatically cached into database via Room library. The architecture also uses a simple dependency injection\nmechanism which provides all you need to make your classes testable and mockable. See the `com.strv.ktools` package for all tools. Key tools are described below.\n\nMVVM Architecture via ViewModelBinding\n----------------\n\nViewModelBinding (vmb) connects Architecture ViewModels with the View (Activity/Fragment) via Data Binding automatically. All you have to do is to initialize it in your View via Kotlin delegate:\n\n```kotlin\nprivate val vmb by vmb\u003cMainViewModel, ActivityMainBinding\u003e(R.layout.activity_main) \n//...\n// then in onCreate():\nvmb.viewModel.doSomething()\nvmb.binding.myView.doSomethingElse()\n```\n\n_see MainView.kt class for more_\n\nView Class (Activity/Fragment) does not have to implement or extend anything special. In case of a Fragment, you still need to override the `onCreateView()` method though. But you can just return `vmb.rootView`.\nViewModel needs to extend Architecture Components `ViewModel` or `AndroidViewModel`. \n\nIf you want to instantiate ViewModel yourself, you can provide a lambda functions that provides the instance. \nThis function will be called just in time when you have access to Intent and other data within the Activity/Fragment so you can pass any parameters to your ViewModel constructor:\n\n```kotlin\nprivate val vmb by vmb\u003cSignUpViewModel, ActivitySignUpBinding\u003e(R.layout.activity_sign_up) { SignUpViewModel(intent.getStringExtra(EXTRA_DEFAULT_EMAIL), intent.getStringExtra(EXTRA_DEFAULT_PASSWORD)) }\n```\n\nViewModels hold data exclusively within LiveData. ObservableFields were replaced by MutableLiveData which can be directly consumed by the View. Once you use LiveData instance within a layout file DataBinding mechanism will observe \nthe data with a proper LifecycleOwner - Activity or Fragment.\n\nView communicates with ViewModel directly via public API. ViewModels communicate with View via LiveData or EventLiveData - see `SignUpViewModel` for an example\n\nDependency Injection\n--------------------\n\nWe use basic DI mechanism consisting of two kotlin functions. First you need to call `provide { Gson() }` or `provideSingleton { Gson() }` somewhere (see `DIModule.kt`) and then to inject the dependency into a property\njust use `val gson by inject\u003cGson\u003e()`. You can also use different scopes for the DI by providing String scope name to both functions.\n\nIn your test suite you can use different module which will provide different or mock variants of your classes.\n\nRepository Pattern\n------------------\n\nRepository pattern used in this project is based on [this article](https://developer.android.com/topic/libraries/architecture/guide.html) but heavily modified. The idea is that both network calls and database operations\nreturn data in form of LiveData. NetworkBoundResource takes care of merging those two data sources into single LiveData using `MediatorLiveData`. It can manage both simple (not cached) network calls as well as automatically cached calls.\nData is always wrapped within a `Resource` class adding status, message and potentially a Throwable instance. See `repository.kt` for more and `DashboardViewModel` with `TickerLiveData` for an example.\n\nTo be able to receive data from REST API within a LiveData there is a custom Retrofit `CallAdapterFactory` which transforms `Call\u003cT\u003e` to `LiveData\u003cResource\u003cT\u003e\u003e`\n\nShared Preferences Delegate\n---------------------------\nShared Preferences Delegates massively simplifies work with Android SharedPreferences. Just declare a property using the delegate and read/set its value. It will be automatically stored within SharedPreferences of your choice.\nExample:\n\n```kotlin\nvar accessToken by sharedPrefs().string() // property name will be used as the key\nvar userEmail by sharedPrefs().string(key = \"email\")\nvar hasReadConditions by sharedPrefs().boolean(false)\n//...\n// then\naccessToken = \"asd328y0823hdkajshd238\"\nif (!hasReadConditions){\n\t//...\n\thasReadConditions = true\n}\n```\n\nIf you rather work with LiveData, there are delegates for that as well. When active, the LiveData listens to SharedPreferences changes for the given key and gets updated automatically. It supports setting value as well so\nyou can connect it to the layout directly:\n\n```kotlin\n// in ViewModel\nvar userName by sharedPrefs().stringLiveData()\n```\n\n```xml\n\u003c!-- in layout --\u003e\n\u003cEditText android:text=\"@={viewModel.userName}\"/\u003e\n```\n\nOther Tools\n-----------\n**Logs** - multiple logging functions - see `log.kt`\n\n**LiveData** - `LiveData.map()`, `LiveData.switchMap()` extension shorthands, `EventLiveData` for delivering one-time events to View, `mutableLiveDataOf()` shorthand, etc. - see `livedata.kt`\n\n**Async** - `doAsync {}` and `uiThread {}` functions to simplify background operations\n\t\n\nAuthor\n------\n\n\u003cp align=\"center\"\u003e\n\t\u003ca href=\"http://www.strv.com\"\u003e\n\t\t\u003cimg src=\"https://github.com/strvcom/dundee/raw/master/extras/strv-logo.png\" width=\"320\"/\u003e\n\t\u003c/a\u003e\n\t\u003cbr/\u003e\n\t\u003cb\u003eJakub Kinst\u003c/b\u003e (jakub.kinst@strv.com)\n\t\u003cbr/\u003e\n\t\u003cb\u003eLeos Dostal\u003c/b\u003e (leos.dostal@strv.com)\n\u003c/p\u003e","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrvcom%2Fdundee","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fstrvcom%2Fdundee","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fstrvcom%2Fdundee/lists"}