├── .gitignore ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE.md ├── LICENSE.md ├── PULL_REQUEST_TEMPLATE.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── chintansoni │ │ └── android │ │ └── repositorypattern │ │ ├── MyApplication.kt │ │ ├── di │ │ ├── DatabaseModule.kt │ │ ├── NetworkModule.kt │ │ └── ViewModelModule.kt │ │ ├── model │ │ ├── EntityMapper.kt │ │ ├── NetworkBoundResource.kt │ │ ├── Resource.kt │ │ ├── Status.kt │ │ ├── UserRepository.kt │ │ ├── local │ │ │ ├── AppDatabase.kt │ │ │ ├── DatabaseConstants.kt │ │ │ ├── dao │ │ │ │ └── UserDao.kt │ │ │ └── entity │ │ │ │ └── User.kt │ │ └── remote │ │ │ ├── ApiService.kt │ │ │ └── response │ │ │ └── RandomUserResponse.kt │ │ ├── util │ │ ├── BindingUtils.kt │ │ ├── FutureStudioAppGlideModule.kt │ │ ├── InfiniteScrollListener.kt │ │ ├── RvExtension.kt │ │ ├── UserDiffUtil.kt │ │ └── widget │ │ │ └── CircleImageView.kt │ │ ├── view │ │ ├── activity │ │ │ └── UsersActivity.kt │ │ ├── adapter │ │ │ └── UserRecyclerAdapter.kt │ │ ├── fragment │ │ │ ├── DetailFragment.kt │ │ │ └── ListFragment.kt │ │ └── viewholder │ │ │ ├── LoaderViewHolder.kt │ │ │ └── UserViewHolder.kt │ │ └── viewmodel │ │ ├── DetailsViewModel.kt │ │ └── ListViewModel.kt │ └── res │ ├── anim │ ├── slide_down.xml │ └── slide_up.xml │ ├── drawable-v24 │ └── ic_launcher_foreground.xml │ ├── drawable │ ├── bg_grdnt_white_transparent.xml │ ├── ic_arrow_back_black_24dp.xml │ ├── ic_close_black_24dp.xml │ ├── ic_female_placeholder.jpg │ ├── ic_launcher_background.xml │ └── ic_male_placeholder.jpeg │ ├── layout │ ├── frag_details.xml │ ├── list_fragment.xml │ ├── list_item_loader.xml │ ├── list_item_user.xml │ └── users_activity.xml │ ├── menu │ └── details_menu.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea 5 | .DS_Store 6 | /build 7 | /captures 8 | .externalNativeBuild 9 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at chintansoni202@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | We'd love for you to contribute to our source code and to make the project even better than it is today! 3 | Here are the guidelines we'd like you to follow: 4 | 5 | - [Code of Conduct](#coc) 6 | - [Git Commit Messages](#commit) 7 | - [Got a Question or Problem?](#question) 8 | - [Found an Issue?](#issue) 9 | 10 | ## Code of Conduct 11 | [Code of Conduct](https://github.com/ChintanSoni202/RepositoryPattern/blob/master/CODE_OF_CONDUCT.md) 12 | 13 | ## Git Commit Messages 14 | 15 | * Use the present tense ("Add feature" not "Added feature") 16 | * Use the imperative mood ("Move cursor to..." not "Moves cursor to...") 17 | * Limit the first line to 72 characters or less 18 | * Reference issues and pull requests liberally after the first line 19 | * When only changing documentation, include `[ci skip]` in the commit title 20 | * Consider starting the commit message with an applicable emoji: 21 | * :art: `:art:` when improving the format/structure of the code 22 | * :racehorse: `:racehorse:` when improving performance 23 | * :non-potable_water: `:non-potable_water:` when plugging memory leaks 24 | * :memo: `:memo:` when writing docs 25 | * :bug: `:bug:` when fixing a bug 26 | * :fire: `:fire:` when removing code or files 27 | * :green_heart: `:green_heart:` when fixing the CI build 28 | * :white_check_mark: `:white_check_mark:` when adding tests 29 | * :lock: `:lock:` when dealing with security 30 | * :arrow_up: `:arrow_up:` when upgrading dependencies 31 | * :arrow_down: `:arrow_down:` when downgrading dependencies 32 | * :shirt: `:shirt:` when removing lint/checkstyle warnings 33 | 34 | ## Got a Question or Problem? 35 | 36 | If you feel that we're missing an important bit of documentation, feel free to 37 | file an issue so we can help. Here's an example to get you started: 38 | 39 | ``` 40 | What are you trying to do or find out more about? 41 | 42 | Where have you looked? 43 | 44 | Where did you expect to find this information? 45 | ``` 46 | 47 | ## Found an Issue? 48 | If you find a bug in the source code or a mistake in the documentation, you can help us by 49 | submitting an issue to our project. 50 | 51 | To submit an issue, please check the [Issue Template](https://github.com/ChintanSoni202/RepositoryPattern/blob/master/ISSUE_TEMPLATE.md). 52 | 53 | Even better you can submit a Pull Request with a fix. 54 | 55 | ### Pull Request 56 | To generate a pull request, please consider following [Pull Request Template](https://github.com/ChintanSoni202/RepositoryPattern/blob/master/PULL_REQUEST_TEMPLATE.md). 57 | 58 | * Search [GitHub](https://github.com/ChintanSoni202/RepositoryPattern/pulls) for an open or closed Pull Request 59 | that relates to your submission. You don't want to duplicate effort. 60 | * Please have a look at [License](https://github.com/ChintanSoni202/RepositoryPattern/blob/master/LICENSE.md) before sending pull 61 | requests. We cannot accept code without this. 62 | 63 | That's it! Thank you for your contribution! 64 | -------------------------------------------------------------------------------- /ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Prerequisites 2 | 3 | * [ ] Put an X between the brackets on this line if you have done all of the following: 4 | * Reproduced the problem 5 | * Checked that your issue isn't already filed: https://github.com/issues?utf8=✓&q=is%3Aissue+user%3AChintanSoni202 6 | 7 | ### Description 8 | 9 | [Description of the issue] 10 | 11 | ### Steps to Reproduce 12 | 13 | 1. [First Step] 14 | 2. [Second Step] 15 | 3. [and so on...] 16 | 17 | **Expected behavior:** [What you expect to happen] 18 | 19 | **Actual behavior:** [What actually happens] 20 | 21 | **Reproduces how often:** [What percentage of the time does it reproduce?] 22 | 23 | ### Additional Information 24 | 25 | Any additional information, configuration or data that might be necessary to reproduce the issue. 26 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2018 Chintan Soni 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ### Requirements 2 | 3 | * Filling out the template is required. Any pull request that does not include enough information to be reviewed in a timely manner may be closed at the maintainers' discretion. 4 | * All new code requires tests to ensure against regressions 5 | 6 | ### Description of the Change 7 | 8 | 13 | 14 | ### Alternate Designs 15 | 16 | 17 | 18 | ### Why Should This Be In Core? 19 | 20 | 21 | 22 | ### Benefits 23 | 24 | 25 | 26 | ### Possible Drawbacks 27 | 28 | 29 | 30 | ### Verification Process 31 | 32 | 43 | 44 | ### Applicable Issues 45 | 46 | 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Repository Pattern 2 | --- 3 | This is a sample app that is developed to showcase MVVM architecture for Android with 4 | Repository pattern and Offline-First strategy with Single Source of Truth. 5 | 6 | **NOTE** It is a relatively more complex and in progress architecture example so if you are not familiar 7 | with [Architecture Components](https://developer.android.com/arch), you are highly recommended to check other examples 8 | in their repository first. 9 | 10 | Introduction 11 | --- 12 | ### Functionality 13 | The app is structured with best practices and guidelines which showcases use of: 14 | * Android Framework with Kotlin 15 | * Repository pattern with offline-first strategy 16 | * MVVM architectural pattern 17 | * Android Architecture Components 18 | * Dependency injection using Koin 19 | * Network and API call using Retrofit 2 20 | * Database using Room Architecture Component 21 | 22 | **NOTE** The app architecture is still in progress. 23 | 24 | ### Building 25 | You can open the project in Android studio and press run. 26 | 27 | ### Libraries 28 | * [Android Support Library](https://developer.android.com/topic/libraries/support-library/index.html) 29 | * [Android Architecture Components](https://developer.android.com/arch) 30 | * [Koin](https://insert-koin.io/) 31 | * [Coroutines](https://kotlinlang.org/docs/reference/coroutines-overview.html) 32 | * [Retrofit 2](http://square.github.io/retrofit) 33 | * [Android ConstraintLayout](https://developer.android.com/training/constraint-layout/index.html) 34 | * [Android Data Binding](https://developer.android.com/topic/libraries/data-binding/index.html) 35 | * [Glide](https://github.com/bumptech/glide) 36 | * [Timber](https://github.com/JakeWharton/timber) 37 | 38 | Contributing 39 | --- 40 | You are most welcome to contribute to this project! 41 | 42 | Please have a look at [Contributing Guidelines](https://github.com/iChintanSoni/RepositoryPattern/blob/master/CONTRIBUTING.md), before contributing and proposing a change. 43 | 44 | ### Pull Request 45 | To generate a pull request, please consider following [Pull Request Template](https://github.com/iChintanSoni/RepositoryPattern/blob/master/PULL_REQUEST_TEMPLATE.md). 46 | 47 | ### Issues 48 | To submit an issue, please check the [Issue Template](https://github.com/iChintanSoni/RepositoryPattern/blob/master/ISSUE_TEMPLATE.md). 49 | 50 | Code of Conduct 51 | --- 52 | [Code of Conduct](https://github.com/iChintanSoni/RepositoryPattern/blob/master/CODE_OF_CONDUCT.md) 53 | 54 | License 55 | --- 56 | [Copyright 2018 Chintan Soni - Apache License 2.0](https://github.com/iChintanSoni/RepositoryPattern/blob/master/LICENSE.md) 57 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion 29 8 | defaultConfig { 9 | applicationId "com.chintansoni.android.repositorypattern" 10 | minSdkVersion 21 11 | targetSdkVersion 29 12 | versionCode 1 13 | versionName "1.0" 14 | dataBinding.enabled true 15 | multiDexEnabled true 16 | } 17 | buildTypes { 18 | buildTypes.each { 19 | it.buildConfigField("String", "BASE_URL", '"https://randomuser.me/"') 20 | } 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | androidExtensions { 27 | experimental true 28 | } 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_1_8 31 | targetCompatibility JavaVersion.VERSION_1_8 32 | } 33 | } 34 | 35 | repositories { 36 | mavenCentral() 37 | google() 38 | maven { url "https://jitpack.io" } 39 | } 40 | 41 | dependencies { 42 | implementation fileTree(include: ['*.jar'], dir: 'libs') 43 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" 44 | implementation 'androidx.appcompat:appcompat:1.1.0' 45 | implementation 'androidx.cardview:cardview:1.0.0' 46 | implementation 'androidx.recyclerview:recyclerview:1.1.0-beta05' 47 | implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0" 48 | implementation 'androidx.constraintlayout:constraintlayout:2.0.0-beta2' 49 | implementation 'com.google.android.material:material:1.1.0-beta01' 50 | 51 | // Retrofit and OkHttp 52 | implementation 'com.squareup.retrofit2:retrofit:2.6.0' 53 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.5.0' 54 | implementation 'com.squareup.retrofit2:converter-gson:2.6.0' 55 | implementation 'com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:0.9.2' 56 | implementation 'com.squareup.okhttp3:okhttp:4.0.1' 57 | implementation 'com.squareup.okhttp3:logging-interceptor:4.0.1' 58 | implementation 'com.jakewharton.timber:timber:4.7.1' 59 | 60 | // Room 61 | def room = "2.2.0" 62 | implementation "androidx.room:room-runtime:$room" 63 | kapt "androidx.room:room-compiler:$room" 64 | implementation "androidx.room:room-ktx:$room" 65 | 66 | // Glide 67 | implementation 'com.github.bumptech.glide:glide:4.9.0' 68 | kapt 'androidx.annotation:annotation:1.1.0' 69 | kapt 'com.github.bumptech.glide:compiler:4.9.0' 70 | implementation 'com.github.ybq:Android-SpinKit:1.2.0' 71 | 72 | // Koin 73 | def koin_version = "2.0.1" 74 | implementation "org.koin:koin-core:$koin_version" 75 | implementation "org.koin:koin-android:$koin_version" 76 | implementation "org.koin:koin-androidx-viewmodel:$koin_version" 77 | implementation "org.koin:koin-androidx-scope:$koin_version" 78 | 79 | // Coroutines and Retrofit 80 | def coroutine = "1.1.1" 81 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutine" 82 | implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutine" 83 | 84 | // LifeCycle 85 | def lifecycle = "2.2.0-beta01" 86 | implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle" 87 | implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle" 88 | implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle" 89 | implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle" 90 | } 91 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/MyApplication.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern 2 | 3 | import android.app.Application 4 | import com.chintansoni.android.repositorypattern.di.databaseModule 5 | import com.chintansoni.android.repositorypattern.di.networkModule 6 | import com.chintansoni.android.repositorypattern.di.viewModelModule 7 | import org.koin.android.ext.koin.androidContext 8 | import org.koin.core.context.startKoin 9 | import timber.log.Timber 10 | 11 | class MyApplication : Application() { 12 | 13 | override fun onCreate() { 14 | super.onCreate() 15 | 16 | initializeTimber() 17 | initializeKoin() 18 | } 19 | 20 | private fun initializeKoin() { 21 | startKoin { 22 | androidContext(this@MyApplication) 23 | modules(listOf(networkModule, databaseModule, viewModelModule)) 24 | } 25 | } 26 | 27 | private fun initializeTimber() { 28 | if (BuildConfig.DEBUG) { 29 | Timber.plant(Timber.DebugTree()) 30 | } 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.di 2 | 3 | import androidx.room.Room 4 | import com.chintansoni.android.repositorypattern.model.local.AppDatabase 5 | import com.chintansoni.android.repositorypattern.model.local.DatabaseConstants 6 | import org.koin.android.ext.koin.androidContext 7 | import org.koin.dsl.module 8 | 9 | val databaseModule = module { 10 | 11 | // Dependency: AppDatabase 12 | single { 13 | Room.databaseBuilder(androidContext(), AppDatabase::class.java, DatabaseConstants.mName).build() 14 | } 15 | 16 | // Dependency: 17 | single { 18 | val appDatabase: AppDatabase = get() 19 | appDatabase.userDao() 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/di/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.di 2 | 3 | import com.chintansoni.android.repositorypattern.BuildConfig 4 | import com.chintansoni.android.repositorypattern.model.remote.ApiService 5 | import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory 6 | import okhttp3.Interceptor 7 | import okhttp3.OkHttpClient 8 | import okhttp3.logging.HttpLoggingInterceptor 9 | import org.koin.dsl.module 10 | import retrofit2.Retrofit 11 | import retrofit2.converter.gson.GsonConverterFactory 12 | import timber.log.Timber 13 | import java.util.concurrent.TimeUnit 14 | 15 | val networkModule = module { 16 | 17 | // Dependency: HttpLoggingInterceptor 18 | single { 19 | HttpLoggingInterceptor(object : HttpLoggingInterceptor.Logger { 20 | override fun log(message: String) { 21 | Timber.tag("OkHttp").d(message) 22 | } 23 | 24 | }).apply { 25 | level = HttpLoggingInterceptor.Level.BODY 26 | } 27 | } 28 | 29 | // Dependency: OkHttpClient 30 | single { 31 | OkHttpClient.Builder() 32 | .connectTimeout(10, TimeUnit.SECONDS) 33 | .readTimeout(10, TimeUnit.SECONDS) 34 | .writeTimeout(10, TimeUnit.SECONDS) 35 | .addInterceptor(get()) 36 | .build() 37 | } 38 | 39 | // Dependency: Retrofit 40 | single { 41 | Retrofit.Builder() 42 | .baseUrl(BuildConfig.BASE_URL) 43 | .client(get()) 44 | .addCallAdapterFactory(CoroutineCallAdapterFactory()) 45 | .addConverterFactory(GsonConverterFactory.create()) 46 | .build() 47 | } 48 | 49 | // Dependency: ApiService 50 | single { 51 | val retrofit: Retrofit = get() 52 | retrofit.create(ApiService::class.java) 53 | } 54 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/di/ViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.di 2 | 3 | import com.chintansoni.android.repositorypattern.model.UserRepository 4 | import com.chintansoni.android.repositorypattern.viewmodel.ListViewModel 5 | import org.koin.androidx.viewmodel.dsl.viewModel 6 | import org.koin.dsl.module 7 | 8 | val viewModelModule = module { 9 | 10 | // Dependency: ListViewModel 11 | viewModel { 12 | ListViewModel(get()) 13 | } 14 | 15 | // Dependency: UserRepository 16 | single { 17 | UserRepository(get(), get()) 18 | } 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/model/EntityMapper.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.model 2 | 3 | import com.chintansoni.android.repositorypattern.model.local.entity.User 4 | import com.chintansoni.android.repositorypattern.model.remote.response.RandomUserResponse 5 | import io.reactivex.functions.Function 6 | 7 | object EntityMapper { 8 | fun convert(): Function> { 9 | return Function { 10 | val users: ArrayList = ArrayList() 11 | for (result in it.results!!.iterator()) { 12 | users.add(User(id = 0, 13 | name = result.name, 14 | email = result.email, 15 | gender = result.gender, 16 | cell = result.cell, 17 | picture = result.picture, 18 | location = result.location, 19 | dob = result.dob)) 20 | } 21 | users.toList() 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/model/NetworkBoundResource.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.model 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import kotlinx.coroutines.Deferred 5 | 6 | abstract class NetworkBoundResource { 7 | 8 | private val mutableLiveData = MutableLiveData>() 9 | 10 | abstract suspend fun getRemoteAsync(): Deferred 11 | 12 | abstract suspend fun getLocal(): LocalType 13 | 14 | abstract suspend fun saveCallResult(data: LocalType, isForced: Boolean) 15 | 16 | abstract suspend fun mapper(remoteType: RemoteType): LocalType 17 | 18 | suspend fun refresh() { 19 | getRemoteData(true) 20 | } 21 | 22 | suspend fun fetch(isForced: Boolean) { 23 | try { 24 | mutableLiveData.postValue(Resource.Success(getLocal())) 25 | val remoteData = getRemoteAsync().await() 26 | saveCallResult(mapper(remoteData), isForced) 27 | mutableLiveData.postValue(Resource.Success(getLocal())) 28 | } catch (exception: Exception) { 29 | mutableLiveData.postValue(Resource.Error(exception)) 30 | } 31 | } 32 | 33 | suspend fun getRemoteData(isForced: Boolean) { 34 | try { 35 | mutableLiveData.postValue(Resource.Loading()) 36 | val remoteData = getRemoteAsync().await() 37 | saveCallResult(mapper(remoteData), isForced) 38 | mutableLiveData.postValue(Resource.Success(getLocal())) 39 | } catch (exception: Exception) { 40 | mutableLiveData.postValue(Resource.Error(exception)) 41 | } 42 | } 43 | 44 | fun asLiveData(): MutableLiveData> { 45 | return mutableLiveData 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/model/Resource.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.model 2 | 3 | /** 4 | * Created by chintan.soni on 23/02/18. 5 | */ 6 | sealed class Resource { 7 | 8 | class Success(val data: T) : Resource() 9 | class Error(throwable: Throwable) : Resource() 10 | class Loading : Resource() 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/model/Status.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.model 2 | 3 | /** 4 | * Created by chintan.soni on 23/02/18. 5 | */ 6 | enum class Status { 7 | SUCCESS, 8 | ERROR, 9 | LOADING 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/model/UserRepository.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.model 2 | 3 | import com.chintansoni.android.repositorypattern.model.local.dao.UserDao 4 | import com.chintansoni.android.repositorypattern.model.local.entity.User 5 | import com.chintansoni.android.repositorypattern.model.remote.ApiService 6 | import com.chintansoni.android.repositorypattern.model.remote.response.RandomUserResponse 7 | import kotlinx.coroutines.Deferred 8 | 9 | class UserRepository constructor(private var apiService: ApiService, private var userDao: UserDao) { 10 | 11 | 12 | private var pageNumber: Int = 0 13 | private var networkBoundSource: NetworkBoundResource, RandomUserResponse> = 14 | object : NetworkBoundResource, RandomUserResponse>() { 15 | override suspend fun getRemoteAsync(): Deferred { 16 | return apiService.getUsers(pageNumber) 17 | } 18 | 19 | override suspend fun getLocal(): List { 20 | return userDao.getAll() 21 | } 22 | 23 | override suspend fun saveCallResult(data: List, isForced: Boolean) { 24 | if (isForced) { 25 | userDao.deleteAll() 26 | } 27 | userDao.insertAllUsers(data) 28 | } 29 | 30 | override suspend fun mapper(remoteType: RandomUserResponse): List { 31 | val users: ArrayList = ArrayList() 32 | for (result in remoteType.results!!.iterator()) { 33 | users.add(User(id = 0, 34 | name = result.name, 35 | email = result.email, 36 | gender = result.gender, 37 | cell = result.cell, 38 | picture = result.picture, 39 | location = result.location, 40 | dob = result.dob)) 41 | } 42 | return users.toList() 43 | } 44 | } 45 | 46 | val userLiveData = networkBoundSource.asLiveData() 47 | 48 | suspend fun refresh() { 49 | networkBoundSource.refresh() 50 | } 51 | 52 | suspend fun fetchUsers(isForced: Boolean) { 53 | networkBoundSource.fetch(isForced) 54 | } 55 | 56 | suspend fun getNextPageUsers() { 57 | pageNumber += 1 58 | networkBoundSource.getRemoteData(false) 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/model/local/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.model.local 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import com.chintansoni.android.repositorypattern.model.local.dao.UserDao 6 | import com.chintansoni.android.repositorypattern.model.local.entity.User 7 | 8 | @Database(entities = [User::class], version = DatabaseConstants.mVersion) 9 | abstract class AppDatabase : RoomDatabase() { 10 | abstract fun userDao(): UserDao 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/model/local/DatabaseConstants.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.model.local 2 | 3 | object DatabaseConstants { 4 | 5 | /** 6 | * Database Version 7 | */ 8 | const val mVersion: Int = 1 9 | 10 | /** 11 | * Database Name 12 | */ 13 | const val mName: String = "RandomUser.db" 14 | 15 | /** 16 | * User Table 17 | */ 18 | const val mTableUser: String = "User" 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/model/local/dao/UserDao.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.model.local.dao 2 | 3 | import androidx.room.* 4 | import com.chintansoni.android.repositorypattern.model.local.DatabaseConstants 5 | import com.chintansoni.android.repositorypattern.model.local.entity.User 6 | 7 | @Dao 8 | interface UserDao { 9 | @Query("SELECT * FROM " + DatabaseConstants.mTableUser) 10 | suspend fun getAll(): List 11 | 12 | @Insert(onConflict = OnConflictStrategy.REPLACE) 13 | suspend fun insert(userList: List) 14 | 15 | @Query("DELETE FROM " + DatabaseConstants.mTableUser) 16 | suspend fun deleteAll() 17 | 18 | @Transaction 19 | suspend fun insertAllUsers(userList: List) { 20 | insert(userList) 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/model/local/entity/User.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.model.local.entity 2 | 3 | import android.os.Parcelable 4 | import androidx.room.Embedded 5 | import androidx.room.Entity 6 | import androidx.room.PrimaryKey 7 | import com.chintansoni.android.repositorypattern.model.local.DatabaseConstants 8 | import com.chintansoni.android.repositorypattern.model.remote.response.Dob 9 | import com.chintansoni.android.repositorypattern.model.remote.response.Location 10 | import com.chintansoni.android.repositorypattern.model.remote.response.Name 11 | import com.chintansoni.android.repositorypattern.model.remote.response.Picture 12 | import kotlinx.android.parcel.Parcelize 13 | 14 | 15 | @Entity(tableName = DatabaseConstants.mTableUser) 16 | @Parcelize 17 | data class User( 18 | @PrimaryKey(autoGenerate = true) 19 | val id: Int = 0, 20 | @Embedded 21 | val name: Name, 22 | val email: String, 23 | val gender: String, 24 | val cell: String, 25 | @Embedded 26 | val picture: Picture, 27 | @Embedded 28 | val location: Location, 29 | @Embedded 30 | val dob: Dob) : Parcelable -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/model/remote/ApiService.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.model.remote 2 | 3 | import com.chintansoni.android.repositorypattern.model.remote.response.RandomUserResponse 4 | import kotlinx.coroutines.Deferred 5 | import retrofit2.http.GET 6 | import retrofit2.http.Query 7 | 8 | interface ApiService { 9 | 10 | @GET("api") 11 | fun getUsers(@Query("page") page: Int, 12 | @Query("results") result: Int = 10, 13 | @Query("lang") lang: String = "en"): Deferred 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/model/remote/response/RandomUserResponse.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.model.remote.response 2 | 3 | 4 | import android.os.Parcelable 5 | import com.google.gson.annotations.SerializedName 6 | import kotlinx.android.parcel.Parcelize 7 | 8 | data class RandomUserResponse(@SerializedName("results") 9 | var results: List?) 10 | 11 | data class ResultsItem(@SerializedName("phone") 12 | var phone: String = "", 13 | @SerializedName("dob") 14 | var dob: Dob, 15 | @SerializedName("name") 16 | var name: Name, 17 | @SerializedName("location") 18 | var location: Location, 19 | @SerializedName("cell") 20 | var cell: String = "", 21 | @SerializedName("email") 22 | var email: String = "", 23 | @SerializedName("gender") 24 | var gender: String = "", 25 | @SerializedName("picture") 26 | var picture: Picture) 27 | 28 | @Parcelize 29 | data class Dob(@SerializedName("date") 30 | var date: String = "", 31 | @SerializedName("age") 32 | var age: Int = 0) : Parcelable 33 | 34 | @Parcelize 35 | data class Picture(@SerializedName("thumbnail") 36 | var thumbnail: String = "", 37 | @SerializedName("large") 38 | var large: String = "", 39 | @SerializedName("medium") 40 | var medium: String = "") : Parcelable 41 | 42 | @Parcelize 43 | data class Name(@SerializedName("last") 44 | var last: String = "", 45 | @SerializedName("first") 46 | var first: String = "") : Parcelable 47 | 48 | @Parcelize 49 | data class Location(@SerializedName("city") 50 | var city: String = "", 51 | @SerializedName("state") 52 | var state: String = "") : Parcelable 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/util/BindingUtils.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.util 2 | 3 | import androidx.appcompat.widget.AppCompatImageView 4 | import androidx.databinding.BindingAdapter 5 | import com.bumptech.glide.load.engine.DiskCacheStrategy 6 | import com.chintansoni.android.repositorypattern.R 7 | import com.chintansoni.android.repositorypattern.model.remote.response.Location 8 | import com.chintansoni.android.repositorypattern.model.remote.response.Name 9 | 10 | object BindingUtils { 11 | @JvmStatic 12 | @BindingAdapter("app:imageUrl", "app:gender") 13 | fun loadImage(view: AppCompatImageView, url: String, gender: String) { 14 | GlideApp.with(view.context) 15 | .load(url) 16 | .placeholder( 17 | when (gender) { 18 | "male" -> R.drawable.ic_male_placeholder 19 | else -> R.drawable.ic_female_placeholder 20 | } 21 | ) 22 | .diskCacheStrategy(DiskCacheStrategy.AUTOMATIC) 23 | .into(view) 24 | } 25 | 26 | @JvmStatic 27 | fun createFullName(name: Name): String { 28 | return name.first.capitalize() + " " + name.last.capitalize() 29 | } 30 | 31 | @JvmStatic 32 | fun createLocation(location: Location): String { 33 | return location.city.capitalize() + ", " + location.state.capitalize() 34 | } 35 | 36 | @JvmStatic 37 | fun getDate(s: String): String? { 38 | return s.split("T")[0] 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/util/FutureStudioAppGlideModule.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.util 2 | 3 | import com.bumptech.glide.annotation.GlideModule 4 | import com.bumptech.glide.module.AppGlideModule 5 | 6 | @GlideModule 7 | class FutureStudioAppGlideModule : AppGlideModule() -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/util/InfiniteScrollListener.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.util 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | 5 | abstract class InfiniteScrollListener(private val adapter: RecyclerView.Adapter) : androidx.recyclerview.widget.RecyclerView.OnScrollListener() { 6 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { 7 | super.onScrolled(recyclerView, dx, dy) 8 | val linearLayoutManager: androidx.recyclerview.widget.LinearLayoutManager = 9 | recyclerView.layoutManager as androidx.recyclerview.widget.LinearLayoutManager 10 | if (linearLayoutManager.findLastCompletelyVisibleItemPosition() >= linearLayoutManager.itemCount - 2) { 11 | fetchNext() 12 | } 13 | } 14 | 15 | abstract fun fetchNext() 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/util/RvExtension.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.util 2 | 3 | import androidx.recyclerview.widget.RecyclerView 4 | 5 | fun RecyclerView.setInfiniteScroll(func: RecyclerView.() -> Unit) { 6 | this.adapter?.let { 7 | this.addOnScrollListener(object : InfiniteScrollListener(it) { 8 | override fun fetchNext() { 9 | func.invoke(this@setInfiniteScroll) 10 | } 11 | }) 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/util/UserDiffUtil.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.util 2 | 3 | import androidx.recyclerview.widget.DiffUtil 4 | import com.chintansoni.android.repositorypattern.model.local.entity.User 5 | 6 | class UserDiffUtil(private val newList: ArrayList? = null, private val oldList: ArrayList? = null) : DiffUtil.Callback() { 7 | 8 | override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 9 | return oldList?.get(oldItemPosition)?.id == newList?.get(newItemPosition)?.id 10 | } 11 | 12 | override fun getOldListSize(): Int { 13 | return oldList?.size ?: 0 14 | } 15 | 16 | override fun getNewListSize(): Int { 17 | return newList?.size ?: 0 18 | } 19 | 20 | override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { 21 | return oldList?.get(oldItemPosition) === newList?.get(newItemPosition) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/util/widget/CircleImageView.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.util.widget 2 | 3 | import android.content.Context 4 | import android.graphics.Canvas 5 | import android.graphics.Path 6 | import android.util.AttributeSet 7 | import androidx.appcompat.widget.AppCompatImageView 8 | 9 | class CircleImageView @JvmOverloads constructor( 10 | context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 11 | ) : AppCompatImageView(context, attrs, defStyleAttr) { 12 | 13 | var path = Path() 14 | 15 | var radius: Float = 0f 16 | var centerX: Float = 0f 17 | var centerY: Float = 0f 18 | 19 | override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 20 | super.onMeasure(widthMeasureSpec, heightMeasureSpec) 21 | radius = measuredWidth / 2f 22 | centerX = measuredWidth / 2f 23 | centerY = measuredHeight / 2f 24 | 25 | } 26 | 27 | override fun onDraw(canvas: Canvas?) { 28 | path.addCircle(centerX, centerY, radius, Path.Direction.CW) 29 | canvas?.clipPath(path) 30 | super.onDraw(canvas) 31 | } 32 | 33 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/view/activity/UsersActivity.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.view.activity 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import com.chintansoni.android.repositorypattern.R 6 | import com.chintansoni.android.repositorypattern.model.local.entity.User 7 | import com.chintansoni.android.repositorypattern.view.adapter.UserRecyclerAdapter 8 | import com.chintansoni.android.repositorypattern.view.fragment.DetailFragment 9 | import com.chintansoni.android.repositorypattern.view.fragment.ListFragment 10 | 11 | class UsersActivity : AppCompatActivity(), UserRecyclerAdapter.ItemTouchListener { 12 | override fun onItemClick(user: User) { 13 | supportFragmentManager.beginTransaction() 14 | .setCustomAnimations( 15 | R.anim.slide_up, 16 | R.anim.slide_down, 17 | R.anim.slide_up, 18 | R.anim.slide_down 19 | ) 20 | .add(R.id.container, DetailFragment.newInstance(user)) 21 | .addToBackStack(detailFragmentTag) 22 | .commit() 23 | } 24 | 25 | companion object { 26 | const val detailFragmentTag = "details_fragment" 27 | } 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | setContentView(R.layout.users_activity) 32 | if (savedInstanceState == null) { 33 | supportFragmentManager.beginTransaction() 34 | .replace(R.id.container, ListFragment.newInstance()) 35 | .commit() 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/view/adapter/UserRecyclerAdapter.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.view.adapter 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.databinding.DataBindingUtil 8 | import androidx.recyclerview.widget.DiffUtil 9 | import com.chintansoni.android.repositorypattern.R 10 | import com.chintansoni.android.repositorypattern.databinding.ItemLoadingBinding 11 | import com.chintansoni.android.repositorypattern.databinding.ItemUserBinding 12 | import com.chintansoni.android.repositorypattern.model.local.entity.User 13 | import com.chintansoni.android.repositorypattern.model.remote.response.Dob 14 | import com.chintansoni.android.repositorypattern.model.remote.response.Location 15 | import com.chintansoni.android.repositorypattern.model.remote.response.Name 16 | import com.chintansoni.android.repositorypattern.model.remote.response.Picture 17 | import com.chintansoni.android.repositorypattern.util.UserDiffUtil 18 | import com.chintansoni.android.repositorypattern.view.viewholder.LoaderViewHolder 19 | import com.chintansoni.android.repositorypattern.view.viewholder.UserViewHolder 20 | 21 | class UserRecyclerAdapter(context: Context) : 22 | androidx.recyclerview.widget.RecyclerView.Adapter() { 23 | 24 | private val list: ArrayList = ArrayList() 25 | private val ITEM_TYPE_NORMAL = 1 26 | private val ITEM_TYPE_LOADER = 2 27 | private var listener: ItemTouchListener 28 | 29 | init { 30 | listener = context as ItemTouchListener 31 | } 32 | 33 | override fun getItemViewType(position: Int): Int { 34 | return if (list[position].id == 0) { 35 | ITEM_TYPE_LOADER 36 | } else { 37 | ITEM_TYPE_NORMAL 38 | } 39 | } 40 | 41 | override fun onCreateViewHolder( 42 | parent: ViewGroup, 43 | viewType: Int 44 | ): androidx.recyclerview.widget.RecyclerView.ViewHolder { 45 | return if (viewType == ITEM_TYPE_NORMAL) { 46 | val mUserBinding: ItemUserBinding = DataBindingUtil 47 | .inflate(LayoutInflater.from(parent.context), R.layout.list_item_user, parent, false) 48 | UserViewHolder(mUserBinding) 49 | } else { 50 | val mLoadingBinding: ItemLoadingBinding = DataBindingUtil 51 | .inflate(LayoutInflater.from(parent.context), R.layout.list_item_loader, parent, false) 52 | LoaderViewHolder(mLoadingBinding) 53 | } 54 | } 55 | 56 | override fun getItemCount(): Int { 57 | return list.size 58 | } 59 | 60 | override fun onBindViewHolder(holder: androidx.recyclerview.widget.RecyclerView.ViewHolder, position: Int) { 61 | 62 | var clickListener: View.OnClickListener = View.OnClickListener { 63 | listener.onItemClick(list[holder.adapterPosition]) 64 | } 65 | 66 | when (holder.itemViewType) { 67 | ITEM_TYPE_NORMAL -> { 68 | holder as UserViewHolder 69 | holder.setUser(list[position]) 70 | holder.setClickListener(clickListener) 71 | } 72 | ITEM_TYPE_LOADER -> { 73 | // Do Nothing 74 | } 75 | } 76 | } 77 | 78 | fun setList(newList: ArrayList) { 79 | val diffResult = DiffUtil.calculateDiff(UserDiffUtil(newList, list)) 80 | diffResult.dispatchUpdatesTo(this) 81 | list.clear() 82 | list.addAll(newList) 83 | } 84 | 85 | private fun getLoaderItem(): User { 86 | return User( 87 | id = 0, 88 | name = Name(), 89 | picture = Picture(), 90 | location = Location(), 91 | email = "", 92 | dob = Dob(), 93 | cell = "", 94 | gender = "" 95 | ) 96 | } 97 | 98 | fun addLoader() { 99 | if (!isLoading()) { 100 | val newList = ArrayList(list) 101 | newList.add(getLoaderItem()) 102 | setList(newList) 103 | } 104 | } 105 | 106 | fun removeLoader() { 107 | if (isLoading()) { 108 | if (!list.isEmpty()) { 109 | val newList = ArrayList(list) 110 | newList.remove(getLoaderItem()) 111 | setList(newList) 112 | } 113 | } 114 | } 115 | 116 | fun isLoading(): Boolean { 117 | return list.isEmpty() || list.last().id == 0 118 | } 119 | 120 | fun getItem(position: Int): User { 121 | return list[position] 122 | } 123 | 124 | interface ItemTouchListener { 125 | fun onItemClick(user: User) 126 | } 127 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/view/fragment/DetailFragment.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.view.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.databinding.DataBindingUtil 8 | import com.chintansoni.android.repositorypattern.R 9 | import com.chintansoni.android.repositorypattern.databinding.DetailFragmentBinding 10 | import com.chintansoni.android.repositorypattern.model.local.entity.User 11 | import kotlinx.android.synthetic.main.frag_details.* 12 | 13 | class DetailFragment : androidx.fragment.app.Fragment() { 14 | 15 | lateinit var mDetailFragmentBinding: DetailFragmentBinding 16 | private var user: User? = null 17 | 18 | companion object { 19 | const val ARG_USER: String = "User" 20 | fun newInstance(user: User): DetailFragment { 21 | val detailsFragment = DetailFragment() 22 | val bundle = Bundle() 23 | bundle.putParcelable(ARG_USER, user) 24 | detailsFragment.arguments = bundle 25 | return detailsFragment 26 | } 27 | } 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | arguments?.let { 32 | (it.getParcelable(ARG_USER) as? User)?.let { 33 | user = it 34 | } 35 | } 36 | } 37 | 38 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 39 | mDetailFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.frag_details, container, false) 40 | return mDetailFragmentBinding.root 41 | } 42 | 43 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 44 | user?.let { 45 | mDetailFragmentBinding.user = it 46 | } 47 | ivClose.setOnClickListener { 48 | requireActivity().onBackPressed() 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/view/fragment/ListFragment.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.view.fragment 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import androidx.databinding.DataBindingUtil 8 | import androidx.fragment.app.Fragment 9 | import androidx.lifecycle.Observer 10 | import com.chintansoni.android.repositorypattern.R 11 | import com.chintansoni.android.repositorypattern.databinding.ListFragmentBinding 12 | import com.chintansoni.android.repositorypattern.model.Resource 13 | import com.chintansoni.android.repositorypattern.util.setInfiniteScroll 14 | import com.chintansoni.android.repositorypattern.view.adapter.UserRecyclerAdapter 15 | import com.chintansoni.android.repositorypattern.viewmodel.ListViewModel 16 | import kotlinx.android.synthetic.main.list_fragment.* 17 | import org.koin.androidx.viewmodel.ext.android.viewModel 18 | 19 | class ListFragment : Fragment() { 20 | 21 | lateinit var mFragmentBinding: ListFragmentBinding 22 | 23 | companion object { 24 | fun newInstance() = ListFragment() 25 | } 26 | 27 | private val viewModel by viewModel() 28 | 29 | override fun onCreateView( 30 | inflater: LayoutInflater, container: ViewGroup?, 31 | savedInstanceState: Bundle? 32 | ): View { 33 | mFragmentBinding = DataBindingUtil.inflate(inflater, R.layout.list_fragment, container, false) 34 | return mFragmentBinding.root 35 | } 36 | 37 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) { 38 | super.onViewCreated(view, savedInstanceState) 39 | initViews() 40 | listenToViewModel() 41 | fetchUsers() 42 | } 43 | 44 | private fun fetchUsers() { 45 | viewModel.getUsers() 46 | } 47 | 48 | private fun listenToViewModel() { 49 | viewModel.userLiveData.observe(this, Observer { resource -> 50 | srl_list.isRefreshing = false 51 | resource?.let { 52 | when (it) { 53 | is Resource.Success -> { 54 | userRecyclerAdapter.removeLoader() 55 | userRecyclerAdapter.setList(ArrayList(it.data)) 56 | } 57 | is Resource.Error -> 58 | userRecyclerAdapter.removeLoader() 59 | is Resource.Loading -> 60 | userRecyclerAdapter.addLoader() 61 | } 62 | } 63 | }) 64 | } 65 | 66 | private lateinit var userRecyclerAdapter: UserRecyclerAdapter 67 | 68 | private fun initViews() { 69 | userRecyclerAdapter = UserRecyclerAdapter(requireContext()) 70 | rvUsers.adapter = userRecyclerAdapter 71 | 72 | val animator = rvUsers.itemAnimator 73 | if (animator is androidx.recyclerview.widget.SimpleItemAnimator) { 74 | animator.supportsChangeAnimations = false 75 | } 76 | 77 | rvUsers.setInfiniteScroll { 78 | if (!userRecyclerAdapter.isLoading()) { 79 | post { 80 | userRecyclerAdapter.addLoader() 81 | } 82 | viewModel.getNextPageUsers() 83 | } 84 | } 85 | 86 | srl_list.setOnRefreshListener { 87 | viewModel.refreshUsers() 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/view/viewholder/LoaderViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.view.viewholder 2 | 3 | import com.chintansoni.android.repositorypattern.databinding.ItemLoadingBinding 4 | 5 | class LoaderViewHolder(mLoadingBinding: ItemLoadingBinding) : 6 | androidx.recyclerview.widget.RecyclerView.ViewHolder(mLoadingBinding.root) -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/view/viewholder/UserViewHolder.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.view.viewholder 2 | 3 | import android.view.View 4 | import com.chintansoni.android.repositorypattern.databinding.ItemUserBinding 5 | import com.chintansoni.android.repositorypattern.model.local.entity.User 6 | 7 | class UserViewHolder(var mUserBinding: ItemUserBinding) : 8 | androidx.recyclerview.widget.RecyclerView.ViewHolder(mUserBinding.root) { 9 | 10 | fun setUser(user: User) { 11 | mUserBinding.user = user 12 | } 13 | 14 | fun setClickListener(clickListener: View.OnClickListener) { 15 | mUserBinding.root.setOnClickListener(clickListener) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/viewmodel/DetailsViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.viewmodel 2 | 3 | import androidx.lifecycle.ViewModel 4 | 5 | class DetailsViewModel : ViewModel() -------------------------------------------------------------------------------- /app/src/main/java/com/chintansoni/android/repositorypattern/viewmodel/ListViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.chintansoni.android.repositorypattern.viewmodel 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.Observer 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import com.chintansoni.android.repositorypattern.model.Resource 8 | import com.chintansoni.android.repositorypattern.model.UserRepository 9 | import com.chintansoni.android.repositorypattern.model.local.entity.User 10 | import kotlinx.coroutines.launch 11 | 12 | class ListViewModel constructor(private var userRepository: UserRepository) : ViewModel() { 13 | 14 | val userLiveData = MutableLiveData>>() 15 | private val observer = Observer>> { 16 | userLiveData.postValue(it) 17 | } 18 | 19 | init { 20 | userRepository.userLiveData.observeForever(observer) 21 | } 22 | 23 | fun refreshUsers() { 24 | viewModelScope.launch { 25 | userRepository.refresh() 26 | } 27 | } 28 | 29 | fun getUsers() { 30 | viewModelScope.launch { 31 | userRepository.fetchUsers(true) 32 | } 33 | } 34 | 35 | fun getNextPageUsers() { 36 | viewModelScope.launch { 37 | userRepository.getNextPageUsers() 38 | } 39 | } 40 | 41 | override fun onCleared() { 42 | super.onCleared() 43 | userRepository.userLiveData.removeObserver(observer) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_down.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_up.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_grdnt_white_transparent.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_back_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_close_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_female_placeholder.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChintanSoni/RepositoryPattern/bd84fcb7f73a69d60d4d16fb7ada8cc6288c2d73/app/src/main/res/drawable/ic_female_placeholder.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_male_placeholder.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChintanSoni/RepositoryPattern/bd84fcb7f73a69d60d4d16fb7ada8cc6288c2d73/app/src/main/res/drawable/ic_male_placeholder.jpeg -------------------------------------------------------------------------------- /app/src/main/res/layout/frag_details.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 22 | 23 | 30 | 31 | 41 | 42 | 52 | 53 | 60 | 61 | 71 | 72 | 81 | 82 | 89 | 90 | 99 | 100 | 107 | 108 | 117 | 118 | 121 | 122 | 130 | 131 | 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_fragment.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 13 | 14 | 18 | 19 | 22 | 23 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item_loader.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 18 | 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/list_item_user.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 22 | 23 | 32 | 33 | 39 | 40 | 52 | 53 | 63 | 64 | 65 | -------------------------------------------------------------------------------- /app/src/main/res/layout/users_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/menu/details_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChintanSoni/RepositoryPattern/bd84fcb7f73a69d60d4d16fb7ada8cc6288c2d73/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChintanSoni/RepositoryPattern/bd84fcb7f73a69d60d4d16fb7ada8cc6288c2d73/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChintanSoni/RepositoryPattern/bd84fcb7f73a69d60d4d16fb7ada8cc6288c2d73/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChintanSoni/RepositoryPattern/bd84fcb7f73a69d60d4d16fb7ada8cc6288c2d73/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChintanSoni/RepositoryPattern/bd84fcb7f73a69d60d4d16fb7ada8cc6288c2d73/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChintanSoni/RepositoryPattern/bd84fcb7f73a69d60d4d16fb7ada8cc6288c2d73/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChintanSoni/RepositoryPattern/bd84fcb7f73a69d60d4d16fb7ada8cc6288c2d73/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChintanSoni/RepositoryPattern/bd84fcb7f73a69d60d4d16fb7ada8cc6288c2d73/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChintanSoni/RepositoryPattern/bd84fcb7f73a69d60d4d16fb7ada8cc6288c2d73/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChintanSoni/RepositoryPattern/bd84fcb7f73a69d60d4d16fb7ada8cc6288c2d73/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #A2A2A2 5 | #2196f3 6 | #adafb1 7 | #333840 8 | #edeced 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Repository Pattern 3 | U S E R S 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.3.50' 3 | repositories { 4 | google() 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:3.5.0' 9 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 10 | } 11 | } 12 | 13 | allprojects { 14 | repositories { 15 | google() 16 | jcenter() 17 | } 18 | } 19 | 20 | task clean(type: Delete) { 21 | delete rootProject.buildDir 22 | } 23 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | org.gradle.jvmargs=-Xmx4096m 12 | # When configured, Gradle will run in incubating parallel mode. 13 | # This option should only be used with decoupled projects. More details, visit 14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 15 | # org.gradle.parallel=true 16 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/iChintanSoni/RepositoryPattern/bd84fcb7f73a69d60d4d16fb7ada8cc6288c2d73/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Jan 19 17:55:26 IST 2019 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------