├── .gitignore ├── .idea ├── compiler.xml ├── copyright │ ├── Apache_Version_Two.xml │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── runConfigurations.xml └── vcs.xml ├── License.md ├── README.md ├── Screenshot_20170708-095736_framed.png ├── Screenshot_20170713-081532_framed.png ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── forgettery │ │ └── wiseass │ │ └── com │ │ └── recyclerviewprebuilt │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── forgettery │ │ │ └── wiseass │ │ │ └── com │ │ │ └── recyclerviewprebuilt │ │ │ ├── data │ │ │ ├── DataSourceInterface.java │ │ │ ├── FakeDataSource.java │ │ │ └── ListItem.java │ │ │ ├── logic │ │ │ └── Controller.java │ │ │ └── view │ │ │ ├── DetailActivity.java │ │ │ ├── ListActivity.java │ │ │ └── ViewInterface.java │ └── res │ │ ├── drawable-hdpi │ │ ├── ic_add_black_24dp.png │ │ ├── ic_event_black_24dp.png │ │ ├── ic_event_white_24dp.png │ │ └── ic_view_list_white_24dp.png │ │ ├── drawable-mdpi │ │ ├── ic_add_black_24dp.png │ │ ├── ic_event_black_24dp.png │ │ ├── ic_event_white_24dp.png │ │ └── ic_view_list_white_24dp.png │ │ ├── drawable-xhdpi │ │ ├── ic_add_black_24dp.png │ │ ├── ic_event_black_24dp.png │ │ ├── ic_event_white_24dp.png │ │ └── ic_view_list_white_24dp.png │ │ ├── drawable-xxhdpi │ │ ├── ic_event_black_24dp.png │ │ └── ic_event_white_24dp.png │ │ ├── drawable-xxxhdpi │ │ ├── ic_add_black_24dp.png │ │ ├── ic_event_black_24dp.png │ │ ├── ic_event_white_24dp.png │ │ └── ic_view_list_white_24dp.png │ │ ├── drawable │ │ ├── blue_drawable.xml │ │ ├── divider_white.xml │ │ ├── green_drawable.xml │ │ ├── red_drawable.xml │ │ └── yellow_drawable.xml │ │ ├── layout │ │ ├── activity_detail.xml │ │ ├── activity_list.xml │ │ └── item_data.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── values-v21 │ │ └── styles.xml │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── forgettery │ └── wiseass │ └── com │ └── recyclerviewprebuilt │ └── ControllerUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/Apache_Version_Two.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 17 | 18 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 19 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 46 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## RecyclerViewTutorial2017 2 | 2017 RecyclerView Tutorial by Ryan Kay (wiseAss). 3 | 4 | **Part 1: Project OverView, is available to watch here:** 5 | 6 | https://www.youtube.com/watch?v=RfTJ2SePYaU&feature=youtu.be 7 | 8 | 9 | 10 | **Part 2: Basic RecyclerView** 11 | 12 | https://www.youtube.com/watch?v=mw1wkYEJ1vI 13 | 14 | Preview: 15 | Part Two Preview 16 | 17 | **Part 3: Advanced RecyclerView Design and Features** 18 | 19 | https://www.youtube.com/watch?v=-xuhicDXMvw 20 | 21 | **Preview:** 22 | Part Three Preview 23 | 24 | Welcome to my Android RecyclerView Tutorial. About a year ago, I got so pissed off at how overly complicated it was to get a decent RecyclerView running (which is also my fault for not being a better learner), that I decided to make series on Youtube about it. Much to my surprise, my long winded explanations and random tangents was something that a few people actually enjoyed. 25 | 26 | While I didn't make any MAJOR mistakes with the old tutorial, I was still pretty much a Junior Developer on the Android Platform at that point. Since then, I've learned a great deal about making Android Apps, and even more about teaching people how to make them; so here you have it. 27 | 28 | Please bare in mind that while I'm getting ok at this Android thing, there's always room to learn. If there's anything in my Code which strikes you as being wrong or sub-optimal, please open a GitHub Issue and let me know about it! Bonus points if you take the time to actually explain why I'm wrong; I don't have a lot of spare time and I appreciate good feedback when I can get it. 29 | 30 | ## For Beginners 31 | The Videos are made for you. I'll do my best to give you the following: 32 | - A fundamental understanding of how RecyclerViews and RecyclerView.Adapters work 33 | - A working App which implements the above 34 | - Good Software Architecture Principles whenever I feel like I can work them in to what I'm talking about 35 | - A brief explanation of Unit Tests (let's not worry about what they ARE, I'll actually show you what they can DO for us) 36 | - General Best Practices for Android App building (well, as far as I'm aware) 37 | - Some of the Layouts use ConstraintLayout 38 | - A West Coast Canadian accent. 39 | 40 | ## For Not Beginners 41 | Read my code. I try to give everything good descriptive names, and I built my Software to basics standards of Software Architecture and SOLID Principles whenever possible. 42 | 43 | ## For All 44 | If nothing else, I hope this project demonstrates that while it's great to learn stuff from Videos and Textbooks, looking at/creating well documented Code with good naming Conventions, is something you should work towards. Whether you are looking at your own old Code, or working with other people, do us all a favour and don't just bang out a bunch of ugly crap with bad names and a Package Structure that doesn't make any sense. The names and Package Structure should help people understand your code, not piss them off. 45 | 46 | ## About Me 47 | I'm an Indie Android Dev living in Victoria BC Canada. I'm a major Nerd, and rambling about Software is quite therapeutic for me, to the extent that I have a [Youtube Channel](https://www.youtube.com/user/gosuddr93) mostly dedicated to that. I also do a Weekly Live Android Developer Q&A Series most Sundays at 9:00AM PDT (GMT -7). You can watch that at the aforementioned time [here](https://www.youtube.com/c/wiseAss/live). 48 | 49 | **Please Read** 50 | I don't release this stuff for free because I don't like money. Actually, I could use all the help I can get to improve my Equipment, Production Quality, Content, and pay my living expenses. If my content is worth even the average fancy Hipster Coffee to you, then please Consider sending me a Fiver once on Patreon. 51 | 52 | ## Social Media 53 | 54 | Facebook: https://www.facebook.com/wiseassblog/ 55 | 56 | G+: https://plus.google.com/+wiseass 57 | 58 | Twitter: https://twitter.com/wiseass301 59 | 60 | Patreon: https://www.patreon.com/bePatron?u=5114325 61 | 62 | Blog: http://wiseassblog.com/ 63 | -------------------------------------------------------------------------------- /Screenshot_20170708-095736_framed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/Screenshot_20170708-095736_framed.png -------------------------------------------------------------------------------- /Screenshot_20170713-081532_framed.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/Screenshot_20170713-081532_framed.png -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | android { 4 | compileSdkVersion 25 5 | buildToolsVersion "25.0.2" 6 | defaultConfig { 7 | applicationId "forgettery.wiseass.com.recyclerviewprebuilt" 8 | minSdkVersion 16 9 | targetSdkVersion 25 10 | versionCode 1 11 | versionName "1.0" 12 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 13 | } 14 | buildTypes { 15 | release { 16 | minifyEnabled false 17 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 18 | } 19 | } 20 | } 21 | 22 | dependencies { 23 | compile fileTree(dir: 'libs', include: ['*.jar']) 24 | androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { 25 | exclude group: 'com.android.support', module: 'support-annotations' 26 | }) 27 | 28 | compile 'com.android.support:recyclerview-v7:25.3.1' 29 | 30 | compile 'com.android.support:appcompat-v7:25.3.1' 31 | compile 'com.android.support.constraint:constraint-layout:1.0.2' 32 | compile 'com.android.support:design:25.3.1' 33 | 34 | /* 35 | https://github.com/hdodenhof/CircleImageView 36 | */ 37 | compile 'de.hdodenhof:circleimageview:2.1.0' 38 | 39 | testCompile 'org.mockito:mockito-core:2.8.9' 40 | testCompile 'junit:junit:4.12' 41 | } 42 | -------------------------------------------------------------------------------- /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 C:\Users\R_KAY\AppData\Local\Android\Sdk/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 | 19 | # Uncomment this to preserve the line number information for 20 | # debugging stack traces. 21 | #-keepattributes SourceFile,LineNumberTable 22 | 23 | # If you keep the line number information, uncomment this to 24 | # hide the original source file name. 25 | #-renamesourcefileattribute SourceFile 26 | -------------------------------------------------------------------------------- /app/src/androidTest/java/forgettery/wiseass/com/recyclerviewprebuilt/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * * 3 | * * Copyright (C) 2017 Ryan Kay Open Source Project 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package forgettery.wiseass.com.recyclerviewprebuilt; 20 | 21 | import android.content.Context; 22 | import android.support.test.InstrumentationRegistry; 23 | import android.support.test.runner.AndroidJUnit4; 24 | 25 | import org.junit.Test; 26 | import org.junit.runner.RunWith; 27 | 28 | import static org.junit.Assert.*; 29 | 30 | /** 31 | * Instrumentation test, which will execute on an Android device. 32 | * 33 | * @see Testing documentation 34 | */ 35 | @RunWith(AndroidJUnit4.class) 36 | public class ExampleInstrumentedTest { 37 | @Test 38 | public void useAppContext() throws Exception { 39 | // Context of the app under test. 40 | Context appContext = InstrumentationRegistry.getTargetContext(); 41 | 42 | assertEquals("forgettery.wiseass.com.recyclerviewprebuilt", appContext.getPackageName()); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | 23 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/java/forgettery/wiseass/com/recyclerviewprebuilt/data/DataSourceInterface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * * 3 | * * Copyright (C) 2017 Ryan Kay Open Source Project 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package forgettery.wiseass.com.recyclerviewprebuilt.data; 20 | 21 | import java.util.List; 22 | 23 | /** 24 | * This interface is the Contract which dictates how our Controller can talk to our Data Layer and 25 | * Vice Versa. 26 | * 27 | * @see Android Java Interfaces by Example 28 | * Created by R_KAY on 6/3/2017. 29 | */ 30 | 31 | public interface DataSourceInterface { 32 | 33 | List getListOfData(); 34 | 35 | ListItem createNewListItem(); 36 | 37 | void deleteListItem(ListItem listItem); 38 | 39 | void insertListItem(ListItem temporaryListItem); 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/forgettery/wiseass/com/recyclerviewprebuilt/data/FakeDataSource.java: -------------------------------------------------------------------------------- 1 | /* 2 | * * 3 | * * Copyright (C) 2017 Ryan Kay Open Source Project 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package forgettery.wiseass.com.recyclerviewprebuilt.data; 20 | 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import java.util.Random; 24 | 25 | import forgettery.wiseass.com.recyclerviewprebuilt.R; 26 | 27 | /** 28 | * "Fake objects actually have working implementations, but usually take some shortcut which makes 29 | * them not suitable for production (an in memory database is a good example)." 30 | *

31 | * -Martin Fowler, 32 | * https://www.martinfowler.com/articles/mocksArentStubs.html 33 | *

34 | * Since this is a tutorial about building a RecyclerView, our Activity will be talking to a "Fake" 35 | * DataSource. As long as our "Fake" Data looks the same as the Data we'd expect from a "Real" 36 | * Datasource, we can use these Fakes to Test our App! 37 | *

38 | *

39 | * Created by R_KAY on 6/1/2017. 40 | */ 41 | 42 | public class FakeDataSource implements DataSourceInterface{ 43 | 44 | private static final int sizeOfCollection = 12; 45 | private Random random; 46 | 47 | private final String[] datesAndTimes = { 48 | "6:30AM 06/01/2017", 49 | "9:26PM 04/22/2013", 50 | "2:01PM 12/02/2015", 51 | "2:43AM 09/7/2018", 52 | }; 53 | 54 | private final String[] messages = { 55 | "Check out content like Fragmented Podcast to expose yourself to the knowledge, ideas, " + 56 | "and opinions of experts in your field", 57 | "Look at Open Source Projects like Android Architecture Blueprints to see how experts" + 58 | " design and build Apps", 59 | "Write lots of Code and Example Apps. Writing good Quality Code in an efficient manner " 60 | + "is a Skill to be practiced like any other.", 61 | "If at first something doesn't make any sense, find another explanation. We all " + 62 | "learn/teach different from each other. Find an explanation that speaks to you." 63 | }; 64 | 65 | private final int[] drawables = { 66 | R.drawable.green_drawable, 67 | R.drawable.red_drawable, 68 | R.drawable.blue_drawable, 69 | R.drawable.yellow_drawable 70 | }; 71 | 72 | 73 | public FakeDataSource() { 74 | random = new Random(); 75 | } 76 | 77 | /** 78 | * Creates a list of ListItems. 79 | * 80 | * @return A list of 12 semi-random ListItems for testing purposes 81 | */ 82 | @Override 83 | public List getListOfData() { 84 | ArrayList listOfData = new ArrayList<>(); 85 | Random random = new Random(); 86 | //make 12 semi-random items 87 | for (int i = 0; i < 12; i++) { 88 | 89 | listOfData.add( 90 | createNewListItem() 91 | ); 92 | } 93 | 94 | return listOfData; 95 | } 96 | 97 | @Override 98 | public ListItem createNewListItem() { 99 | 100 | //these will be 0, 1, 2, or 3 101 | int randOne = random.nextInt(4); 102 | int randTwo = random.nextInt(4); 103 | int randThree = random.nextInt(4); 104 | 105 | //creates a semi-random ListItem 106 | ListItem listItem = new ListItem( 107 | datesAndTimes[randOne], 108 | messages[randTwo], 109 | drawables[randThree] 110 | ); 111 | 112 | return listItem; 113 | } 114 | 115 | @Override 116 | public void deleteListItem(ListItem listItem) { 117 | 118 | } 119 | 120 | @Override 121 | public void insertListItem(ListItem temporaryListItem) { 122 | 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/forgettery/wiseass/com/recyclerviewprebuilt/data/ListItem.java: -------------------------------------------------------------------------------- 1 | /* 2 | * * 3 | * * Copyright (C) 2017 Ryan Kay Open Source Project 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package forgettery.wiseass.com.recyclerviewprebuilt.data; 20 | 21 | /** 22 | * 3. 23 | * This class describes an "Item" of "Data". In a real App, I'd call them things like: 24 | * - Workout 25 | * - Reminder 26 | * - Task 27 | * - Note 28 | * - User 29 | *

30 | * Created by R_KAY on 5/31/2017. 31 | */ 32 | 33 | public class ListItem { 34 | 35 | private String dateAndTime; 36 | private String message; 37 | private int colorResource; 38 | 39 | /*It's common for an "Item" to have a unique Id for storing an a Database 40 | private String uniqueIdentifier;*/ 41 | 42 | public ListItem(String dateAndTime, String message, int colorResource) { 43 | this.dateAndTime = dateAndTime; 44 | this.message = message; 45 | this.colorResource = colorResource; 46 | } 47 | 48 | public int getColorResource() { 49 | return colorResource; 50 | } 51 | 52 | public void setColorResource(int colorResource) { 53 | this.colorResource = colorResource; 54 | } 55 | 56 | public String getDateAndTime() { 57 | return dateAndTime; 58 | } 59 | 60 | public void setDateAndTime(String dateAndTime) { 61 | this.dateAndTime = dateAndTime; 62 | } 63 | 64 | 65 | public String getMessage() { 66 | return message; 67 | } 68 | 69 | public void setMessage(String message) { 70 | this.message = message; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /app/src/main/java/forgettery/wiseass/com/recyclerviewprebuilt/logic/Controller.java: -------------------------------------------------------------------------------- 1 | /* 2 | * * 3 | * * Copyright (C) 2017 Ryan Kay Open Source Project 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package forgettery.wiseass.com.recyclerviewprebuilt.logic; 20 | 21 | import android.view.View; 22 | 23 | import forgettery.wiseass.com.recyclerviewprebuilt.data.DataSourceInterface; 24 | import forgettery.wiseass.com.recyclerviewprebuilt.data.ListItem; 25 | import forgettery.wiseass.com.recyclerviewprebuilt.view.ViewInterface; 26 | 27 | /** 28 | * 16. 29 | * It's an unfortunate fact that in Programming, people like to use many names for the same thing, 30 | * and one name for many things at the same time. I would normally call a class like this a 31 | * "Presenter", since I currently use Model-View-Presenter (Passive View) style Architecture in my 32 | * own Apps. However, since this isn't an MVP App but a RecyclerView tutorial which is likely to be 33 | * watched by a fair number of Beginners, let me explain this a little further: 34 | *

35 | * Whether it's MVP, MVC, MVVM, etc., a rough (and INCOMPLETE!!!) idea of how these three layer 36 | * Architectures seperate their concerns (what they do) is: 37 | *

38 | * 1. Drawing things on the Screen and Listening for Click Events Layer. In Android, this will 39 | * almost always be a Fragment or Activity (although there are other options to create Views with 40 | * 3rd Party Libs). Most Architectures will call this the "View". In most cases, it doesn't 41 | * make it's own decisions, but simply passes "Events" to Layer 2, and let's Layer 2 do the 42 | * thinking. 43 | *

44 | * 2. Decision Making and Communication Layer. This Layer will generally have the responsibility of 45 | * coordinating "Events" from other Layers, such as a "Click" on the UI, or an Error Message from 46 | * the Database. It receives these events, and then using Logic, decides how the App will respond to 47 | * such events. It turns out that if you write your App well, there will be a relatively finite 48 | * number of "Events" and "Decisions" we can choose based on those events. Controller and Presenter 49 | * are common names for Classes in this Layer, but at this point there seems very little consistency 50 | * for naming this Layer. 51 | * 52 | *

53 | * 3. Data Layer. This Layer manages access to a Database/Repository of some kind. It could be Local 54 | * (stored on the device) or it could be remote (cloud storage). Generally it will just manage 55 | * whatever Database it is using, and provide Layer 2 with Methods it can use to Create, Read, 56 | * Update, Delete (CRUD) Data, and error Messages from the Database. 57 | *

58 | * Created by R_KAY on 6/3/2017. 59 | */ 60 | 61 | public class Controller { 62 | 63 | private ListItem temporaryListItem; 64 | private int temporaryListItemPosition; 65 | 66 | /* 67 | All that's going on with these Variables, is that we're talking to both ListActivity and 68 | FakeDataSource through Interfaces. This has many benefits, but I'd invite you to research 69 | "Code to an Interface" for a fairly clear example. 70 | */ 71 | private ViewInterface view; 72 | 73 | 74 | private DataSourceInterface dataSource; 75 | 76 | /** 77 | * As soon as this object is created, it does a few things: 78 | * 1. Assigns Interfaces Variables so that it can talk to the DataSource and that Activity 79 | * 2. Tells the dataSource to fetch a List of ListItems. 80 | * 3. Tells the View to draw the fetched List of Data. 81 | * @param view 82 | * @param dataSource 83 | */ 84 | public Controller(ViewInterface view, DataSourceInterface dataSource) { 85 | this.view = view; 86 | this.dataSource = dataSource; 87 | 88 | getListFromDataSource(); 89 | } 90 | 91 | public void onListItemClick(ListItem selectedItem, View viewRoot){ 92 | view.startDetailActivity( 93 | selectedItem.getDateAndTime(), 94 | selectedItem.getMessage(), 95 | selectedItem.getColorResource(), 96 | viewRoot 97 | ); 98 | } 99 | 100 | /** 101 | * In a real App, I would normally talk to this DataSource using RxJava 2. This is because most 102 | * calls to Services like a Database/Server should be executed on a seperate thread that the 103 | * mainThread (UI Thread). See my full projects for examples of this. 104 | */ 105 | public void getListFromDataSource(){ 106 | view.setUpAdapterAndView( 107 | dataSource.getListOfData() 108 | ); 109 | } 110 | 111 | 112 | public void createNewListItem() { 113 | /* 114 | To simulate telling the DataSource to create a new record and waiting for it's response, 115 | we'll simply have it return a new ListItem. 116 | 117 | In a real App, I'd use RxJava 2 (or some other 118 | API/Framework for Asynchronous Communication) to have the Datasource do this on the 119 | IO thread, and respond via an Asynchronous callback to the Main thread. 120 | */ 121 | 122 | ListItem newItem = dataSource.createNewListItem(); 123 | 124 | view.addNewListItemToView(newItem); 125 | } 126 | 127 | public void onListItemSwiped(int position, ListItem listItem) { 128 | //ensure that the view and data layers have consistent state 129 | dataSource.deleteListItem(listItem); 130 | view.deleteListItemAt(position); 131 | 132 | temporaryListItemPosition = position; 133 | temporaryListItem = listItem; 134 | 135 | view.showUndoSnackbar(); 136 | 137 | } 138 | 139 | public void onUndoConfirmed() { 140 | if (temporaryListItem != null){ 141 | //ensure View/Data consistency 142 | dataSource.insertListItem(temporaryListItem); 143 | view.insertListItemAt(temporaryListItemPosition, temporaryListItem); 144 | 145 | temporaryListItem = null; 146 | temporaryListItemPosition = 0; 147 | 148 | } else { 149 | 150 | } 151 | 152 | } 153 | 154 | public void onSnackbarTimeout() { 155 | temporaryListItem = null; 156 | temporaryListItemPosition = 0; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /app/src/main/java/forgettery/wiseass/com/recyclerviewprebuilt/view/DetailActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * * 3 | * * Copyright (C) 2017 Ryan Kay Open Source Project 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package forgettery.wiseass.com.recyclerviewprebuilt.view; 20 | 21 | import android.content.Intent; 22 | import android.os.Bundle; 23 | import android.support.v4.content.ContextCompat; 24 | import android.support.v7.app.AppCompatActivity; 25 | import android.view.View; 26 | import android.widget.TextView; 27 | 28 | import forgettery.wiseass.com.recyclerviewprebuilt.R; 29 | 30 | /** 31 | * 18. 32 | * 33 | * This Activity simply displays Data from a ListItem in a "Detailed" View. 34 | * Although I'd still give it it's own Presenter/Controller/Whatever in a real App, I won't bother 35 | * doing that here since the Activity is so simplistic anyway. 36 | * Created by R_KAY on 6/3/2017. 37 | */ 38 | 39 | public class DetailActivity extends AppCompatActivity { 40 | 41 | private static final String EXTRA_DATE_AND_TIME = "EXTRA_DATE_AND_TIME"; 42 | private static final String EXTRA_MESSAGE = "EXTRA_MESSAGE"; 43 | private static final String EXTRA_DRAWABLE = "EXTRA_DRAWABLE"; 44 | 45 | private TextView dateAndTime; 46 | private TextView message; 47 | private View coloredBackground; 48 | 49 | @Override 50 | protected void onCreate(Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | setContentView(R.layout.activity_detail); 53 | 54 | /*I wouldn't normally pass all this Data via Intent, so understand that this is just a quick 55 | implementation to get things working for the Demo. I'd normally pass just a Unique id as an 56 | extra, and then retrieve the appropriate Data from a Service.*/ 57 | Intent i = getIntent(); 58 | String dateAndTimeExtra = i.getStringExtra(EXTRA_DATE_AND_TIME); 59 | String messageExtra = i.getStringExtra(EXTRA_MESSAGE); 60 | int drawableResourceExtra = i.getIntExtra(EXTRA_DRAWABLE, 0); 61 | 62 | dateAndTime = (TextView) findViewById(R.id.lbl_date_and_time_header); 63 | dateAndTime.setText(dateAndTimeExtra); 64 | 65 | message = (TextView) findViewById(R.id.lbl_message_body); 66 | message.setText(messageExtra); 67 | 68 | coloredBackground = findViewById(R.id.imv_colored_background); 69 | coloredBackground.setBackgroundResource( 70 | drawableResourceExtra 71 | ); 72 | 73 | 74 | 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/main/java/forgettery/wiseass/com/recyclerviewprebuilt/view/ListActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * * 3 | * * Copyright (C) 2017 Ryan Kay Open Source Project 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package forgettery.wiseass.com.recyclerviewprebuilt.view; 20 | 21 | import android.app.ActivityOptions; 22 | import android.content.Intent; 23 | import android.os.Build; 24 | import android.os.Bundle; 25 | import android.support.design.widget.BaseTransientBottomBar; 26 | import android.support.design.widget.FloatingActionButton; 27 | import android.support.design.widget.Snackbar; 28 | import android.support.v4.content.ContextCompat; 29 | import android.support.v7.app.AppCompatActivity; 30 | import android.support.v7.widget.DividerItemDecoration; 31 | import android.support.v7.widget.LinearLayoutManager; 32 | import android.support.v7.widget.RecyclerView; 33 | import android.support.v7.widget.Toolbar; 34 | import android.support.v7.widget.helper.ItemTouchHelper; 35 | import android.transition.Fade; 36 | import android.util.Pair; 37 | import android.view.LayoutInflater; 38 | import android.view.View; 39 | import android.view.ViewGroup; 40 | import android.widget.ProgressBar; 41 | import android.widget.TextView; 42 | 43 | import java.util.List; 44 | 45 | import de.hdodenhof.circleimageview.CircleImageView; 46 | import forgettery.wiseass.com.recyclerviewprebuilt.R; 47 | import forgettery.wiseass.com.recyclerviewprebuilt.data.FakeDataSource; 48 | import forgettery.wiseass.com.recyclerviewprebuilt.data.ListItem; 49 | import forgettery.wiseass.com.recyclerviewprebuilt.logic.Controller; 50 | 51 | /** 52 | * 1. 53 | * List Activity is responsible for 54 | * - Coordinating the User Interface 55 | * - Relaying Click events to the Controller 56 | * - Starting a Detail Activity 57 | * - 58 | */ 59 | public class ListActivity extends AppCompatActivity implements ViewInterface, View.OnClickListener { 60 | 61 | private static final String EXTRA_DATE_AND_TIME = "EXTRA_DATE_AND_TIME"; 62 | private static final String EXTRA_MESSAGE = "EXTRA_MESSAGE"; 63 | private static final String EXTRA_DRAWABLE = "EXTRA_DRAWABLE"; 64 | 65 | /** 66 | * 2. 67 | * Obviously you wouldn't use such an ambiguous name in a non-demo App. 68 | */ 69 | private List listOfData; 70 | 71 | //12. In order to create each ViewHolder in the UI, we need a LayoutInflater. 72 | private LayoutInflater layoutInflater; 73 | private RecyclerView recyclerView; 74 | private CustomAdapter adapter; 75 | private Toolbar toolbar; 76 | 77 | private Controller controller; 78 | 79 | @Override 80 | protected void onCreate(Bundle savedInstanceState) { 81 | super.onCreate(savedInstanceState); 82 | setContentView(R.layout.activity_list); 83 | 84 | recyclerView = (RecyclerView) findViewById(R.id.rec_list_activity); 85 | layoutInflater = getLayoutInflater(); 86 | toolbar = (Toolbar) findViewById(R.id.tlb_list_activity); 87 | 88 | toolbar.setTitle(R.string.title_toolbar); 89 | toolbar.setLogo(R.drawable.ic_view_list_white_24dp); 90 | toolbar.setTitleMarginStart(72); 91 | 92 | FloatingActionButton fabulous = (FloatingActionButton) findViewById(R.id.fab_create_new_item); 93 | 94 | fabulous.setOnClickListener(this); 95 | 96 | controller = new Controller(this, new FakeDataSource()); 97 | } 98 | 99 | /** 100 | * 17. 101 | * So, I'd normally just pass an Item's Unique ID (Key) to the other Activity, and then fetch 102 | * the Item from the Database their. However, this is a RecyclerView Demo App and I'm going to 103 | * simplify things like this. Also, by decomposing ListItem, it saves me having to make ListItem 104 | * Parcelable and bla bla bla whatever. 105 | * 106 | * @param dateAndTime 107 | * @param message 108 | * @param colorResource 109 | */ 110 | @Override 111 | public void startDetailActivity(String dateAndTime, String message, int colorResource, View viewRoot) { 112 | Intent i = new Intent(this, DetailActivity.class); 113 | i.putExtra(EXTRA_DATE_AND_TIME, dateAndTime); 114 | i.putExtra(EXTRA_MESSAGE, message); 115 | i.putExtra(EXTRA_DRAWABLE, colorResource); 116 | 117 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { 118 | getWindow().setEnterTransition(new Fade(Fade.IN)); 119 | getWindow().setEnterTransition(new Fade(Fade.OUT)); 120 | 121 | ActivityOptions options = ActivityOptions 122 | .makeSceneTransitionAnimation(this, 123 | new Pair(viewRoot.findViewById(R.id.imv_list_item_circle), 124 | getString(R.string.transition_drawable)), 125 | new Pair(viewRoot.findViewById(R.id.lbl_message), 126 | getString(R.string.transition_message)), 127 | new Pair(viewRoot.findViewById(R.id.lbl_date_and_time), 128 | getString(R.string.transition_time_and_date))); 129 | 130 | startActivity(i, options.toBundle()); 131 | 132 | 133 | } else { 134 | startActivity(i); 135 | } 136 | } 137 | 138 | 139 | /** 140 | * In order to make sure things execute in the proper order, we have our Controller tell the 141 | * View when to set up it's stuff. 142 | * 143 | * @param listOfData 144 | */ 145 | @Override 146 | public void setUpAdapterAndView(List listOfData) { 147 | this.listOfData = listOfData; 148 | LinearLayoutManager layoutManager = new LinearLayoutManager(this); 149 | 150 | recyclerView.setLayoutManager(layoutManager); 151 | 152 | adapter = new CustomAdapter(); 153 | recyclerView.setAdapter(adapter); 154 | 155 | DividerItemDecoration itemDecoration = new DividerItemDecoration( 156 | recyclerView.getContext(), 157 | layoutManager.getOrientation() 158 | ); 159 | 160 | itemDecoration.setDrawable( 161 | ContextCompat.getDrawable( 162 | ListActivity.this, 163 | R.drawable.divider_white 164 | ) 165 | ); 166 | 167 | recyclerView.addItemDecoration( 168 | itemDecoration 169 | ); 170 | 171 | ItemTouchHelper itemTouchHelper = new ItemTouchHelper(createHelperCallback()); 172 | itemTouchHelper.attachToRecyclerView(recyclerView); 173 | 174 | 175 | } 176 | 177 | @Override 178 | public void addNewListItemToView(ListItem newItem) { 179 | listOfData.add(newItem); 180 | 181 | int endOfList = listOfData.size() - 1; 182 | 183 | adapter.notifyItemInserted(endOfList); 184 | 185 | recyclerView.smoothScrollToPosition(endOfList); 186 | } 187 | 188 | @Override 189 | public void deleteListItemAt(int position) { 190 | listOfData.remove(position); 191 | 192 | adapter.notifyItemRemoved(position); 193 | } 194 | 195 | @Override 196 | public void showUndoSnackbar() { 197 | Snackbar.make( 198 | findViewById(R.id.root_list_activity), 199 | getString(R.string.action_delete_item), 200 | Snackbar.LENGTH_LONG 201 | ) 202 | .setAction(R.string.action_undo, new View.OnClickListener() { 203 | @Override 204 | public void onClick(View v) { 205 | controller.onUndoConfirmed(); 206 | } 207 | }) 208 | .addCallback(new BaseTransientBottomBar.BaseCallback() { 209 | @Override 210 | public void onDismissed(Snackbar transientBottomBar, int event) { 211 | super.onDismissed(transientBottomBar, event); 212 | 213 | controller.onSnackbarTimeout(); 214 | } 215 | }) 216 | .show(); 217 | } 218 | 219 | @Override 220 | public void insertListItemAt(int position, ListItem listItem) { 221 | listOfData.add(position, listItem); 222 | 223 | adapter.notifyItemInserted(position); 224 | } 225 | 226 | @Override 227 | public void onClick(View v) { 228 | int viewId = v.getId(); 229 | if (viewId == R.id.fab_create_new_item) { 230 | //User wishes to creat a new RecyclerView Item 231 | controller.createNewListItem(); 232 | } 233 | } 234 | 235 | /** 236 | * 4. 237 | * (Opinion) 238 | * Why is the Adapter inside the Activity (a.k.a. a Nested Class)? 239 | *

240 | * I used to pull this Class outside of the Activity and have it communicate back to the 241 | * Activity via Interface. This was actually detrimental in retrospect, as I ended up having to 242 | * manage a List of Data in the Activity, as well as a copy List of Data in the Adapter itself. 243 | *

244 | * Also, since I needed to have my ViewHolder implement an OnClickListener, and then have the 245 | * result of that talk to the Activity through another interface, it was pretty confusing to 246 | * Beginners (not to mention pointless). 247 | *

248 | * That being said, you don't *have* to nest the Adapter. Do what makes sense for the Software 249 | * Architecture in front of you. 250 | */ 251 | private class CustomAdapter extends RecyclerView.Adapter {//6 252 | 253 | /** 254 | * 13. 255 | * Inflates a new View (in this case, R.layout.item_data), and then creates/returns a new 256 | * CustomViewHolder object. 257 | * 258 | * @param parent Unfortunately the docs currently don't explain this at all :( 259 | * @param viewType Unfortunately the docs currently don't explain this at all :( 260 | * @return 261 | */ 262 | @Override 263 | public CustomAdapter.CustomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 264 | View v = layoutInflater.inflate(R.layout.item_data, parent, false); 265 | return new CustomViewHolder(v); 266 | } 267 | 268 | /** 269 | * This method "Binds" or assigns Data (from listOfData) to each View (ViewHolder). 270 | * 271 | * @param holder The current ViewHolder instance for a given position 272 | * @param position The current position of the ViewHolder we are Binding to, based upon 273 | * our (listOfData). So for the second ViewHolder we create, we'll bind data 274 | * from the second Item in listOfData. 275 | */ 276 | @Override 277 | public void onBindViewHolder(CustomAdapter.CustomViewHolder holder, int position) { 278 | //11. and now the ViewHolder data 279 | ListItem currentItem = listOfData.get(position); 280 | 281 | holder.coloredCircle.setImageResource( 282 | currentItem.getColorResource() 283 | ); 284 | 285 | holder.message.setText( 286 | currentItem.getMessage() 287 | ); 288 | 289 | holder.dateAndTime.setText( 290 | currentItem.getDateAndTime() 291 | ); 292 | 293 | holder.loading.setVisibility(View.INVISIBLE); 294 | } 295 | 296 | /** 297 | * This method let's our Adapter determine how many ViewHolders it needs to create, based on 298 | * the size of the Dataset (List) which it is working with. 299 | * 300 | * @return the size of the dataset, generally via List.size() 301 | */ 302 | @Override 303 | public int getItemCount() { 304 | // 12. Returning 0 here will tell our Adapter not to make any Items. Let's fix that. 305 | return listOfData.size(); 306 | } 307 | 308 | /** 309 | * 5. 310 | * Each ViewHolder contains Bindings to the Views we wish to populate with Data. 311 | */ 312 | class CustomViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 313 | 314 | //10. now that we've made our layouts, let's bind them 315 | private CircleImageView coloredCircle; 316 | private TextView dateAndTime; 317 | private TextView message; 318 | private ViewGroup container; 319 | private ProgressBar loading; 320 | 321 | public CustomViewHolder(View itemView) { 322 | super(itemView); 323 | this.coloredCircle = (CircleImageView) itemView.findViewById(R.id.imv_list_item_circle); 324 | this.dateAndTime = (TextView) itemView.findViewById(R.id.lbl_date_and_time); 325 | this.message = (TextView) itemView.findViewById(R.id.lbl_message); 326 | this.loading = (ProgressBar) itemView.findViewById(R.id.pro_item_data); 327 | 328 | this.container = (ViewGroup) itemView.findViewById(R.id.root_list_item); 329 | /* 330 | We can pass "this" as an Argument, because "this", which refers to the Current 331 | Instance of type CustomViewHolder currently conforms to (implements) the 332 | View.OnClickListener interface. I have a Video on my channel which goes into 333 | Interfaces with Detailed Examples. 334 | 335 | Search "Android WTF: Java Interfaces by Example" 336 | */ 337 | this.container.setOnClickListener(this); 338 | } 339 | 340 | /** 341 | * 6. 342 | * Since I'm ok with the whole Container being the Listener, View v isn't super useful 343 | * in this Use Case. However, if I had a Single RecyclerView Item with multiple 344 | * Clickable Views, I could use v.getId() to tell which specific View was clicked. 345 | * See the comment within the method. 346 | * 347 | * @param v 348 | */ 349 | @Override 350 | public void onClick(View v) { 351 | //getAdapterPosition() get's an Integer based on which the position of the current 352 | //ViewHolder (this) in the Adapter. This is how we get the correct Data. 353 | ListItem listItem = listOfData.get( 354 | this.getAdapterPosition() 355 | ); 356 | 357 | controller.onListItemClick( 358 | listItem, 359 | v 360 | ); 361 | 362 | } 363 | } 364 | } 365 | 366 | private ItemTouchHelper.Callback createHelperCallback() { 367 | /*First Param is for Up/Down motion, second is for Left/Right. 368 | Note that we can supply 0, one constant (e.g. ItemTouchHelper.LEFT), or two constants (e.g. 369 | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) to specify what directions are allowed. 370 | */ 371 | ItemTouchHelper.SimpleCallback simpleItemTouchCallback = new ItemTouchHelper.SimpleCallback( 372 | 0, 373 | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { 374 | 375 | //not used, as the first parameter above is 0 376 | @Override 377 | public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, 378 | RecyclerView.ViewHolder target) { 379 | return false; 380 | } 381 | 382 | 383 | @Override 384 | public void onSwiped(final RecyclerView.ViewHolder viewHolder, int swipeDir) { 385 | int position = viewHolder.getAdapterPosition(); 386 | controller.onListItemSwiped( 387 | position, 388 | listOfData.get(position) 389 | ); 390 | } 391 | }; 392 | 393 | return simpleItemTouchCallback; 394 | } 395 | } 396 | -------------------------------------------------------------------------------- /app/src/main/java/forgettery/wiseass/com/recyclerviewprebuilt/view/ViewInterface.java: -------------------------------------------------------------------------------- 1 | /* 2 | * * 3 | * * Copyright (C) 2017 Ryan Kay Open Source Project 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package forgettery.wiseass.com.recyclerviewprebuilt.view; 20 | 21 | import android.view.View; 22 | 23 | import java.util.List; 24 | 25 | import forgettery.wiseass.com.recyclerviewprebuilt.data.ListItem; 26 | 27 | /** 28 | * This Interface is the Contract which dictates how our View and Presenter can talk to each other. 29 | * 30 | * @see Android Java Interfaces by Example 31 | * Created by R_KAY on 6/3/2017. 32 | */ 33 | 34 | public interface ViewInterface { 35 | 36 | void startDetailActivity(String dateAndTime, String message, int colorResource, View viewRoot); 37 | 38 | void setUpAdapterAndView(List listOfData); 39 | 40 | void addNewListItemToView(ListItem newItem); 41 | 42 | void deleteListItemAt(int position); 43 | 44 | void showUndoSnackbar(); 45 | 46 | void insertListItemAt(int temporaryListItemPosition, ListItem temporaryListItem); 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-hdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_event_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-hdpi/ic_event_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_event_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-hdpi/ic_event_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_view_list_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-hdpi/ic_view_list_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-mdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_event_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-mdpi/ic_event_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_event_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-mdpi/ic_event_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_view_list_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-mdpi/ic_view_list_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-xhdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_event_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-xhdpi/ic_event_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_event_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-xhdpi/ic_event_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_view_list_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-xhdpi/ic_view_list_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_event_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-xxhdpi/ic_event_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_event_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-xxhdpi/ic_event_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_add_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-xxxhdpi/ic_add_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_event_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-xxxhdpi/ic_event_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_event_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-xxxhdpi/ic_event_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxxhdpi/ic_view_list_white_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/drawable-xxxhdpi/ic_view_list_white_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/blue_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/divider_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/green_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/red_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/yellow_drawable.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 26 | 27 | 28 | 34 | 35 | 36 | 46 | 47 | 55 | 56 | 65 | 66 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 27 | 28 | 31 | 32 | 43 | 44 | 58 | 59 | 69 | 70 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_data.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 29 | 30 | 43 | 44 | 57 | 58 | 78 | 79 | 93 | 94 | 107 | 108 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 19 | 20 | 21 | #121212 22 | #000000 23 | #80D8FF 24 | #121212 25 | 26 | 27 | #52000000 28 | #52FFFFFF 29 | 30 | #FF0000 31 | #0000FF 32 | #00FF00 33 | #FFEB3B 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | RecyclerViewPrebuilt 21 | Item Deleted 22 | UNDO? 23 | 24 | 25 | drawable 26 | message 27 | date 28 | List Activity 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 18 | 19 | 20 | 21 | 28 | 29 | 30 | 34 | 35 | 38 | 39 | 43 | 44 | 48 | 49 | 50 | 51 | 52 | 53 | -------------------------------------------------------------------------------- /app/src/test/java/forgettery/wiseass/com/recyclerviewprebuilt/ControllerUnitTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * * 3 | * * Copyright (C) 2017 Ryan Kay Open Source Project 4 | * * 5 | * * Licensed under the Apache License, Version 2.0 (the "License"); 6 | * * you may not use this file except in compliance with the License. 7 | * * You may obtain a copy of the License at 8 | * * 9 | * * http://www.apache.org/licenses/LICENSE-2.0 10 | * * 11 | * * Unless required by applicable law or agreed to in writing, software 12 | * * distributed under the License is distributed on an "AS IS" BASIS, 13 | * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | * * See the License for the specific language governing permissions and 15 | * * limitations under the License. 16 | * 17 | */ 18 | 19 | package forgettery.wiseass.com.recyclerviewprebuilt; 20 | 21 | import android.view.View; 22 | 23 | import org.junit.Before; 24 | import org.junit.Test; 25 | import org.junit.runner.RunWith; 26 | import org.mockito.Mock; 27 | import org.mockito.Mockito; 28 | import org.mockito.junit.MockitoJUnitRunner; 29 | 30 | import java.util.ArrayList; 31 | import java.util.List; 32 | 33 | import forgettery.wiseass.com.recyclerviewprebuilt.data.DataSourceInterface; 34 | import forgettery.wiseass.com.recyclerviewprebuilt.data.ListItem; 35 | import forgettery.wiseass.com.recyclerviewprebuilt.logic.Controller; 36 | import forgettery.wiseass.com.recyclerviewprebuilt.view.ViewInterface; 37 | 38 | /** 39 | * Test Driven Development Bonus: Before writing the methods themselves (a.k.a. writing the 40 | * "Implementation"), I've written some Unit Tests. In a nutshell, these Tests help me to figure out 41 | * what methods and logic I'll need for the class which I'm testing. This process is slow at first, 42 | * but once you become fast at writing Tests, you'll start to see how they actually help you write 43 | * Better Code, often at a Faster Pace. 44 | *

45 | * I have some Videos on this topic on my youtube channel. Check them out for more info. 46 | */ 47 | @RunWith(MockitoJUnitRunner.class) 48 | public class ControllerUnitTest { 49 | 50 | //We technically could've just used the FakeDataSource here, but you don't always want to use 51 | //Mocks over Fakes and vice versa. Depends on your use case. 52 | @Mock 53 | DataSourceInterface dataSource; 54 | 55 | @Mock 56 | ViewInterface view; 57 | 58 | @Mock 59 | View testViewRoot; 60 | 61 | /** 62 | * Often times we'll want data to run our tests against. I like to define such Data as a series 63 | * of Static variables in the Test file itself for convenience. 64 | */ 65 | private static final ListItem TEST_ITEM = new ListItem( 66 | "6:30AM 06/01/2017", 67 | "Look at Open Source Projects", 68 | R.drawable.red_drawable); 69 | 70 | 71 | private static final int POSITION = 3; 72 | 73 | /** 74 | * Since we're testing our Controller Class, this one is the Real implementation! 75 | * How this works is we make a Real instance of the Controller talk to Fake Instances of 76 | * what it would normally talk to (i.e. our Mocks above). Since the Controller does most of the 77 | * thinking (Logic), it follows that it would benefit the most from a good suite of Tests. 78 | */ 79 | Controller controller; 80 | 81 | /** 82 | * the @Before annotation makes this Code run before the Tests themselves. You can use this 83 | * to initialize the different components of your Test, should you need to. You can also 84 | * initialize things in the Test methods themselves. Whatever works best for your Use Case. 85 | */ 86 | @Before 87 | public void setUpTest() { 88 | controller = new Controller(view, dataSource); 89 | } 90 | 91 | @Test 92 | public void onGetListDataSuccessful() { 93 | //Since we require a List to be returned by the dataSource, we can define it here: 94 | List listOfData = new ArrayList<>(); 95 | listOfData.add(TEST_ITEM); 96 | 97 | 98 | //This is where we tell our "Mocks" what to do when our Controller talks to them. Since they 99 | //aren't real objects, we must tell them exactly what to do if we want responses from them. 100 | Mockito.when(dataSource.getListOfData()) 101 | .thenReturn(listOfData); 102 | 103 | //This is the method we are testing 104 | controller.getListFromDataSource(); 105 | 106 | //Check how the Tested Class responds to the data it receives 107 | //or test it's behaviour 108 | Mockito.verify(view).setUpAdapterAndView(listOfData); 109 | } 110 | 111 | 112 | 113 | @Test 114 | public void onListItemClicked() { 115 | controller.onListItemClick(TEST_ITEM, testViewRoot); 116 | 117 | Mockito.verify(view).startDetailActivity( 118 | TEST_ITEM.getDateAndTime(), 119 | TEST_ITEM.getMessage(), 120 | TEST_ITEM.getColorResource(), 121 | testViewRoot); 122 | } 123 | 124 | 125 | // @Test 126 | // public void onGetListDataUnsuccessful() { 127 | /************************** 128 | * 129 | * Unit Test Homework: 130 | * 131 | * Implement the "View", so that when we don't recieve a List, it shows some kind of 132 | * error message to the user. This is common practice that you should learn! 133 | * 134 | * I've written some hints you'll have to decipher into Java code: 135 | *************************/ 136 | //1 Set up your Mock dataSource 137 | 138 | //2 Call the method you wish to test on the Controller 139 | 140 | //3 Verify that the View has been told to show a message (I'd suggest showing a Toast for now) 141 | 142 | //Profit??? 143 | 144 | // } 145 | 146 | @Test 147 | public void onCreateNewListItemClick() { 148 | //1 Set up your Mock dataSource 149 | Mockito.when(dataSource.createNewListItem()) 150 | .thenReturn(TEST_ITEM); 151 | 152 | //2 Call the method you wish to test on the Controller 153 | controller.createNewListItem(); 154 | 155 | //3 Verify the behaviour of the View, based on the event 156 | Mockito.verify(view).addNewListItemToView( 157 | TEST_ITEM 158 | ); 159 | } 160 | 161 | /** 162 | * User has swiped to delete an Item from the List. Since this operation may be accidental, it 163 | * is necessary to provide a way for the User to Undo this operation. For now, we will 164 | * 1. Temporarily store ListItem and position in adapter 165 | * 2. Tell the Datasource to delete the Item (this ensures view/data layer consistency as much as 166 | * possible) 167 | * 168 | * After that, the user will either undo, or the snackbar dialog will timeout (see tests below) 169 | */ 170 | @Test 171 | public void onListItemSwiped() { 172 | 173 | controller.onListItemSwiped(POSITION, TEST_ITEM); 174 | 175 | //ensure consistency between View and Data Layers 176 | Mockito.verify(dataSource).deleteListItem(TEST_ITEM); 177 | Mockito.verify(view).deleteListItemAt(POSITION); 178 | 179 | //give user the option to undo action 180 | Mockito.verify(view).showUndoSnackbar(); 181 | 182 | } 183 | 184 | 185 | /** 186 | * When the User Undoes delete operation, we must do the following: 187 | * - Add the temp item back into the Datasource 188 | * - Animate the item back into the View 189 | */ 190 | @Test 191 | public void onUndoDeleteOperation() { 192 | Mockito.when(dataSource.createNewListItem()) 193 | .thenReturn(TEST_ITEM); 194 | 195 | //this test requires temporary position and item to be set. We can achieve this by calling 196 | //controller.onListItemSwiped() first. 197 | controller.onListItemSwiped(POSITION, TEST_ITEM); 198 | 199 | controller.onUndoConfirmed(); 200 | 201 | Mockito.verify(dataSource).insertListItem(TEST_ITEM); 202 | 203 | Mockito.verify(view).insertListItemAt( 204 | POSITION, 205 | TEST_ITEM 206 | ); 207 | } 208 | 209 | /** 210 | * Since the only interaction with Controller is from methods called on it within the Test, 211 | * there isn't really anything to verify against. However, we could use the Debugger to step 212 | * through the Test if necessary. 213 | * 214 | * ¯\_(ツ)_/¯ 215 | */ 216 | @Test 217 | public void onSnackbarTimeout() { 218 | controller.onListItemSwiped(POSITION, TEST_ITEM); 219 | 220 | controller.onSnackbarTimeout(); 221 | } 222 | 223 | 224 | } -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | jcenter() 6 | } 7 | dependencies { 8 | classpath 'com.android.tools.build:gradle:2.3.3' 9 | 10 | // NOTE: Do not place your application dependencies here; they belong 11 | // in the individual module build.gradle files 12 | } 13 | } 14 | 15 | allprojects { 16 | repositories { 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BracketCove/RecyclerViewTutorial2017/5d2234992993cf92600b2dff4494e010aec0d756/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed May 31 18:49:52 PDT 2017 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-3.3-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------