├── .gitignore ├── LICENSE.txt ├── MVPActivity ├── globals.xml.ftl ├── recipe.xml.ftl ├── root │ └── src │ │ └── app_package │ │ ├── Activity.java.ftl │ │ ├── Presenter.java.ftl │ │ └── View.java.ftl ├── template.xml └── template_mvp_activity.png ├── MVPFragment ├── globals.xml.ftl ├── recipe.xml.ftl ├── root │ ├── res │ │ └── layout │ │ │ └── fragment_blank.xml.ftl │ └── src │ │ └── app_package │ │ ├── Fragment.java.ftl │ │ ├── Presenter.java.ftl │ │ └── View.java.ftl ├── template.xml └── template_mvp_fragment.png ├── MVPStarter ├── .gitignore ├── base_files_recipe.xml.ftl ├── hierarchy_recipe.xml.ftl ├── package.json ├── recipe.xml.ftl ├── root │ ├── .travis.yml │ ├── README.md │ ├── app │ │ ├── build.gradle │ │ ├── gitignore │ │ ├── proguard-rules.pro │ │ └── src │ │ │ ├── androidTest │ │ │ └── java │ │ │ │ └── in │ │ │ │ └── mvpstarter │ │ │ │ └── sample │ │ │ │ ├── DetailActivityTest.java.ftl │ │ │ │ ├── MainActivityTest.java.ftl │ │ │ │ ├── runner │ │ │ │ ├── RxAndroidJUnitRunner.java.ftl │ │ │ │ └── UnlockDeviceAndroidJUnitRunner.java.ftl │ │ │ │ └── util │ │ │ │ ├── ErrorTestUtil.java.ftl │ │ │ │ ├── RxIdlingExecutionHook.java.ftl │ │ │ │ └── RxIdlingResource.java.ftl │ │ │ ├── commonTest │ │ │ └── java │ │ │ │ └── in │ │ │ │ └── mvpstarter │ │ │ │ └── sample │ │ │ │ └── common │ │ │ │ ├── TestComponentRule.java.ftl │ │ │ │ ├── TestDataFactory.java.ftl │ │ │ │ └── injection │ │ │ │ ├── component │ │ │ │ └── TestComponent.java.ftl │ │ │ │ └── module │ │ │ │ └── ApplicationTestModule.java.ftl │ │ │ ├── debug │ │ │ ├── AndroidManifest.xml.ftl │ │ │ └── res │ │ │ │ └── values │ │ │ │ └── google_maps_api.xml.ftl │ │ │ ├── main │ │ │ ├── AndroidManifest.xml.ftl │ │ │ ├── java │ │ │ │ └── in │ │ │ │ │ └── mvpstarter │ │ │ │ │ └── sample │ │ │ │ │ ├── MvpStarterApplication.java.ftl │ │ │ │ │ ├── data │ │ │ │ │ ├── DataManager.java.ftl │ │ │ │ │ ├── model │ │ │ │ │ │ ├── NamedResource.java.ftl │ │ │ │ │ │ ├── Pokemon.java.ftl │ │ │ │ │ │ ├── PokemonListResponse.java.ftl │ │ │ │ │ │ ├── Sprites.java.ftl │ │ │ │ │ │ └── Statistic.java.ftl │ │ │ │ │ └── remote │ │ │ │ │ │ ├── MvpStarterService.java.ftl │ │ │ │ │ │ └── MvpStarterServiceFactory.java.ftl │ │ │ │ │ ├── injection │ │ │ │ │ ├── ActivityContext.java.ftl │ │ │ │ │ ├── ApplicationContext.java.ftl │ │ │ │ │ ├── ConfigPersistent.java.ftl │ │ │ │ │ ├── PerActivity.java.ftl │ │ │ │ │ ├── PerFragment.java.ftl │ │ │ │ │ ├── component │ │ │ │ │ │ ├── ActivityComponent.java.ftl │ │ │ │ │ │ ├── ApplicationComponent.java.ftl │ │ │ │ │ │ ├── ConfigPersistentComponent.java.ftl │ │ │ │ │ │ └── FragmentComponent.java.ftl │ │ │ │ │ └── module │ │ │ │ │ │ ├── ActivityModule.java.ftl │ │ │ │ │ │ ├── ApplicationModule.java.ftl │ │ │ │ │ │ └── FragmentModule.java.ftl │ │ │ │ │ ├── ui │ │ │ │ │ ├── base │ │ │ │ │ │ ├── BaseActivity.java.ftl │ │ │ │ │ │ ├── BaseFragment.java.ftl │ │ │ │ │ │ ├── BasePresenter.java.ftl │ │ │ │ │ │ ├── MvpView.java.ftl │ │ │ │ │ │ └── Presenter.java.ftl │ │ │ │ │ ├── common │ │ │ │ │ │ └── ErrorView.java.ftl │ │ │ │ │ ├── detail │ │ │ │ │ │ ├── DetailActivity.java.ftl │ │ │ │ │ │ ├── DetailMvpView.java.ftl │ │ │ │ │ │ ├── DetailPresenter.java.ftl │ │ │ │ │ │ ├── MapsSampleActivity.java.ftl │ │ │ │ │ │ └── widget │ │ │ │ │ │ │ └── StatisticView.java.ftl │ │ │ │ │ └── main │ │ │ │ │ │ ├── MainActivity.java.ftl │ │ │ │ │ │ ├── MainMvpView.java.ftl │ │ │ │ │ │ ├── MainPresenter.java.ftl │ │ │ │ │ │ └── PokemonAdapter.java.ftl │ │ │ │ │ └── util │ │ │ │ │ ├── DialogFactory.java.ftl │ │ │ │ │ ├── NetworkUtil.java.ftl │ │ │ │ │ └── ViewUtil.java.ftl │ │ │ └── res │ │ │ │ ├── layout │ │ │ │ ├── activity_detail.xml.ftl │ │ │ │ ├── activity_main.xml.ftl │ │ │ │ ├── activity_maps_sample.xml.ftl │ │ │ │ ├── item_pokemon.xml.ftl │ │ │ │ ├── view_error.xml.ftl │ │ │ │ └── view_statistic.xml.ftl │ │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ │ └── values │ │ │ │ ├── colors.xml.ftl │ │ │ │ ├── dimens.xml.ftl │ │ │ │ ├── strings.xml.ftl │ │ │ │ └── styles.xml.ftl │ │ │ ├── release │ │ │ └── res │ │ │ │ └── values │ │ │ │ └── google_maps_api.xml.ftl │ │ │ └── test │ │ │ └── java │ │ │ └── in │ │ │ └── mvpstarter │ │ │ └── sample │ │ │ ├── data │ │ │ └── DataManagerTest.java.ftl │ │ │ └── util │ │ │ ├── DefaultConfig.java.ftl │ │ │ └── RxSchedulersOverrideRule.java.ftl │ ├── art │ │ └── screens.png │ ├── build.gradle │ ├── config │ │ └── quality │ │ │ ├── checkstyle │ │ │ └── checkstyle-config.xml.ftl │ │ │ ├── findbugs │ │ │ └── android-exclude-filter.xml.ftl │ │ │ ├── pmd │ │ │ └── pmd-ruleset.xml.ftl │ │ │ └── quality.gradle │ ├── gitignore │ ├── gradle.properties │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ └── settings.gradle ├── sync_template.js ├── template.xml └── template_mvp_activity.png ├── README.md ├── install.sh └── screenshots ├── menu.png ├── mvp_activity.png └── mvp_fragment.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [2016] [Benoit LETONDOR] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /MVPActivity/globals.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <#include "../common/common_globals.xml.ftl" /> 9 | 10 | -------------------------------------------------------------------------------- /MVPActivity/recipe.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | <#include "../common/recipe_manifest.xml.ftl" /> 4 | 5 | <#if generateLayout> 6 | <#include "../common/recipe_simple.xml.ftl" /> 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | <#if generateView> 16 | 18 | 19 | 20 | 21 | 22 | <#if generatePresenter> 23 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /MVPActivity/root/src/app_package/Activity.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | import android.os.Bundle; 4 | 5 | import ${applicationPackage}.R; 6 | import ${applicationPackage}.features.base.BaseActivity; 7 | import ${applicationPackage}.injection.component.ActivityComponent; 8 | import javax.inject.Inject; 9 | 10 | public class ${activityClass} extends BaseActivity <#if generateView>implements ${viewName}{ 11 | <#if generatePresenter> 12 | @Inject 13 | public ${presenterName} presenter; 14 | 15 | 16 | @Override 17 | protected void onCreate(Bundle savedInstanceState) { 18 | super.onCreate(savedInstanceState); 19 | } 20 | 21 | <#if generateLayout> 22 | @Override 23 | public int getLayout() { 24 | return R.layout.${layoutName}; 25 | } 26 | 27 | 28 | @Override 29 | protected void inject(ActivityComponent activityComponent) { 30 | activityComponent.inject(this); 31 | } 32 | 33 | <#if generatePresenter> 34 | @Override 35 | protected void attachView() { 36 | presenter.attachView(this); 37 | } 38 | 39 | @Override 40 | protected void detachPresenter() { 41 | presenter.detachView(); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /MVPActivity/root/src/app_package/Presenter.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | import ${applicationPackage}.features.base.BasePresenter; 4 | import ${applicationPackage}.injection.ConfigPersistent; 5 | import javax.inject.Inject; 6 | 7 | @ConfigPersistent 8 | public class ${presenterName} extends BasePresenter<${viewName}> { 9 | 10 | @Inject 11 | public ${presenterName}() { 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /MVPActivity/root/src/app_package/View.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | import ${applicationPackage}.features.base.MvpView; 4 | 5 | public interface ${viewName} extends MvpView { 6 | } -------------------------------------------------------------------------------- /MVPActivity/template.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /MVPActivity/template_mvp_activity.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidstarters/androidstarters-template/fc1f211820047303cc49c3d9f832460fc0676dba/MVPActivity/template_mvp_activity.png -------------------------------------------------------------------------------- /MVPFragment/globals.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | <#assign useSupport=(minApiLevel lt 23)> 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /MVPFragment/recipe.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <#if includeLayout> 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | <#if generateView> 18 | 20 | 21 | 22 | 23 | 24 | <#if generatePresenter> 25 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /MVPFragment/root/res/layout/fragment_blank.xml.ftl: -------------------------------------------------------------------------------- 1 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /MVPFragment/root/src/app_package/Fragment.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | import android.os.Bundle; 4 | 5 | import ${applicationPackage}.R; 6 | import ${applicationPackage}.features.base.BaseFragment; 7 | import ${applicationPackage}.injection.component.FragmentComponent; 8 | import javax.inject.Inject; 9 | 10 | public class ${className} extends BaseFragment <#if generateView>implements ${viewName}{ 11 | 12 | <#if generatePresenter> 13 | @Inject 14 | public ${presenterName} presenter; 15 | 16 | 17 | public static ${className} newInstance() { 18 | return new ${className}(); 19 | } 20 | 21 | @Override 22 | public void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | } 25 | 26 | 27 | <#if generatePresenter> 28 | @Override 29 | public void onDestroyView() { 30 | presenter.detachView(); 31 | super.onDestroyView(); 32 | } 33 | 34 | 35 | <#if includeLayout> 36 | @Override 37 | public int getLayout() { 38 | return R.layout.${fragmentName}; 39 | } 40 | 41 | 42 | @Override 43 | protected void inject(FragmentComponent fragmentComponent) { 44 | fragmentComponent.inject(this); 45 | } 46 | 47 | @Override 48 | protected void attachView() { 49 | presenter.attachView(this); 50 | } 51 | 52 | @Override 53 | protected void detachPresenter() { 54 | presenter.detachView(); 55 | } 56 | 57 | } -------------------------------------------------------------------------------- /MVPFragment/root/src/app_package/Presenter.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | import ${applicationPackage}.features.base.BasePresenter; 4 | import ${applicationPackage}.injection.annotation.ConfigPersistent; 5 | import javax.inject.Inject; 6 | 7 | @ConfigPersistent 8 | public class ${presenterName} extends BasePresenter<${viewName}> { 9 | 10 | @Inject 11 | public ${presenterName}() { 12 | } 13 | 14 | } 15 | -------------------------------------------------------------------------------- /MVPFragment/root/src/app_package/View.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | import ${applicationPackage}.features.base.MvpView; 4 | 5 | public interface ${viewName} extends MvpView { 6 | } -------------------------------------------------------------------------------- /MVPFragment/template.xml: -------------------------------------------------------------------------------- 1 | 2 | 80 | -------------------------------------------------------------------------------- /MVPFragment/template_mvp_fragment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/androidstarters/androidstarters-template/fc1f211820047303cc49c3d9f832460fc0676dba/MVPFragment/template_mvp_fragment.png -------------------------------------------------------------------------------- /MVPStarter/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | *.pid.lock 11 | 12 | # Directory for instrumented libs generated by jscoverage/JSCover 13 | lib-cov 14 | 15 | # Coverage directory used by tools like istanbul 16 | coverage 17 | 18 | # nyc test coverage 19 | .nyc_output 20 | 21 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 22 | .grunt 23 | 24 | # node-waf configuration 25 | .lock-wscript 26 | 27 | # Compiled binary addons (http://nodejs.org/api/addons.html) 28 | build/Release 29 | 30 | # Dependency directories 31 | node_modules 32 | jspm_packages 33 | 34 | # Optional npm cache directory 35 | .npm 36 | 37 | # Optional eslint cache 38 | .eslintcache 39 | 40 | # Optional REPL history 41 | .node_repl_history 42 | -------------------------------------------------------------------------------- /MVPStarter/base_files_recipe.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 10 | 13 | 14 | 17 | 18 | 21 | 22 | 25 | 26 | 29 | 30 | 33 | 34 | 37 | 38 | 41 | 42 | 45 | 46 | 49 | 50 | 53 | 54 | 57 | 58 | 61 | 62 | 65 | 66 | 69 | 70 | 73 | 74 | 77 | 78 | 81 | 82 | 85 | 86 | 89 | 90 | 93 | 94 | 97 | 98 | 101 | 102 | 105 | 106 | 109 | 110 | 113 | 114 | 117 | 118 | 121 | 122 | 125 | 126 | 129 | 130 | 133 | 134 | 137 | 138 | 141 | 142 | 145 | 146 | 149 | 150 | 153 | 154 | 155 | -------------------------------------------------------------------------------- /MVPStarter/hierarchy_recipe.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /MVPStarter/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "mvpstarter", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "sync_template.js", 6 | "dependencies": { 7 | "fs-finder": "^1.8.1", 8 | "mkdirp": "^0.5.1", 9 | "mv": "^2.1.1", 10 | "ncp": "^2.0.0", 11 | "nodegit": "^0.14.1", 12 | "path": "^0.12.7", 13 | "replace": "^0.3.0", 14 | "rimraf": "^2.5.4" 15 | }, 16 | "devDependencies": {}, 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "author": "", 21 | "license": "ISC" 22 | } 23 | -------------------------------------------------------------------------------- /MVPStarter/recipe.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | <#include "hierarchy_recipe.xml.ftl" /> 4 | 5 | <#-- <#include "base_files_recipe.xml.ftl" /> --> 6 | 7 | -------------------------------------------------------------------------------- /MVPStarter/root/.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | language: android 4 | jdk: oraclejdk8 5 | 6 | android: 7 | components: 8 | - platform-tools 9 | - tools 10 | - build-tools-24.0.2 11 | - android-24 12 | - extra-android-m2repository 13 | - extra-google-m2repository 14 | - extra-android-support 15 | - extra-google-google_play_services 16 | 17 | before_script: 18 | - chmod +x gradlew 19 | 20 | script: "./gradlew build testDebug -PdisablePreDex" 21 | -------------------------------------------------------------------------------- /MVPStarter/root/README.md: -------------------------------------------------------------------------------- 1 | # AndroidMvpStarter [![Build Status](https://travis-ci.org/ravidsrk/android-mvp-starter.svg?branch=master)](https://travis-ci.org/ravidsrk/android-mvp-starter) 2 | 3 | An MVP Boilerplate to save me having to create the same project over from scratch every time! :) 4 | 5 | ## This project uses: 6 | - [RxJava](https://github.com/ReactiveX/RxJava) and [RxAndroid](https://github.com/ReactiveX/RxAndroid) 7 | - [Retrofit](http://square.github.io/retrofit/) / [OkHttp](http://square.github.io/okhttp/) 8 | - [Gson](https://github.com/google/gson) 9 | - [Dagger 2](http://google.github.io/dagger/) 10 | - [Butterknife](https://github.com/JakeWharton/butterknife) 11 | - [Google Play Services](https://developers.google.com/android/guides/overview) 12 | - [Timber](https://github.com/JakeWharton/timber) 13 | - [Glide 3](https://github.com/bumptech/glide) 14 | - [Stetho](http://facebook.github.io/stetho/) 15 | - [Espresso](https://google.github.io/android-testing-support-library/) for UI tests 16 | - [Robolectric](http://robolectric.org/) for framework specific unit tests 17 | - [Mockito](http://mockito.org/) 18 | - [Checkstyle](http://checkstyle.sourceforge.net/), [PMD](https://pmd.github.io/) and [Findbugs](http://findbugs.sourceforge.net/) for code analysis 19 | 20 | ## Create new project using yeoman [generator-android-mvp-starter](https://github.com/ravidsrk/generator-android-mvp-starter) 21 | ```bash 22 | npm install -g yo 23 | npm install -g generator-android-mvp-starter 24 | mkdir NewApp && cd $_ 25 | yo android-mvp-starter 26 | ``` 27 | 28 | ## Building 29 | 30 | To build, install and run a debug version, run this from the root of the project: 31 | ```sh 32 | ./gradlew app:assembleDebug 33 | ``` 34 | 35 | ## Testing 36 | 37 | To run **unit** tests on your machine: 38 | 39 | ```sh 40 | ./gradlew test 41 | ``` 42 | 43 | To run **instrumentation** tests on connected devices: 44 | 45 | ```sh 46 | ./gradlew connectedAndroidTest 47 | ``` 48 | 49 | ## Code Analysis tools 50 | 51 | The following code analysis tools are set up on this project: 52 | 53 | * [PMD](https://pmd.github.io/) 54 | 55 | ```sh 56 | ./gradlew pmd 57 | ``` 58 | 59 | * [Findbugs](http://findbugs.sourceforge.net/) 60 | 61 | ```sh 62 | ./gradlew findbugs 63 | ``` 64 | 65 | * [Checkstyle](http://checkstyle.sourceforge.net/) 66 | 67 | ```sh 68 | ./gradlew checkstyle 69 | ``` 70 | 71 | ## The check task 72 | 73 | To ensure that your code is valid and stable use check: 74 | 75 | ```sh 76 | ./gradlew check 77 | ``` 78 | 79 | ## License 80 | ``` 81 | MIT License 82 | 83 | Copyright (c) 2016 Ravindra Kumar 84 | 85 | Permission is hereby granted, free of charge, to any person obtaining a copy 86 | of this software and associated documentation files (the "Software"), to deal 87 | in the Software without restriction, including without limitation the rights 88 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 89 | copies of the Software, and to permit persons to whom the Software is 90 | furnished to do so, subject to the following conditions: 91 | 92 | The above copyright notice and this permission notice shall be included in all 93 | copies or substantial portions of the Software. 94 | 95 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 96 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 97 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 98 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 99 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 100 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 101 | SOFTWARE. 102 | ``` 103 | -------------------------------------------------------------------------------- /MVPStarter/root/app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply from: '../config/quality/quality.gradle' 3 | apply plugin: 'com.neenbedankt.android-apt' 4 | apply plugin: 'me.tatarka.retrolambda' 5 | //apply plugin: 'io.fabric' 6 | 7 | android { 8 | compileSdkVersion 24 9 | buildToolsVersion "24.0.2" 10 | 11 | dexOptions { 12 | maxProcessCount 4 13 | preDexLibraries false 14 | javaMaxHeapSize "8g" 15 | } 16 | 17 | defaultConfig { 18 | applicationId "${packageName}" 19 | minSdkVersion 18 20 | targetSdkVersion 24 21 | testInstrumentationRunner "${applicationId}.runner.RxAndroidJUnitRunner" 22 | versionCode 1000 23 | // Major -> Millions, Minor -> Thousands, Bugfix -> Hundreds. E.g 1.3.72 == 1,003,072 24 | versionName '0.1.0' 25 | 26 | buildConfigField("String", "POKEAPI_API_URL", 27 | "\"${PokeapiApiUrl}\"") 28 | } 29 | buildTypes { 30 | release { 31 | minifyEnabled false 32 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 33 | } 34 | } 35 | 36 | sourceSets { 37 | def commonTestDir = 'src/commonTest/java' 38 | test { 39 | java.srcDir commonTestDir 40 | } 41 | androidTest { 42 | java.srcDir commonTestDir 43 | } 44 | } 45 | 46 | lintOptions { 47 | warning 'InvalidPackage' 48 | textOutput 'stdout' 49 | fatal 'UnusedResources' 50 | warning 'ResourceType' 51 | abortOnError false 52 | textReport true 53 | } 54 | 55 | packagingOptions { 56 | exclude 'META-INF/services/javax.annotation.processing.Processor' 57 | exclude 'LICENSE.txt' 58 | exclude 'META-INF/license/LICENSE.base64.txt' 59 | exclude 'META-INF/NOTICE.txt' 60 | exclude 'META-INF/LICENSE.txt' 61 | } 62 | 63 | compileOptions { 64 | sourceCompatibility JavaVersion.VERSION_1_8 65 | targetCompatibility JavaVersion.VERSION_1_8 66 | } 67 | } 68 | 69 | retrolambda { 70 | jvmArgs '-noverify' 71 | incremental true 72 | } 73 | 74 | dependencies { 75 | final PLAY_SERVICES_VERSION = '9.4.0' 76 | final SUPPORT_LIBRARY_VERSION = '24.2.0' 77 | final RETROFIT_VERSION = '2.1.0' 78 | final OKHTTP_VERSION = '3.4.1' 79 | final DAGGER_VERSION = '2.5' 80 | final DEXMAKER_VERSION = '1.4' 81 | final HAMCREST_VERSION = '1.3' 82 | final ESPRESSO_VERSION = '2.2.2' 83 | final RUNNER_VERSION = '0.5' 84 | final BUTTERKNIFE_VERSION = '8.3.0' 85 | 86 | def daggerCompiler = "com.google.dagger:dagger-compiler:$DAGGER_VERSION" 87 | def jUnit = "junit:junit:4.12" 88 | def mockito = "org.mockito:mockito-core:1.10.19" 89 | 90 | // App Dependencies 91 | compile "com.android.support:appcompat-v7:$SUPPORT_LIBRARY_VERSION" 92 | compile "com.android.support:recyclerview-v7:$SUPPORT_LIBRARY_VERSION" 93 | compile "com.android.support:cardview-v7:$SUPPORT_LIBRARY_VERSION" 94 | compile "com.android.support:design:$SUPPORT_LIBRARY_VERSION" 95 | compile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION" 96 | 97 | // Google Play Services 98 | compile "com.google.android.gms:play-services-analytics:$PLAY_SERVICES_VERSION" 99 | compile "com.google.android.gms:play-services-maps:$PLAY_SERVICES_VERSION" 100 | 101 | compile 'com.squareup.sqlbrite:sqlbrite:0.5.0' 102 | compile "com.squareup.retrofit2:retrofit:$RETROFIT_VERSION" 103 | compile "com.squareup.retrofit2:converter-gson:$RETROFIT_VERSION" 104 | compile "com.squareup.retrofit2:adapter-rxjava:$RETROFIT_VERSION" 105 | compile "com.squareup.okhttp3:okhttp:$OKHTTP_VERSION" 106 | compile "com.squareup.okhttp3:logging-interceptor:$OKHTTP_VERSION" 107 | 108 | compile 'com.github.bumptech.glide:glide:3.7.0' 109 | compile('com.github.bumptech.glide:okhttp3-integration:1.4.0') { 110 | exclude group: 'glide-parent' 111 | } 112 | 113 | compile 'io.reactivex:rxandroid:1.2.1' 114 | compile 'io.reactivex:rxjava:1.1.7' 115 | compile "com.jakewharton:butterknife:$BUTTERKNIFE_VERSION" 116 | compile 'com.jakewharton.timber:timber:4.1.0' 117 | compile 'com.facebook.stetho:stetho-okhttp3:1.3.1' 118 | compile "com.google.dagger:dagger:$DAGGER_VERSION" 119 | provided 'org.glassfish:javax.annotation:10.0-b28' //Required by Dagger2 120 | 121 | debugCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta2' 122 | releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2' 123 | testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta2' 124 | 125 | // Instrumentation test dependencies 126 | androidTestCompile jUnit 127 | androidTestCompile mockito 128 | androidTestCompile "com.android.support:support-annotations:$SUPPORT_LIBRARY_VERSION" 129 | androidTestCompile("com.android.support.test.espresso:espresso-contrib:$ESPRESSO_VERSION") { 130 | exclude group: 'com.android.support', module: 'appcompat' 131 | exclude group: 'com.android.support', module: 'design' 132 | exclude group: 'com.android.support', module: 'support-v4' 133 | exclude group: 'com.android.support', module: 'recyclerview-v7' 134 | } 135 | androidTestCompile "com.android.support.test.espresso:espresso-core:$ESPRESSO_VERSION" 136 | androidTestCompile "com.android.support.test:runner:$RUNNER_VERSION" 137 | androidTestCompile "com.android.support.test:rules:$RUNNER_VERSION" 138 | androidTestCompile "com.crittercism.dexmaker:dexmaker:$DEXMAKER_VERSION" 139 | androidTestCompile "com.crittercism.dexmaker:dexmaker-dx:$DEXMAKER_VERSION" 140 | androidTestCompile "com.crittercism.dexmaker:dexmaker-mockito:$DEXMAKER_VERSION" 141 | 142 | // Unit tests dependencies 143 | testCompile jUnit 144 | testCompile mockito 145 | testCompile "org.hamcrest:hamcrest-core:$HAMCREST_VERSION" 146 | testCompile "org.hamcrest:hamcrest-library:$HAMCREST_VERSION" 147 | testCompile "org.hamcrest:hamcrest-integration:$HAMCREST_VERSION" 148 | testCompile 'org.robolectric:robolectric:3.1' 149 | 150 | // APT dependencies 151 | apt "com.jakewharton:butterknife-compiler:$BUTTERKNIFE_VERSION" 152 | apt daggerCompiler 153 | testApt daggerCompiler 154 | androidTestApt daggerCompiler 155 | } 156 | 157 | // Log out test results to console 158 | tasks.matching { it instanceof Test }.all { 159 | testLogging.events = ["failed", "passed", "skipped"] 160 | } 161 | -------------------------------------------------------------------------------- /MVPStarter/root/app/gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *iml 3 | *.iml 4 | .idea -------------------------------------------------------------------------------- /MVPStarter/root/app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/24.4.1/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/androidTest/java/in/mvpstarter/sample/DetailActivityTest.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | import android.support.test.InstrumentationRegistry; 4 | import android.support.test.rule.ActivityTestRule; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Rule; 8 | import org.junit.Test; 9 | import org.junit.rules.RuleChain; 10 | import org.junit.rules.TestRule; 11 | import org.junit.runner.RunWith; 12 | 13 | import ${packageName}.common.TestComponentRule; 14 | import ${packageName}.common.TestDataFactory; 15 | import ${packageName}.data.model.Pokemon; 16 | import ${packageName}.data.model.Statistic; 17 | import ${packageName}.features.detail.DetailActivity; 18 | import ${packageName}.util.ErrorTestUtil; 19 | import rx.Single; 20 | 21 | import static android.support.test.espresso.Espresso.onView; 22 | import static android.support.test.espresso.assertion.ViewAssertions.matches; 23 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 24 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 25 | import static org.mockito.Matchers.anyString; 26 | import static org.mockito.Mockito.when; 27 | 28 | @RunWith(AndroidJUnit4.class) 29 | public class DetailActivityTest { 30 | 31 | public final TestComponentRule component = 32 | new TestComponentRule(InstrumentationRegistry.getTargetContext()); 33 | public final ActivityTestRule main = 34 | new ActivityTestRule<>(DetailActivity.class, false, false); 35 | 36 | // TestComponentRule needs to go first to make sure the Dagger ApplicationTestComponent is set 37 | // in the Application before any Activity is launched. 38 | @Rule 39 | public TestRule chain = RuleChain.outerRule(component).around(main); 40 | 41 | @Test 42 | public void checkPokemonDisplays() { 43 | Pokemon pokemon = TestDataFactory.makePokemon("id"); 44 | stubDataManagerGetPokemon(Single.just(pokemon)); 45 | main.launchActivity( 46 | DetailActivity.getStartIntent(InstrumentationRegistry.getContext(), pokemon.name)); 47 | 48 | for (Statistic stat : pokemon.stats) { 49 | onView(withText(stat.stat.name)) 50 | .check(matches(isDisplayed())); 51 | } 52 | } 53 | 54 | @Test 55 | public void checkErrorViewDisplays() { 56 | stubDataManagerGetPokemon(Single.error(new RuntimeException())); 57 | Pokemon pokemon = TestDataFactory.makePokemon("id"); 58 | main.launchActivity( 59 | DetailActivity.getStartIntent(InstrumentationRegistry.getContext(), pokemon.name)); 60 | ErrorTestUtil.checkErrorViewsDisplay(); 61 | } 62 | 63 | public void stubDataManagerGetPokemon(Single single) { 64 | when(component.getMockDataManager().getPokemon(anyString())) 65 | .thenReturn(single); 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/androidTest/java/in/mvpstarter/sample/MainActivityTest.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | import android.support.test.InstrumentationRegistry; 4 | import android.support.test.rule.ActivityTestRule; 5 | import android.support.test.runner.AndroidJUnit4; 6 | 7 | import org.junit.Rule; 8 | import org.junit.Test; 9 | import org.junit.rules.RuleChain; 10 | import org.junit.rules.TestRule; 11 | import org.junit.runner.RunWith; 12 | 13 | import java.util.List; 14 | 15 | import ${packageName}.common.TestComponentRule; 16 | import ${packageName}.common.TestDataFactory; 17 | import ${packageName}.data.model.NamedResource; 18 | import ${packageName}.data.model.Pokemon; 19 | import ${packageName}.features.main.MainActivity; 20 | import ${packageName}.util.ErrorTestUtil; 21 | import rx.Single; 22 | 23 | import static android.support.test.espresso.Espresso.onView; 24 | import static android.support.test.espresso.action.ViewActions.click; 25 | import static android.support.test.espresso.assertion.ViewAssertions.matches; 26 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 27 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 28 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 29 | import static org.mockito.Matchers.anyInt; 30 | import static org.mockito.Matchers.anyString; 31 | import static org.mockito.Mockito.when; 32 | 33 | @RunWith(AndroidJUnit4.class) 34 | public class MainActivityTest { 35 | 36 | private final TestComponentRule mComponent = 37 | new TestComponentRule(InstrumentationRegistry.getTargetContext()); 38 | private final ActivityTestRule mMain = 39 | new ActivityTestRule<>(MainActivity.class, false, false); 40 | 41 | // TestComponentRule needs to go first to make sure the Dagger ApplicationTestComponent is set 42 | // in the Application before any Activity is launched. 43 | @Rule 44 | public TestRule chain = RuleChain.outerRule(mComponent).around(mMain); 45 | 46 | @Test 47 | public void checkPokemonsDisplay() { 48 | List namedResourceList = TestDataFactory.makeNamedResourceList(5); 49 | List pokemonList = TestDataFactory.makePokemonNameList(namedResourceList); 50 | stubDataManagerGetPokemonList(Single.just(pokemonList)); 51 | mMain.launchActivity(null); 52 | 53 | for (NamedResource pokemonName : namedResourceList) { 54 | onView(withText(pokemonName.name)) 55 | .check(matches(isDisplayed())); 56 | } 57 | } 58 | 59 | @Test 60 | public void clickingPokemonLaunchesDetailActivity() { 61 | List namedResourceList = TestDataFactory.makeNamedResourceList(5); 62 | List pokemonList = TestDataFactory.makePokemonNameList(namedResourceList); 63 | stubDataManagerGetPokemonList(Single.just(pokemonList)); 64 | stubDataManagerGetPokemon(Single.just(TestDataFactory.makePokemon("id"))); 65 | mMain.launchActivity(null); 66 | 67 | onView(withText(pokemonList.get(0))) 68 | .perform(click()); 69 | 70 | onView(withId(R.id.image_pokemon)) 71 | .check(matches(isDisplayed())); 72 | } 73 | 74 | @Test 75 | public void checkErrorViewDisplays() { 76 | stubDataManagerGetPokemonList(Single.>error(new RuntimeException())); 77 | mMain.launchActivity(null); 78 | ErrorTestUtil.checkErrorViewsDisplay(); 79 | } 80 | 81 | public void stubDataManagerGetPokemonList(Single> single) { 82 | when(mComponent.getMockDataManager().getPokemonList(anyInt())) 83 | .thenReturn(single); 84 | } 85 | 86 | public void stubDataManagerGetPokemon(Single single) { 87 | when(mComponent.getMockDataManager().getPokemon(anyString())) 88 | .thenReturn(single); 89 | } 90 | 91 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/androidTest/java/in/mvpstarter/sample/runner/RxAndroidJUnitRunner.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.runner; 2 | 3 | import android.os.Bundle; 4 | import android.support.test.espresso.Espresso; 5 | 6 | import ${packageName}.util.RxIdlingExecutionHook; 7 | import ${packageName}.util.RxIdlingResource; 8 | import rx.plugins.RxJavaPlugins; 9 | 10 | /** 11 | * Runner that registers a Espresso Indling resource that handles waiting for 12 | * RxJava Observables to finish. 13 | * WARNING - Using this runner will block the tests if the application uses long-lived hot 14 | * Observables such us event buses, etc. 15 | */ 16 | public class RxAndroidJUnitRunner extends UnlockDeviceAndroidJUnitRunner { 17 | 18 | @Override 19 | public void onCreate(Bundle arguments) { 20 | super.onCreate(arguments); 21 | RxIdlingResource rxIdlingResource = new RxIdlingResource(); 22 | RxJavaPlugins.getInstance() 23 | .registerObservableExecutionHook(new RxIdlingExecutionHook(rxIdlingResource)); 24 | Espresso.registerIdlingResources(rxIdlingResource); 25 | } 26 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/androidTest/java/in/mvpstarter/sample/runner/UnlockDeviceAndroidJUnitRunner.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.runner; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.Application; 5 | import android.app.KeyguardManager; 6 | import android.os.PowerManager; 7 | import android.support.test.runner.AndroidJUnitRunner; 8 | 9 | import static android.content.Context.KEYGUARD_SERVICE; 10 | import static android.content.Context.POWER_SERVICE; 11 | import static android.os.PowerManager.ACQUIRE_CAUSES_WAKEUP; 12 | import static android.os.PowerManager.FULL_WAKE_LOCK; 13 | import static android.os.PowerManager.ON_AFTER_RELEASE; 14 | 15 | public class UnlockDeviceAndroidJUnitRunner extends AndroidJUnitRunner { 16 | 17 | private PowerManager.WakeLock mWakeLock; 18 | 19 | @SuppressLint("MissingPermission") 20 | @Override 21 | public void onStart() { 22 | Application application = (Application) getTargetContext().getApplicationContext(); 23 | String simpleName = UnlockDeviceAndroidJUnitRunner.class.getSimpleName(); 24 | // Unlock the device so that the tests can input keystrokes. 25 | ((KeyguardManager) application.getSystemService(KEYGUARD_SERVICE)) 26 | .newKeyguardLock(simpleName) 27 | .disableKeyguard(); 28 | // Wake up the screen. 29 | PowerManager powerManager = ((PowerManager) application.getSystemService(POWER_SERVICE)); 30 | mWakeLock = powerManager.newWakeLock(FULL_WAKE_LOCK | ACQUIRE_CAUSES_WAKEUP | 31 | ON_AFTER_RELEASE, simpleName); 32 | mWakeLock.acquire(); 33 | super.onStart(); 34 | } 35 | 36 | @Override 37 | public void onDestroy() { 38 | super.onDestroy(); 39 | mWakeLock.release(); 40 | } 41 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/androidTest/java/in/mvpstarter/sample/util/ErrorTestUtil.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.util; 2 | 3 | import ${packageName}.R; 4 | 5 | import static android.support.test.espresso.Espresso.onView; 6 | import static android.support.test.espresso.action.ViewActions.click; 7 | import static android.support.test.espresso.assertion.ViewAssertions.matches; 8 | import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed; 9 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 10 | import static org.hamcrest.Matchers.allOf; 11 | 12 | public class ErrorTestUtil { 13 | 14 | public static void checkErrorViewsDisplay() { 15 | onView(allOf(withText(R.string.error_title), isDisplayed())) 16 | .check(matches(isDisplayed())); 17 | onView(allOf(withText(R.string.error_message), isDisplayed())) 18 | .check(matches(isDisplayed())); 19 | } 20 | 21 | public static void checkClickingReloadShowsContentWithText(String expectedText) { 22 | onView(allOf(withText(R.string.error_reload), isDisplayed())) 23 | .perform(click()); 24 | onView(withText(expectedText)) 25 | .check(matches(isDisplayed())); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/androidTest/java/in/mvpstarter/sample/util/RxIdlingExecutionHook.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.util; 2 | 3 | import rx.Observable; 4 | import rx.Subscription; 5 | import rx.plugins.RxJavaObservableExecutionHook; 6 | 7 | /** 8 | * RxJava Observable execution hook that handles updating the active subscription 9 | * count for a given Espresso RxIdlingResource. 10 | */ 11 | public class RxIdlingExecutionHook extends RxJavaObservableExecutionHook { 12 | 13 | private RxIdlingResource mRxIdlingResource; 14 | 15 | public RxIdlingExecutionHook(RxIdlingResource rxIdlingResource) { 16 | mRxIdlingResource = rxIdlingResource; 17 | } 18 | 19 | @Override 20 | public Observable.OnSubscribe onSubscribeStart( 21 | Observable observableInstance, Observable.OnSubscribe onSubscribe) { 22 | mRxIdlingResource.incrementActiveSubscriptionsCount(); 23 | return super.onSubscribeStart(observableInstance, onSubscribe); 24 | } 25 | 26 | @Override 27 | public Throwable onSubscribeError(Throwable e) { 28 | mRxIdlingResource.decrementActiveSubscriptionsCount(); 29 | return super.onSubscribeError(e); 30 | } 31 | 32 | @Override 33 | public Subscription onSubscribeReturn(Subscription subscription) { 34 | mRxIdlingResource.decrementActiveSubscriptionsCount(); 35 | return super.onSubscribeReturn(subscription); 36 | } 37 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/androidTest/java/in/mvpstarter/sample/util/RxIdlingResource.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.util; 2 | 3 | import android.support.test.espresso.IdlingResource; 4 | 5 | import java.util.concurrent.atomic.AtomicInteger; 6 | 7 | import timber.log.Timber; 8 | 9 | /** 10 | * Espresso Idling resource that handles waiting for RxJava Observables executions. 11 | * This class must be used with RxIdlingExecutionHook. 12 | * Before registering this idling resource you must: 13 | * 1. Create an instance of RxIdlingExecutionHook by passing an instance of this class. 14 | * 2. Register RxIdlingExecutionHook with the RxJavaPlugins using registerObservableExecutionHook() 15 | * 3. Register this idle resource with Espresso using Espresso.registerIdlingResources() 16 | */ 17 | public class RxIdlingResource implements IdlingResource { 18 | 19 | private final AtomicInteger mActiveSubscriptionsCount = new AtomicInteger(0); 20 | private ResourceCallback mResourceCallback; 21 | 22 | @Override 23 | public String getName() { 24 | return getClass().getSimpleName(); 25 | } 26 | 27 | @Override 28 | public boolean isIdleNow() { 29 | return mActiveSubscriptionsCount.get() <= 0; 30 | } 31 | 32 | @Override 33 | public void registerIdleTransitionCallback(ResourceCallback callback) { 34 | mResourceCallback = callback; 35 | } 36 | 37 | public void incrementActiveSubscriptionsCount() { 38 | int count = mActiveSubscriptionsCount.incrementAndGet(); 39 | Timber.i("Active subscriptions count increased to %d", count); 40 | } 41 | 42 | public void decrementActiveSubscriptionsCount() { 43 | int count = mActiveSubscriptionsCount.decrementAndGet(); 44 | Timber.i("Active subscriptions count decreased to %d", count); 45 | if (isIdleNow()) { 46 | Timber.i("There is no active subscriptions, transitioning to Idle"); 47 | mResourceCallback.onTransitionToIdle(); 48 | } 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/commonTest/java/in/mvpstarter/sample/common/TestComponentRule.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.common; 2 | 3 | import android.content.Context; 4 | 5 | import org.junit.rules.TestRule; 6 | import org.junit.runner.Description; 7 | import org.junit.runners.model.Statement; 8 | 9 | import ${packageName}.MvpStarterApplication; 10 | import ${packageName}.common.injection.component.DaggerTestComponent; 11 | import ${packageName}.common.injection.component.TestComponent; 12 | import ${packageName}.common.injection.module.ApplicationTestModule; 13 | import ${packageName}.data.DataManager; 14 | 15 | /** 16 | * Test rule that creates and sets a Dagger TestComponent into the application overriding the 17 | * existing application component. 18 | * Use this rule in your test case in order for the app to use mock dependencies. 19 | * It also exposes some of the dependencies so they can be easily accessed from the tests, e.g. to 20 | * stub mocks etc. 21 | */ 22 | public class TestComponentRule implements TestRule { 23 | 24 | private final TestComponent mTestComponent; 25 | private final Context mContext; 26 | 27 | public TestComponentRule(Context context) { 28 | mContext = context; 29 | MvpStarterApplication application = MvpStarterApplication.get(context); 30 | mTestComponent = DaggerTestComponent.builder() 31 | .applicationTestModule(new ApplicationTestModule(application)) 32 | .build(); 33 | } 34 | 35 | public TestComponent getTestComponent() { 36 | return mTestComponent; 37 | } 38 | 39 | public Context getContext() { 40 | return mContext; 41 | } 42 | 43 | public DataManager getMockDataManager() { 44 | return mTestComponent.dataManager(); 45 | } 46 | 47 | @Override 48 | public Statement apply(final Statement base, Description description) { 49 | return new Statement() { 50 | @Override 51 | public void evaluate() throws Throwable { 52 | MvpStarterApplication application = MvpStarterApplication.get(mContext); 53 | application.setComponent(mTestComponent); 54 | base.evaluate(); 55 | application.setComponent(null); 56 | } 57 | }; 58 | } 59 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/commonTest/java/in/mvpstarter/sample/common/TestDataFactory.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.common; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | import java.util.Random; 6 | import java.util.UUID; 7 | 8 | import ${packageName}.data.model.NamedResource; 9 | import ${packageName}.data.model.Pokemon; 10 | import ${packageName}.data.model.Sprites; 11 | import ${packageName}.data.model.Statistic; 12 | 13 | /** 14 | * Factory class that makes instances of data models with random field values. 15 | * The aim of this class is to help setting up test fixtures. 16 | */ 17 | public class TestDataFactory { 18 | 19 | private static final Random sRandom = new Random(); 20 | 21 | public static String randomUuid() { 22 | return UUID.randomUUID().toString(); 23 | } 24 | 25 | public static Pokemon makePokemon(String id) { 26 | Pokemon pokemon = new Pokemon(); 27 | pokemon.id = id; 28 | pokemon.name = randomUuid() + id; 29 | pokemon.stats = makeStatisticList(3); 30 | pokemon.sprites = makeSprites(); 31 | return pokemon; 32 | } 33 | 34 | public static List makePokemonList(int count) { 35 | List pokemonList = new ArrayList<>(); 36 | for (int i = 0; i < count; i++) { 37 | pokemonList.add(makePokemon(String.valueOf(i))); 38 | } 39 | return pokemonList; 40 | } 41 | 42 | public static List makePokemonNameList(List pokemonList) { 43 | List names = new ArrayList<>(); 44 | for (NamedResource pokemon : pokemonList) { 45 | names.add(pokemon.name); 46 | } 47 | return names; 48 | } 49 | 50 | public static Statistic makeStatistic() { 51 | Statistic statistic = new Statistic(); 52 | statistic.baseStat = sRandom.nextInt(); 53 | statistic.stat = makeNamedResource(randomUuid()); 54 | return statistic; 55 | } 56 | 57 | public static List makeStatisticList(int count) { 58 | List statisticList = new ArrayList<>(); 59 | for (int i = 0; i < count; i++) { 60 | statisticList.add(makeStatistic()); 61 | } 62 | return statisticList; 63 | } 64 | 65 | public static Sprites makeSprites() { 66 | Sprites sprites = new Sprites(); 67 | sprites.frontDefault = randomUuid(); 68 | return sprites; 69 | } 70 | 71 | public static NamedResource makeNamedResource(String unique) { 72 | NamedResource namedResource = new NamedResource(); 73 | namedResource.name = randomUuid() + unique; 74 | namedResource.url = randomUuid(); 75 | return namedResource; 76 | } 77 | 78 | public static List makeNamedResourceList(int count) { 79 | List namedResourceList = new ArrayList<>(); 80 | for (int i = 0; i < count; i++) { 81 | namedResourceList.add(makeNamedResource(String.valueOf(i))); 82 | } 83 | return namedResourceList; 84 | } 85 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/commonTest/java/in/mvpstarter/sample/common/injection/component/TestComponent.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.common.injection.component; 2 | 3 | import javax.inject.Singleton; 4 | 5 | import dagger.Component; 6 | import ${packageName}.common.injection.module.ApplicationTestModule; 7 | import ${packageName}.injection.component.ApplicationComponent; 8 | 9 | @Singleton 10 | @Component(modules = ApplicationTestModule.class) 11 | public interface TestComponent extends ApplicationComponent { 12 | 13 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/commonTest/java/in/mvpstarter/sample/common/injection/module/ApplicationTestModule.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.common.injection.module; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import javax.inject.Singleton; 7 | 8 | import dagger.Module; 9 | import dagger.Provides; 10 | import ${packageName}.data.DataManager; 11 | import ${packageName}.data.remote.MvpStarterService; 12 | import ${packageName}.injection.ApplicationContext; 13 | 14 | import static org.mockito.Mockito.mock; 15 | 16 | /** 17 | * Provides application-level dependencies for an app running on a testing environment 18 | * This allows injecting mocks if necessary. 19 | */ 20 | @Module 21 | public class ApplicationTestModule { 22 | private final Application mApplication; 23 | 24 | public ApplicationTestModule(Application application) { 25 | mApplication = application; 26 | } 27 | 28 | @Provides 29 | @Singleton 30 | Application provideApplication() { 31 | return mApplication; 32 | } 33 | 34 | @Provides 35 | @ApplicationContext 36 | Context provideContext() { 37 | return mApplication; 38 | } 39 | 40 | /************* 41 | * MOCKS 42 | *************/ 43 | 44 | @Provides 45 | @Singleton 46 | DataManager providesDataManager() { 47 | return mock(DataManager.class); 48 | } 49 | 50 | @Provides 51 | @Singleton 52 | MvpStarterService provideMvpBoilerplateService() { 53 | return mock(MvpStarterService.class); 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/debug/AndroidManifest.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/debug/res/values/google_maps_api.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 18 | 19 | YOUR_KEY_HERE 20 | 21 | 22 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/AndroidManifest.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 12 | 13 | 14 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 34 | 35 | 43 | 46 | 47 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/MvpStarterApplication.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import com.facebook.stetho.Stetho; 7 | import com.squareup.leakcanary.LeakCanary; 8 | 9 | import ${packageName}.injection.component.ApplicationComponent; 10 | import ${packageName}.injection.component.DaggerApplicationComponent; 11 | import ${packageName}.injection.module.ApplicationModule; 12 | import timber.log.Timber; 13 | 14 | public class MvpStarterApplication extends Application { 15 | 16 | ApplicationComponent mApplicationComponent; 17 | 18 | public static MvpStarterApplication get(Context context) { 19 | return (MvpStarterApplication) context.getApplicationContext(); 20 | } 21 | 22 | @Override 23 | public void onCreate() { 24 | super.onCreate(); 25 | 26 | if (BuildConfig.DEBUG) { 27 | Timber.plant(new Timber.DebugTree()); 28 | Stetho.initializeWithDefaults(this); 29 | LeakCanary.install(this); 30 | } 31 | } 32 | 33 | public ApplicationComponent getComponent() { 34 | if (mApplicationComponent == null) { 35 | mApplicationComponent = DaggerApplicationComponent.builder() 36 | .applicationModule(new ApplicationModule(this)) 37 | .build(); 38 | } 39 | return mApplicationComponent; 40 | } 41 | 42 | // Needed to replace the component with a test specific one 43 | public void setComponent(ApplicationComponent applicationComponent) { 44 | mApplicationComponent = applicationComponent; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/data/DataManager.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.data; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | import javax.inject.Inject; 7 | import javax.inject.Singleton; 8 | 9 | import ${packageName}.data.model.NamedResource; 10 | import ${packageName}.data.model.Pokemon; 11 | import ${packageName}.data.model.PokemonListResponse; 12 | import ${packageName}.data.remote.MvpStarterService; 13 | import rx.Single; 14 | import rx.functions.Func1; 15 | 16 | @Singleton 17 | public class DataManager { 18 | 19 | private final MvpStarterService mMvpStarterService; 20 | 21 | @Inject 22 | public DataManager(MvpStarterService mvpStarterService) { 23 | mMvpStarterService = mvpStarterService; 24 | } 25 | 26 | public Single> getPokemonList(int limit) { 27 | return mMvpStarterService.getPokemonList(limit) 28 | .flatMap(new Func1>>() { 29 | @Override 30 | public Single> call(PokemonListResponse pokemonListResponse) { 31 | List pokemonNames = new ArrayList<>(); 32 | for (NamedResource pokemon : pokemonListResponse.results) { 33 | pokemonNames.add(pokemon.name); 34 | } 35 | return Single.just(pokemonNames); 36 | } 37 | }); 38 | } 39 | 40 | public Single getPokemon(String name) { 41 | return mMvpStarterService.getPokemon(name); 42 | } 43 | 44 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/data/model/NamedResource.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.data.model; 2 | 3 | public class NamedResource { 4 | public String name; 5 | public String url; 6 | } 7 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/data/model/Pokemon.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.data.model; 2 | 3 | import java.util.List; 4 | 5 | public class Pokemon { 6 | public String id; 7 | public String name; 8 | public Sprites sprites; 9 | public List stats; 10 | } 11 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/data/model/PokemonListResponse.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.data.model; 2 | 3 | import java.util.List; 4 | 5 | public class PokemonListResponse { 6 | public List results; 7 | } 8 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/data/model/Sprites.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.data.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class Sprites { 6 | @SerializedName("front_default") 7 | public String frontDefault; 8 | } 9 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/data/model/Statistic.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.data.model; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class Statistic { 6 | public NamedResource stat; 7 | @SerializedName("base_stat") 8 | public int baseStat; 9 | } 10 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/data/remote/MvpStarterService.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.data.remote; 2 | 3 | 4 | import ${packageName}.data.model.Pokemon; 5 | import ${packageName}.data.model.PokemonListResponse; 6 | import retrofit2.http.GET; 7 | import retrofit2.http.Path; 8 | import retrofit2.http.Query; 9 | import rx.Single; 10 | 11 | public interface MvpStarterService { 12 | 13 | @GET("pokemon") 14 | Single getPokemonList(@Query("limit") int limit); 15 | 16 | @GET("pokemon/{name}") 17 | Single getPokemon(@Path("name") String name); 18 | 19 | } 20 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/data/remote/MvpStarterServiceFactory.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.data.remote; 2 | 3 | import com.facebook.stetho.okhttp3.StethoInterceptor; 4 | import com.google.gson.FieldNamingPolicy; 5 | import com.google.gson.Gson; 6 | import com.google.gson.GsonBuilder; 7 | 8 | import ${packageName}.BuildConfig; 9 | import okhttp3.OkHttpClient; 10 | import okhttp3.logging.HttpLoggingInterceptor; 11 | import retrofit2.Retrofit; 12 | import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; 13 | import retrofit2.converter.gson.GsonConverterFactory; 14 | import timber.log.Timber; 15 | 16 | /** 17 | * Provide "make" methods to create instances of {@link MvpStarterService} 18 | * and its related dependencies, such as OkHttpClient, Gson, etc. 19 | */ 20 | public class MvpStarterServiceFactory { 21 | 22 | public static MvpStarterService makeStarterService() { 23 | return makeMvpStarterService(makeGson()); 24 | } 25 | 26 | private static MvpStarterService makeMvpStarterService(Gson gson) { 27 | Retrofit retrofit = new Retrofit.Builder() 28 | .baseUrl(BuildConfig.POKEAPI_API_URL) 29 | .client(makeOkHttpClient()) 30 | .addConverterFactory(GsonConverterFactory.create(gson)) 31 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 32 | .build(); 33 | return retrofit.create(MvpStarterService.class); 34 | } 35 | 36 | private static OkHttpClient makeOkHttpClient() { 37 | 38 | OkHttpClient.Builder httpClientBuilder = new OkHttpClient.Builder(); 39 | 40 | if (BuildConfig.DEBUG) { 41 | HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(message 42 | -> Timber.d(message)); 43 | loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY); 44 | httpClientBuilder.addInterceptor(loggingInterceptor); 45 | httpClientBuilder.addNetworkInterceptor(new StethoInterceptor()); 46 | } 47 | 48 | return httpClientBuilder.build(); 49 | } 50 | 51 | private static Gson makeGson() { 52 | return new GsonBuilder() 53 | .setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") 54 | .setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES) 55 | .create(); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/injection/ActivityContext.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.injection; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Qualifier; 7 | 8 | @Qualifier 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ActivityContext { 11 | } 12 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/injection/ApplicationContext.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.injection; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Qualifier; 7 | 8 | @Qualifier 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ApplicationContext { 11 | } 12 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/injection/ConfigPersistent.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.injection; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | import ${packageName}.injection.component.ConfigPersistentComponent; 9 | 10 | /** 11 | * A scoping annotation to permit dependencies conform to the life of the 12 | * {@link ConfigPersistentComponent} 13 | */ 14 | @Scope 15 | @Retention(RetentionPolicy.RUNTIME) 16 | public @interface ConfigPersistent { 17 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/injection/PerActivity.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.injection; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | /** 9 | * A scoping annotation to permit objects whose lifetime should 10 | * conform to the life of the Activity to be memorised in the 11 | * correct component. 12 | */ 13 | @Scope 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface PerActivity { 16 | } 17 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/injection/PerFragment.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.injection; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | /** 9 | * A scoping annotation to permit objects whose lifetime should 10 | * conform to the life of the Fragment to be memorised in the 11 | * correct component. 12 | */ 13 | @Scope 14 | @Retention(RetentionPolicy.RUNTIME) 15 | public @interface PerFragment { 16 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/injection/component/ActivityComponent.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.injection.component; 2 | 3 | import dagger.Subcomponent; 4 | import ${packageName}.injection.PerActivity; 5 | import ${packageName}.injection.module.ActivityModule; 6 | import ${packageName}.features.base.BaseActivity; 7 | import ${packageName}.features.detail.DetailActivity; 8 | import ${packageName}.features.main.MainActivity; 9 | 10 | @PerActivity 11 | @Subcomponent(modules = ActivityModule.class) 12 | public interface ActivityComponent { 13 | void inject(BaseActivity baseActivity); 14 | 15 | void inject(MainActivity mainActivity); 16 | 17 | void inject(DetailActivity detailActivity); 18 | } 19 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/injection/component/ApplicationComponent.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.injection.component; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import javax.inject.Singleton; 7 | 8 | import dagger.Component; 9 | import ${packageName}.data.DataManager; 10 | import ${packageName}.data.remote.MvpStarterService; 11 | import ${packageName}.injection.ApplicationContext; 12 | import ${packageName}.injection.module.ApplicationModule; 13 | 14 | @Singleton 15 | @Component(modules = ApplicationModule.class) 16 | public interface ApplicationComponent { 17 | 18 | @ApplicationContext 19 | Context context(); 20 | 21 | Application application(); 22 | 23 | DataManager dataManager(); 24 | 25 | MvpStarterService mvpBoilerplateService(); 26 | } 27 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/injection/component/ConfigPersistentComponent.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.injection.component; 2 | 3 | import dagger.Component; 4 | import ${packageName}.injection.ConfigPersistent; 5 | import ${packageName}.injection.module.ActivityModule; 6 | import ${packageName}.injection.module.FragmentModule; 7 | import ${packageName}.features.base.BaseActivity; 8 | import ${packageName}.features.base.BaseFragment; 9 | 10 | /** 11 | * A dagger component that will live during the lifecycle of an Activity or Fragment but it won't 12 | * be destroy during configuration changes. Check {@link BaseActivity} and {@link BaseFragment} to 13 | * see how this components survives configuration changes. 14 | * Use the {@link ConfigPersistent} scope to annotate dependencies that need to survive 15 | * configuration changes (for example Presenters). 16 | */ 17 | @ConfigPersistent 18 | @Component(dependencies = ApplicationComponent.class) 19 | public interface ConfigPersistentComponent { 20 | 21 | ActivityComponent activityComponent(ActivityModule activityModule); 22 | 23 | FragmentComponent fragmentComponent(FragmentModule fragmentModule); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/injection/component/FragmentComponent.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.injection.component; 2 | 3 | import dagger.Subcomponent; 4 | import ${packageName}.injection.PerFragment; 5 | import ${packageName}.injection.module.FragmentModule; 6 | 7 | /** 8 | * This component inject dependencies to all Fragments across the application 9 | */ 10 | @PerFragment 11 | @Subcomponent(modules = FragmentModule.class) 12 | public interface FragmentComponent { 13 | 14 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/injection/module/ActivityModule.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.injection.module; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | 6 | import dagger.Module; 7 | import dagger.Provides; 8 | import ${packageName}.injection.ActivityContext; 9 | 10 | @Module 11 | public class ActivityModule { 12 | 13 | private Activity mActivity; 14 | 15 | public ActivityModule(Activity activity) { 16 | mActivity = activity; 17 | } 18 | 19 | @Provides 20 | Activity provideActivity() { 21 | return mActivity; 22 | } 23 | 24 | @Provides 25 | @ActivityContext 26 | Context providesContext() { 27 | return mActivity; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/injection/module/ApplicationModule.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.injection.module; 2 | 3 | import android.app.Application; 4 | import android.content.Context; 5 | 6 | import javax.inject.Singleton; 7 | 8 | import dagger.Module; 9 | import dagger.Provides; 10 | import ${packageName}.data.remote.MvpStarterService; 11 | import ${packageName}.data.remote.MvpStarterServiceFactory; 12 | import ${packageName}.injection.ApplicationContext; 13 | 14 | @Module 15 | public class ApplicationModule { 16 | protected final Application mApplication; 17 | 18 | public ApplicationModule(Application application) { 19 | mApplication = application; 20 | } 21 | 22 | @Provides 23 | Application provideApplication() { 24 | return mApplication; 25 | } 26 | 27 | @Provides 28 | @ApplicationContext 29 | Context provideContext() { 30 | return mApplication; 31 | } 32 | 33 | @Provides 34 | @Singleton 35 | MvpStarterService provideMvpBoilerplateService() { 36 | return MvpStarterServiceFactory.makeStarterService(); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/injection/module/FragmentModule.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.injection.module; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.support.v4.app.Fragment; 6 | 7 | import dagger.Module; 8 | import dagger.Provides; 9 | import ${packageName}.injection.ActivityContext; 10 | 11 | @Module 12 | public class FragmentModule { 13 | private Fragment mFragment; 14 | 15 | public FragmentModule(Fragment fragment) { 16 | mFragment = fragment; 17 | } 18 | 19 | @Provides 20 | Fragment providesFragment() { 21 | return mFragment; 22 | } 23 | 24 | @Provides 25 | Activity provideActivity() { 26 | return mFragment.getActivity(); 27 | } 28 | 29 | @Provides 30 | @ActivityContext 31 | Context providesContext() { 32 | return mFragment.getActivity(); 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/base/BaseActivity.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.base; 2 | 3 | import android.os.Bundle; 4 | import android.support.v7.app.AppCompatActivity; 5 | import android.view.MenuItem; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.concurrent.atomic.AtomicLong; 10 | 11 | import ${packageName}.MvpStarterApplication; 12 | import ${packageName}.injection.component.ActivityComponent; 13 | import ${packageName}.injection.component.ConfigPersistentComponent; 14 | import ${packageName}.injection.component.DaggerConfigPersistentComponent; 15 | import ${packageName}.injection.module.ActivityModule; 16 | import timber.log.Timber; 17 | 18 | /** 19 | * Abstract activity that every other Activity in this application must implement. It provides the 20 | * following functionality: 21 | * - Handles creation of Dagger components and makes sure that instances of 22 | * ConfigPersistentComponent are kept across configuration changes. 23 | * - Set up and handles a GoogleApiClient instance that can be used to access the Google sign in 24 | * api. 25 | * - Handles signing out when an authentication error event is received. 26 | */ 27 | public abstract class BaseActivity extends AppCompatActivity { 28 | 29 | private static final String KEY_ACTIVITY_ID = "KEY_ACTIVITY_ID"; 30 | private static final AtomicLong NEXT_ID = new AtomicLong(0); 31 | private static final Map sComponentsMap = new HashMap<>(); 32 | 33 | private ActivityComponent mActivityComponent; 34 | private long mActivityId; 35 | 36 | @Override 37 | protected void onCreate(Bundle savedInstanceState) { 38 | super.onCreate(savedInstanceState); 39 | 40 | // Create the ActivityComponent and reuses cached ConfigPersistentComponent if this is 41 | // being called after a configuration change. 42 | mActivityId = savedInstanceState != null ? 43 | savedInstanceState.getLong(KEY_ACTIVITY_ID) : NEXT_ID.getAndIncrement(); 44 | ConfigPersistentComponent configPersistentComponent; 45 | if (!sComponentsMap.containsKey(mActivityId)) { 46 | Timber.i("Creating new ConfigPersistentComponent id=%d", mActivityId); 47 | configPersistentComponent = DaggerConfigPersistentComponent.builder() 48 | .applicationComponent(MvpStarterApplication.get(this).getComponent()) 49 | .build(); 50 | sComponentsMap.put(mActivityId, configPersistentComponent); 51 | } else { 52 | Timber.i("Reusing ConfigPersistentComponent id=%d", mActivityId); 53 | configPersistentComponent = sComponentsMap.get(mActivityId); 54 | } 55 | mActivityComponent = configPersistentComponent.activityComponent(new ActivityModule(this)); 56 | mActivityComponent.inject(this); 57 | } 58 | 59 | @Override 60 | protected void onSaveInstanceState(Bundle outState) { 61 | super.onSaveInstanceState(outState); 62 | outState.putLong(KEY_ACTIVITY_ID, mActivityId); 63 | } 64 | 65 | @Override 66 | protected void onDestroy() { 67 | if (!isChangingConfigurations()) { 68 | Timber.i("Clearing ConfigPersistentComponent id=%d", mActivityId); 69 | sComponentsMap.remove(mActivityId); 70 | } 71 | super.onDestroy(); 72 | } 73 | 74 | @Override 75 | public boolean onOptionsItemSelected(MenuItem item) { 76 | switch (item.getItemId()) { 77 | case android.R.id.home: 78 | finish(); 79 | return true; 80 | default: 81 | return super.onOptionsItemSelected(item); 82 | } 83 | } 84 | 85 | public ActivityComponent activityComponent() { 86 | return mActivityComponent; 87 | } 88 | 89 | } 90 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/base/BaseFragment.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.base; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | 7 | import java.util.HashMap; 8 | import java.util.Map; 9 | import java.util.concurrent.atomic.AtomicLong; 10 | 11 | import ${packageName}.MvpStarterApplication; 12 | import ${packageName}.injection.component.ConfigPersistentComponent; 13 | import ${packageName}.injection.component.DaggerConfigPersistentComponent; 14 | import ${packageName}.injection.component.FragmentComponent; 15 | import ${packageName}.injection.module.FragmentModule; 16 | import timber.log.Timber; 17 | 18 | /** 19 | * Abstract Fragment that every other Fragment in this application must implement. It handles 20 | * creation of Dagger components and makes sure that instances of ConfigPersistentComponent are kept 21 | * across configuration changes. 22 | */ 23 | public abstract class BaseFragment extends Fragment { 24 | 25 | private static final String KEY_FRAGMENT_ID = "KEY_FRAGMENT_ID"; 26 | private static final Map sComponentsMap = new HashMap<>(); 27 | private static final AtomicLong NEXT_ID = new AtomicLong(0); 28 | 29 | private FragmentComponent mFragmentComponent; 30 | private long mFragmentId; 31 | 32 | @Override 33 | public void onCreate(@Nullable Bundle savedInstanceState) { 34 | super.onCreate(savedInstanceState); 35 | 36 | // Create the FragmentComponent and reuses cached ConfigPersistentComponent if this is 37 | // being called after a configuration change. 38 | mFragmentId = savedInstanceState != null ? 39 | savedInstanceState.getLong(KEY_FRAGMENT_ID) : NEXT_ID.getAndIncrement(); 40 | ConfigPersistentComponent configPersistentComponent; 41 | if (!sComponentsMap.containsKey(mFragmentId)) { 42 | Timber.i("Creating new ConfigPersistentComponent id=%d", mFragmentId); 43 | configPersistentComponent = DaggerConfigPersistentComponent.builder() 44 | .applicationComponent(MvpStarterApplication.get( 45 | getActivity()).getComponent()) 46 | .build(); 47 | sComponentsMap.put(mFragmentId, configPersistentComponent); 48 | } else { 49 | Timber.i("Reusing ConfigPersistentComponent id=%d", mFragmentId); 50 | configPersistentComponent = sComponentsMap.get(mFragmentId); 51 | } 52 | mFragmentComponent = configPersistentComponent.fragmentComponent(new FragmentModule(this)); 53 | } 54 | 55 | @Override 56 | public void onSaveInstanceState(Bundle outState) { 57 | super.onSaveInstanceState(outState); 58 | outState.putLong(KEY_FRAGMENT_ID, mFragmentId); 59 | } 60 | 61 | @Override 62 | public void onDestroy() { 63 | if (!getActivity().isChangingConfigurations()) { 64 | Timber.i("Clearing ConfigPersistentComponent id=%d", mFragmentId); 65 | sComponentsMap.remove(mFragmentId); 66 | } 67 | super.onDestroy(); 68 | } 69 | 70 | public FragmentComponent fragmentComponent() { 71 | return mFragmentComponent; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/base/BasePresenter.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.base; 2 | 3 | import rx.Observable; 4 | import rx.Single; 5 | 6 | /** 7 | * Base class that implements the Presenter interface and provides a base implementation for 8 | * attachView() and detachView(). It also handles keeping a reference to the mvpView that 9 | * can be accessed from the children classes by calling getMvpView(). 10 | */ 11 | public class BasePresenter implements Presenter { 12 | 13 | private T mMvpView; 14 | 15 | @Override 16 | public void attachView(T mvpView) { 17 | mMvpView = mvpView; 18 | } 19 | 20 | @Override 21 | public void detachView() { 22 | mMvpView = null; 23 | } 24 | 25 | public boolean isViewAttached() { 26 | return mMvpView != null; 27 | } 28 | 29 | public T getMvpView() { 30 | return mMvpView; 31 | } 32 | 33 | public void checkViewAttached() { 34 | if (!isViewAttached()) throw new MvpViewNotAttachedException(); 35 | } 36 | 37 | public static class MvpViewNotAttachedException extends RuntimeException { 38 | public MvpViewNotAttachedException() { 39 | super("Please call Presenter.attachView(MvpView) before" + 40 | " requesting data to the Presenter"); 41 | } 42 | } 43 | 44 | /** 45 | * Encapsulate the result of an rx Observable. 46 | * This model is meant to be used by the children presenters to easily keep a reference 47 | * to the latest loaded result so that it can be easily emitted again when on configuration 48 | * changes. 49 | */ 50 | protected static class DataResult { 51 | 52 | private T mData; 53 | private Throwable mError; 54 | 55 | public DataResult(T data) { 56 | mData = data; 57 | } 58 | 59 | public DataResult(Throwable error) { 60 | mError = error; 61 | } 62 | 63 | public Single toSingle() { 64 | if (mError != null) { 65 | return Single.error(mError); 66 | } 67 | return Single.just(mData); 68 | } 69 | 70 | public Observable toObservable() { 71 | if (mError != null) { 72 | return Observable.error(mError); 73 | } 74 | return Observable.just(mData); 75 | } 76 | } 77 | } 78 | 79 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/base/MvpView.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.base; 2 | 3 | 4 | /** 5 | * Base interface that any class that wants to act as a View in the MVP (Model View Presenter) 6 | * pattern must implement. Generally this interface will be extended by a more specific interface 7 | * that then usually will be implemented by an Activity or Fragment. 8 | */ 9 | public interface MvpView { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/base/Presenter.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.base; 2 | 3 | /** 4 | * Every presenter in the app must either implement this interface or extend BasePresenter 5 | * indicating the MvpView type that wants to be attached with. 6 | */ 7 | public interface Presenter { 8 | 9 | void attachView(V mvpView); 10 | 11 | void detachView(); 12 | } 13 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/common/ErrorView.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.common; 2 | 3 | import android.annotation.TargetApi; 4 | import android.content.Context; 5 | import android.os.Build; 6 | import android.util.AttributeSet; 7 | import android.view.Gravity; 8 | import android.view.LayoutInflater; 9 | import android.widget.LinearLayout; 10 | 11 | import butterknife.ButterKnife; 12 | import butterknife.OnClick; 13 | import ${packageName}.R; 14 | 15 | public class ErrorView extends LinearLayout { 16 | 17 | private ErrorListener mErrorListener; 18 | 19 | public ErrorView(Context context) { 20 | super(context); 21 | init(); 22 | } 23 | 24 | public ErrorView(Context context, AttributeSet attrs) { 25 | super(context, attrs); 26 | init(); 27 | } 28 | 29 | public ErrorView(Context context, AttributeSet attrs, int defStyleAttr) { 30 | super(context, attrs, defStyleAttr); 31 | init(); 32 | } 33 | 34 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 35 | public ErrorView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 36 | super(context, attrs, defStyleAttr, defStyleRes); 37 | init(); 38 | } 39 | 40 | private void init() { 41 | setOrientation(VERTICAL); 42 | setGravity(Gravity.CENTER); 43 | LayoutInflater.from(getContext()).inflate(R.layout.view_error, this); 44 | ButterKnife.bind(this); 45 | } 46 | 47 | @OnClick(R.id.button_reload) 48 | public void onReloadButtonClick() { 49 | if (mErrorListener != null) { 50 | mErrorListener.onReloadData(); 51 | } 52 | } 53 | 54 | public void setErrorListener(ErrorListener errorListener) { 55 | mErrorListener = errorListener; 56 | } 57 | 58 | public interface ErrorListener { 59 | void onReloadData(); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/detail/DetailActivity.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.detail; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.os.Bundle; 6 | import android.support.v7.app.ActionBar; 7 | import android.support.v7.widget.Toolbar; 8 | import android.view.View; 9 | import android.widget.ImageView; 10 | import android.widget.LinearLayout; 11 | import android.widget.ProgressBar; 12 | 13 | import com.bumptech.glide.Glide; 14 | 15 | import javax.inject.Inject; 16 | 17 | import butterknife.BindView; 18 | import butterknife.ButterKnife; 19 | import ${packageName}.R; 20 | import ${packageName}.data.model.Pokemon; 21 | import ${packageName}.data.model.Statistic; 22 | import ${packageName}.features.base.BaseActivity; 23 | import ${packageName}.features.common.ErrorView; 24 | import ${packageName}.features.detail.widget.StatisticView; 25 | 26 | public class DetailActivity extends BaseActivity implements DetailMvpView, ErrorView.ErrorListener { 27 | 28 | public static final String EXTRA_POKEMON_NAME = "EXTRA_POKEMON_NAME"; 29 | 30 | @Inject 31 | DetailPresenter mDetailPresenter; 32 | 33 | @BindView(R.id.view_error) 34 | ErrorView mErrorView; 35 | @BindView(R.id.image_pokemon) 36 | ImageView mPokemonImage; 37 | @BindView(R.id.progress) 38 | ProgressBar mProgress; 39 | @BindView(R.id.toolbar) 40 | Toolbar mToolbar; 41 | @BindView(R.id.layout_stats) 42 | LinearLayout mStatLayout; 43 | @BindView(R.id.layout_pokemon) 44 | View mPokemonLayout; 45 | 46 | private String mPokemonName; 47 | 48 | public static Intent getStartIntent(Context context, String pokemonName) { 49 | Intent intent = new Intent(context, DetailActivity.class); 50 | intent.putExtra(EXTRA_POKEMON_NAME, pokemonName); 51 | return intent; 52 | } 53 | 54 | @Override 55 | protected void onCreate(Bundle savedInstanceState) { 56 | super.onCreate(savedInstanceState); 57 | activityComponent().inject(this); 58 | setContentView(R.layout.activity_detail); 59 | ButterKnife.bind(this); 60 | mDetailPresenter.attachView(this); 61 | 62 | mPokemonName = getIntent().getStringExtra(EXTRA_POKEMON_NAME); 63 | if (mPokemonName == null) { 64 | throw new IllegalArgumentException("Detail Activity requires a pokemon name@"); 65 | } 66 | 67 | setSupportActionBar(mToolbar); 68 | ActionBar actionBar = getSupportActionBar(); 69 | if (actionBar != null) actionBar.setDisplayHomeAsUpEnabled(true); 70 | setTitle(mPokemonName.substring(0, 1).toUpperCase() + mPokemonName.substring(1)); 71 | 72 | mErrorView.setErrorListener(this); 73 | 74 | mDetailPresenter.getPokemon(mPokemonName); 75 | } 76 | 77 | @Override 78 | protected void onDestroy() { 79 | super.onDestroy(); 80 | mDetailPresenter.detachView(); 81 | } 82 | 83 | @Override 84 | public void showPokemon(Pokemon pokemon) { 85 | if (pokemon.sprites != null && pokemon.sprites.frontDefault != null) { 86 | Glide.with(this) 87 | .load(pokemon.sprites.frontDefault) 88 | .into(mPokemonImage); 89 | } 90 | mPokemonLayout.setVisibility(View.VISIBLE); 91 | } 92 | 93 | @Override 94 | public void showStat(Statistic statistic) { 95 | StatisticView statisticView = new StatisticView(this); 96 | statisticView.setStat(statistic); 97 | mStatLayout.addView(statisticView); 98 | } 99 | 100 | @Override 101 | public void showProgress(boolean show) { 102 | mErrorView.setVisibility(View.GONE); 103 | mProgress.setVisibility(show ? View.VISIBLE : View.GONE); 104 | } 105 | 106 | @Override 107 | public void showError() { 108 | mPokemonLayout.setVisibility(View.GONE); 109 | mErrorView.setVisibility(View.VISIBLE); 110 | } 111 | 112 | @Override 113 | public void onReloadData() { 114 | mDetailPresenter.getPokemon(mPokemonName); 115 | } 116 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/detail/DetailMvpView.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.detail; 2 | 3 | import ${packageName}.data.model.Pokemon; 4 | import ${packageName}.data.model.Statistic; 5 | import ${packageName}.features.base.MvpView; 6 | 7 | public interface DetailMvpView extends MvpView { 8 | 9 | void showPokemon(Pokemon pokemon); 10 | 11 | void showStat(Statistic statistic); 12 | 13 | void showProgress(boolean show); 14 | 15 | void showError(); 16 | 17 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/detail/DetailPresenter.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.detail; 2 | 3 | 4 | import javax.inject.Inject; 5 | 6 | import ${packageName}.data.DataManager; 7 | import ${packageName}.data.model.Pokemon; 8 | import ${packageName}.data.model.Statistic; 9 | import ${packageName}.injection.ConfigPersistent; 10 | import ${packageName}.features.base.BasePresenter; 11 | import rx.SingleSubscriber; 12 | import rx.android.schedulers.AndroidSchedulers; 13 | import rx.schedulers.Schedulers; 14 | import rx.subscriptions.CompositeSubscription; 15 | import timber.log.Timber; 16 | 17 | @ConfigPersistent 18 | public class DetailPresenter extends BasePresenter { 19 | 20 | private final DataManager mDataManager; 21 | private CompositeSubscription mSubscriptions; 22 | 23 | @Inject 24 | public DetailPresenter(DataManager dataManager) { 25 | mDataManager = dataManager; 26 | } 27 | 28 | @Override 29 | public void attachView(DetailMvpView mvpView) { 30 | super.attachView(mvpView); 31 | mSubscriptions = new CompositeSubscription(); 32 | } 33 | 34 | @Override 35 | public void detachView() { 36 | super.detachView(); 37 | mSubscriptions.unsubscribe(); 38 | mSubscriptions = null; 39 | } 40 | 41 | public void getPokemon(String name) { 42 | checkViewAttached(); 43 | getMvpView().showProgress(true); 44 | mSubscriptions.add(mDataManager.getPokemon(name) 45 | .observeOn(AndroidSchedulers.mainThread()) 46 | .subscribeOn(Schedulers.io()) 47 | .subscribe(new SingleSubscriber() { 48 | @Override 49 | public void onSuccess(Pokemon pokemon) { 50 | getMvpView().showProgress(false); 51 | getMvpView().showPokemon(pokemon); 52 | for (Statistic statistic : pokemon.stats) { 53 | getMvpView().showStat(statistic); 54 | } 55 | } 56 | 57 | @Override 58 | public void onError(Throwable error) { 59 | getMvpView().showProgress(false); 60 | getMvpView().showError(); 61 | Timber.e(error, "There was a problem retrieving the pokemon..."); 62 | } 63 | })); 64 | } 65 | 66 | 67 | } 68 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/detail/MapsSampleActivity.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.detail; 2 | 3 | import android.os.Bundle; 4 | 5 | import com.google.android.gms.maps.CameraUpdateFactory; 6 | import com.google.android.gms.maps.GoogleMap; 7 | import com.google.android.gms.maps.OnMapReadyCallback; 8 | import com.google.android.gms.maps.SupportMapFragment; 9 | import com.google.android.gms.maps.model.LatLng; 10 | import com.google.android.gms.maps.model.MarkerOptions; 11 | 12 | import ${packageName}.R; 13 | import ${packageName}.features.base.BaseActivity; 14 | 15 | public class MapsSampleActivity extends BaseActivity implements OnMapReadyCallback { 16 | 17 | private GoogleMap mMap; 18 | 19 | @Override 20 | protected void onCreate(Bundle savedInstanceState) { 21 | super.onCreate(savedInstanceState); 22 | setContentView(R.layout.activity_maps_sample); 23 | // Obtain the SupportMapFragment and get notified when the map is ready to be used. 24 | SupportMapFragment mapFragment = (SupportMapFragment) getSupportFragmentManager() 25 | .findFragmentById(R.id.map); 26 | mapFragment.getMapAsync(this); 27 | } 28 | 29 | 30 | /** 31 | * Manipulates the map once available. 32 | * This callback is triggered when the map is ready to be used. 33 | * This is where we can add markers or lines, add listeners or move the camera. In this case, 34 | * we just add a marker near Sydney, Australia. 35 | * If Google Play services is not installed on the device, the user will be prompted to install 36 | * it inside the SupportMapFragment. This method will only be triggered once the user has 37 | * installed Google Play services and returned to the app. 38 | */ 39 | @Override 40 | public void onMapReady(GoogleMap googleMap) { 41 | mMap = googleMap; 42 | 43 | // Add a marker in Sydney and move the camera 44 | LatLng sydney = new LatLng(-34, 151); 45 | mMap.addMarker(new MarkerOptions().position(sydney).title("Marker in Sydney")); 46 | mMap.moveCamera(CameraUpdateFactory.newLatLng(sydney)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/detail/widget/StatisticView.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.detail.widget; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.annotation.TargetApi; 5 | import android.content.Context; 6 | import android.os.Build; 7 | import android.util.AttributeSet; 8 | import android.view.LayoutInflater; 9 | import android.widget.ProgressBar; 10 | import android.widget.RelativeLayout; 11 | import android.widget.TextView; 12 | 13 | import butterknife.BindView; 14 | import butterknife.ButterKnife; 15 | import ${packageName}.R; 16 | import ${packageName}.data.model.Statistic; 17 | 18 | public class StatisticView extends RelativeLayout { 19 | 20 | @BindView(R.id.text_name) 21 | TextView mNameText; 22 | @BindView(R.id.progress_stat) 23 | ProgressBar mStatProgress; 24 | 25 | public StatisticView(Context context) { 26 | super(context); 27 | init(); 28 | } 29 | 30 | public StatisticView(Context context, AttributeSet attrs) { 31 | super(context, attrs); 32 | init(); 33 | } 34 | 35 | public StatisticView(Context context, AttributeSet attrs, int defStyleAttr) { 36 | super(context, attrs, defStyleAttr); 37 | init(); 38 | } 39 | 40 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 41 | public StatisticView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { 42 | super(context, attrs, defStyleAttr, defStyleRes); 43 | init(); 44 | } 45 | 46 | private void init() { 47 | LayoutInflater.from(getContext()).inflate(R.layout.view_statistic, this); 48 | ButterKnife.bind(this); 49 | } 50 | 51 | @SuppressLint("SetTextI18n") 52 | public void setStat(Statistic statistic) { 53 | mNameText.setText(statistic.stat.name.substring(0, 1).toUpperCase() + 54 | statistic.stat.name.substring(1)); 55 | mStatProgress.setProgress(statistic.baseStat); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/main/MainActivity.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.main; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.widget.SwipeRefreshLayout; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.support.v7.widget.Toolbar; 8 | import android.view.View; 9 | import android.widget.ProgressBar; 10 | 11 | import java.util.List; 12 | 13 | import javax.inject.Inject; 14 | 15 | import butterknife.BindView; 16 | import butterknife.ButterKnife; 17 | import ${packageName}.R; 18 | import ${packageName}.features.base.BaseActivity; 19 | import ${packageName}.features.common.ErrorView; 20 | import ${packageName}.features.detail.DetailActivity; 21 | 22 | public class MainActivity extends BaseActivity implements MainMvpView, PokemonAdapter.ClickListener, 23 | ErrorView.ErrorListener { 24 | 25 | private static final int POKEMON_COUNT = 20; 26 | 27 | @Inject 28 | PokemonAdapter mPokemonAdapter; 29 | @Inject 30 | MainPresenter mMainPresenter; 31 | 32 | @BindView(R.id.view_error) 33 | ErrorView mErrorView; 34 | @BindView(R.id.progress) 35 | ProgressBar mProgress; 36 | @BindView(R.id.recycler_pokemon) 37 | RecyclerView mPokemonRecycler; 38 | @BindView(R.id.swipe_to_refresh) 39 | SwipeRefreshLayout mSwipeRefreshLayout; 40 | @BindView(R.id.toolbar) 41 | Toolbar mToolbar; 42 | 43 | @Override 44 | protected void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | activityComponent().inject(this); 47 | setContentView(R.layout.activity_main); 48 | ButterKnife.bind(this); 49 | mMainPresenter.attachView(this); 50 | 51 | setSupportActionBar(mToolbar); 52 | 53 | mSwipeRefreshLayout.setProgressBackgroundColorSchemeResource(R.color.primary); 54 | mSwipeRefreshLayout.setColorSchemeResources(R.color.white); 55 | mSwipeRefreshLayout.setOnRefreshListener( 56 | new SwipeRefreshLayout.OnRefreshListener() { 57 | @Override 58 | public void onRefresh() { 59 | mMainPresenter.getPokemon(POKEMON_COUNT); 60 | } 61 | }); 62 | 63 | mPokemonAdapter.setClickListener(this); 64 | mPokemonRecycler.setLayoutManager(new LinearLayoutManager(this)); 65 | mPokemonRecycler.setAdapter(mPokemonAdapter); 66 | 67 | mErrorView.setErrorListener(this); 68 | 69 | mMainPresenter.getPokemon(POKEMON_COUNT); 70 | } 71 | 72 | @Override 73 | protected void onDestroy() { 74 | super.onDestroy(); 75 | mMainPresenter.detachView(); 76 | } 77 | 78 | @Override 79 | public void showPokemon(List pokemon) { 80 | mPokemonAdapter.setPokemon(pokemon); 81 | mPokemonAdapter.notifyDataSetChanged(); 82 | 83 | mPokemonRecycler.setVisibility(View.VISIBLE); 84 | mSwipeRefreshLayout.setVisibility(View.VISIBLE); 85 | } 86 | 87 | @Override 88 | public void showProgress(boolean show) { 89 | if (show) { 90 | if (mPokemonRecycler.getVisibility() == View.VISIBLE 91 | && mPokemonAdapter.getItemCount() > 0) { 92 | mSwipeRefreshLayout.setRefreshing(true); 93 | } else { 94 | mProgress.setVisibility(View.VISIBLE); 95 | 96 | mPokemonRecycler.setVisibility(View.GONE); 97 | mSwipeRefreshLayout.setVisibility(View.GONE); 98 | } 99 | 100 | mErrorView.setVisibility(View.GONE); 101 | } else { 102 | mSwipeRefreshLayout.setRefreshing(false); 103 | mProgress.setVisibility(View.GONE); 104 | } 105 | } 106 | 107 | @Override 108 | public void showError() { 109 | mPokemonRecycler.setVisibility(View.GONE); 110 | mSwipeRefreshLayout.setVisibility(View.GONE); 111 | mErrorView.setVisibility(View.VISIBLE); 112 | } 113 | 114 | @Override 115 | public void onPokemonClick(String pokemon) { 116 | startActivity(DetailActivity.getStartIntent(this, pokemon)); 117 | } 118 | 119 | @Override 120 | public void onReloadData() { 121 | mMainPresenter.getPokemon(POKEMON_COUNT); 122 | } 123 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/main/MainMvpView.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.main; 2 | 3 | import java.util.List; 4 | 5 | import ${packageName}.features.base.MvpView; 6 | 7 | public interface MainMvpView extends MvpView { 8 | 9 | void showPokemon(List pokemon); 10 | 11 | void showProgress(boolean show); 12 | 13 | void showError(); 14 | 15 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/main/MainPresenter.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.main; 2 | 3 | 4 | import java.util.List; 5 | 6 | import javax.inject.Inject; 7 | 8 | import ${packageName}.data.DataManager; 9 | import ${packageName}.injection.ConfigPersistent; 10 | import ${packageName}.features.base.BasePresenter; 11 | import rx.SingleSubscriber; 12 | import rx.android.schedulers.AndroidSchedulers; 13 | import rx.schedulers.Schedulers; 14 | import rx.subscriptions.CompositeSubscription; 15 | import timber.log.Timber; 16 | 17 | @ConfigPersistent 18 | public class MainPresenter extends BasePresenter { 19 | 20 | private final DataManager mDataManager; 21 | private CompositeSubscription mSubscriptions; 22 | 23 | @Inject 24 | public MainPresenter(DataManager dataManager) { 25 | mDataManager = dataManager; 26 | } 27 | 28 | @Override 29 | public void attachView(MainMvpView mvpView) { 30 | super.attachView(mvpView); 31 | mSubscriptions = new CompositeSubscription(); 32 | } 33 | 34 | @Override 35 | public void detachView() { 36 | super.detachView(); 37 | mSubscriptions.unsubscribe(); 38 | mSubscriptions = null; 39 | } 40 | 41 | public void getPokemon(int limit) { 42 | checkViewAttached(); 43 | getMvpView().showProgress(true); 44 | mSubscriptions.add(mDataManager.getPokemonList(limit) 45 | .observeOn(AndroidSchedulers.mainThread()) 46 | .subscribeOn(Schedulers.io()) 47 | .subscribe(new SingleSubscriber>() { 48 | @Override 49 | public void onSuccess(List pokemon) { 50 | getMvpView().showProgress(false); 51 | getMvpView().showPokemon(pokemon); 52 | } 53 | 54 | @Override 55 | public void onError(Throwable error) { 56 | getMvpView().showProgress(false); 57 | getMvpView().showError(); 58 | Timber.e(error, "There was an error retrieving the pokemon"); 59 | } 60 | })); 61 | } 62 | 63 | 64 | } 65 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/ui/main/PokemonAdapter.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.features.main; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | 9 | import java.util.Collections; 10 | import java.util.List; 11 | 12 | import javax.inject.Inject; 13 | 14 | import butterknife.BindView; 15 | import butterknife.ButterKnife; 16 | import ${packageName}.R; 17 | 18 | public class PokemonAdapter extends RecyclerView.Adapter { 19 | 20 | private List mPokemon; 21 | private ClickListener mClickListener; 22 | 23 | @Inject 24 | public PokemonAdapter() { 25 | mPokemon = Collections.emptyList(); 26 | } 27 | 28 | public void setPokemon(List pokemon) { 29 | mPokemon = pokemon; 30 | } 31 | 32 | public void setClickListener(ClickListener clickListener) { 33 | mClickListener = clickListener; 34 | } 35 | 36 | @Override 37 | public PokemonViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 38 | View view = LayoutInflater 39 | .from(parent.getContext()) 40 | .inflate(R.layout.item_pokemon, parent, false); 41 | return new PokemonViewHolder(view); 42 | } 43 | 44 | @Override 45 | public void onBindViewHolder(PokemonViewHolder holder, int position) { 46 | String pokemon = mPokemon.get(position); 47 | holder.mPokemon = pokemon; 48 | holder.nameText.setText(String.format("%s%s" 49 | , pokemon.substring(0, 1).toUpperCase(), pokemon.substring(1))); 50 | } 51 | 52 | @Override 53 | public int getItemCount() { 54 | return mPokemon.size(); 55 | } 56 | 57 | public interface ClickListener { 58 | void onPokemonClick(String pokemon); 59 | } 60 | 61 | class PokemonViewHolder extends RecyclerView.ViewHolder { 62 | 63 | public String mPokemon; 64 | @BindView(R.id.text_name) 65 | TextView nameText; 66 | 67 | public PokemonViewHolder(View itemView) { 68 | super(itemView); 69 | ButterKnife.bind(this, itemView); 70 | itemView.setOnClickListener(new View.OnClickListener() { 71 | @Override 72 | public void onClick(View v) { 73 | if (mClickListener != null) mClickListener.onPokemonClick(mPokemon); 74 | } 75 | }); 76 | } 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/util/DialogFactory.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.util; 2 | 3 | import android.app.Dialog; 4 | import android.app.ProgressDialog; 5 | import android.content.Context; 6 | import android.support.annotation.StringRes; 7 | import android.support.v7.app.AlertDialog; 8 | 9 | import ${packageName}.R; 10 | 11 | 12 | public final class DialogFactory { 13 | 14 | public static Dialog createSimpleOkErrorDialog(Context context, String title, String message) { 15 | AlertDialog.Builder alertDialog = new AlertDialog.Builder(context) 16 | .setTitle(title) 17 | .setMessage(message) 18 | .setNeutralButton(R.string.dialog_action_ok, null); 19 | return alertDialog.create(); 20 | } 21 | 22 | public static Dialog createSimpleOkErrorDialog(Context context, 23 | @StringRes int titleResource, 24 | @StringRes int messageResource) { 25 | 26 | return createSimpleOkErrorDialog(context, 27 | context.getString(titleResource), 28 | context.getString(messageResource)); 29 | } 30 | 31 | public static Dialog createGenericErrorDialog(Context context, String message) { 32 | AlertDialog.Builder alertDialog = new AlertDialog.Builder(context) 33 | .setTitle(context.getString(R.string.dialog_error_title)) 34 | .setMessage(message) 35 | .setNeutralButton(R.string.dialog_action_ok, null); 36 | return alertDialog.create(); 37 | } 38 | 39 | public static Dialog createGenericErrorDialog(Context context, @StringRes int messageResource) { 40 | return createGenericErrorDialog(context, context.getString(messageResource)); 41 | } 42 | 43 | public static ProgressDialog createProgressDialog(Context context, String message) { 44 | ProgressDialog progressDialog = new ProgressDialog(context); 45 | progressDialog.setMessage(message); 46 | return progressDialog; 47 | } 48 | 49 | public static ProgressDialog createProgressDialog(Context context, 50 | @StringRes int messageResource) { 51 | return createProgressDialog(context, context.getString(messageResource)); 52 | } 53 | 54 | } 55 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/util/NetworkUtil.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.util; 2 | 3 | import android.content.Context; 4 | import android.net.ConnectivityManager; 5 | import android.net.NetworkInfo; 6 | 7 | import retrofit2.adapter.rxjava.HttpException; 8 | 9 | public class NetworkUtil { 10 | 11 | /** 12 | * Returns true if the Throwable is an instance of RetrofitError with an 13 | * http status code equals to the given one. 14 | */ 15 | public static boolean isHttpStatusCode(Throwable throwable, int statusCode) { 16 | return throwable instanceof HttpException 17 | && ((HttpException) throwable).code() == statusCode; 18 | } 19 | 20 | public static boolean isNetworkConnected(Context context) { 21 | ConnectivityManager cm = 22 | (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 23 | NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); 24 | return activeNetwork != null && activeNetwork.isConnectedOrConnecting(); 25 | } 26 | 27 | } -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/java/in/mvpstarter/sample/util/ViewUtil.java.ftl: -------------------------------------------------------------------------------- 1 | package ${packageName}.util; 2 | 3 | import android.app.Activity; 4 | import android.content.Context; 5 | import android.content.res.Resources; 6 | import android.view.inputmethod.InputMethodManager; 7 | 8 | public final class ViewUtil { 9 | 10 | public static float pxToDp(float px) { 11 | float densityDpi = Resources.getSystem().getDisplayMetrics().densityDpi; 12 | return px / (densityDpi / 160f); 13 | } 14 | 15 | public static int dpToPx(int dp) { 16 | float density = Resources.getSystem().getDisplayMetrics().density; 17 | return Math.round(dp * density); 18 | } 19 | 20 | public static void hideKeyboard(Activity activity) { 21 | InputMethodManager imm = 22 | (InputMethodManager) activity.getSystemService(Context.INPUT_METHOD_SERVICE); 23 | imm.hideSoftInputFromWindow(activity.getWindow().getDecorView().getWindowToken(), 0); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/res/layout/activity_detail.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 11 | 12 | 18 | 19 | 20 | 21 | 26 | 27 | 34 | 35 | 44 | 45 | 46 | 47 | <${packageName}.features.common.ErrorView 48 | android:id="@+id/view_error" 49 | android:layout_width="wrap_content" 50 | android:layout_height="match_parent" 51 | android:visibility="gone" /> 52 | 53 | 60 | 61 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/res/layout/activity_main.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 10 | 11 | 17 | 18 | 19 | 20 | 25 | 26 | 30 | 31 | 32 | 33 | <${packageName}.features.common.ErrorView 34 | android:id="@+id/view_error" 35 | android:layout_width="wrap_content" 36 | android:layout_height="match_parent" 37 | android:visibility="gone" /> 38 | 39 | 45 | 46 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/res/layout/activity_maps_sample.xml.ftl: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/res/layout/item_pokemon.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 9 | -------------------------------------------------------------------------------- /MVPStarter/root/app/src/main/res/layout/view_error.xml.ftl: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 12 | 13 | 22 | 23 |