├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── pascalwelsch │ │ └── getreactive │ │ └── ApplicationTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── pascalwelsch │ │ │ └── getreactive │ │ │ ├── LaunchScreen.java │ │ │ ├── OtherCodeFromPresentation.java │ │ │ ├── RepoListActivity.java │ │ │ ├── retrofit │ │ │ ├── GitApiInterface.java │ │ │ ├── GitHubResponse.java │ │ │ ├── Repository.java │ │ │ └── User.java │ │ │ └── util │ │ │ ├── ArrayAdapter.java │ │ │ └── BindingViewHolder.java │ └── res │ │ ├── drawable-nodpi │ │ └── devoxx_preview.jpg │ │ ├── drawable │ │ └── ic_action_search.xml │ │ ├── layout │ │ ├── activity_launch.xml │ │ ├── activity_test.xml │ │ ├── item_repo.xml │ │ └── repo_list.xml │ │ ├── menu │ │ └── search.xml │ │ ├── mipmap-hdpi │ │ └── ic_launcher.png │ │ ├── mipmap-mdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxhdpi │ │ └── ic_launcher.png │ │ ├── mipmap-xxxhdpi │ │ └── ic_launcher.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── pascalwelsch │ └── getreactive │ └── ExampleUnitTest.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | /captures 8 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 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 | 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Get Reactive (on Andorid) 2 | GitHub repository search with RxJava. This is the code from talk "Get Reactive" given at DroidconUk15 and Devoxx15 3 | 4 | ### [Watch it on YouTube](https://www.youtube.com/watch?v=ssC4nX_pP3o) 5 | 6 | [![youtube preview](https://raw.githubusercontent.com/passsy/android-GetReactive/master/app/src/main/res/drawable-nodpi/devoxx_preview.jpg)](https://www.youtube.com/watch?v=ssC4nX_pP3o) 7 | https://www.youtube.com/watch?v=ssC4nX_pP3o 8 | 9 | ### [Slides](https://speakerdeck.com/passsy/get-reactive-devoxx) 10 | 11 | https://speakerdeck.com/passsy/get-reactive-devoxx 12 | 13 | ## License 14 | 15 | Copyright 2015 Pascal Welsch 16 | 17 | Licensed under the Apache License, Version 2.0 (the "License"); 18 | you may not use this file except in compliance with the License. 19 | You may obtain a copy of the License at 20 | 21 | http://www.apache.org/licenses/LICENSE-2.0 22 | 23 | Unless required by applicable law or agreed to in writing, software 24 | distributed under the License is distributed on an "AS IS" BASIS, 25 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 26 | See the License for the specific language governing permissions and 27 | limitations under the License. 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'com.android.databinding' 3 | apply plugin: 'me.tatarka.retrolambda' 4 | 5 | 6 | String java8 = getJavaVersion(8) 7 | String java7 = getJavaVersion(7) 8 | 9 | android { 10 | compileSdkVersion 23 11 | buildToolsVersion "23.0.1" 12 | 13 | defaultConfig { 14 | applicationId "com.pascalwelsch.getreactive" 15 | minSdkVersion 21 16 | targetSdkVersion 23 17 | versionCode 1 18 | versionName "1.0" 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | compileOptions { 27 | sourceCompatibility JavaVersion.VERSION_1_8 28 | targetCompatibility JavaVersion.VERSION_1_8 29 | } 30 | 31 | retrolambda { 32 | jdk java8 33 | oldJdk java7 34 | javaVersion JavaVersion.VERSION_1_7 35 | defaultMethods false 36 | incremental true 37 | } 38 | } 39 | 40 | /** 41 | * env variables don't get passed from android studio. this is a workaround from 42 | * https://github.com/evant/gradle-retrolambda/issues/61#issuecomment-66581029 43 | * @param v java version 44 | * @return path to java version 45 | */ 46 | String getJavaVersion(Integer v) { 47 | def sout = new StringBuffer() 48 | def proc = "/usr/libexec/java_home -v 1.$v".execute() 49 | proc.consumeProcessOutput(sout, new StringBuffer()) 50 | proc.waitForOrKill(1000) 51 | return sout.toString().replace("\n", "").replace("\r", "") 52 | } 53 | 54 | 55 | dependencies { 56 | compile fileTree(include: ['*.jar'], dir: 'libs') 57 | testCompile 'junit:junit:4.12' 58 | compile 'com.android.support:appcompat-v7:23.1.0' 59 | compile 'com.android.support:recyclerview-v7:23.1.0' 60 | compile 'com.squareup.retrofit:retrofit:2.0.0-beta2' 61 | compile 'com.squareup.retrofit:converter-gson:2.0.0-beta2' 62 | compile 'com.squareup.retrofit:adapter-rxjava:2.0.0-beta2' 63 | compile 'com.squareup.okhttp:okhttp:2.5.0' 64 | compile 'com.squareup.okhttp:logging-interceptor:2.6.0' 65 | compile 'io.reactivex:rxandroid:1.0.1' 66 | compile 'com.jakewharton.rxbinding:rxbinding:0.3.0' 67 | compile 'com.jakewharton.rxbinding:rxbinding-appcompat-v7:0.3.0' 68 | compile 'com.jakewharton.rxbinding:rxbinding-recyclerview-v7:0.3.0' 69 | retrolambdaConfig 'net.orfjackal.retrolambda:retrolambda:2.0.4' 70 | } 71 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/Cellar/android-sdk/23.0.2/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 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/pascalwelsch/getreactive/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.getreactive; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | 11 | public ApplicationTest() { 12 | super(Application.class); 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/java/com/pascalwelsch/getreactive/LaunchScreen.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.getreactive; 2 | 3 | import com.pascalwelsch.getreactive.databinding.ActivityLaunchBinding; 4 | 5 | import android.app.Activity; 6 | import android.content.Intent; 7 | import android.databinding.DataBindingUtil; 8 | import android.net.Uri; 9 | import android.os.Bundle; 10 | 11 | /** 12 | * Created by pascalwelsch on 11/25/15. 13 | */ 14 | public class LaunchScreen extends Activity { 15 | 16 | public static final Uri YOUTUBE_URI = Uri.parse("https://www.youtube.com/watch?v=ssC4nX_pP3o"); 17 | 18 | @Override 19 | protected void onCreate(final Bundle savedInstanceState) { 20 | super.onCreate(savedInstanceState); 21 | 22 | final ActivityLaunchBinding binding = DataBindingUtil 23 | .setContentView(this, R.layout.activity_launch); 24 | 25 | binding.androidWay.setOnClickListener(v -> startActivity( 26 | RepoListActivity.newInstance(this, RepoListActivity.METHOD_THE_ANDROID_WAY))); 27 | binding.rxBeginnery.setOnClickListener(v -> startActivity( 28 | RepoListActivity.newInstance(this, RepoListActivity.METHOD_RX_BEGINNER))); 29 | binding.rxExpert.setOnClickListener(v -> startActivity( 30 | RepoListActivity.newInstance(this, RepoListActivity.METHOD_RX_EXPERT))); 31 | 32 | binding.videoFrame.setOnClickListener( 33 | v -> startActivity(new Intent(Intent.ACTION_VIEW, YOUTUBE_URI))); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/pascalwelsch/getreactive/OtherCodeFromPresentation.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.getreactive; 2 | 3 | import com.jakewharton.rxbinding.view.RxView; 4 | 5 | import android.app.Activity; 6 | import android.os.Bundle; 7 | import android.text.TextUtils; 8 | import android.util.Log; 9 | import android.widget.Button; 10 | 11 | import java.util.concurrent.TimeUnit; 12 | 13 | import rx.Observable; 14 | import rx.schedulers.Schedulers; 15 | 16 | /** 17 | * Created by pascalwelsch on 10/25/15. 18 | */ 19 | public class OtherCodeFromPresentation extends Activity { 20 | 21 | private class RegistrationModel { 22 | 23 | private final String mFullName; 24 | 25 | private final String mPassword; 26 | 27 | private final String mUsername; 28 | 29 | public RegistrationModel(final CharSequence username, 30 | final CharSequence password, final CharSequence fullName) { 31 | mUsername = username.toString(); 32 | mPassword = password.toString(); 33 | mFullName = fullName.toString(); 34 | } 35 | 36 | public Boolean isValid() { 37 | return !TextUtils.isEmpty(mUsername) 38 | && !TextUtils.isEmpty(mPassword); 39 | } 40 | } 41 | 42 | private static final String TAG = OtherCodeFromPresentation.class.getSimpleName(); 43 | 44 | @Override 45 | protected void onCreate(final Bundle savedInstanceState) { 46 | super.onCreate(savedInstanceState); 47 | setContentView(R.layout.activity_test); 48 | 49 | final Button myButton = (Button) findViewById(R.id.myButton); 50 | /*myButton.setOnClickListener(new View.OnClickListener() { 51 | @Override 52 | public void onClick(final View v) { 53 | triggerAction(); 54 | } 55 | });*/ 56 | 57 | final Observable myButtonObservable = 58 | Observable.create(subscriber -> { 59 | myButton.setOnClickListener(v -> { 60 | if (!subscriber.isUnsubscribed()) { 61 | subscriber.onNext(null); 62 | } 63 | }); 64 | }); 65 | 66 | myButtonObservable 67 | .delay(100, TimeUnit.MILLISECONDS) 68 | .observeOn(Schedulers.io()) 69 | .subscribe(v -> triggerAction()); 70 | 71 | RxView.clicks(myButton) 72 | .delay(100, TimeUnit.MILLISECONDS) 73 | .observeOn(Schedulers.io()) 74 | .subscribe(v -> triggerAction()); 75 | /* 76 | 77 | final TextView username_tv = (TextView) findViewById(R.id.username); 78 | final TextView password_tv = (TextView) findViewById(R.id.password); 79 | final TextView fullName_tv = (TextView) findViewById(R.id.fullName); // optional 80 | 81 | Observable rxUsername = RxTextView.textChanges(username_tv); 82 | Observable rxPassword = RxTextView.textChanges(password_tv); 83 | 84 | 85 | 86 | Observable rxFullName = RxTextView.textChanges(fullName_tv) 87 | .mergeWith(Observable.just("")); 88 | 89 | Observable.combineLatest( 90 | rxUsername, rxPassword, rxFullName, RegistrationModel::new) 91 | .filter(RegistrationModel::isValid) 92 | .subscribe(LoginActivity::enableSubmitButton); 93 | */ 94 | 95 | 96 | } 97 | 98 | private void triggerAction() { 99 | Log.v(TAG, "trigger action"); 100 | } 101 | 102 | } 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/pascalwelsch/getreactive/RepoListActivity.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.getreactive; 2 | 3 | import com.jakewharton.rxbinding.support.v7.widget.RxSearchView; 4 | import com.pascalwelsch.getreactive.databinding.ItemRepoBinding; 5 | import com.pascalwelsch.getreactive.retrofit.GitApiInterface; 6 | import com.pascalwelsch.getreactive.retrofit.GitHubResponse; 7 | import com.pascalwelsch.getreactive.retrofit.Repository; 8 | import com.pascalwelsch.getreactive.util.ArrayAdapter; 9 | import com.pascalwelsch.getreactive.util.BindingViewHolder; 10 | import com.squareup.okhttp.OkHttpClient; 11 | import com.squareup.okhttp.logging.HttpLoggingInterceptor; 12 | 13 | import android.content.Context; 14 | import android.content.Intent; 15 | import android.os.Bundle; 16 | import android.support.annotation.NonNull; 17 | import android.support.annotation.Nullable; 18 | import android.support.v4.view.MenuItemCompat; 19 | import android.support.v7.app.AppCompatActivity; 20 | import android.support.v7.widget.LinearLayoutManager; 21 | import android.support.v7.widget.RecyclerView; 22 | import android.support.v7.widget.SearchView; 23 | import android.support.v7.widget.Toolbar; 24 | import android.text.TextUtils; 25 | import android.util.Log; 26 | import android.view.LayoutInflater; 27 | import android.view.Menu; 28 | import android.view.ViewGroup; 29 | import android.widget.Toast; 30 | 31 | import java.io.IOException; 32 | import java.util.List; 33 | import java.util.concurrent.TimeUnit; 34 | 35 | import retrofit.Callback; 36 | import retrofit.GsonConverterFactory; 37 | import retrofit.HttpException; 38 | import retrofit.Response; 39 | import retrofit.Retrofit; 40 | import retrofit.RxJavaCallAdapterFactory; 41 | import rx.Observable; 42 | import rx.Single; 43 | import rx.android.schedulers.AndroidSchedulers; 44 | import rx.functions.Func1; 45 | import rx.schedulers.Schedulers; 46 | import rx.subjects.PublishSubject; 47 | 48 | /** 49 | * Created by pascalwelsch on 10/24/15. 50 | */ 51 | public class RepoListActivity extends AppCompatActivity { 52 | 53 | private static class RepoItemViewHolder extends BindingViewHolder { 54 | 55 | public RepoItemViewHolder(final ItemRepoBinding viewBinding) { 56 | super(viewBinding); 57 | } 58 | } 59 | 60 | private static class RepoAdapter extends ArrayAdapter { 61 | 62 | public RepoAdapter() { 63 | super(null); 64 | } 65 | 66 | @Override 67 | public void onBindViewHolder(final RepoItemViewHolder holder, final int position) { 68 | final Repository repo = getItem(position); 69 | holder.getBinding().setRepo(repo); 70 | } 71 | 72 | @Override 73 | public RepoItemViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) { 74 | final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); 75 | return new RepoItemViewHolder(ItemRepoBinding.inflate(inflater, parent, false)); 76 | } 77 | } 78 | 79 | private static final String TAG = RepoListActivity.class.getSimpleName(); 80 | 81 | public static final String METHOD_THE_ANDROID_WAY = "METHOD_THE_ANDROID_WAY"; 82 | 83 | public static final String METHOD_RX_BEGINNER = "METHOD_RX_BEGINNER"; 84 | 85 | public static final String METHOD_RX_EXPERT = "METHOD_RX_EXPERT"; 86 | 87 | private static final String METHOD_TYPE = "METHOD_TYPE"; 88 | 89 | PublishSubject mCallSubject = PublishSubject.create(); 90 | 91 | private RepoAdapter mAdapter; 92 | 93 | private GitApiInterface mGitApiService; 94 | 95 | private String mMethod; 96 | 97 | private SearchView mSearchView; 98 | 99 | /** 100 | * @param method one of {@link #METHOD_THE_ANDROID_WAY}, {@link #METHOD_RX_BEGINNER}, {@link 101 | * #METHOD_RX_EXPERT} 102 | */ 103 | public static Intent newInstance(@NonNull final Context context, 104 | @Nullable final String method) { 105 | final Intent intent = new Intent(context, RepoListActivity.class); 106 | final Bundle bundle = new Bundle(); 107 | bundle.putString(METHOD_TYPE, method); 108 | intent.putExtras(bundle); 109 | return intent; 110 | } 111 | 112 | /** 113 | * support for onErrorResumeNext for Single because Single does not support Observable.empty() 114 | * which is crucial to consume the errors 115 | */ 116 | public static Observable onErrorResumeNext(final Single single, 117 | final Func1> resumeFunction) { 118 | return single.toObservable().onErrorResumeNext(resumeFunction); 119 | } 120 | 121 | @Override 122 | public boolean onCreateOptionsMenu(Menu menu) { 123 | getMenuInflater().inflate(R.menu.search, menu); 124 | mSearchView = (SearchView) MenuItemCompat.getActionView(menu.findItem(R.id.action_search)); 125 | registerToSearchViewEvents(mSearchView); 126 | return super.onCreateOptionsMenu(menu); 127 | } 128 | 129 | public void showRepositories(List repositories) { 130 | mAdapter.swap(repositories); 131 | } 132 | 133 | @Override 134 | protected void onCreate(final Bundle savedInstanceState) { 135 | super.onCreate(savedInstanceState); 136 | 137 | mMethod = getIntent().getStringExtra(METHOD_TYPE); 138 | if (mMethod == null) { 139 | throw new IllegalStateException("use #newInstance(Context, String)"); 140 | } 141 | 142 | setContentView(R.layout.repo_list); 143 | setSupportActionBar(((Toolbar) findViewById(R.id.toolbar))); 144 | 145 | final RecyclerView list = (RecyclerView) findViewById(R.id.list); 146 | list.setLayoutManager(new LinearLayoutManager(this)); 147 | mAdapter = new RepoAdapter(); 148 | list.setAdapter(mAdapter); 149 | 150 | final OkHttpClient okClient = new OkHttpClient(); 151 | final HttpLoggingInterceptor logging = new HttpLoggingInterceptor(); 152 | logging.setLevel(HttpLoggingInterceptor.Level.BASIC); 153 | //logging.setLevel(HttpLoggingInterceptor.Level.BODY); 154 | okClient.interceptors().add(logging); 155 | Retrofit retrofit = new Retrofit.Builder().baseUrl("https://api.github.com") 156 | .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) 157 | .addConverterFactory(GsonConverterFactory.create()) 158 | .client(okClient) 159 | .build(); 160 | mGitApiService = retrofit.create(GitApiInterface.class); 161 | } 162 | 163 | private void registerToSearchViewEvents(final SearchView searchView) { 164 | switch (mMethod) { 165 | case METHOD_THE_ANDROID_WAY: 166 | theAndroidWay(searchView); 167 | return; 168 | case METHOD_RX_BEGINNER: 169 | theRxBeginner(searchView); 170 | return; 171 | case METHOD_RX_EXPERT: 172 | default: 173 | theRxExpert(searchView); 174 | } 175 | } 176 | 177 | /** 178 | * search for repos and handle observable errors 179 | */ 180 | @NonNull 181 | private Observable> searchRepositories( 182 | final CharSequence charSequence) { 183 | return mGitApiService.searchRepositoriesObservable(charSequence.toString()) 184 | .subscribeOn(Schedulers.io()) 185 | .observeOn(AndroidSchedulers.mainThread()) 186 | .onErrorResumeNext( 187 | throwable -> { 188 | try { 189 | throw throwable; 190 | } catch (HttpException httpException) { 191 | showEmptyErrorView(httpException.message()); 192 | } catch (Throwable other) { 193 | showEmptyErrorView(other.getMessage()); 194 | other.printStackTrace(); 195 | } 196 | return Observable.empty(); 197 | }); 198 | } 199 | 200 | /** 201 | * search for repos using rx.Single for network request an handle errors 202 | */ 203 | private Observable> searchRepositoriesSingle( 204 | final CharSequence charSequence) { 205 | return onErrorResumeNext( 206 | mGitApiService.searchRepositoriesSingle(charSequence.toString()) 207 | .subscribeOn(Schedulers.io()) 208 | .observeOn(AndroidSchedulers.mainThread()), 209 | throwable -> { 210 | try { 211 | throw throwable; 212 | } catch (HttpException httpException) { 213 | showEmptyErrorView(httpException.message()); 214 | } catch (Throwable other) { 215 | showEmptyErrorView(other.getMessage()); 216 | other.printStackTrace(); 217 | } 218 | return Observable.empty(); 219 | }); 220 | } 221 | 222 | private void showEmptyErrorView(final String s) { 223 | Toast.makeText(this, s, Toast.LENGTH_SHORT).show(); 224 | } 225 | 226 | /** 227 | * straight forward implementation of a search in plain Java/Android code 228 | * @param searchView 229 | */ 230 | private void theAndroidWay(final SearchView searchView) { 231 | 232 | final Callback> callback 233 | = new Callback>() { 234 | @Override 235 | public void onFailure(final Throwable t) { 236 | showEmptyErrorView(t.getMessage()); 237 | } 238 | 239 | @Override 240 | public void onResponse( 241 | final Response> response, 242 | final Retrofit retrofit) { 243 | if (response.isSuccess()) { 244 | showRepositories(response.body().getItems()); 245 | } else { 246 | try { 247 | showEmptyErrorView(response.errorBody().string()); 248 | } catch (IOException e) { 249 | showEmptyErrorView(response.message()); 250 | } 251 | } 252 | } 253 | }; 254 | 255 | searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { 256 | @Override 257 | public boolean onQueryTextChange(final String query) { 258 | if (TextUtils.isEmpty(query)) { 259 | return true; 260 | } 261 | mGitApiService.searchRepositories(query).enqueue(callback); 262 | return true; 263 | } 264 | 265 | @Override 266 | public boolean onQueryTextSubmit(final String query) { 267 | return false; 268 | } 269 | }); 270 | } 271 | 272 | /** 273 | * working solution until an error occurs. Does not handle backpressure well 274 | * @param searchView 275 | */ 276 | private void theRxBeginner(final SearchView searchView) { 277 | RxSearchView.queryTextChanges(searchView) 278 | .filter(charSequence -> !TextUtils.isEmpty(charSequence)) 279 | .flatMap(charSequence -> { 280 | return mGitApiService.searchRepositoriesObservable(charSequence.toString()) 281 | .subscribeOn(Schedulers.io()); 282 | }) 283 | .observeOn(AndroidSchedulers.mainThread()) 284 | .subscribe(response -> { 285 | showRepositories(response.getItems()); 286 | }, throwable -> { 287 | throwable.printStackTrace(); 288 | showEmptyErrorView(throwable.getMessage()); 289 | Toast.makeText(RepoListActivity.this, 290 | "Completed Observable 'RxSearchView.queryTextChanges(searchView)' " 291 | + "with error! Should never happen", 292 | Toast.LENGTH_SHORT).show(); 293 | }); 294 | } 295 | 296 | /** 297 | * with backpressure and error handling 298 | * @param searchView 299 | */ 300 | private void theRxExpert(final SearchView searchView) { 301 | RxSearchView.queryTextChanges(searchView) 302 | .skip(1) 303 | .doOnNext(charSequence -> Log.v(TAG, "searching: " + charSequence)) 304 | .throttleLast(100, TimeUnit.MILLISECONDS) 305 | .debounce(200, TimeUnit.MILLISECONDS) 306 | .onBackpressureLatest() 307 | .observeOn(AndroidSchedulers.mainThread()) 308 | .filter(charSequence -> { 309 | final boolean empty = TextUtils.isEmpty(charSequence); 310 | if (empty) { 311 | Log.v(TAG, "empty view"); 312 | mAdapter.clear(); 313 | } 314 | return !empty; 315 | }) 316 | .concatMap(query -> { 317 | Log.v(TAG, "requesting " + query); 318 | // with rx.Observable 319 | //return searchRepositories(query) 320 | 321 | // with rx.Single (proof of concept) 322 | return searchRepositoriesSingle(query); 323 | }) 324 | .doOnNext(charSequence -> Log.v(TAG, "got data")) 325 | .subscribe(response -> { 326 | showRepositories(response.getItems()); 327 | }, throwable -> { 328 | throwable.printStackTrace(); 329 | showEmptyErrorView(throwable.getMessage()); 330 | }); 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /app/src/main/java/com/pascalwelsch/getreactive/retrofit/GitApiInterface.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.getreactive.retrofit; 2 | 3 | 4 | import retrofit.Call; 5 | import retrofit.http.GET; 6 | import retrofit.http.Query; 7 | import rx.Observable; 8 | import rx.Single; 9 | 10 | /** 11 | * Created by pascalwelsch on 10/24/15. 12 | */ 13 | public interface GitApiInterface { 14 | 15 | @GET("/search/repositories") 16 | Call> searchRepositories( 17 | @Query("q") String name); 18 | 19 | @GET("/search/repositories") 20 | Observable> searchRepositoriesObservable(@Query("q") String name); 21 | 22 | @GET("/search/repositories") 23 | Single> searchRepositoriesSingle( 24 | @Query("q") String name); 25 | 26 | @GET("/search/users") 27 | Single> searchUser(@Query("q") String name); 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/pascalwelsch/getreactive/retrofit/GitHubResponse.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.getreactive.retrofit; 2 | 3 | import java.util.ArrayList; 4 | import java.util.HashMap; 5 | import java.util.List; 6 | import java.util.Map; 7 | 8 | /** 9 | * Created by pascalwelsch on 10/24/15. 10 | */ 11 | public class GitHubResponse { 12 | 13 | private Map additionalProperties = new HashMap(); 14 | 15 | private boolean incompleteResults; 16 | 17 | private List items = new ArrayList<>(); 18 | 19 | private long totalCount; 20 | 21 | public Map getAdditionalProperties() { 22 | return this.additionalProperties; 23 | } 24 | 25 | /** 26 | * @return The items 27 | */ 28 | public List getItems() { 29 | return items; 30 | } 31 | 32 | /** 33 | * @return The totalCount 34 | */ 35 | public long getTotalCount() { 36 | return totalCount; 37 | } 38 | 39 | /** 40 | * @return The incompleteResults 41 | */ 42 | public boolean isIncompleteResults() { 43 | return incompleteResults; 44 | } 45 | 46 | public void setAdditionalProperty(String name, Object value) { 47 | this.additionalProperties.put(name, value); 48 | } 49 | 50 | /** 51 | * @param incompleteResults The incomplete_results 52 | */ 53 | public void setIncompleteResults(boolean incompleteResults) { 54 | this.incompleteResults = incompleteResults; 55 | } 56 | 57 | /** 58 | * @param items The items 59 | */ 60 | public void setItems(List items) { 61 | this.items = items; 62 | } 63 | 64 | /** 65 | * @param totalCount The total_count 66 | */ 67 | public void setTotalCount(long totalCount) { 68 | this.totalCount = totalCount; 69 | } 70 | } -------------------------------------------------------------------------------- /app/src/main/java/com/pascalwelsch/getreactive/retrofit/Repository.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.getreactive.retrofit; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class Repository { 7 | 8 | private boolean _private; 9 | 10 | private Map additionalProperties = new HashMap(); 11 | 12 | private String archiveUrl; 13 | 14 | private String assigneesUrl; 15 | 16 | private String blobsUrl; 17 | 18 | private String branchesUrl; 19 | 20 | private String cloneUrl; 21 | 22 | private String collaboratorsUrl; 23 | 24 | private String commentsUrl; 25 | 26 | private String commitsUrl; 27 | 28 | private String compareUrl; 29 | 30 | private String contentsUrl; 31 | 32 | private String contributorsUrl; 33 | 34 | private String createdAt; 35 | 36 | private String defaultBranch; 37 | 38 | private String description; 39 | 40 | private String downloadsUrl; 41 | 42 | private String eventsUrl; 43 | 44 | private boolean fork; 45 | 46 | private long forks; 47 | 48 | private long forksCount; 49 | 50 | private String forksUrl; 51 | 52 | private String fullName; 53 | 54 | private String gitCommitsUrl; 55 | 56 | private String gitRefsUrl; 57 | 58 | private String gitTagsUrl; 59 | 60 | private String gitUrl; 61 | 62 | private boolean hasDownloads; 63 | 64 | private boolean hasIssues; 65 | 66 | private boolean hasPages; 67 | 68 | private boolean hasWiki; 69 | 70 | private Object homepage; 71 | 72 | private String hooksUrl; 73 | 74 | private String htmlUrl; 75 | 76 | private long id; 77 | 78 | private String issueCommentUrl; 79 | 80 | private String issueEventsUrl; 81 | 82 | private String issuesUrl; 83 | 84 | private String keysUrl; 85 | 86 | private String labelsUrl; 87 | 88 | private String language; 89 | 90 | private String languagesUrl; 91 | 92 | private String mergesUrl; 93 | 94 | private String milestonesUrl; 95 | 96 | private Object mirrorUrl; 97 | 98 | private String name; 99 | 100 | private String notificationsUrl; 101 | 102 | private long openIssues; 103 | 104 | private long openIssuesCount; 105 | 106 | private User owner; 107 | 108 | private String pullsUrl; 109 | 110 | private String pushedAt; 111 | 112 | private String releasesUrl; 113 | 114 | private double score; 115 | 116 | private long size; 117 | 118 | private String sshUrl; 119 | 120 | private long stargazersCount; 121 | 122 | private String stargazersUrl; 123 | 124 | private String statusesUrl; 125 | 126 | private String subscribersUrl; 127 | 128 | private String subscriptionUrl; 129 | 130 | private String svnUrl; 131 | 132 | private String tagsUrl; 133 | 134 | private String teamsUrl; 135 | 136 | private String treesUrl; 137 | 138 | private String updatedAt; 139 | 140 | private String url; 141 | 142 | private long watchers; 143 | 144 | private long watchersCount; 145 | 146 | public Map getAdditionalProperties() { 147 | return this.additionalProperties; 148 | } 149 | 150 | /** 151 | * @return The archiveUrl 152 | */ 153 | public String getArchiveUrl() { 154 | return archiveUrl; 155 | } 156 | 157 | /** 158 | * @return The assigneesUrl 159 | */ 160 | public String getAssigneesUrl() { 161 | return assigneesUrl; 162 | } 163 | 164 | /** 165 | * @return The blobsUrl 166 | */ 167 | public String getBlobsUrl() { 168 | return blobsUrl; 169 | } 170 | 171 | /** 172 | * @return The branchesUrl 173 | */ 174 | public String getBranchesUrl() { 175 | return branchesUrl; 176 | } 177 | 178 | /** 179 | * @return The cloneUrl 180 | */ 181 | public String getCloneUrl() { 182 | return cloneUrl; 183 | } 184 | 185 | /** 186 | * @return The collaboratorsUrl 187 | */ 188 | public String getCollaboratorsUrl() { 189 | return collaboratorsUrl; 190 | } 191 | 192 | /** 193 | * @return The commentsUrl 194 | */ 195 | public String getCommentsUrl() { 196 | return commentsUrl; 197 | } 198 | 199 | /** 200 | * @return The commitsUrl 201 | */ 202 | public String getCommitsUrl() { 203 | return commitsUrl; 204 | } 205 | 206 | /** 207 | * @return The compareUrl 208 | */ 209 | public String getCompareUrl() { 210 | return compareUrl; 211 | } 212 | 213 | /** 214 | * @return The contentsUrl 215 | */ 216 | public String getContentsUrl() { 217 | return contentsUrl; 218 | } 219 | 220 | /** 221 | * @return The contributorsUrl 222 | */ 223 | public String getContributorsUrl() { 224 | return contributorsUrl; 225 | } 226 | 227 | /** 228 | * @return The createdAt 229 | */ 230 | public String getCreatedAt() { 231 | return createdAt; 232 | } 233 | 234 | /** 235 | * @return The defaultBranch 236 | */ 237 | public String getDefaultBranch() { 238 | return defaultBranch; 239 | } 240 | 241 | /** 242 | * @return The description 243 | */ 244 | public String getDescription() { 245 | return description; 246 | } 247 | 248 | /** 249 | * @return The downloadsUrl 250 | */ 251 | public String getDownloadsUrl() { 252 | return downloadsUrl; 253 | } 254 | 255 | /** 256 | * @return The eventsUrl 257 | */ 258 | public String getEventsUrl() { 259 | return eventsUrl; 260 | } 261 | 262 | /** 263 | * @return The forks 264 | */ 265 | public long getForks() { 266 | return forks; 267 | } 268 | 269 | /** 270 | * @return The forksCount 271 | */ 272 | public long getForksCount() { 273 | return forksCount; 274 | } 275 | 276 | /** 277 | * @return The forksUrl 278 | */ 279 | public String getForksUrl() { 280 | return forksUrl; 281 | } 282 | 283 | /** 284 | * @return The fullName 285 | */ 286 | public String getFullName() { 287 | return fullName; 288 | } 289 | 290 | /** 291 | * @return The gitCommitsUrl 292 | */ 293 | public String getGitCommitsUrl() { 294 | return gitCommitsUrl; 295 | } 296 | 297 | /** 298 | * @return The gitRefsUrl 299 | */ 300 | public String getGitRefsUrl() { 301 | return gitRefsUrl; 302 | } 303 | 304 | /** 305 | * @return The gitTagsUrl 306 | */ 307 | public String getGitTagsUrl() { 308 | return gitTagsUrl; 309 | } 310 | 311 | /** 312 | * @return The gitUrl 313 | */ 314 | public String getGitUrl() { 315 | return gitUrl; 316 | } 317 | 318 | /** 319 | * @return The homepage 320 | */ 321 | public Object getHomepage() { 322 | return homepage; 323 | } 324 | 325 | /** 326 | * @return The hooksUrl 327 | */ 328 | public String getHooksUrl() { 329 | return hooksUrl; 330 | } 331 | 332 | /** 333 | * @return The htmlUrl 334 | */ 335 | public String getHtmlUrl() { 336 | return htmlUrl; 337 | } 338 | 339 | /** 340 | * @return The id 341 | */ 342 | public long getId() { 343 | return id; 344 | } 345 | 346 | /** 347 | * @return The issueCommentUrl 348 | */ 349 | public String getIssueCommentUrl() { 350 | return issueCommentUrl; 351 | } 352 | 353 | /** 354 | * @return The issueEventsUrl 355 | */ 356 | public String getIssueEventsUrl() { 357 | return issueEventsUrl; 358 | } 359 | 360 | /** 361 | * @return The issuesUrl 362 | */ 363 | public String getIssuesUrl() { 364 | return issuesUrl; 365 | } 366 | 367 | /** 368 | * @return The keysUrl 369 | */ 370 | public String getKeysUrl() { 371 | return keysUrl; 372 | } 373 | 374 | /** 375 | * @return The labelsUrl 376 | */ 377 | public String getLabelsUrl() { 378 | return labelsUrl; 379 | } 380 | 381 | /** 382 | * @return The language 383 | */ 384 | public String getLanguage() { 385 | return language; 386 | } 387 | 388 | /** 389 | * @return The languagesUrl 390 | */ 391 | public String getLanguagesUrl() { 392 | return languagesUrl; 393 | } 394 | 395 | /** 396 | * @return The mergesUrl 397 | */ 398 | public String getMergesUrl() { 399 | return mergesUrl; 400 | } 401 | 402 | /** 403 | * @return The milestonesUrl 404 | */ 405 | public String getMilestonesUrl() { 406 | return milestonesUrl; 407 | } 408 | 409 | /** 410 | * @return The mirrorUrl 411 | */ 412 | public Object getMirrorUrl() { 413 | return mirrorUrl; 414 | } 415 | 416 | /** 417 | * @return The name 418 | */ 419 | public String getName() { 420 | return name; 421 | } 422 | 423 | /** 424 | * @return The notificationsUrl 425 | */ 426 | public String getNotificationsUrl() { 427 | return notificationsUrl; 428 | } 429 | 430 | /** 431 | * @return The openIssues 432 | */ 433 | public long getOpenIssues() { 434 | return openIssues; 435 | } 436 | 437 | /** 438 | * @return The openIssuesCount 439 | */ 440 | public long getOpenIssuesCount() { 441 | return openIssuesCount; 442 | } 443 | 444 | /** 445 | * @return The owner 446 | */ 447 | public User getOwner() { 448 | return owner; 449 | } 450 | 451 | /** 452 | * @return The pullsUrl 453 | */ 454 | public String getPullsUrl() { 455 | return pullsUrl; 456 | } 457 | 458 | /** 459 | * @return The pushedAt 460 | */ 461 | public String getPushedAt() { 462 | return pushedAt; 463 | } 464 | 465 | /** 466 | * @return The releasesUrl 467 | */ 468 | public String getReleasesUrl() { 469 | return releasesUrl; 470 | } 471 | 472 | /** 473 | * @return The score 474 | */ 475 | public double getScore() { 476 | return score; 477 | } 478 | 479 | /** 480 | * @return The size 481 | */ 482 | public long getSize() { 483 | return size; 484 | } 485 | 486 | /** 487 | * @return The sshUrl 488 | */ 489 | public String getSshUrl() { 490 | return sshUrl; 491 | } 492 | 493 | /** 494 | * @return The stargazersCount 495 | */ 496 | public long getStargazersCount() { 497 | return stargazersCount; 498 | } 499 | 500 | /** 501 | * @return The stargazersUrl 502 | */ 503 | public String getStargazersUrl() { 504 | return stargazersUrl; 505 | } 506 | 507 | /** 508 | * @return The statusesUrl 509 | */ 510 | public String getStatusesUrl() { 511 | return statusesUrl; 512 | } 513 | 514 | /** 515 | * @return The subscribersUrl 516 | */ 517 | public String getSubscribersUrl() { 518 | return subscribersUrl; 519 | } 520 | 521 | /** 522 | * @return The subscriptionUrl 523 | */ 524 | public String getSubscriptionUrl() { 525 | return subscriptionUrl; 526 | } 527 | 528 | /** 529 | * @return The svnUrl 530 | */ 531 | public String getSvnUrl() { 532 | return svnUrl; 533 | } 534 | 535 | /** 536 | * @return The tagsUrl 537 | */ 538 | public String getTagsUrl() { 539 | return tagsUrl; 540 | } 541 | 542 | /** 543 | * @return The teamsUrl 544 | */ 545 | public String getTeamsUrl() { 546 | return teamsUrl; 547 | } 548 | 549 | /** 550 | * @return The treesUrl 551 | */ 552 | public String getTreesUrl() { 553 | return treesUrl; 554 | } 555 | 556 | /** 557 | * @return The updatedAt 558 | */ 559 | public String getUpdatedAt() { 560 | return updatedAt; 561 | } 562 | 563 | /** 564 | * @return The url 565 | */ 566 | public String getUrl() { 567 | return url; 568 | } 569 | 570 | /** 571 | * @return The watchers 572 | */ 573 | public long getWatchers() { 574 | return watchers; 575 | } 576 | 577 | /** 578 | * @return The watchersCount 579 | */ 580 | public long getWatchersCount() { 581 | return watchersCount; 582 | } 583 | 584 | /** 585 | * @return The fork 586 | */ 587 | public boolean isFork() { 588 | return fork; 589 | } 590 | 591 | /** 592 | * @return The hasDownloads 593 | */ 594 | public boolean isHasDownloads() { 595 | return hasDownloads; 596 | } 597 | 598 | /** 599 | * @return The hasIssues 600 | */ 601 | public boolean isHasIssues() { 602 | return hasIssues; 603 | } 604 | 605 | /** 606 | * @return The hasPages 607 | */ 608 | public boolean isHasPages() { 609 | return hasPages; 610 | } 611 | 612 | /** 613 | * @return The hasWiki 614 | */ 615 | public boolean isHasWiki() { 616 | return hasWiki; 617 | } 618 | 619 | /** 620 | * @return The _private 621 | */ 622 | public boolean isPrivate() { 623 | return _private; 624 | } 625 | 626 | public void setAdditionalProperty(String name, Object value) { 627 | this.additionalProperties.put(name, value); 628 | } 629 | 630 | /** 631 | * @param archiveUrl The archive_url 632 | */ 633 | public void setArchiveUrl(String archiveUrl) { 634 | this.archiveUrl = archiveUrl; 635 | } 636 | 637 | /** 638 | * @param assigneesUrl The assignees_url 639 | */ 640 | public void setAssigneesUrl(String assigneesUrl) { 641 | this.assigneesUrl = assigneesUrl; 642 | } 643 | 644 | /** 645 | * @param blobsUrl The blobs_url 646 | */ 647 | public void setBlobsUrl(String blobsUrl) { 648 | this.blobsUrl = blobsUrl; 649 | } 650 | 651 | /** 652 | * @param branchesUrl The branches_url 653 | */ 654 | public void setBranchesUrl(String branchesUrl) { 655 | this.branchesUrl = branchesUrl; 656 | } 657 | 658 | /** 659 | * @param cloneUrl The clone_url 660 | */ 661 | public void setCloneUrl(String cloneUrl) { 662 | this.cloneUrl = cloneUrl; 663 | } 664 | 665 | /** 666 | * @param collaboratorsUrl The collaborators_url 667 | */ 668 | public void setCollaboratorsUrl(String collaboratorsUrl) { 669 | this.collaboratorsUrl = collaboratorsUrl; 670 | } 671 | 672 | /** 673 | * @param commentsUrl The comments_url 674 | */ 675 | public void setCommentsUrl(String commentsUrl) { 676 | this.commentsUrl = commentsUrl; 677 | } 678 | 679 | /** 680 | * @param commitsUrl The commits_url 681 | */ 682 | public void setCommitsUrl(String commitsUrl) { 683 | this.commitsUrl = commitsUrl; 684 | } 685 | 686 | /** 687 | * @param compareUrl The compare_url 688 | */ 689 | public void setCompareUrl(String compareUrl) { 690 | this.compareUrl = compareUrl; 691 | } 692 | 693 | /** 694 | * @param contentsUrl The contents_url 695 | */ 696 | public void setContentsUrl(String contentsUrl) { 697 | this.contentsUrl = contentsUrl; 698 | } 699 | 700 | /** 701 | * @param contributorsUrl The contributors_url 702 | */ 703 | public void setContributorsUrl(String contributorsUrl) { 704 | this.contributorsUrl = contributorsUrl; 705 | } 706 | 707 | /** 708 | * @param createdAt The created_at 709 | */ 710 | public void setCreatedAt(String createdAt) { 711 | this.createdAt = createdAt; 712 | } 713 | 714 | /** 715 | * @param defaultBranch The default_branch 716 | */ 717 | public void setDefaultBranch(String defaultBranch) { 718 | this.defaultBranch = defaultBranch; 719 | } 720 | 721 | /** 722 | * @param description The description 723 | */ 724 | public void setDescription(String description) { 725 | this.description = description; 726 | } 727 | 728 | /** 729 | * @param downloadsUrl The downloads_url 730 | */ 731 | public void setDownloadsUrl(String downloadsUrl) { 732 | this.downloadsUrl = downloadsUrl; 733 | } 734 | 735 | /** 736 | * @param eventsUrl The events_url 737 | */ 738 | public void setEventsUrl(String eventsUrl) { 739 | this.eventsUrl = eventsUrl; 740 | } 741 | 742 | /** 743 | * @param fork The fork 744 | */ 745 | public void setFork(boolean fork) { 746 | this.fork = fork; 747 | } 748 | 749 | /** 750 | * @param forks The forks 751 | */ 752 | public void setForks(long forks) { 753 | this.forks = forks; 754 | } 755 | 756 | /** 757 | * @param forksCount The forks_count 758 | */ 759 | public void setForksCount(long forksCount) { 760 | this.forksCount = forksCount; 761 | } 762 | 763 | /** 764 | * @param forksUrl The forks_url 765 | */ 766 | public void setForksUrl(String forksUrl) { 767 | this.forksUrl = forksUrl; 768 | } 769 | 770 | /** 771 | * @param fullName The full_name 772 | */ 773 | public void setFullName(String fullName) { 774 | this.fullName = fullName; 775 | } 776 | 777 | /** 778 | * @param gitCommitsUrl The git_commits_url 779 | */ 780 | public void setGitCommitsUrl(String gitCommitsUrl) { 781 | this.gitCommitsUrl = gitCommitsUrl; 782 | } 783 | 784 | /** 785 | * @param gitRefsUrl The git_refs_url 786 | */ 787 | public void setGitRefsUrl(String gitRefsUrl) { 788 | this.gitRefsUrl = gitRefsUrl; 789 | } 790 | 791 | /** 792 | * @param gitTagsUrl The git_tags_url 793 | */ 794 | public void setGitTagsUrl(String gitTagsUrl) { 795 | this.gitTagsUrl = gitTagsUrl; 796 | } 797 | 798 | /** 799 | * @param gitUrl The git_url 800 | */ 801 | public void setGitUrl(String gitUrl) { 802 | this.gitUrl = gitUrl; 803 | } 804 | 805 | /** 806 | * @param hasDownloads The has_downloads 807 | */ 808 | public void setHasDownloads(boolean hasDownloads) { 809 | this.hasDownloads = hasDownloads; 810 | } 811 | 812 | /** 813 | * @param hasIssues The has_issues 814 | */ 815 | public void setHasIssues(boolean hasIssues) { 816 | this.hasIssues = hasIssues; 817 | } 818 | 819 | /** 820 | * @param hasPages The has_pages 821 | */ 822 | public void setHasPages(boolean hasPages) { 823 | this.hasPages = hasPages; 824 | } 825 | 826 | /** 827 | * @param hasWiki The has_wiki 828 | */ 829 | public void setHasWiki(boolean hasWiki) { 830 | this.hasWiki = hasWiki; 831 | } 832 | 833 | /** 834 | * @param homepage The homepage 835 | */ 836 | public void setHomepage(Object homepage) { 837 | this.homepage = homepage; 838 | } 839 | 840 | /** 841 | * @param hooksUrl The hooks_url 842 | */ 843 | public void setHooksUrl(String hooksUrl) { 844 | this.hooksUrl = hooksUrl; 845 | } 846 | 847 | /** 848 | * @param htmlUrl The html_url 849 | */ 850 | public void setHtmlUrl(String htmlUrl) { 851 | this.htmlUrl = htmlUrl; 852 | } 853 | 854 | /** 855 | * @param id The id 856 | */ 857 | public void setId(long id) { 858 | this.id = id; 859 | } 860 | 861 | /** 862 | * @param issueCommentUrl The issue_comment_url 863 | */ 864 | public void setIssueCommentUrl(String issueCommentUrl) { 865 | this.issueCommentUrl = issueCommentUrl; 866 | } 867 | 868 | /** 869 | * @param issueEventsUrl The issue_events_url 870 | */ 871 | public void setIssueEventsUrl(String issueEventsUrl) { 872 | this.issueEventsUrl = issueEventsUrl; 873 | } 874 | 875 | /** 876 | * @param issuesUrl The issues_url 877 | */ 878 | public void setIssuesUrl(String issuesUrl) { 879 | this.issuesUrl = issuesUrl; 880 | } 881 | 882 | /** 883 | * @param keysUrl The keys_url 884 | */ 885 | public void setKeysUrl(String keysUrl) { 886 | this.keysUrl = keysUrl; 887 | } 888 | 889 | /** 890 | * @param labelsUrl The labels_url 891 | */ 892 | public void setLabelsUrl(String labelsUrl) { 893 | this.labelsUrl = labelsUrl; 894 | } 895 | 896 | /** 897 | * @param language The language 898 | */ 899 | public void setLanguage(String language) { 900 | this.language = language; 901 | } 902 | 903 | /** 904 | * @param languagesUrl The languages_url 905 | */ 906 | public void setLanguagesUrl(String languagesUrl) { 907 | this.languagesUrl = languagesUrl; 908 | } 909 | 910 | /** 911 | * @param mergesUrl The merges_url 912 | */ 913 | public void setMergesUrl(String mergesUrl) { 914 | this.mergesUrl = mergesUrl; 915 | } 916 | 917 | /** 918 | * @param milestonesUrl The milestones_url 919 | */ 920 | public void setMilestonesUrl(String milestonesUrl) { 921 | this.milestonesUrl = milestonesUrl; 922 | } 923 | 924 | /** 925 | * @param mirrorUrl The mirror_url 926 | */ 927 | public void setMirrorUrl(Object mirrorUrl) { 928 | this.mirrorUrl = mirrorUrl; 929 | } 930 | 931 | /** 932 | * @param name The name 933 | */ 934 | public void setName(String name) { 935 | this.name = name; 936 | } 937 | 938 | /** 939 | * @param notificationsUrl The notifications_url 940 | */ 941 | public void setNotificationsUrl(String notificationsUrl) { 942 | this.notificationsUrl = notificationsUrl; 943 | } 944 | 945 | /** 946 | * @param openIssues The open_issues 947 | */ 948 | public void setOpenIssues(long openIssues) { 949 | this.openIssues = openIssues; 950 | } 951 | 952 | /** 953 | * @param openIssuesCount The open_issues_count 954 | */ 955 | public void setOpenIssuesCount(long openIssuesCount) { 956 | this.openIssuesCount = openIssuesCount; 957 | } 958 | 959 | /** 960 | * @param owner The owner 961 | */ 962 | public void setOwner(User owner) { 963 | this.owner = owner; 964 | } 965 | 966 | /** 967 | * @param _private The private 968 | */ 969 | public void setPrivate(boolean _private) { 970 | this._private = _private; 971 | } 972 | 973 | /** 974 | * @param pullsUrl The pulls_url 975 | */ 976 | public void setPullsUrl(String pullsUrl) { 977 | this.pullsUrl = pullsUrl; 978 | } 979 | 980 | /** 981 | * @param pushedAt The pushed_at 982 | */ 983 | public void setPushedAt(String pushedAt) { 984 | this.pushedAt = pushedAt; 985 | } 986 | 987 | /** 988 | * @param releasesUrl The releases_url 989 | */ 990 | public void setReleasesUrl(String releasesUrl) { 991 | this.releasesUrl = releasesUrl; 992 | } 993 | 994 | /** 995 | * @param score The score 996 | */ 997 | public void setScore(double score) { 998 | this.score = score; 999 | } 1000 | 1001 | /** 1002 | * @param size The size 1003 | */ 1004 | public void setSize(long size) { 1005 | this.size = size; 1006 | } 1007 | 1008 | /** 1009 | * @param sshUrl The ssh_url 1010 | */ 1011 | public void setSshUrl(String sshUrl) { 1012 | this.sshUrl = sshUrl; 1013 | } 1014 | 1015 | /** 1016 | * @param stargazersCount The stargazers_count 1017 | */ 1018 | public void setStargazersCount(long stargazersCount) { 1019 | this.stargazersCount = stargazersCount; 1020 | } 1021 | 1022 | /** 1023 | * @param stargazersUrl The stargazers_url 1024 | */ 1025 | public void setStargazersUrl(String stargazersUrl) { 1026 | this.stargazersUrl = stargazersUrl; 1027 | } 1028 | 1029 | /** 1030 | * @param statusesUrl The statuses_url 1031 | */ 1032 | public void setStatusesUrl(String statusesUrl) { 1033 | this.statusesUrl = statusesUrl; 1034 | } 1035 | 1036 | /** 1037 | * @param subscribersUrl The subscribers_url 1038 | */ 1039 | public void setSubscribersUrl(String subscribersUrl) { 1040 | this.subscribersUrl = subscribersUrl; 1041 | } 1042 | 1043 | /** 1044 | * @param subscriptionUrl The subscription_url 1045 | */ 1046 | public void setSubscriptionUrl(String subscriptionUrl) { 1047 | this.subscriptionUrl = subscriptionUrl; 1048 | } 1049 | 1050 | /** 1051 | * @param svnUrl The svn_url 1052 | */ 1053 | public void setSvnUrl(String svnUrl) { 1054 | this.svnUrl = svnUrl; 1055 | } 1056 | 1057 | /** 1058 | * @param tagsUrl The tags_url 1059 | */ 1060 | public void setTagsUrl(String tagsUrl) { 1061 | this.tagsUrl = tagsUrl; 1062 | } 1063 | 1064 | /** 1065 | * @param teamsUrl The teams_url 1066 | */ 1067 | public void setTeamsUrl(String teamsUrl) { 1068 | this.teamsUrl = teamsUrl; 1069 | } 1070 | 1071 | /** 1072 | * @param treesUrl The trees_url 1073 | */ 1074 | public void setTreesUrl(String treesUrl) { 1075 | this.treesUrl = treesUrl; 1076 | } 1077 | 1078 | /** 1079 | * @param updatedAt The updated_at 1080 | */ 1081 | public void setUpdatedAt(String updatedAt) { 1082 | this.updatedAt = updatedAt; 1083 | } 1084 | 1085 | /** 1086 | * @param url The url 1087 | */ 1088 | public void setUrl(String url) { 1089 | this.url = url; 1090 | } 1091 | 1092 | /** 1093 | * @param watchers The watchers 1094 | */ 1095 | public void setWatchers(long watchers) { 1096 | this.watchers = watchers; 1097 | } 1098 | 1099 | /** 1100 | * @param watchersCount The watchers_count 1101 | */ 1102 | public void setWatchersCount(long watchersCount) { 1103 | this.watchersCount = watchersCount; 1104 | } 1105 | } 1106 | -------------------------------------------------------------------------------- /app/src/main/java/com/pascalwelsch/getreactive/retrofit/User.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.getreactive.retrofit; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class User { 7 | 8 | private Map additionalProperties = new HashMap(); 9 | 10 | private String avatarUrl; 11 | 12 | private String eventsUrl; 13 | 14 | private String followersUrl; 15 | 16 | private String followingUrl; 17 | 18 | private String gistsUrl; 19 | 20 | private String gravatarId; 21 | 22 | private String htmlUrl; 23 | 24 | private long id; 25 | 26 | private String login; 27 | 28 | private String organizationsUrl; 29 | 30 | private String receivedEventsUrl; 31 | 32 | private String reposUrl; 33 | 34 | private boolean siteAdmin; 35 | 36 | private String starredUrl; 37 | 38 | private String subscriptionsUrl; 39 | 40 | private String type; 41 | 42 | private String url; 43 | 44 | public Map getAdditionalProperties() { 45 | return this.additionalProperties; 46 | } 47 | 48 | /** 49 | * @return The avatarUrl 50 | */ 51 | public String getAvatarUrl() { 52 | return avatarUrl; 53 | } 54 | 55 | /** 56 | * @param avatarUrl The avatar_url 57 | */ 58 | public void setAvatarUrl(String avatarUrl) { 59 | this.avatarUrl = avatarUrl; 60 | } 61 | 62 | /** 63 | * @return The eventsUrl 64 | */ 65 | public String getEventsUrl() { 66 | return eventsUrl; 67 | } 68 | 69 | /** 70 | * @param eventsUrl The events_url 71 | */ 72 | public void setEventsUrl(String eventsUrl) { 73 | this.eventsUrl = eventsUrl; 74 | } 75 | 76 | /** 77 | * @return The followersUrl 78 | */ 79 | public String getFollowersUrl() { 80 | return followersUrl; 81 | } 82 | 83 | /** 84 | * @param followersUrl The followers_url 85 | */ 86 | public void setFollowersUrl(String followersUrl) { 87 | this.followersUrl = followersUrl; 88 | } 89 | 90 | /** 91 | * @return The followingUrl 92 | */ 93 | public String getFollowingUrl() { 94 | return followingUrl; 95 | } 96 | 97 | /** 98 | * @param followingUrl The following_url 99 | */ 100 | public void setFollowingUrl(String followingUrl) { 101 | this.followingUrl = followingUrl; 102 | } 103 | 104 | /** 105 | * @return The gistsUrl 106 | */ 107 | public String getGistsUrl() { 108 | return gistsUrl; 109 | } 110 | 111 | /** 112 | * @param gistsUrl The gists_url 113 | */ 114 | public void setGistsUrl(String gistsUrl) { 115 | this.gistsUrl = gistsUrl; 116 | } 117 | 118 | /** 119 | * @return The gravatarId 120 | */ 121 | public String getGravatarId() { 122 | return gravatarId; 123 | } 124 | 125 | /** 126 | * @param gravatarId The gravatar_id 127 | */ 128 | public void setGravatarId(String gravatarId) { 129 | this.gravatarId = gravatarId; 130 | } 131 | 132 | /** 133 | * @return The htmlUrl 134 | */ 135 | public String getHtmlUrl() { 136 | return htmlUrl; 137 | } 138 | 139 | /** 140 | * @param htmlUrl The html_url 141 | */ 142 | public void setHtmlUrl(String htmlUrl) { 143 | this.htmlUrl = htmlUrl; 144 | } 145 | 146 | /** 147 | * @return The id 148 | */ 149 | public long getId() { 150 | return id; 151 | } 152 | 153 | /** 154 | * @param id The id 155 | */ 156 | public void setId(long id) { 157 | this.id = id; 158 | } 159 | 160 | /** 161 | * @return The login 162 | */ 163 | public String getLogin() { 164 | return login; 165 | } 166 | 167 | /** 168 | * @param login The login 169 | */ 170 | public void setLogin(String login) { 171 | this.login = login; 172 | } 173 | 174 | /** 175 | * @return The organizationsUrl 176 | */ 177 | public String getOrganizationsUrl() { 178 | return organizationsUrl; 179 | } 180 | 181 | /** 182 | * @param organizationsUrl The organizations_url 183 | */ 184 | public void setOrganizationsUrl(String organizationsUrl) { 185 | this.organizationsUrl = organizationsUrl; 186 | } 187 | 188 | /** 189 | * @return The receivedEventsUrl 190 | */ 191 | public String getReceivedEventsUrl() { 192 | return receivedEventsUrl; 193 | } 194 | 195 | /** 196 | * @param receivedEventsUrl The received_events_url 197 | */ 198 | public void setReceivedEventsUrl(String receivedEventsUrl) { 199 | this.receivedEventsUrl = receivedEventsUrl; 200 | } 201 | 202 | /** 203 | * @return The reposUrl 204 | */ 205 | public String getReposUrl() { 206 | return reposUrl; 207 | } 208 | 209 | /** 210 | * @param reposUrl The repos_url 211 | */ 212 | public void setReposUrl(String reposUrl) { 213 | this.reposUrl = reposUrl; 214 | } 215 | 216 | /** 217 | * @return The starredUrl 218 | */ 219 | public String getStarredUrl() { 220 | return starredUrl; 221 | } 222 | 223 | /** 224 | * @param starredUrl The starred_url 225 | */ 226 | public void setStarredUrl(String starredUrl) { 227 | this.starredUrl = starredUrl; 228 | } 229 | 230 | /** 231 | * @return The subscriptionsUrl 232 | */ 233 | public String getSubscriptionsUrl() { 234 | return subscriptionsUrl; 235 | } 236 | 237 | /** 238 | * @param subscriptionsUrl The subscriptions_url 239 | */ 240 | public void setSubscriptionsUrl(String subscriptionsUrl) { 241 | this.subscriptionsUrl = subscriptionsUrl; 242 | } 243 | 244 | /** 245 | * @return The type 246 | */ 247 | public String getType() { 248 | return type; 249 | } 250 | 251 | /** 252 | * @param type The type 253 | */ 254 | public void setType(String type) { 255 | this.type = type; 256 | } 257 | 258 | /** 259 | * @return The url 260 | */ 261 | public String getUrl() { 262 | return url; 263 | } 264 | 265 | /** 266 | * @param url The url 267 | */ 268 | public void setUrl(String url) { 269 | this.url = url; 270 | } 271 | 272 | /** 273 | * @return The siteAdmin 274 | */ 275 | public boolean isSiteAdmin() { 276 | return siteAdmin; 277 | } 278 | 279 | /** 280 | * @param siteAdmin The site_admin 281 | */ 282 | public void setSiteAdmin(boolean siteAdmin) { 283 | this.siteAdmin = siteAdmin; 284 | } 285 | 286 | public void setAdditionalProperty(String name, Object value) { 287 | this.additionalProperties.put(name, value); 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /app/src/main/java/com/pascalwelsch/getreactive/util/ArrayAdapter.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.getreactive.util; 2 | 3 | import android.support.annotation.Nullable; 4 | import android.support.v7.widget.RecyclerView; 5 | 6 | import java.util.ArrayList; 7 | import java.util.Collections; 8 | import java.util.Comparator; 9 | import java.util.List; 10 | 11 | /** 12 | * Created by pascalwelsch on 04.07.14. 13 | */ 14 | public abstract class ArrayAdapter 15 | extends RecyclerView.Adapter { 16 | 17 | private List mObjects; 18 | 19 | public ArrayAdapter(@Nullable final List objects) { 20 | mObjects = objects != null ? objects : new ArrayList(); 21 | } 22 | 23 | /** 24 | * Adds the specified object at the end of the array. 25 | * 26 | * @param object The object to add at the end of the array. 27 | */ 28 | public void add(final T object) { 29 | mObjects.add(object); 30 | notifyItemInserted(getItemCount() - 1); 31 | } 32 | 33 | /** 34 | * Adds the specified list of objects at the end of the array. 35 | * 36 | * @param objects The objects to add at the end of the array. 37 | */ 38 | public void addAll(final List objects) { 39 | if (objects == null) { 40 | mObjects.clear(); 41 | } else { 42 | mObjects.addAll(objects); 43 | } 44 | notifyDataSetChanged(); 45 | } 46 | 47 | /** 48 | * Remove all elements from the list. 49 | */ 50 | public void clear() { 51 | final int size = getItemCount(); 52 | mObjects.clear(); 53 | notifyItemRangeRemoved(0, size); 54 | } 55 | 56 | public T getItem(final int position) { 57 | try { 58 | return mObjects.get(position); 59 | } catch (IndexOutOfBoundsException e) { 60 | return null; 61 | } 62 | } 63 | 64 | @Override 65 | public int getItemCount() { 66 | return mObjects.size(); 67 | } 68 | 69 | public long getItemId(final int position) { 70 | return position; 71 | } 72 | 73 | /** 74 | * Returns the position of the specified item in the array. 75 | * 76 | * @param item The item to retrieve the position of. 77 | * @return The position of the specified item. 78 | */ 79 | public int getPosition(final T item) { 80 | return mObjects.indexOf(item); 81 | } 82 | 83 | /** 84 | * Inserts the specified object at the specified index in the array. 85 | * 86 | * @param object The object to insert into the array. 87 | * @param index The index at which the object must be inserted. 88 | */ 89 | public void insert(final T object, int index) { 90 | mObjects.add(index, object); 91 | notifyItemInserted(index); 92 | } 93 | 94 | /** 95 | * Removes the specified object from the array. 96 | * 97 | * @param object The object to remove. 98 | */ 99 | public void remove(T object) { 100 | final int position = getPosition(object); 101 | mObjects.remove(object); 102 | notifyItemRemoved(position); 103 | } 104 | 105 | public void removeLastObject() { 106 | mObjects.remove(mObjects.size() - 1); 107 | notifyItemRemoved(mObjects.size() - 1); 108 | } 109 | 110 | public void replaceItem(final T oldObject, final T newObject) { 111 | final int position = getPosition(oldObject); 112 | mObjects.remove(position); 113 | mObjects.add(position, newObject); 114 | notifyItemChanged(position); 115 | } 116 | 117 | public void replaceItemWithHeader(final T oldObject, final T newObject) { 118 | final int position = getPosition(oldObject); 119 | mObjects.remove(position); 120 | mObjects.add(position, newObject); 121 | notifyItemChanged(position + 1); 122 | } 123 | 124 | /** 125 | * Removes all elements and replaces them with the given guys from the list, all done with an 126 | * animation. 127 | * 128 | * @param objects The new Objects to display 129 | */ 130 | public void replaceObjectsWithAnimations(final List objects) { 131 | animateRemoveObjects(objects); 132 | animateAddObjects(objects); 133 | } 134 | 135 | /** 136 | * Sorts the content of this adapter using the specified comparator. 137 | * 138 | * @param comparator The comparator used to sort the objects contained in this adapter. 139 | */ 140 | public void sort(Comparator comparator) { 141 | Collections.sort(mObjects, comparator); 142 | notifyItemRangeChanged(0, getItemCount()); 143 | } 144 | 145 | /** 146 | * replaces the data with the given list 147 | * 148 | * @param objects new data 149 | */ 150 | public void swap(final List objects) { 151 | mObjects = objects; 152 | notifyDataSetChanged(); 153 | } 154 | 155 | protected T removeItem(final int position) { 156 | final T object = mObjects.remove(position); 157 | notifyItemRemoved(position); 158 | return object; 159 | } 160 | 161 | protected T removeItemWithHeader(final int position) { 162 | final T object = mObjects.remove(position); 163 | notifyItemRemoved(position + 1); 164 | return object; 165 | } 166 | 167 | private void addItem(final int postion, final T object) { 168 | mObjects.add(postion, object); 169 | notifyItemInserted(postion); 170 | } 171 | 172 | private void animateAddObjects(final List objects) { 173 | for (int i = 0; i < objects.size(); i++) { 174 | final T object = objects.get(i); 175 | if (!mObjects.contains(object)) { 176 | addItem(i, object); 177 | } 178 | } 179 | } 180 | 181 | private void animateRemoveObjects(final List objects) { 182 | for (int i = mObjects.size() - 1; i >= 0; i--) { 183 | final T object = mObjects.get(i); 184 | if (!objects.contains(object)) { 185 | removeItem(i); 186 | } 187 | } 188 | } 189 | 190 | private void moveItem(final int fromPosition, final int toPosition) { 191 | final T object = mObjects.remove(fromPosition); 192 | mObjects.add(toPosition, object); 193 | notifyItemMoved(fromPosition, toPosition); 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /app/src/main/java/com/pascalwelsch/getreactive/util/BindingViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.pascalwelsch.getreactive.util; 2 | 3 | import android.databinding.DataBindingUtil; 4 | import android.databinding.ViewDataBinding; 5 | import android.support.v7.widget.RecyclerView; 6 | 7 | /** 8 | * Helper for RecyclerView ViewHolders baked with databinding 9 | * 10 | * Created by pascalwelsch on 10/24/15. 11 | */ 12 | public class BindingViewHolder extends RecyclerView.ViewHolder { 13 | 14 | private final T mBinding; 15 | 16 | public BindingViewHolder(T viewBinding) { 17 | super(viewBinding.getRoot()); 18 | //noinspection unchecked 19 | mBinding = DataBindingUtil.bind(itemView); 20 | } 21 | 22 | public T getBinding() { 23 | return mBinding; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-nodpi/devoxx_preview.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/passsy/android-GetReactive/4ecd9c14f76edf2c8b3ec0270f39ec097b411b6c/app/src/main/res/drawable-nodpi/devoxx_preview.jpg -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_search.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_launch.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 10 | 11 | 12 | 16 | 17 | 22 | 23 | 29 | 30 | 35 | 36 | 37 | 43 | 44 | 50 | 51 |