├── .gitignore ├── .idea ├── .name ├── atlassian-ide-plugin.xml ├── compiler.xml ├── copyright │ ├── MIT.xml │ └── profiles_settings.xml ├── gradle.xml ├── misc.xml ├── modules.xml ├── qaplug_profiles.xml ├── uiDesigner.xml └── vcs.xml ├── CONTRIBUTING.md ├── README.md ├── RxRecyclerView.iml ├── app ├── .gitignore ├── app.iml ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── exallium │ │ └── rxrecyclerview │ │ └── app │ │ ├── MainEspressoTest.java │ │ └── RecyclerViewAssertions.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── exallium │ │ └── rxrecyclerview │ │ └── app │ │ ├── Adapter.java │ │ ├── AnotherActivity.java │ │ ├── MainActivity.java │ │ ├── model │ │ └── ObjectModel.java │ │ └── rx │ │ └── transformers │ │ └── IdAggregator.java │ └── res │ ├── layout │ ├── activity_another.xml │ └── activity_main.xml │ ├── mipmap-hdpi │ └── ic_launcher.png │ ├── mipmap-mdpi │ └── ic_launcher.png │ ├── mipmap-xhdpi │ └── ic_launcher.png │ ├── mipmap-xxhdpi │ └── ic_launcher.png │ └── values │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── lib ├── .gitignore ├── build.gradle ├── lib.iml ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── exallium │ │ └── rxrecyclerview │ │ └── lib │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── exallium │ │ └── rxrecyclerview │ │ └── lib │ │ ├── GroupComparator.java │ │ ├── InsertionOrderComparator.java │ │ ├── RxRecyclerViewAdapter.java │ │ ├── element │ │ ├── EmptyElement.java │ │ ├── EventElement.java │ │ ├── FooterElement.java │ │ └── HeaderElement.java │ │ ├── event │ │ └── Event.java │ │ └── operators │ │ └── ElementGenerationOperator.java │ └── res │ └── values │ └── strings.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | /local.properties 3 | /.idea/workspace.xml 4 | /.idea/libraries 5 | .DS_Store 6 | /build 7 | -------------------------------------------------------------------------------- /.idea/.name: -------------------------------------------------------------------------------- 1 | RxRecyclerView -------------------------------------------------------------------------------- /.idea/atlassian-ide-plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | efa1cd1a-66a7-4333-a010-cedab2dd2e1a 5 | 6 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | -------------------------------------------------------------------------------- /.idea/copyright/MIT.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/copyright/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 18 | 19 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | Abstraction issues 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 60 | 61 | 63 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/qaplug_profiles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 200 | -------------------------------------------------------------------------------- /.idea/uiDesigner.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to Contribute to RxRecyclerView 2 | 3 | 1. Fork\! 4 | 2. Make your changes, test your code\! 5 | 3. Pull Request\! 6 | 4. Code Review / Changes\! 7 | 5. Profit\! 8 | 9 | That's basically all there is to it. If Contributing becomes a regular thing, I'll add a checkstyle. 10 | Basically, here's some things to watch out for: 11 | 12 | 1. Do not use mX variable naming 13 | 2. Do stick to Java best practices 14 | 3. Do stick a space between operators, braces, etc. 15 | 4. Keep it small\! That means no Guava... 16 | 5. Please, please, please test your code. At least make certain that the Espresso tests still pass. 17 | 18 | Things I'd like see contributed are: 19 | 20 | 1. Simple scenario Adapter classes 21 | 2. More Activities and Espresso tests for them 22 | 23 | As always, any contributions are awesome, and I'm interested in hearing feedback from users. 24 | 25 | ## Code Guidelines: 26 | 27 | ```java 28 | 29 | // If this shouldn't be extensible, finalize it. 30 | public class X extends Y { 31 | 32 | // Static final Variables in all caps 33 | private static final int VARIABLE_NAME = 0; 34 | 35 | // ENUMs are ALL CAPS 36 | public enum TYPE { 37 | FIRST, SECOND, THIRD; 38 | } 39 | 40 | // None of this m____ stuff. Use is___ before booleans 41 | private final int otherVar; 42 | private boolean isFalse = true; 43 | 44 | public X() { 45 | otherVar = 0; 46 | } 47 | 48 | // If we're in an extendible class, finalize methods as necessary. 49 | public final void x() { 50 | // be minimalistic 51 | return x == 0 ? x : y; 52 | } 53 | 54 | // Use proper spacing between braces and operators. 55 | public void y() { 56 | if (x == 0) { 57 | doZ(); 58 | } else { 59 | // Note else goes on same line as if 60 | doW(); 61 | } 62 | } 63 | } 64 | 65 | ``` -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NOTE 2 | 3 | This library is DEPRECATED and not recommended for utilization, as it uses very old versions of Rx and Gradle. However, I am leaving it here so that you might be able to draw on some of the concepts if you're implementing your own version, or for you to freely fork and modify / update. 4 | 5 | Known issues: 6 | 7 | The gradle version / bintray version are super duper old. Please remove any references to bintray if you clone this yourself, or update them as necessary. 8 | 9 | # RxRecyclerViewAdapter Library 2.0 10 | 11 | Crazy easy to use RecyclerView Adapter for Reactive Applications 12 | 13 | ![Data Flow Visualization](http://i.imgur.com/SxDOIyB.png) 14 | 15 | ## Interface 16 | 17 | * ```RxRecyclerViewAdapter::onCreateViewHolder``` is the same as ```RecyclerView.Adapter``` 18 | * ```RxRecyclerViewAdapter::onBindViewHolder``` gives you the Element you are 19 | binding to 20 | * ```RxRecyclerViewAdapter::preProcessElement``` gives you the chance to work with elements before they enter the underlying tree set. 21 | * ```RxRecyclerViewAdapter::postProcessElement``` gives you the chance to work with elements after the RecyclerView has been notified of the element. 22 | * ```Event``` is Immutable and takes an ```Event.TYPE```, Key, and Value. 23 | * ```Event``` also contains an UNKNOWN type and is overridable for custom 24 | processing. 25 | * ```EventElement``` and it's subclasses wrap Events and contain a view 26 | type for easy addition of headers, footers, and "list is empty" view. 27 | 28 | ## Creating an Adapter 29 | 30 | You need to have an Observable that you've merged all of your event emitters 31 | into. You then need to either map those into EventElements or utilize 32 | GroupComparator and it's corresponding Operator via Observable::lift to do the 33 | conversion for you. 34 | 35 | ## Sorting and Grouping your stuff 36 | 37 | There is an interface called ```GroupComparator``` that lets you sort and group your 38 | Events. These are passed to an instance of ```ElementGenerationOperator``` 39 | which will then add in Header and Footer items, as well as handle Empty items 40 | per your provided Options. The Adapter uses a TreeSet internally, which allows 41 | for automatic sorting by natural keys (Elements subclass Comparator). 42 | 43 | ## View Types 44 | 45 | You can create your own new view types by extending the appropriate EventElement subclass, or EventElement 46 | itself. Each View type has a corresponding bit mask that are placed in the 11th and 12th bits of the 47 | View type integer. This means that when you want to know what kind of view you are looking at, you can 48 | simply shift it's view type like so: ```element.getViewType() >> EventElement.MASK_SHIFT``` and compare it 49 | to the defined static integer masks within ```EventElement```. This allows you to do things like create 50 | your own Header elements and whatnot, with unique viewtypes, and rest assured that they'll work properly. 51 | 52 | This system allows us to avoid using instanceof calls everywhere, and stick to switch cases. 53 | 54 | ## Examples 55 | 56 | Are available in the app module! 57 | 58 | ## Licensing 59 | 60 | This work is (C) under the MIT License. 61 | 62 | ## Gradle 63 | 64 | This has been released on Bintray 65 | 66 | ```groovy 67 | repositories { 68 | maven { 69 | url "http://dl.bintray.com/exallium/maven" 70 | } 71 | } 72 | 73 | dependencies { 74 | compile 'com.exallium.rxrecyclerview:lib:2.1.2' 75 | } 76 | ``` 77 | -------------------------------------------------------------------------------- /RxRecyclerView.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/app.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 8 | 9 | 10 | 11 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Alex Hart 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | buildscript { 26 | repositories { 27 | jcenter() 28 | } 29 | dependencies { 30 | classpath 'com.android.tools.build:gradle:1.1.1' 31 | } 32 | } 33 | apply plugin: 'com.android.application' 34 | 35 | repositories { 36 | jcenter() 37 | } 38 | 39 | android { 40 | compileSdkVersion 22 41 | buildToolsVersion "21.1.2" 42 | 43 | defaultConfig { 44 | applicationId "com.exallium.rxrecyclerview.app" 45 | minSdkVersion 19 46 | targetSdkVersion 22 47 | versionCode 1 48 | versionName "1.0" 49 | 50 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 51 | } 52 | 53 | packagingOptions { 54 | exclude 'LICENSE.txt' 55 | } 56 | 57 | buildTypes { 58 | release { 59 | minifyEnabled false 60 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 61 | } 62 | } 63 | } 64 | 65 | dependencies { 66 | compile project(":lib") 67 | compile 'com.jakewharton:butterknife:6.1.0' 68 | compile 'com.android.support:appcompat-v7:22.2.0' 69 | 70 | androidTestCompile 'com.android.support.test:runner:0.3' 71 | androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2' 72 | androidTestCompile ('com.android.support.test.espresso:espresso-contrib:2.2') { 73 | exclude group: 'com.android.support', module: 'appcompat' 74 | exclude group: 'com.android.support', module: 'support-v4' 75 | exclude module: 'recyclerview-v7' 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /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 /Users/ahart/src/android-sdk-macosx/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/exallium/rxrecyclerview/app/MainEspressoTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Alex Hart 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.exallium.rxrecyclerview.app; 26 | 27 | import android.support.test.espresso.assertion.ViewAssertions; 28 | import android.support.test.espresso.contrib.RecyclerViewActions; 29 | import android.support.test.rule.ActivityTestRule; 30 | import android.support.test.runner.AndroidJUnit4; 31 | import android.test.suitebuilder.annotation.LargeTest; 32 | import org.junit.Rule; 33 | import org.junit.Test; 34 | import org.junit.runner.RunWith; 35 | 36 | import static android.support.test.espresso.Espresso.onView; 37 | import static android.support.test.espresso.action.ViewActions.click; 38 | import static android.support.test.espresso.matcher.ViewMatchers.withId; 39 | import static android.support.test.espresso.matcher.ViewMatchers.withText; 40 | import static org.hamcrest.Matchers.notNullValue; 41 | 42 | /** 43 | * MainActivity Test. 44 | * 45 | * MainActivity contains an RxRecyclerView with Headers and Empty enabled. 46 | */ 47 | @RunWith(AndroidJUnit4.class) 48 | @LargeTest 49 | public class MainEspressoTest { 50 | 51 | @Rule 52 | public ActivityTestRule mainActivityRule = new ActivityTestRule<>(MainActivity.class); 53 | 54 | private void addClick() { 55 | onView(withId(R.id.addButton)).perform(click()); 56 | } 57 | 58 | private void itemClick(int position) { 59 | onView(withId(R.id.recyclerView)) 60 | .perform(RecyclerViewActions.actionOnItemAtPosition(position, click())); 61 | } 62 | 63 | private void itemCount(int size) { 64 | onView(withId(R.id.recyclerView)).check(RecyclerViewAssertions.count(size)); 65 | } 66 | 67 | @Test 68 | public void emptyItemTest() { 69 | itemCount(1); 70 | } 71 | 72 | @Test 73 | public void addItemsTest() { 74 | for (int i = 0; i < 10; i++) 75 | addClick(); 76 | 77 | // 1. and 10. will be in the same group. 78 | itemCount(19); 79 | } 80 | 81 | @Test 82 | public void removeItemsTest() { 83 | addClick(); 84 | itemCount(2); 85 | itemClick(1); 86 | itemCount(1); 87 | } 88 | 89 | @Test 90 | public void changeItemTest() { 91 | addClick(); 92 | itemCount(2); 93 | onView(withText("1 - Item")).check(ViewAssertions.matches(notNullValue())); 94 | onView(withId(R.id.refreshButton)).perform(click()); 95 | itemCount(2); 96 | onView(withText("1- Item")).check(ViewAssertions.doesNotExist()); 97 | } 98 | 99 | } 100 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/exallium/rxrecyclerview/app/RecyclerViewAssertions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Alex Hart 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.exallium.rxrecyclerview.app; 26 | 27 | import android.support.test.espresso.NoMatchingViewException; 28 | import android.support.test.espresso.ViewAssertion; 29 | import android.support.test.espresso.matcher.ViewMatchers; 30 | import android.support.v7.widget.RecyclerView; 31 | import android.util.Log; 32 | import android.view.View; 33 | import org.hamcrest.Matchers; 34 | import org.hamcrest.StringDescription; 35 | 36 | import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom; 37 | 38 | public class RecyclerViewAssertions { 39 | 40 | private static final String TAG = RecyclerViewAssertions.class.getSimpleName(); 41 | 42 | public static ViewAssertion count(int n) { 43 | return new RecyclerViewCount(n); 44 | } 45 | 46 | static class RecyclerViewCount implements ViewAssertion { 47 | 48 | private final int n; 49 | 50 | private RecyclerViewCount(int n) { 51 | this.n = n; 52 | } 53 | 54 | @Override 55 | public void check(View view, NoMatchingViewException noViewException) { 56 | StringDescription description = new StringDescription(); 57 | description.appendText("\'RecyclerView Count"); 58 | if(noViewException != null) { 59 | description.appendText(String.format("\' check could not be performed because view \'%s\' was not found.\n", new Object[]{noViewException.getViewMatcherDescription()})); 60 | Log.e(RecyclerViewAssertions.TAG, description.toString()); 61 | throw noViewException; 62 | } else if (!isAssignableFrom(RecyclerView.class).matches(view)) { 63 | description.appendText(String.format("\' check could not be performed because view \'%s\' is not a RecyclerView", view)); 64 | Log.e(RecyclerViewAssertions.TAG, description.toString()); 65 | } else if (((RecyclerView) view).getAdapter() == null) { 66 | description.appendText(String.format("\' check could not be performed because view \'%s\' has no adapter", view)); 67 | Log.e(RecyclerViewAssertions.TAG, description.toString()); 68 | } else { 69 | description.appendText(String.format("\' doesn\'t match the selected count.", new Object[0])); 70 | ViewMatchers.assertThat(description.toString(), n, Matchers.equalTo(((RecyclerView) view).getAdapter().getItemCount())); 71 | } 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 24 | 25 | 27 | 28 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/exallium/rxrecyclerview/app/Adapter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Alex Hart 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.exallium.rxrecyclerview.app; 26 | 27 | import android.support.v7.widget.RecyclerView; 28 | import android.util.Log; 29 | import android.view.View; 30 | import android.view.ViewGroup; 31 | import android.widget.TextView; 32 | import com.exallium.rxrecyclerview.app.model.ObjectModel; 33 | import com.exallium.rxrecyclerview.lib.element.EventElement; 34 | import com.exallium.rxrecyclerview.lib.event.Event; 35 | import com.exallium.rxrecyclerview.lib.RxRecyclerViewAdapter; 36 | import rx.Observable; 37 | 38 | public class Adapter extends RxRecyclerViewAdapter { 39 | private static final String TAG = Adapter.class.getSimpleName(); 40 | 41 | public Adapter(Observable> observable) { 42 | super(observable); 43 | } 44 | 45 | @Override 46 | public void onBindViewHolder(ViewHolder holder, EventElement element) { 47 | final String dataString; 48 | switch (element.getViewType() >> EventElement.MASK_SHIFT) { 49 | case EventElement.EMPTY_MASK: 50 | dataString = "EMPTY"; 51 | break; 52 | case EventElement.HEADER_MASK: 53 | dataString = "HEADER"; 54 | break; 55 | case EventElement.FOOTER_MASK: 56 | dataString = "FOOTER"; 57 | break; 58 | default: 59 | dataString = element.getData().getValue(); 60 | break; 61 | } 62 | holder.onBind(element.getData().getKey(), dataString, element.getViewType() >> EventElement.MASK_SHIFT); 63 | } 64 | 65 | @Override 66 | public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 67 | return new ViewHolder(new TextView(parent.getContext())); 68 | } 69 | 70 | @Override 71 | protected void postProcessElement(EventElement element) { 72 | Log.d(TAG, String.format("POST PROCESS ELEMENT %d - %s", 73 | element.getData().getKey(), element.getData().getValue())); 74 | } 75 | 76 | @Override 77 | protected void preProcessElement(EventElement element) { 78 | Log.d(TAG, String.format("PRE PROCESS ELEMENT %d - %s", 79 | element.getData().getKey(), element.getData().getValue())); 80 | final int indexOf = getIndexOf(element); 81 | if (indexOf != -1) { 82 | switch (element.getData().getType()) { 83 | case ADD: 84 | final EventElement oldElement = getItemAt(indexOf); 85 | Log.d(TAG, String.format("REPLACE ELEMENT %d - %s", 86 | oldElement.getData().getKey(), oldElement.getData().getValue())); 87 | break; 88 | case REMOVE: 89 | Log.d(TAG, String.format("REMOVE ELEMENT %d - %s", 90 | element.getData().getKey(), element.getData().getValue())); 91 | break; 92 | default: 93 | } 94 | } 95 | } 96 | 97 | public static class ViewHolder extends RecyclerView.ViewHolder implements View.OnClickListener { 98 | 99 | private Long key; 100 | private String value; 101 | 102 | public ViewHolder(View itemView) { 103 | super(itemView); 104 | } 105 | 106 | public void onBind(Long key, String value, int viewMask) { 107 | ((TextView)itemView).setText(String.format("%d - %s", key, value)); 108 | this.key = key; 109 | this.value = value; 110 | itemView.setOnClickListener(viewMask == EventElement.DATA_MASK ? this : null); 111 | } 112 | 113 | @Override 114 | public void onClick(View v) { 115 | ObjectModel.getInstance().getEventObserver().onNext(new Event<>(Event.TYPE.REMOVE, key, value)); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /app/src/main/java/com/exallium/rxrecyclerview/app/AnotherActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Alex Hart 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.exallium.rxrecyclerview.app; 26 | 27 | import android.app.Activity; 28 | import android.os.Bundle; 29 | import com.exallium.rxrecyclerview.app.model.ObjectModel; 30 | import com.exallium.rxrecyclerview.lib.event.Event; 31 | 32 | public class AnotherActivity extends Activity { 33 | @Override 34 | protected void onCreate(Bundle savedInstanceState) { 35 | super.onCreate(savedInstanceState); 36 | setContentView(R.layout.activity_another); 37 | ObjectModel.getInstance().getEventObserver().onNext(new Event<>(Event.TYPE.ADD, 100L, "Another Item")); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /app/src/main/java/com/exallium/rxrecyclerview/app/MainActivity.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Alex Hart 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.exallium.rxrecyclerview.app; 26 | 27 | import android.app.Activity; 28 | import android.content.Intent; 29 | import android.os.Bundle; 30 | import android.support.v7.widget.LinearLayoutManager; 31 | import android.support.v7.widget.RecyclerView; 32 | import android.widget.Button; 33 | import butterknife.ButterKnife; 34 | import butterknife.InjectView; 35 | import com.exallium.rxrecyclerview.app.model.ObjectModel; 36 | import com.exallium.rxrecyclerview.app.rx.transformers.IdAggregator; 37 | import com.exallium.rxrecyclerview.lib.GroupComparator; 38 | import com.exallium.rxrecyclerview.lib.event.Event; 39 | import com.exallium.rxrecyclerview.lib.operators.ElementGenerationOperator; 40 | import rx.Observable; 41 | import rx.android.view.OnClickEvent; 42 | import rx.android.view.ViewObservable; 43 | import rx.functions.Action1; 44 | import rx.functions.Func1; 45 | import rx.functions.Func2; 46 | 47 | import java.util.Comparator; 48 | import java.util.Random; 49 | 50 | public class MainActivity extends Activity { 51 | 52 | @InjectView(R.id.refreshButton) 53 | Button refreshButton; 54 | 55 | @InjectView(R.id.recyclerView) 56 | RecyclerView recyclerView; 57 | 58 | @InjectView(R.id.addButton) 59 | Button addButton; 60 | 61 | @InjectView(R.id.anotherActivityButton) 62 | Button anotherActivityButton; 63 | 64 | private final GroupComparator adapterComparator = new GroupComparator() { 65 | @Override 66 | public String getGroupKey(Event longStringEvent) { 67 | if (longStringEvent.getKey() != null) 68 | return longStringEvent.getKey().toString().substring(0,1); 69 | return "EMPTY"; 70 | } 71 | 72 | @Override 73 | public Event getEmptyEvent(Event.TYPE eventType) { 74 | return new Event<>(eventType, 0L, "EMPTY"); 75 | } 76 | 77 | @Override 78 | public int compare(Event lhs, Event rhs) { 79 | return lhs.getKey().compareTo(rhs.getKey()); 80 | } 81 | }; 82 | 83 | @Override 84 | protected void onCreate(Bundle savedInstanceState) { 85 | super.onCreate(savedInstanceState); 86 | setContentView(R.layout.activity_main); 87 | ButterKnife.inject(this); 88 | 89 | recyclerView.setLayoutManager(new LinearLayoutManager(this)); 90 | final Random random = new Random(System.currentTimeMillis()); 91 | 92 | Observable addClicks = ViewObservable.clicks(addButton); 93 | Observable idAggregator = addClicks.compose(new IdAggregator()); 94 | 95 | Observable> createEvents = Observable.zip(addClicks, idAggregator, new Func2>() { 96 | @Override 97 | public Event call(OnClickEvent onClickEvent, Long key) { 98 | return new Event<>(Event.TYPE.ADD, key, "Item"); 99 | } 100 | }); 101 | 102 | Observable> updateEvents = ViewObservable.clicks(refreshButton).map(new Func1>() { 103 | @Override 104 | public Event call(OnClickEvent onClickEvent) { 105 | // send a new event saying position 0 string becomes random number 106 | return new Event<>(Event.TYPE.ADD, 1L, Integer.toString(random.nextInt())); 107 | } 108 | }); 109 | 110 | Observable.merge(createEvents, updateEvents).subscribe(ObjectModel.getInstance().getEventObserver()); 111 | 112 | Adapter adapter = new Adapter(ObjectModel 113 | .getInstance() 114 | .getEventObservable() 115 | .lift(new ElementGenerationOperator.Builder<>(adapterComparator) 116 | .hasHeader(true).hasEmpty(true).build())); 117 | recyclerView.setAdapter(adapter); 118 | 119 | ViewObservable.clicks(anotherActivityButton).forEach(new Action1() { 120 | @Override 121 | public void call(OnClickEvent onClickEvent) { 122 | Intent i = new Intent(); 123 | i.setClass(MainActivity.this, AnotherActivity.class); 124 | startActivity(i); 125 | } 126 | }); 127 | } 128 | 129 | } 130 | -------------------------------------------------------------------------------- /app/src/main/java/com/exallium/rxrecyclerview/app/model/ObjectModel.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Alex Hart 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.exallium.rxrecyclerview.app.model; 26 | 27 | import com.exallium.rxrecyclerview.lib.event.Event; 28 | import rx.Observable; 29 | import rx.Observer; 30 | import rx.functions.Action1; 31 | import rx.functions.Func1; 32 | import rx.subjects.PublishSubject; 33 | 34 | import java.util.Collections; 35 | import java.util.LinkedHashMap; 36 | import java.util.Map; 37 | 38 | /** 39 | * Singleton to act as our Model layer. 40 | */ 41 | public final class ObjectModel { 42 | 43 | private static ObjectModel instance; 44 | 45 | public static ObjectModel getInstance() { 46 | if (instance == null) 47 | instance = new ObjectModel(); 48 | return instance; 49 | } 50 | 51 | // My... "database"... 52 | public Map itemMap = Collections.synchronizedMap(new LinkedHashMap()); 53 | 54 | // When I get an item, I want to add it to my itemMap, and then transmit that item down the road. 55 | private Observable> getEventCacheObservable() { 56 | return Observable.from(itemMap.entrySet()).map(new Func1, Event>() { 57 | @Override 58 | public Event call(Map.Entry longStringEntry) { 59 | return new Event<>(Event.TYPE.ADD, longStringEntry.getKey(), longStringEntry.getValue()); 60 | } 61 | }); 62 | } 63 | 64 | private final PublishSubject> eventPublishSubject = PublishSubject.create(); 65 | 66 | public final Observable> getEventObservable() { 67 | return getEventCacheObservable().mergeWith(eventPublishSubject.doOnNext(new Action1>() { 68 | @Override 69 | public void call(Event longStringEvent) { 70 | switch (longStringEvent.getType()) { 71 | case ADD: 72 | itemMap.put(longStringEvent.getKey(), longStringEvent.getValue()); 73 | break; 74 | case REMOVE: 75 | itemMap.remove(longStringEvent.getKey()); 76 | break; 77 | } 78 | } 79 | })); 80 | } 81 | 82 | public final Observer> getEventObserver() { 83 | return eventPublishSubject; 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /app/src/main/java/com/exallium/rxrecyclerview/app/rx/transformers/IdAggregator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License (MIT) 3 | * 4 | * Copyright (c) 2015 Alex Hart 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | package com.exallium.rxrecyclerview.app.rx.transformers; 26 | 27 | import rx.Observable; 28 | import rx.functions.Func1; 29 | import rx.functions.Func2; 30 | 31 | public class IdAggregator implements Observable.Transformer { 32 | 33 | @Override 34 | public Observable call(Observable tObservable) { 35 | return tObservable.map(new Func1() { 36 | @Override 37 | public Long call(T t) { 38 | return 1L; 39 | } 40 | }).scan(new Func2() { 41 | @Override 42 | public Long call(Long accumulator, Long t2) { 43 | return accumulator + t2; 44 | } 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_another.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 25 | 26 | 30 |