├── .gitignore ├── .idea └── codeStyleSettings.xml ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── morihacky │ │ └── android │ │ └── rxjava │ │ └── app │ │ └── ApplicationTest.java │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── com │ │ └── morihacky │ │ └── android │ │ └── rxjava │ │ ├── MainActivity.java │ │ ├── MyApp.java │ │ ├── fragments │ │ ├── BaseFragment.java │ │ ├── BufferDemoFragment.java │ │ ├── ConcurrencyWithSchedulersDemoFragment.java │ │ ├── DebounceSearchEmitterFragment.java │ │ ├── DoubleBindingTextViewFragment.java │ │ ├── ExponentialBackoffFragment.java │ │ ├── FormValidationCombineLatestFragment.java │ │ ├── MainFragment.java │ │ ├── NetworkDetectorFragment.java │ │ ├── PollingFragment.java │ │ ├── PseudoCacheFragment.java │ │ ├── PseudoCacheMergeFragment.java │ │ ├── RetrofitAsyncTaskDeathFragment.java │ │ ├── RetrofitFragment.java │ │ ├── RotationPersist1Fragment.java │ │ ├── RotationPersist1WorkerFragment.java │ │ ├── RotationPersist2Fragment.java │ │ ├── RotationPersist2WorkerFragment.java │ │ ├── RotationPersist3Fragment.kt │ │ ├── TimeoutDemoFragment.java │ │ └── TimingDemoFragment.java │ │ ├── pagination │ │ ├── PaginationAdapter.java │ │ ├── PaginationAutoAdapter.java │ │ ├── PaginationAutoFragment.java │ │ └── PaginationFragment.java │ │ ├── retrofit │ │ ├── Contributor.java │ │ ├── GithubApi.java │ │ ├── GithubService.java │ │ └── User.java │ │ ├── rxbus │ │ ├── RxBus.java │ │ ├── RxBusDemoFragment.java │ │ ├── RxBusDemo_Bottom1Fragment.java │ │ ├── RxBusDemo_Bottom2Fragment.java │ │ ├── RxBusDemo_Bottom3Fragment.java │ │ └── RxBusDemo_TopFragment.java │ │ ├── volley │ │ ├── MyVolley.java │ │ └── VolleyDemoFragment.java │ │ └── wiring │ │ └── LogAdapter.java │ ├── kotlin │ └── com │ │ └── morihacky │ │ └── android │ │ └── rxjava │ │ ├── ext │ │ └── RxExt.kt │ │ └── fragments │ │ ├── MulticastPlaygroundFragment.kt │ │ ├── PlaygroundFragment.kt │ │ └── UsingFragment.kt │ └── res │ ├── drawable-hdpi │ └── ic_launcher.png │ ├── drawable-mdpi │ └── ic_launcher.png │ ├── drawable-xhdpi │ └── ic_launcher.png │ ├── drawable-xxhdpi │ └── ic_launcher.png │ ├── drawable │ └── btn_round.xml │ ├── layout │ ├── fragment_buffer.xml │ ├── fragment_concurrency_schedulers.xml │ ├── fragment_debounce.xml │ ├── fragment_demo_timing.xml │ ├── fragment_double_binding_textview.xml │ ├── fragment_exponential_backoff.xml │ ├── fragment_form_validation_comb_latest.xml │ ├── fragment_main.xml │ ├── fragment_multicast_playground.xml │ ├── fragment_network_detector.xml │ ├── fragment_pagination.xml │ ├── fragment_polling.xml │ ├── fragment_pseudo_cache.xml │ ├── fragment_pseudo_cache_concat.xml │ ├── fragment_retrofit.xml │ ├── fragment_retrofit_async_task_death.xml │ ├── fragment_rotation_persist.xml │ ├── fragment_rxbus_bottom.xml │ ├── fragment_rxbus_demo.xml │ ├── fragment_rxbus_frag3.xml │ ├── fragment_rxbus_top.xml │ ├── fragment_subject_timeout.xml │ ├── fragment_timer_demo.xml │ ├── fragment_volley.xml │ ├── item_btn.xml │ ├── item_log.xml │ └── item_log_white.xml │ ├── menu │ └── demo.xml │ ├── values-w820dp │ └── dimens.xml │ └── values │ ├── colors.xml │ ├── dimens.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # files for the dex VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # generated files 12 | bin/ 13 | gen/ 14 | build/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | subProjects/facebook/build 19 | 20 | # Buck files 21 | buck-out/ 22 | .buckd/ 23 | 24 | # Intellij project files 25 | .idea/ 26 | .idea/libraries 27 | .idea/.name 28 | .idea/compiler.xml 29 | .idea/gradle.xml 30 | .idea/modules.xml 31 | .idea/runConfigurations.xml 32 | .idea/vcs.xml 33 | .idea/workspace.xml 34 | .idea/misc.xml 35 | gen-external-apklibs/ 36 | *.iml 37 | *.iws 38 | 39 | # Local configuration file (sdk path, etc) 40 | local.properties 41 | 42 | # Mac-specific stuff 43 | .DS_Store 44 | 45 | #Maven 46 | target 47 | release.properties 48 | pom.xml.* 49 | 50 | #Ant 51 | build.xml 52 | ant.properties 53 | profiles_settings.xml 54 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | // mavenCentral() 4 | jcenter() 5 | } 6 | dependencies { 7 | classpath 'me.tatarka:gradle-retrolambda:3.6.0' 8 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}" 9 | } 10 | 11 | // Exclude the lombok version that the android plugin depends on. 12 | configurations.classpath.exclude group: 'com.android.tools.external.lombok' 13 | } 14 | 15 | apply plugin: 'com.android.application' 16 | apply plugin: 'me.tatarka.retrolambda' 17 | apply plugin: 'com.f2prateek.javafmt' 18 | apply plugin: 'kotlin-android' 19 | 20 | dependencies { 21 | compile 'com.android.support:multidex:1.0.1' 22 | compile "com.android.support:support-v13:${supportLibVersion}" 23 | compile "com.android.support:appcompat-v7:${supportLibVersion}" 24 | compile "com.android.support:recyclerview-v7:${supportLibVersion}" 25 | 26 | compile 'com.github.kaushikgopal:CoreTextUtils:c703fa12b6' 27 | compile "com.jakewharton:butterknife:${butterKnifeVersion}" 28 | kapt "com.jakewharton:butterknife-compiler:${butterKnifeVersion}" 29 | compile 'com.jakewharton.timber:timber:4.5.1' 30 | compile "com.squareup.retrofit2:retrofit:${retrofitVersion}" 31 | compile "com.squareup.retrofit2:converter-gson:${retrofitVersion}" 32 | compile "com.squareup.okhttp3:okhttp:${okhttpVersion}" 33 | compile "com.squareup.okhttp3:okhttp-urlconnection:${okhttpVersion}" 34 | compile 'com.mcxiaoke.volley:library:1.0.19' 35 | 36 | compile "org.jetbrains.kotlin:kotlin-stdlib:${kotlinVersion}" 37 | compile "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}" 38 | 39 | compile "android.arch.lifecycle:runtime:${archComponentsVersion}" 40 | compile "android.arch.lifecycle:extensions:${archComponentsVersion}" 41 | kapt "android.arch.lifecycle:compiler:${archComponentsVersion}" 42 | 43 | // ---------------------------------- 44 | // Rx dependencies 45 | 46 | compile 'io.reactivex.rxjava2:rxjava:2.0.7' 47 | 48 | // Because RxAndroid releases are few and far between, it is recommended you also 49 | // explicitly depend on RxJava's latest version for bug fixes and new features. 50 | compile 'io.reactivex.rxjava2:rxandroid:2.0.1' 51 | 52 | compile 'com.jakewharton.rx:replaying-share-kotlin:2.0.0' 53 | compile "com.github.akarnokd:rxjava2-extensions:0.16.0" 54 | compile 'com.jakewharton.rxrelay2:rxrelay:2.0.0' 55 | 56 | compile 'com.jakewharton.rxbinding2:rxbinding:2.0.0' 57 | compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0' 58 | 59 | // ---------------------------------- 60 | 61 | debugCompile 'com.squareup.leakcanary:leakcanary-android:1.5.1' 62 | releaseCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.5.1' 63 | } 64 | 65 | android { 66 | compileSdkVersion sdkVersion 67 | buildToolsVersion buildToolsVrs 68 | 69 | defaultConfig { 70 | applicationId "com.morihacky.android.rxjava" 71 | minSdkVersion 15 72 | targetSdkVersion sdkVersion 73 | versionCode 2 74 | versionName "1.2" 75 | multiDexEnabled true 76 | } 77 | buildTypes { 78 | release { 79 | minifyEnabled true 80 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 81 | } 82 | } 83 | sourceSets { 84 | main.java.srcDirs += 'src/main/kotlin' 85 | } 86 | compileOptions { 87 | sourceCompatibility JavaVersion.VERSION_1_8 88 | targetCompatibility JavaVersion.VERSION_1_8 89 | } 90 | packagingOptions { 91 | pickFirst 'META-INF/rxjava.properties' 92 | } 93 | } -------------------------------------------------------------------------------- /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 /Applications/Android Studio.app/sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/morihacky/android/rxjava/app/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.app; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 13 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/MainActivity.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava; 2 | 3 | import android.os.Bundle; 4 | import android.support.v4.app.Fragment; 5 | import android.support.v7.app.AppCompatActivity; 6 | import com.morihacky.android.rxjava.fragments.MainFragment; 7 | import com.morihacky.android.rxjava.fragments.RotationPersist1WorkerFragment; 8 | import com.morihacky.android.rxjava.fragments.RotationPersist2WorkerFragment; 9 | import com.morihacky.android.rxjava.rxbus.RxBus; 10 | 11 | public class MainActivity extends AppCompatActivity { 12 | 13 | private RxBus _rxBus = null; 14 | 15 | @Override 16 | public void onBackPressed() { 17 | super.onBackPressed(); 18 | _removeWorkerFragments(); 19 | } 20 | 21 | @Override 22 | protected void onCreate(Bundle savedInstanceState) { 23 | super.onCreate(savedInstanceState); 24 | 25 | if (savedInstanceState == null) { 26 | getSupportFragmentManager() 27 | .beginTransaction() 28 | .replace(android.R.id.content, new MainFragment(), this.toString()) 29 | .commit(); 30 | } 31 | } 32 | 33 | // This is better done with a DI Library like Dagger 34 | public RxBus getRxBusSingleton() { 35 | if (_rxBus == null) { 36 | _rxBus = new RxBus(); 37 | } 38 | 39 | return _rxBus; 40 | } 41 | 42 | private void _removeWorkerFragments() { 43 | Fragment frag = 44 | getSupportFragmentManager() 45 | .findFragmentByTag(RotationPersist1WorkerFragment.class.getName()); 46 | 47 | if (frag != null) { 48 | getSupportFragmentManager().beginTransaction().remove(frag).commit(); 49 | } 50 | 51 | frag = 52 | getSupportFragmentManager() 53 | .findFragmentByTag(RotationPersist2WorkerFragment.class.getName()); 54 | 55 | if (frag != null) { 56 | getSupportFragmentManager().beginTransaction().remove(frag).commit(); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/MyApp.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava; 2 | 3 | import android.support.multidex.MultiDexApplication; 4 | import com.morihacky.android.rxjava.volley.MyVolley; 5 | import com.squareup.leakcanary.LeakCanary; 6 | import com.squareup.leakcanary.RefWatcher; 7 | import timber.log.Timber; 8 | 9 | public class MyApp extends MultiDexApplication { 10 | 11 | private static MyApp _instance; 12 | private RefWatcher _refWatcher; 13 | 14 | public static MyApp get() { 15 | return _instance; 16 | } 17 | 18 | public static RefWatcher getRefWatcher() { 19 | return MyApp.get()._refWatcher; 20 | } 21 | 22 | @Override 23 | public void onCreate() { 24 | super.onCreate(); 25 | 26 | if (LeakCanary.isInAnalyzerProcess(this)) { 27 | // This process is dedicated to LeakCanary for heap analysis. 28 | // You should not init your app in this process. 29 | return; 30 | } 31 | 32 | _instance = (MyApp) getApplicationContext(); 33 | _refWatcher = LeakCanary.install(this); 34 | 35 | // for better RxJava debugging 36 | //RxJavaHooks.enableAssemblyTracking(); 37 | 38 | // Initialize Volley 39 | MyVolley.init(this); 40 | 41 | Timber.plant(new Timber.DebugTree()); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/BaseFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.support.v4.app.Fragment; 4 | import com.morihacky.android.rxjava.MyApp; 5 | import com.squareup.leakcanary.RefWatcher; 6 | 7 | public class BaseFragment extends Fragment { 8 | 9 | @Override 10 | public void onDestroy() { 11 | super.onDestroy(); 12 | RefWatcher refWatcher = MyApp.getRefWatcher(); 13 | refWatcher.watch(this); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/BufferDemoFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.support.annotation.Nullable; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.Button; 11 | import android.widget.ListView; 12 | 13 | import com.jakewharton.rxbinding2.view.RxView; 14 | import com.morihacky.android.rxjava.R; 15 | import com.morihacky.android.rxjava.wiring.LogAdapter; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | import butterknife.BindView; 22 | import butterknife.ButterKnife; 23 | import butterknife.Unbinder; 24 | import io.reactivex.android.schedulers.AndroidSchedulers; 25 | import io.reactivex.disposables.Disposable; 26 | import io.reactivex.observers.DisposableObserver; 27 | import timber.log.Timber; 28 | 29 | /** 30 | * This is a demonstration of the `buffer` Observable. 31 | * 32 | *

The buffer observable allows taps to be collected only within a time span. So taps outside the 33 | * 2s limit imposed by buffer will get accumulated in the next log statement. 34 | * 35 | *

If you're looking for a more foolproof solution that accumulates "continuous" taps vs a more 36 | * dumb solution as show below (i.e. number of taps within a timespan) look at {@link 37 | * com.morihacky.android.rxjava.rxbus.RxBusDemo_Bottom3Fragment} where a combo of `publish` and 38 | * `buffer` is used. 39 | * 40 | *

Also http://nerds.weddingpartyapp.com/tech/2015/01/05/debouncedbuffer-used-in-rxbus-example/ 41 | * if you're looking for words instead of code 42 | */ 43 | public class BufferDemoFragment extends BaseFragment { 44 | 45 | @BindView(R.id.list_threading_log) 46 | ListView _logsList; 47 | 48 | @BindView(R.id.btn_start_operation) 49 | Button _tapBtn; 50 | 51 | private LogAdapter _adapter; 52 | private List _logs; 53 | 54 | private Disposable _disposable; 55 | private Unbinder unbinder; 56 | 57 | @Override 58 | public void onResume() { 59 | super.onResume(); 60 | _disposable = _getBufferedDisposable(); 61 | } 62 | 63 | @Override 64 | public void onPause() { 65 | super.onPause(); 66 | _disposable.dispose(); 67 | } 68 | 69 | @Override 70 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 71 | super.onActivityCreated(savedInstanceState); 72 | _setupLogger(); 73 | } 74 | 75 | @Override 76 | public View onCreateView( 77 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 78 | View layout = inflater.inflate(R.layout.fragment_buffer, container, false); 79 | unbinder = ButterKnife.bind(this, layout); 80 | return layout; 81 | } 82 | 83 | @Override 84 | public void onDestroyView() { 85 | super.onDestroyView(); 86 | unbinder.unbind(); 87 | } 88 | 89 | // ----------------------------------------------------------------------------------- 90 | // Main Rx entities 91 | 92 | private Disposable _getBufferedDisposable() { 93 | return RxView.clicks(_tapBtn) 94 | .map( 95 | onClickEvent -> { 96 | Timber.d("--------- GOT A TAP"); 97 | _log("GOT A TAP"); 98 | return 1; 99 | }) 100 | .buffer(2, TimeUnit.SECONDS) 101 | .observeOn(AndroidSchedulers.mainThread()) 102 | .subscribeWith( 103 | new DisposableObserver>() { 104 | 105 | @Override 106 | public void onComplete() { 107 | // fyi: you'll never reach here 108 | Timber.d("----- onCompleted"); 109 | } 110 | 111 | @Override 112 | public void onError(Throwable e) { 113 | Timber.e(e, "--------- Woops on error!"); 114 | _log("Dang error! check your logs"); 115 | } 116 | 117 | @Override 118 | public void onNext(List integers) { 119 | Timber.d("--------- onNext"); 120 | if (integers.size() > 0) { 121 | _log(String.format("%d taps", integers.size())); 122 | } else { 123 | Timber.d("--------- No taps received "); 124 | } 125 | } 126 | }); 127 | } 128 | 129 | // ----------------------------------------------------------------------------------- 130 | // Methods that help wiring up the example (irrelevant to RxJava) 131 | 132 | private void _setupLogger() { 133 | _logs = new ArrayList<>(); 134 | _adapter = new LogAdapter(getActivity(), new ArrayList<>()); 135 | _logsList.setAdapter(_adapter); 136 | } 137 | 138 | private void _log(String logMsg) { 139 | 140 | if (_isCurrentlyOnMainThread()) { 141 | _logs.add(0, logMsg + " (main thread) "); 142 | _adapter.clear(); 143 | _adapter.addAll(_logs); 144 | } else { 145 | _logs.add(0, logMsg + " (NOT main thread) "); 146 | 147 | // You can only do below stuff on main thread. 148 | new Handler(Looper.getMainLooper()) 149 | .post( 150 | () -> { 151 | _adapter.clear(); 152 | _adapter.addAll(_logs); 153 | }); 154 | } 155 | } 156 | 157 | private boolean _isCurrentlyOnMainThread() { 158 | return Looper.myLooper() == Looper.getMainLooper(); 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/ConcurrencyWithSchedulersDemoFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.support.annotation.Nullable; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.ArrayAdapter; 12 | import android.widget.ListView; 13 | import android.widget.ProgressBar; 14 | import butterknife.BindView; 15 | import butterknife.ButterKnife; 16 | import butterknife.OnClick; 17 | import com.morihacky.android.rxjava.R; 18 | 19 | import butterknife.Unbinder; 20 | import io.reactivex.Observable; 21 | import io.reactivex.android.schedulers.AndroidSchedulers; 22 | import io.reactivex.disposables.CompositeDisposable; 23 | import io.reactivex.observers.DisposableObserver; 24 | import io.reactivex.schedulers.Schedulers; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | import timber.log.Timber; 28 | 29 | public class ConcurrencyWithSchedulersDemoFragment extends BaseFragment { 30 | 31 | @BindView(R.id.progress_operation_running) 32 | ProgressBar _progress; 33 | 34 | @BindView(R.id.list_threading_log) 35 | ListView _logsList; 36 | 37 | private LogAdapter _adapter; 38 | private List _logs; 39 | private CompositeDisposable _disposables = new CompositeDisposable(); 40 | private Unbinder unbinder; 41 | 42 | @Override 43 | public void onDestroy() { 44 | super.onDestroy(); 45 | unbinder.unbind(); 46 | _disposables.clear(); 47 | } 48 | 49 | @Override 50 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 51 | super.onActivityCreated(savedInstanceState); 52 | _setupLogger(); 53 | } 54 | 55 | @Override 56 | public View onCreateView( 57 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 58 | View layout = inflater.inflate(R.layout.fragment_concurrency_schedulers, container, false); 59 | unbinder = ButterKnife.bind(this, layout); 60 | return layout; 61 | } 62 | 63 | @OnClick(R.id.btn_start_operation) 64 | public void startLongOperation() { 65 | 66 | _progress.setVisibility(View.VISIBLE); 67 | _log("Button Clicked"); 68 | 69 | DisposableObserver d = _getDisposableObserver(); 70 | 71 | _getObservable() 72 | .subscribeOn(Schedulers.io()) 73 | .observeOn(AndroidSchedulers.mainThread()) 74 | .subscribe(d); 75 | 76 | _disposables.add(d); 77 | } 78 | 79 | private Observable _getObservable() { 80 | return Observable.just(true) 81 | .map( 82 | aBoolean -> { 83 | _log("Within Observable"); 84 | _doSomeLongOperation_thatBlocksCurrentThread(); 85 | return aBoolean; 86 | }); 87 | } 88 | 89 | /** 90 | * Observer that handles the result through the 3 important actions: 91 | * 92 | *

1. onCompleted 2. onError 3. onNext 93 | */ 94 | private DisposableObserver _getDisposableObserver() { 95 | return new DisposableObserver() { 96 | 97 | @Override 98 | public void onComplete() { 99 | _log("On complete"); 100 | _progress.setVisibility(View.INVISIBLE); 101 | } 102 | 103 | @Override 104 | public void onError(Throwable e) { 105 | Timber.e(e, "Error in RxJava Demo concurrency"); 106 | _log(String.format("Boo! Error %s", e.getMessage())); 107 | _progress.setVisibility(View.INVISIBLE); 108 | } 109 | 110 | @Override 111 | public void onNext(Boolean bool) { 112 | _log(String.format("onNext with return value \"%b\"", bool)); 113 | } 114 | }; 115 | } 116 | 117 | // ----------------------------------------------------------------------------------- 118 | // Method that help wiring up the example (irrelevant to RxJava) 119 | 120 | private void _doSomeLongOperation_thatBlocksCurrentThread() { 121 | _log("performing long operation"); 122 | 123 | try { 124 | Thread.sleep(3000); 125 | } catch (InterruptedException e) { 126 | Timber.d("Operation was interrupted"); 127 | } 128 | } 129 | 130 | private void _log(String logMsg) { 131 | 132 | if (_isCurrentlyOnMainThread()) { 133 | _logs.add(0, logMsg + " (main thread) "); 134 | _adapter.clear(); 135 | _adapter.addAll(_logs); 136 | } else { 137 | _logs.add(0, logMsg + " (NOT main thread) "); 138 | 139 | // You can only do below stuff on main thread. 140 | new Handler(Looper.getMainLooper()) 141 | .post( 142 | () -> { 143 | _adapter.clear(); 144 | _adapter.addAll(_logs); 145 | }); 146 | } 147 | } 148 | 149 | private void _setupLogger() { 150 | _logs = new ArrayList<>(); 151 | _adapter = new LogAdapter(getActivity(), new ArrayList<>()); 152 | _logsList.setAdapter(_adapter); 153 | } 154 | 155 | private boolean _isCurrentlyOnMainThread() { 156 | return Looper.myLooper() == Looper.getMainLooper(); 157 | } 158 | 159 | private class LogAdapter extends ArrayAdapter { 160 | 161 | public LogAdapter(Context context, List logs) { 162 | super(context, R.layout.item_log, R.id.item_log, logs); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/DebounceSearchEmitterFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.support.annotation.Nullable; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.ArrayAdapter; 12 | import android.widget.EditText; 13 | import android.widget.ListView; 14 | 15 | import com.jakewharton.rxbinding2.widget.RxTextView; 16 | import com.jakewharton.rxbinding2.widget.TextViewTextChangeEvent; 17 | import com.morihacky.android.rxjava.R; 18 | 19 | import java.util.ArrayList; 20 | import java.util.List; 21 | import java.util.concurrent.TimeUnit; 22 | 23 | import butterknife.BindView; 24 | import butterknife.ButterKnife; 25 | import butterknife.OnClick; 26 | import butterknife.Unbinder; 27 | import io.reactivex.android.schedulers.AndroidSchedulers; 28 | import io.reactivex.disposables.Disposable; 29 | import io.reactivex.observers.DisposableObserver; 30 | import timber.log.Timber; 31 | 32 | import static co.kaush.core.util.CoreNullnessUtils.isNotNullOrEmpty; 33 | import static java.lang.String.format; 34 | 35 | public class DebounceSearchEmitterFragment extends BaseFragment { 36 | 37 | @BindView(R.id.list_threading_log) 38 | ListView _logsList; 39 | 40 | @BindView(R.id.input_txt_debounce) 41 | EditText _inputSearchText; 42 | 43 | private LogAdapter _adapter; 44 | private List _logs; 45 | 46 | private Disposable _disposable; 47 | private Unbinder unbinder; 48 | 49 | @Override 50 | public void onDestroy() { 51 | super.onDestroy(); 52 | _disposable.dispose(); 53 | unbinder.unbind(); 54 | } 55 | 56 | @Override 57 | public View onCreateView( 58 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 59 | View layout = inflater.inflate(R.layout.fragment_debounce, container, false); 60 | unbinder = ButterKnife.bind(this, layout); 61 | return layout; 62 | } 63 | 64 | @OnClick(R.id.clr_debounce) 65 | public void onClearLog() { 66 | _logs = new ArrayList<>(); 67 | _adapter.clear(); 68 | } 69 | 70 | @Override 71 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 72 | 73 | super.onActivityCreated(savedInstanceState); 74 | _setupLogger(); 75 | 76 | _disposable = 77 | RxTextView.textChangeEvents(_inputSearchText) 78 | .debounce(400, TimeUnit.MILLISECONDS) // default Scheduler is Computation 79 | .filter(changes -> isNotNullOrEmpty(changes.text().toString())) 80 | .observeOn(AndroidSchedulers.mainThread()) 81 | .subscribeWith(_getSearchObserver()); 82 | } 83 | 84 | // ----------------------------------------------------------------------------------- 85 | // Main Rx entities 86 | 87 | private DisposableObserver _getSearchObserver() { 88 | return new DisposableObserver() { 89 | @Override 90 | public void onComplete() { 91 | Timber.d("--------- onComplete"); 92 | } 93 | 94 | @Override 95 | public void onError(Throwable e) { 96 | Timber.e(e, "--------- Woops on error!"); 97 | _log("Dang error. check your logs"); 98 | } 99 | 100 | @Override 101 | public void onNext(TextViewTextChangeEvent onTextChangeEvent) { 102 | _log(format("Searching for %s", onTextChangeEvent.text().toString())); 103 | } 104 | }; 105 | } 106 | 107 | // ----------------------------------------------------------------------------------- 108 | // Method that help wiring up the example (irrelevant to RxJava) 109 | 110 | private void _setupLogger() { 111 | _logs = new ArrayList<>(); 112 | _adapter = new LogAdapter(getActivity(), new ArrayList<>()); 113 | _logsList.setAdapter(_adapter); 114 | } 115 | 116 | private void _log(String logMsg) { 117 | 118 | if (_isCurrentlyOnMainThread()) { 119 | _logs.add(0, logMsg + " (main thread) "); 120 | _adapter.clear(); 121 | _adapter.addAll(_logs); 122 | } else { 123 | _logs.add(0, logMsg + " (NOT main thread) "); 124 | 125 | // You can only do below stuff on main thread. 126 | new Handler(Looper.getMainLooper()) 127 | .post( 128 | () -> { 129 | _adapter.clear(); 130 | _adapter.addAll(_logs); 131 | }); 132 | } 133 | } 134 | 135 | private boolean _isCurrentlyOnMainThread() { 136 | return Looper.myLooper() == Looper.getMainLooper(); 137 | } 138 | 139 | private class LogAdapter extends ArrayAdapter { 140 | 141 | public LogAdapter(Context context, List logs) { 142 | super(context, R.layout.item_log, R.id.item_log, logs); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/DoubleBindingTextViewFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import android.widget.EditText; 9 | import android.widget.TextView; 10 | import butterknife.BindView; 11 | import butterknife.ButterKnife; 12 | import butterknife.OnTextChanged; 13 | import com.morihacky.android.rxjava.R; 14 | 15 | import butterknife.Unbinder; 16 | import io.reactivex.disposables.Disposable; 17 | import io.reactivex.processors.PublishProcessor; 18 | 19 | import static android.text.TextUtils.isEmpty; 20 | 21 | public class DoubleBindingTextViewFragment extends BaseFragment { 22 | 23 | @BindView(R.id.double_binding_num1) 24 | EditText _number1; 25 | 26 | @BindView(R.id.double_binding_num2) 27 | EditText _number2; 28 | 29 | @BindView(R.id.double_binding_result) 30 | TextView _result; 31 | 32 | Disposable _disposable; 33 | PublishProcessor _resultEmitterSubject; 34 | private Unbinder unbinder; 35 | 36 | @Override 37 | public View onCreateView( 38 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 39 | View layout = inflater.inflate(R.layout.fragment_double_binding_textview, container, false); 40 | unbinder = ButterKnife.bind(this, layout); 41 | 42 | _resultEmitterSubject = PublishProcessor.create(); 43 | 44 | _disposable = 45 | _resultEmitterSubject.subscribe( 46 | aFloat -> { 47 | _result.setText(String.valueOf(aFloat)); 48 | }); 49 | 50 | onNumberChanged(); 51 | _number2.requestFocus(); 52 | 53 | return layout; 54 | } 55 | 56 | @OnTextChanged({R.id.double_binding_num1, R.id.double_binding_num2}) 57 | public void onNumberChanged() { 58 | float num1 = 0; 59 | float num2 = 0; 60 | 61 | if (!isEmpty(_number1.getText().toString())) { 62 | num1 = Float.parseFloat(_number1.getText().toString()); 63 | } 64 | 65 | if (!isEmpty(_number2.getText().toString())) { 66 | num2 = Float.parseFloat(_number2.getText().toString()); 67 | } 68 | 69 | _resultEmitterSubject.onNext(num1 + num2); 70 | } 71 | 72 | @Override 73 | public void onDestroyView() { 74 | super.onDestroyView(); 75 | _disposable.dispose(); 76 | unbinder.unbind(); 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/ExponentialBackoffFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.support.annotation.Nullable; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ListView; 10 | 11 | import butterknife.BindView; 12 | import butterknife.ButterKnife; 13 | import butterknife.OnClick; 14 | import com.morihacky.android.rxjava.R; 15 | import com.morihacky.android.rxjava.wiring.LogAdapter; 16 | 17 | import butterknife.Unbinder; 18 | import hu.akarnokd.rxjava2.math.MathFlowable; 19 | import io.reactivex.Flowable; 20 | import io.reactivex.disposables.CompositeDisposable; 21 | import io.reactivex.functions.Function; 22 | import io.reactivex.subscribers.DisposableSubscriber; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | import java.util.concurrent.TimeUnit; 26 | import org.reactivestreams.Publisher; 27 | import timber.log.Timber; 28 | 29 | import static android.os.Looper.getMainLooper; 30 | 31 | public class ExponentialBackoffFragment extends BaseFragment { 32 | 33 | @BindView(R.id.list_threading_log) 34 | ListView _logList; 35 | 36 | private LogAdapter _adapter; 37 | private CompositeDisposable _disposables = new CompositeDisposable(); 38 | private List _logs; 39 | Unbinder unbinder; 40 | 41 | @Override 42 | public View onCreateView( 43 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 44 | View layout = inflater.inflate(R.layout.fragment_exponential_backoff, container, false); 45 | unbinder = ButterKnife.bind(this, layout); 46 | return layout; 47 | } 48 | 49 | @Override 50 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 51 | super.onActivityCreated(savedInstanceState); 52 | _setupLogger(); 53 | } 54 | 55 | @Override 56 | public void onPause() { 57 | super.onPause(); 58 | 59 | _disposables.clear(); 60 | } 61 | 62 | @Override 63 | public void onDestroyView() { 64 | super.onDestroyView(); 65 | unbinder.unbind(); 66 | } 67 | 68 | // ----------------------------------------------------------------------------------- 69 | 70 | @OnClick(R.id.btn_eb_retry) 71 | public void startRetryingWithExponentialBackoffStrategy() { 72 | _logs = new ArrayList<>(); 73 | _adapter.clear(); 74 | 75 | DisposableSubscriber disposableSubscriber = 76 | new DisposableSubscriber() { 77 | @Override 78 | public void onNext(Object aVoid) { 79 | Timber.d("on Next"); 80 | } 81 | 82 | @Override 83 | public void onComplete() { 84 | Timber.d("on Completed"); 85 | } 86 | 87 | @Override 88 | public void onError(Throwable e) { 89 | _log("Error: I give up!"); 90 | } 91 | }; 92 | 93 | Flowable.error(new RuntimeException("testing")) // always fails 94 | .retryWhen(new RetryWithDelay(5, 1000)) // notice this is called only onError (onNext 95 | // values sent are ignored) 96 | .doOnSubscribe(subscription -> _log("Attempting the impossible 5 times in intervals of 1s")) 97 | .subscribe(disposableSubscriber); 98 | 99 | _disposables.add(disposableSubscriber); 100 | } 101 | 102 | @OnClick(R.id.btn_eb_delay) 103 | public void startExecutingWithExponentialBackoffDelay() { 104 | 105 | _logs = new ArrayList<>(); 106 | _adapter.clear(); 107 | 108 | DisposableSubscriber disposableSubscriber = 109 | new DisposableSubscriber() { 110 | @Override 111 | public void onNext(Integer integer) { 112 | Timber.d("executing Task %d [xx:%02d]", integer, _getSecondHand()); 113 | _log(String.format("executing Task %d [xx:%02d]", integer, _getSecondHand())); 114 | } 115 | 116 | @Override 117 | public void onError(Throwable e) { 118 | Timber.d(e, "arrrr. Error"); 119 | _log("Error"); 120 | } 121 | 122 | @Override 123 | public void onComplete() { 124 | Timber.d("onCompleted"); 125 | _log("Completed"); 126 | } 127 | }; 128 | 129 | Flowable.range(1, 4) 130 | .delay( 131 | integer -> { 132 | // Rx-y way of doing the Fibonnaci :P 133 | return MathFlowable.sumInt(Flowable.range(1, integer)) 134 | .flatMap( 135 | targetSecondDelay -> 136 | Flowable.just(integer).delay(targetSecondDelay, TimeUnit.SECONDS)); 137 | }) 138 | .doOnSubscribe( 139 | s -> 140 | _log( 141 | String.format( 142 | "Execute 4 tasks with delay - time now: [xx:%02d]", _getSecondHand()))) 143 | .subscribe(disposableSubscriber); 144 | 145 | _disposables.add(disposableSubscriber); 146 | } 147 | 148 | // ----------------------------------------------------------------------------------- 149 | 150 | private int _getSecondHand() { 151 | long millis = System.currentTimeMillis(); 152 | return (int) 153 | (TimeUnit.MILLISECONDS.toSeconds(millis) 154 | - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))); 155 | } 156 | 157 | // ----------------------------------------------------------------------------------- 158 | 159 | private void _setupLogger() { 160 | _logs = new ArrayList<>(); 161 | _adapter = new LogAdapter(getActivity(), new ArrayList<>()); 162 | _logList.setAdapter(_adapter); 163 | } 164 | 165 | private void _log(String logMsg) { 166 | _logs.add(logMsg); 167 | 168 | // You can only do below stuff on main thread. 169 | new Handler(getMainLooper()) 170 | .post( 171 | () -> { 172 | _adapter.clear(); 173 | _adapter.addAll(_logs); 174 | }); 175 | } 176 | 177 | // ----------------------------------------------------------------------------------- 178 | 179 | // CAUTION: 180 | // -------------------------------------- 181 | // THIS notificationHandler class HAS NO BUSINESS BEING non-static 182 | // I ONLY did this cause i wanted access to the `_log` method from inside here 183 | // for the purpose of demonstration. In the real world, make it static and LET IT BE!! 184 | 185 | // It's 12am in the morning and i feel lazy dammit !!! 186 | 187 | //public static class RetryWithDelay 188 | public class RetryWithDelay implements Function, Publisher> { 189 | 190 | private final int _maxRetries; 191 | private final int _retryDelayMillis; 192 | private int _retryCount; 193 | 194 | public RetryWithDelay(final int maxRetries, final int retryDelayMillis) { 195 | _maxRetries = maxRetries; 196 | _retryDelayMillis = retryDelayMillis; 197 | _retryCount = 0; 198 | } 199 | 200 | // this is a notificationhandler, all that is cared about here 201 | // is the emission "type" not emission "content" 202 | // only onNext triggers a re-subscription (onError + onComplete kills it) 203 | 204 | @Override 205 | public Publisher apply(Flowable inputObservable) { 206 | 207 | // it is critical to use inputObservable in the chain for the result 208 | // ignoring it and doing your own thing will break the sequence 209 | 210 | return inputObservable.flatMap( 211 | new Function>() { 212 | @Override 213 | public Publisher apply(Throwable throwable) { 214 | if (++_retryCount < _maxRetries) { 215 | 216 | // When this Observable calls onNext, the original 217 | // Observable will be retried (i.e. re-subscribed) 218 | 219 | Timber.d("Retrying in %d ms", _retryCount * _retryDelayMillis); 220 | _log(String.format("Retrying in %d ms", _retryCount * _retryDelayMillis)); 221 | 222 | return Flowable.timer(_retryCount * _retryDelayMillis, TimeUnit.MILLISECONDS); 223 | } 224 | 225 | Timber.d("Argh! i give up"); 226 | 227 | // Max retries hit. Pass an error so the chain is forcibly completed 228 | // only onNext triggers a re-subscription (onError + onComplete kills it) 229 | return Flowable.error(throwable); 230 | } 231 | }); 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/FormValidationCombineLatestFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import static android.text.TextUtils.isEmpty; 4 | import static android.util.Patterns.EMAIL_ADDRESS; 5 | 6 | import android.os.Bundle; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.content.ContextCompat; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.EditText; 13 | import android.widget.TextView; 14 | import butterknife.BindView; 15 | import butterknife.ButterKnife; 16 | import butterknife.Unbinder; 17 | import com.jakewharton.rxbinding2.widget.RxTextView; 18 | import com.morihacky.android.rxjava.R; 19 | import io.reactivex.BackpressureStrategy; 20 | import io.reactivex.Flowable; 21 | import io.reactivex.subscribers.DisposableSubscriber; 22 | import timber.log.Timber; 23 | 24 | public class FormValidationCombineLatestFragment extends BaseFragment { 25 | 26 | @BindView(R.id.btn_demo_form_valid) 27 | TextView _btnValidIndicator; 28 | 29 | @BindView(R.id.demo_combl_email) 30 | EditText _email; 31 | 32 | @BindView(R.id.demo_combl_password) 33 | EditText _password; 34 | 35 | @BindView(R.id.demo_combl_num) 36 | EditText _number; 37 | 38 | private DisposableSubscriber _disposableObserver = null; 39 | private Flowable _emailChangeObservable; 40 | private Flowable _numberChangeObservable; 41 | private Flowable _passwordChangeObservable; 42 | private Unbinder unbinder; 43 | 44 | @Override 45 | public View onCreateView( 46 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 47 | View layout = inflater.inflate(R.layout.fragment_form_validation_comb_latest, container, false); 48 | unbinder = ButterKnife.bind(this, layout); 49 | 50 | _emailChangeObservable = 51 | RxTextView.textChanges(_email).skip(1).toFlowable(BackpressureStrategy.LATEST); 52 | _passwordChangeObservable = 53 | RxTextView.textChanges(_password).skip(1).toFlowable(BackpressureStrategy.LATEST); 54 | _numberChangeObservable = 55 | RxTextView.textChanges(_number).skip(1).toFlowable(BackpressureStrategy.LATEST); 56 | 57 | _combineLatestEvents(); 58 | 59 | return layout; 60 | } 61 | 62 | @Override 63 | public void onDestroyView() { 64 | super.onDestroyView(); 65 | unbinder.unbind(); 66 | _disposableObserver.dispose(); 67 | } 68 | 69 | private void _combineLatestEvents() { 70 | 71 | _disposableObserver = 72 | new DisposableSubscriber() { 73 | @Override 74 | public void onNext(Boolean formValid) { 75 | if (formValid) { 76 | _btnValidIndicator.setBackgroundColor( 77 | ContextCompat.getColor(getContext(), R.color.blue)); 78 | } else { 79 | _btnValidIndicator.setBackgroundColor( 80 | ContextCompat.getColor(getContext(), R.color.gray)); 81 | } 82 | } 83 | 84 | @Override 85 | public void onError(Throwable e) { 86 | Timber.e(e, "there was an error"); 87 | } 88 | 89 | @Override 90 | public void onComplete() { 91 | Timber.d("completed"); 92 | } 93 | }; 94 | 95 | Flowable.combineLatest( 96 | _emailChangeObservable, 97 | _passwordChangeObservable, 98 | _numberChangeObservable, 99 | (newEmail, newPassword, newNumber) -> { 100 | boolean emailValid = !isEmpty(newEmail) && EMAIL_ADDRESS.matcher(newEmail).matches(); 101 | if (!emailValid) { 102 | _email.setError("Invalid Email!"); 103 | } 104 | 105 | boolean passValid = !isEmpty(newPassword) && newPassword.length() > 8; 106 | if (!passValid) { 107 | _password.setError("Invalid Password!"); 108 | } 109 | 110 | boolean numValid = !isEmpty(newNumber); 111 | if (numValid) { 112 | int num = Integer.parseInt(newNumber.toString()); 113 | numValid = num > 0 && num <= 100; 114 | } 115 | if (!numValid) { 116 | _number.setError("Invalid Number!"); 117 | } 118 | 119 | return emailValid && passValid && numValid; 120 | }) 121 | .subscribe(_disposableObserver); 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/MainFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import butterknife.ButterKnife; 11 | import butterknife.OnClick; 12 | import butterknife.Unbinder; 13 | 14 | import com.morihacky.android.rxjava.R; 15 | import com.morihacky.android.rxjava.pagination.PaginationAutoFragment; 16 | import com.morihacky.android.rxjava.rxbus.RxBusDemoFragment; 17 | import com.morihacky.android.rxjava.volley.VolleyDemoFragment; 18 | 19 | public class MainFragment extends BaseFragment { 20 | 21 | private Unbinder unbinder; 22 | 23 | @Override 24 | public View onCreateView( 25 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 26 | View layout = inflater.inflate(R.layout.fragment_main, container, false); 27 | unbinder = ButterKnife.bind(this, layout); 28 | return layout; 29 | } 30 | 31 | @Override 32 | public void onDestroyView() { 33 | super.onDestroyView(); 34 | unbinder.unbind(); 35 | } 36 | 37 | @OnClick(R.id.btn_demo_schedulers) 38 | void demoConcurrencyWithSchedulers() { 39 | clickedOn(new ConcurrencyWithSchedulersDemoFragment()); 40 | } 41 | 42 | @OnClick(R.id.btn_demo_buffer) 43 | void demoBuffer() { 44 | clickedOn(new BufferDemoFragment()); 45 | } 46 | 47 | @OnClick(R.id.btn_demo_debounce) 48 | void demoThrottling() { 49 | clickedOn(new DebounceSearchEmitterFragment()); 50 | } 51 | 52 | @OnClick(R.id.btn_demo_retrofit) 53 | void demoRetrofitCalls() { 54 | clickedOn(new RetrofitFragment()); 55 | } 56 | 57 | @OnClick(R.id.btn_demo_polling) 58 | void demoPolling() { 59 | clickedOn(new PollingFragment()); 60 | } 61 | 62 | @OnClick(R.id.btn_demo_double_binding_textview) 63 | void demoDoubleBindingWithPublishSubject() { 64 | clickedOn(new DoubleBindingTextViewFragment()); 65 | } 66 | 67 | @OnClick(R.id.btn_demo_rxbus) 68 | void demoRxBus() { 69 | clickedOn(new RxBusDemoFragment()); 70 | } 71 | 72 | @OnClick(R.id.btn_demo_form_validation_combinel) 73 | void formValidation() { 74 | clickedOn(new FormValidationCombineLatestFragment()); 75 | } 76 | 77 | @OnClick(R.id.btn_demo_pseudo_cache) 78 | void pseudoCacheDemo() { 79 | clickedOn(new PseudoCacheFragment()); 80 | } 81 | 82 | @OnClick(R.id.btn_demo_timing) 83 | void demoTimerIntervalDelays() { 84 | clickedOn(new TimingDemoFragment()); 85 | } 86 | 87 | @OnClick(R.id.btn_demo_timeout) 88 | void demoTimeout() { 89 | clickedOn(new TimeoutDemoFragment()); 90 | } 91 | 92 | @OnClick(R.id.btn_demo_exponential_backoff) 93 | void demoExponentialBackoff() { 94 | clickedOn(new ExponentialBackoffFragment()); 95 | } 96 | 97 | @OnClick(R.id.btn_demo_rotation_persist) 98 | void demoRotationPersist() { 99 | clickedOn(new RotationPersist3Fragment()); 100 | // clickedOn(new RotationPersist2Fragment()); 101 | // clickedOn(new RotationPersist1Fragment()); 102 | } 103 | 104 | @OnClick(R.id.btn_demo_pagination) 105 | void demoPaging() { 106 | clickedOn(new PaginationAutoFragment()); 107 | //clickedOn(new PaginationFragment()); 108 | } 109 | 110 | @OnClick(R.id.btn_demo_volley) 111 | void demoVolleyRequest() { 112 | clickedOn(new VolleyDemoFragment()); 113 | } 114 | 115 | @OnClick(R.id.btn_demo_networkDetector) 116 | void demoNetworkDetector() { 117 | clickedOn(new NetworkDetectorFragment()); 118 | } 119 | 120 | @OnClick(R.id.btn_demo_using) 121 | void demoUsing() { 122 | clickedOn(new UsingFragment()); 123 | } 124 | 125 | @OnClick(R.id.btn_demo_multicastPlayground) 126 | void demoMulticastPlayground() { 127 | clickedOn(new MulticastPlaygroundFragment()); 128 | } 129 | 130 | private void clickedOn(@NonNull Fragment fragment) { 131 | final String tag = fragment.getClass().toString(); 132 | getActivity() 133 | .getSupportFragmentManager() 134 | .beginTransaction() 135 | .addToBackStack(tag) 136 | .replace(android.R.id.content, fragment, tag) 137 | .commit(); 138 | } 139 | } 140 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/NetworkDetectorFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.content.IntentFilter; 7 | import android.net.ConnectivityManager; 8 | import android.net.NetworkInfo; 9 | import android.os.Bundle; 10 | import android.os.Handler; 11 | import android.os.Looper; 12 | import android.support.annotation.Nullable; 13 | import android.view.LayoutInflater; 14 | import android.view.View; 15 | import android.view.ViewGroup; 16 | import android.widget.ArrayAdapter; 17 | import android.widget.ListView; 18 | import butterknife.BindView; 19 | import butterknife.ButterKnife; 20 | import com.morihacky.android.rxjava.R; 21 | 22 | import butterknife.Unbinder; 23 | import io.reactivex.android.schedulers.AndroidSchedulers; 24 | import io.reactivex.disposables.Disposable; 25 | import io.reactivex.processors.PublishProcessor; 26 | import java.util.ArrayList; 27 | import java.util.List; 28 | 29 | public class NetworkDetectorFragment extends BaseFragment { 30 | 31 | @BindView(R.id.list_threading_log) 32 | ListView logsList; 33 | 34 | private LogAdapter adapter; 35 | private BroadcastReceiver broadcastReceiver; 36 | private List logs; 37 | private Disposable disposable; 38 | private PublishProcessor publishProcessor; 39 | private Unbinder unbinder; 40 | 41 | @Override 42 | public void onDestroy() { 43 | super.onDestroy(); 44 | unbinder.unbind(); 45 | } 46 | 47 | @Override 48 | public View onCreateView( 49 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 50 | View layout = inflater.inflate(R.layout.fragment_network_detector, container, false); 51 | unbinder = ButterKnife.bind(this, layout); 52 | return layout; 53 | } 54 | 55 | @Override 56 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 57 | super.onActivityCreated(savedInstanceState); 58 | setupLogger(); 59 | } 60 | 61 | @Override 62 | public void onStart() { 63 | super.onStart(); 64 | 65 | publishProcessor = PublishProcessor.create(); 66 | 67 | disposable = 68 | publishProcessor 69 | .startWith(getConnectivityStatus(getActivity())) 70 | .distinctUntilChanged() 71 | .observeOn(AndroidSchedulers.mainThread()) 72 | .subscribe( 73 | online -> { 74 | if (online) { 75 | log("You are online"); 76 | } else { 77 | log("You are offline"); 78 | } 79 | }); 80 | 81 | listenToNetworkConnectivity(); 82 | } 83 | 84 | @Override 85 | public void onStop() { 86 | super.onStop(); 87 | 88 | disposable.dispose(); 89 | getActivity().unregisterReceiver(broadcastReceiver); 90 | } 91 | 92 | private void listenToNetworkConnectivity() { 93 | 94 | broadcastReceiver = 95 | new BroadcastReceiver() { 96 | @Override 97 | public void onReceive(Context context, Intent intent) { 98 | publishProcessor.onNext(getConnectivityStatus(context)); 99 | } 100 | }; 101 | 102 | final IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION); 103 | getActivity().registerReceiver(broadcastReceiver, intentFilter); 104 | } 105 | 106 | private boolean getConnectivityStatus(Context context) { 107 | ConnectivityManager cm = 108 | (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 109 | NetworkInfo networkInfo = cm.getActiveNetworkInfo(); 110 | return networkInfo != null && networkInfo.isConnected(); 111 | } 112 | 113 | // ----------------------------------------------------------------------------------- 114 | // Method that help wiring up the example (irrelevant to RxJava) 115 | 116 | private void log(String logMsg) { 117 | 118 | if (isCurrentlyOnMainThread()) { 119 | logs.add(0, logMsg + " (main thread) "); 120 | adapter.clear(); 121 | adapter.addAll(logs); 122 | } else { 123 | logs.add(0, logMsg + " (NOT main thread) "); 124 | 125 | // You can only do below stuff on main thread. 126 | new Handler(Looper.getMainLooper()) 127 | .post( 128 | () -> { 129 | adapter.clear(); 130 | adapter.addAll(logs); 131 | }); 132 | } 133 | } 134 | 135 | private void setupLogger() { 136 | logs = new ArrayList<>(); 137 | adapter = new LogAdapter(getActivity(), new ArrayList<>()); 138 | logsList.setAdapter(adapter); 139 | } 140 | 141 | private boolean isCurrentlyOnMainThread() { 142 | return Looper.myLooper() == Looper.getMainLooper(); 143 | } 144 | 145 | private class LogAdapter extends ArrayAdapter { 146 | 147 | public LogAdapter(Context context, List logs) { 148 | super(context, R.layout.item_log, R.id.item_log, logs); 149 | } 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/PollingFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.os.Handler; 6 | import android.os.Looper; 7 | import android.support.annotation.Nullable; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.ArrayAdapter; 12 | import android.widget.ListView; 13 | import butterknife.BindView; 14 | import butterknife.ButterKnife; 15 | import butterknife.OnClick; 16 | import com.morihacky.android.rxjava.R; 17 | 18 | import butterknife.Unbinder; 19 | import io.reactivex.Flowable; 20 | import io.reactivex.disposables.CompositeDisposable; 21 | import io.reactivex.disposables.Disposable; 22 | import io.reactivex.functions.Function; 23 | import java.util.ArrayList; 24 | import java.util.List; 25 | import java.util.Locale; 26 | import java.util.concurrent.TimeUnit; 27 | import org.reactivestreams.Publisher; 28 | import timber.log.Timber; 29 | 30 | public class PollingFragment extends BaseFragment { 31 | 32 | private static final int INITIAL_DELAY = 0; 33 | private static final int POLLING_INTERVAL = 1000; 34 | private static final int POLL_COUNT = 8; 35 | 36 | @BindView(R.id.list_threading_log) 37 | ListView _logsList; 38 | 39 | private LogAdapter _adapter; 40 | private int _counter = 0; 41 | private CompositeDisposable _disposables; 42 | private List _logs; 43 | private Unbinder unbinder; 44 | 45 | @Override 46 | public void onCreate(@Nullable Bundle savedInstanceState) { 47 | super.onCreate(savedInstanceState); 48 | 49 | _disposables = new CompositeDisposable(); 50 | } 51 | 52 | @Override 53 | public View onCreateView( 54 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 55 | View layout = inflater.inflate(R.layout.fragment_polling, container, false); 56 | unbinder = ButterKnife.bind(this, layout); 57 | return layout; 58 | } 59 | 60 | @Override 61 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 62 | super.onActivityCreated(savedInstanceState); 63 | _setupLogger(); 64 | } 65 | 66 | @Override 67 | public void onDestroy() { 68 | super.onDestroy(); 69 | _disposables.clear(); 70 | unbinder.unbind(); 71 | } 72 | 73 | @OnClick(R.id.btn_start_simple_polling) 74 | public void onStartSimplePollingClicked() { 75 | 76 | final int pollCount = POLL_COUNT; 77 | 78 | Disposable d = 79 | Flowable.interval(INITIAL_DELAY, POLLING_INTERVAL, TimeUnit.MILLISECONDS) 80 | .map(this::_doNetworkCallAndGetStringResult) 81 | .take(pollCount) 82 | .doOnSubscribe( 83 | subscription -> { 84 | _log(String.format("Start simple polling - %s", _counter)); 85 | }) 86 | .subscribe( 87 | taskName -> { 88 | _log( 89 | String.format( 90 | Locale.US, 91 | "Executing polled task [%s] now time : [xx:%02d]", 92 | taskName, 93 | _getSecondHand())); 94 | }); 95 | 96 | _disposables.add(d); 97 | } 98 | 99 | @OnClick(R.id.btn_start_increasingly_delayed_polling) 100 | public void onStartIncreasinglyDelayedPolling() { 101 | _setupLogger(); 102 | 103 | final int pollingInterval = POLLING_INTERVAL; 104 | final int pollCount = POLL_COUNT; 105 | 106 | _log( 107 | String.format( 108 | Locale.US, "Start increasingly delayed polling now time: [xx:%02d]", _getSecondHand())); 109 | 110 | _disposables.add( 111 | Flowable.just(1L) 112 | .repeatWhen(new RepeatWithDelay(pollCount, pollingInterval)) 113 | .subscribe( 114 | o -> 115 | _log( 116 | String.format( 117 | Locale.US, 118 | "Executing polled task now time : [xx:%02d]", 119 | _getSecondHand())), 120 | e -> Timber.d(e, "arrrr. Error"))); 121 | } 122 | 123 | // ----------------------------------------------------------------------------------- 124 | 125 | // CAUTION: 126 | // -------------------------------------- 127 | // THIS notificationHandler class HAS NO BUSINESS BEING non-static 128 | // I ONLY did this cause i wanted access to the `_log` method from inside here 129 | // for the purpose of demonstration. In the real world, make it static and LET IT BE!! 130 | 131 | // It's 12am in the morning and i feel lazy dammit !!! 132 | 133 | private String _doNetworkCallAndGetStringResult(long attempt) { 134 | try { 135 | if (attempt == 4) { 136 | // randomly make one event super long so we test that the repeat logic waits 137 | // and accounts for this. 138 | Thread.sleep(9000); 139 | } else { 140 | Thread.sleep(3000); 141 | } 142 | 143 | } catch (InterruptedException e) { 144 | Timber.d("Operation was interrupted"); 145 | } 146 | _counter++; 147 | 148 | return String.valueOf(_counter); 149 | } 150 | 151 | // ----------------------------------------------------------------------------------- 152 | // Method that help wiring up the example (irrelevant to RxJava) 153 | 154 | private int _getSecondHand() { 155 | long millis = System.currentTimeMillis(); 156 | return (int) 157 | (TimeUnit.MILLISECONDS.toSeconds(millis) 158 | - TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))); 159 | } 160 | 161 | private void _log(String logMsg) { 162 | if (_isCurrentlyOnMainThread()) { 163 | _logs.add(0, logMsg + " (main thread) "); 164 | _adapter.clear(); 165 | _adapter.addAll(_logs); 166 | } else { 167 | _logs.add(0, logMsg + " (NOT main thread) "); 168 | 169 | // You can only do below stuff on main thread. 170 | new Handler(Looper.getMainLooper()) 171 | .post( 172 | () -> { 173 | _adapter.clear(); 174 | _adapter.addAll(_logs); 175 | }); 176 | } 177 | } 178 | 179 | private void _setupLogger() { 180 | _logs = new ArrayList<>(); 181 | _adapter = new LogAdapter(getActivity(), new ArrayList<>()); 182 | _logsList.setAdapter(_adapter); 183 | _counter = 0; 184 | } 185 | 186 | private boolean _isCurrentlyOnMainThread() { 187 | return Looper.myLooper() == Looper.getMainLooper(); 188 | } 189 | 190 | //public static class RepeatWithDelay 191 | public class RepeatWithDelay implements Function, Publisher> { 192 | 193 | private final int _repeatLimit; 194 | private final int _pollingInterval; 195 | private int _repeatCount = 1; 196 | 197 | RepeatWithDelay(int repeatLimit, int pollingInterval) { 198 | _pollingInterval = pollingInterval; 199 | _repeatLimit = repeatLimit; 200 | } 201 | 202 | // this is a notificationhandler, all we care about is 203 | // the emission "type" not emission "content" 204 | // only onNext triggers a re-subscription 205 | 206 | @Override 207 | public Publisher apply(Flowable inputFlowable) throws Exception { 208 | // it is critical to use inputObservable in the chain for the result 209 | // ignoring it and doing your own thing will break the sequence 210 | 211 | return inputFlowable.flatMap( 212 | new Function>() { 213 | @Override 214 | public Publisher apply(Object o) throws Exception { 215 | if (_repeatCount >= _repeatLimit) { 216 | // terminate the sequence cause we reached the limit 217 | _log("Completing sequence"); 218 | return Flowable.empty(); 219 | } 220 | 221 | // since we don't get an input 222 | // we store state in this handler to tell us the point of time we're firing 223 | _repeatCount++; 224 | 225 | return Flowable.timer(_repeatCount * _pollingInterval, TimeUnit.MILLISECONDS); 226 | } 227 | }); 228 | } 229 | } 230 | 231 | private class LogAdapter extends ArrayAdapter { 232 | 233 | public LogAdapter(Context context, List logs) { 234 | super(context, R.layout.item_log, R.id.item_log, logs); 235 | } 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/PseudoCacheMergeFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.util.Pair; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ArrayAdapter; 10 | import android.widget.ListView; 11 | 12 | import com.morihacky.android.rxjava.R; 13 | import com.morihacky.android.rxjava.retrofit.Contributor; 14 | import com.morihacky.android.rxjava.retrofit.GithubApi; 15 | import com.morihacky.android.rxjava.retrofit.GithubService; 16 | 17 | import java.util.ArrayList; 18 | import java.util.HashMap; 19 | import java.util.List; 20 | 21 | import butterknife.BindView; 22 | import butterknife.ButterKnife; 23 | import butterknife.OnClick; 24 | import butterknife.Unbinder; 25 | import io.reactivex.Observable; 26 | import io.reactivex.android.schedulers.AndroidSchedulers; 27 | import io.reactivex.observers.DisposableObserver; 28 | import io.reactivex.schedulers.Schedulers; 29 | import timber.log.Timber; 30 | 31 | public class PseudoCacheMergeFragment extends BaseFragment { 32 | 33 | @BindView(R.id.log_list) 34 | ListView _resultList; 35 | 36 | private ArrayAdapter _adapter; 37 | private HashMap _contributionMap = null; 38 | private HashMap _resultAgeMap = new HashMap<>(); 39 | private Unbinder unbinder; 40 | 41 | @Override 42 | public View onCreateView( 43 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 44 | View layout = inflater.inflate(R.layout.fragment_pseudo_cache_concat, container, false); 45 | unbinder = ButterKnife.bind(this, layout); 46 | _initializeCache(); 47 | return layout; 48 | } 49 | 50 | @Override 51 | public void onDestroyView() { 52 | super.onDestroyView(); 53 | unbinder.unbind(); 54 | } 55 | 56 | @OnClick(R.id.btn_start_pseudo_cache) 57 | public void onDemoPseudoCacheClicked() { 58 | _adapter = 59 | new ArrayAdapter<>(getActivity(), R.layout.item_log, R.id.item_log, new ArrayList<>()); 60 | 61 | _resultList.setAdapter(_adapter); 62 | _initializeCache(); 63 | 64 | Observable.merge(_getCachedData(), _getFreshData()) 65 | .subscribeOn(Schedulers.io()) 66 | .observeOn(AndroidSchedulers.mainThread()) 67 | .subscribe( 68 | new DisposableObserver>() { 69 | @Override 70 | public void onComplete() { 71 | Timber.d("done loading all data"); 72 | } 73 | 74 | @Override 75 | public void onError(Throwable e) { 76 | Timber.e(e, "arr something went wrong"); 77 | } 78 | 79 | @Override 80 | public void onNext(Pair contributorAgePair) { 81 | Contributor contributor = contributorAgePair.first; 82 | 83 | if (_resultAgeMap.containsKey(contributor) 84 | && _resultAgeMap.get(contributor) > contributorAgePair.second) { 85 | return; 86 | } 87 | 88 | _contributionMap.put(contributor.login, contributor.contributions); 89 | _resultAgeMap.put(contributor, contributorAgePair.second); 90 | 91 | _adapter.clear(); 92 | _adapter.addAll(getListStringFromMap()); 93 | } 94 | }); 95 | } 96 | 97 | private List getListStringFromMap() { 98 | List list = new ArrayList<>(); 99 | 100 | for (String username : _contributionMap.keySet()) { 101 | String rowLog = String.format("%s [%d]", username, _contributionMap.get(username)); 102 | list.add(rowLog); 103 | } 104 | 105 | return list; 106 | } 107 | 108 | private Observable> _getCachedData() { 109 | 110 | List> list = new ArrayList<>(); 111 | 112 | Pair dataWithAgePair; 113 | 114 | for (String username : _contributionMap.keySet()) { 115 | Contributor c = new Contributor(); 116 | c.login = username; 117 | c.contributions = _contributionMap.get(username); 118 | 119 | dataWithAgePair = new Pair<>(c, System.currentTimeMillis()); 120 | list.add(dataWithAgePair); 121 | } 122 | 123 | return Observable.fromIterable(list); 124 | } 125 | 126 | private Observable> _getFreshData() { 127 | String githubToken = getResources().getString(R.string.github_oauth_token); 128 | GithubApi githubService = GithubService.createGithubService(githubToken); 129 | 130 | return githubService 131 | .contributors("square", "retrofit") 132 | .flatMap(Observable::fromIterable) 133 | .map(contributor -> new Pair<>(contributor, System.currentTimeMillis())); 134 | } 135 | 136 | private void _initializeCache() { 137 | _contributionMap = new HashMap<>(); 138 | _contributionMap.put("JakeWharton", 0l); 139 | _contributionMap.put("pforhan", 0l); 140 | _contributionMap.put("edenman", 0l); 141 | _contributionMap.put("swankjesse", 0l); 142 | _contributionMap.put("bruceLee", 0l); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/RetrofitAsyncTaskDeathFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.os.AsyncTask; 4 | import android.os.Bundle; 5 | import android.support.annotation.Nullable; 6 | import android.support.v4.app.Fragment; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ArrayAdapter; 11 | import android.widget.EditText; 12 | import android.widget.ListView; 13 | 14 | import com.morihacky.android.rxjava.R; 15 | import com.morihacky.android.rxjava.retrofit.GithubApi; 16 | import com.morihacky.android.rxjava.retrofit.GithubService; 17 | import com.morihacky.android.rxjava.retrofit.User; 18 | 19 | import java.util.ArrayList; 20 | 21 | import butterknife.BindView; 22 | import butterknife.ButterKnife; 23 | import butterknife.OnClick; 24 | import butterknife.Unbinder; 25 | import io.reactivex.android.schedulers.AndroidSchedulers; 26 | import io.reactivex.observers.DisposableObserver; 27 | import io.reactivex.schedulers.Schedulers; 28 | 29 | import static java.lang.String.format; 30 | 31 | public class RetrofitAsyncTaskDeathFragment extends Fragment { 32 | 33 | @BindView(R.id.btn_demo_retrofit_async_death_username) 34 | EditText _username; 35 | 36 | @BindView(R.id.log_list) 37 | ListView _resultList; 38 | 39 | private GithubApi _githubService; 40 | private ArrayAdapter _adapter; 41 | private Unbinder unbinder; 42 | 43 | @Override 44 | public void onCreate(Bundle savedInstanceState) { 45 | super.onCreate(savedInstanceState); 46 | 47 | String githubToken = getResources().getString(R.string.github_oauth_token); 48 | _githubService = GithubService.createGithubService(githubToken); 49 | } 50 | 51 | @Override 52 | public View onCreateView( 53 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 54 | 55 | View layout = inflater.inflate(R.layout.fragment_retrofit_async_task_death, container, false); 56 | unbinder = ButterKnife.bind(this, layout); 57 | 58 | _adapter = 59 | new ArrayAdapter<>(getActivity(), R.layout.item_log, R.id.item_log, new ArrayList<>()); 60 | //_adapter.setNotifyOnChange(true); 61 | _resultList.setAdapter(_adapter); 62 | 63 | return layout; 64 | } 65 | 66 | @Override 67 | public void onDestroyView() { 68 | super.onDestroyView(); 69 | unbinder.unbind(); 70 | } 71 | 72 | @OnClick(R.id.btn_demo_retrofit_async_death) 73 | public void onGetGithubUserClicked() { 74 | _adapter.clear(); 75 | 76 | /*new AsyncTask() { 77 | @Override 78 | protected User doInBackground(String... params) { 79 | return _githubService.getUser(params[0]); 80 | } 81 | 82 | @Override 83 | protected void onPostExecute(User user) { 84 | _adapter.add(format("%s = [%s: %s]", _username.getText(), user.name, user.email)); 85 | } 86 | }.execute(_username.getText().toString());*/ 87 | 88 | _githubService 89 | .user(_username.getText().toString()) 90 | .subscribeOn(Schedulers.io()) 91 | .observeOn(AndroidSchedulers.mainThread()) 92 | .subscribe( 93 | new DisposableObserver() { 94 | @Override 95 | public void onComplete() {} 96 | 97 | @Override 98 | public void onError(Throwable e) {} 99 | 100 | @Override 101 | public void onNext(User user) { 102 | _adapter.add(format("%s = [%s: %s]", _username.getText(), user.name, user.email)); 103 | } 104 | }); 105 | } 106 | 107 | // ----------------------------------------------------------------------------------- 108 | 109 | private class GetGithubUser extends AsyncTask { 110 | 111 | @Override 112 | protected User doInBackground(String... params) { 113 | return _githubService.getUser(params[0]); 114 | } 115 | 116 | @Override 117 | protected void onPostExecute(User user) { 118 | _adapter.add(format("%s = [%s: %s]", _username.getText(), user.name, user.email)); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/RetrofitFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.app.Fragment; 6 | import android.util.Pair; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ArrayAdapter; 11 | import android.widget.EditText; 12 | import android.widget.ListView; 13 | 14 | import com.morihacky.android.rxjava.R; 15 | import com.morihacky.android.rxjava.retrofit.Contributor; 16 | import com.morihacky.android.rxjava.retrofit.GithubApi; 17 | import com.morihacky.android.rxjava.retrofit.GithubService; 18 | import com.morihacky.android.rxjava.retrofit.User; 19 | 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | 23 | import butterknife.BindView; 24 | import butterknife.ButterKnife; 25 | import butterknife.OnClick; 26 | import butterknife.Unbinder; 27 | import io.reactivex.Observable; 28 | import io.reactivex.android.schedulers.AndroidSchedulers; 29 | import io.reactivex.disposables.CompositeDisposable; 30 | import io.reactivex.observers.DisposableObserver; 31 | import io.reactivex.schedulers.Schedulers; 32 | import timber.log.Timber; 33 | 34 | import static android.text.TextUtils.isEmpty; 35 | import static java.lang.String.format; 36 | 37 | public class RetrofitFragment extends Fragment { 38 | 39 | @BindView(R.id.demo_retrofit_contributors_username) 40 | EditText _username; 41 | 42 | @BindView(R.id.demo_retrofit_contributors_repository) 43 | EditText _repo; 44 | 45 | @BindView(R.id.log_list) 46 | ListView _resultList; 47 | 48 | private ArrayAdapter _adapter; 49 | private GithubApi _githubService; 50 | private CompositeDisposable _disposables; 51 | private Unbinder unbinder; 52 | 53 | @Override 54 | public void onCreate(Bundle savedInstanceState) { 55 | super.onCreate(savedInstanceState); 56 | String githubToken = getResources().getString(R.string.github_oauth_token); 57 | _githubService = GithubService.createGithubService(githubToken); 58 | 59 | _disposables = new CompositeDisposable(); 60 | } 61 | 62 | @Override 63 | public View onCreateView( 64 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 65 | 66 | View layout = inflater.inflate(R.layout.fragment_retrofit, container, false); 67 | unbinder = ButterKnife.bind(this, layout); 68 | 69 | _adapter = 70 | new ArrayAdapter<>(getActivity(), R.layout.item_log, R.id.item_log, new ArrayList<>()); 71 | //_adapter.setNotifyOnChange(true); 72 | _resultList.setAdapter(_adapter); 73 | 74 | return layout; 75 | } 76 | 77 | @Override 78 | public void onDestroyView() { 79 | super.onDestroyView(); 80 | unbinder.unbind(); 81 | } 82 | 83 | @Override 84 | public void onDestroy() { 85 | super.onDestroy(); 86 | _disposables.dispose(); 87 | } 88 | 89 | @OnClick(R.id.btn_demo_retrofit_contributors) 90 | public void onListContributorsClicked() { 91 | _adapter.clear(); 92 | 93 | _disposables.add( // 94 | _githubService 95 | .contributors(_username.getText().toString(), _repo.getText().toString()) 96 | .subscribeOn(Schedulers.io()) 97 | .observeOn(AndroidSchedulers.mainThread()) 98 | .subscribeWith( 99 | new DisposableObserver>() { 100 | 101 | @Override 102 | public void onComplete() { 103 | Timber.d("Retrofit call 1 completed"); 104 | } 105 | 106 | @Override 107 | public void onError(Throwable e) { 108 | Timber.e(e, "woops we got an error while getting the list of contributors"); 109 | } 110 | 111 | @Override 112 | public void onNext(List contributors) { 113 | for (Contributor c : contributors) { 114 | _adapter.add( 115 | format( 116 | "%s has made %d contributions to %s", 117 | c.login, c.contributions, _repo.getText().toString())); 118 | 119 | Timber.d( 120 | "%s has made %d contributions to %s", 121 | c.login, c.contributions, _repo.getText().toString()); 122 | } 123 | } 124 | })); 125 | } 126 | 127 | @OnClick(R.id.btn_demo_retrofit_contributors_with_user_info) 128 | public void onListContributorsWithFullUserInfoClicked() { 129 | _adapter.clear(); 130 | 131 | _disposables.add( 132 | _githubService 133 | .contributors(_username.getText().toString(), _repo.getText().toString()) 134 | .flatMap(Observable::fromIterable) 135 | .flatMap( 136 | contributor -> { 137 | Observable _userObservable = 138 | _githubService 139 | .user(contributor.login) 140 | .filter(user -> !isEmpty(user.name) && !isEmpty(user.email)); 141 | 142 | return Observable.zip(_userObservable, Observable.just(contributor), Pair::new); 143 | }) 144 | .subscribeOn(Schedulers.newThread()) 145 | .observeOn(AndroidSchedulers.mainThread()) 146 | .subscribeWith( 147 | new DisposableObserver>() { 148 | @Override 149 | public void onComplete() { 150 | Timber.d("Retrofit call 2 completed "); 151 | } 152 | 153 | @Override 154 | public void onError(Throwable e) { 155 | Timber.e( 156 | e, 157 | "error while getting the list of contributors along with full " + "names"); 158 | } 159 | 160 | @Override 161 | public void onNext(Pair pair) { 162 | User user = pair.first; 163 | Contributor contributor = pair.second; 164 | 165 | _adapter.add( 166 | format( 167 | "%s(%s) has made %d contributions to %s", 168 | user.name, 169 | user.email, 170 | contributor.contributions, 171 | _repo.getText().toString())); 172 | 173 | _adapter.notifyDataSetChanged(); 174 | 175 | Timber.d( 176 | "%s(%s) has made %d contributions to %s", 177 | user.name, 178 | user.email, 179 | contributor.contributions, 180 | _repo.getText().toString()); 181 | } 182 | })); 183 | } 184 | } 185 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist1Fragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import static android.os.Looper.getMainLooper; 4 | 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.app.FragmentManager; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.ListView; 13 | import butterknife.BindView; 14 | import butterknife.ButterKnife; 15 | import butterknife.OnClick; 16 | import butterknife.Unbinder; 17 | import com.morihacky.android.rxjava.R; 18 | import com.morihacky.android.rxjava.wiring.LogAdapter; 19 | import io.reactivex.Flowable; 20 | import io.reactivex.disposables.CompositeDisposable; 21 | import io.reactivex.subscribers.DisposableSubscriber; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import timber.log.Timber; 25 | 26 | public class RotationPersist1Fragment extends BaseFragment 27 | implements RotationPersist1WorkerFragment.IAmYourMaster { 28 | 29 | public static final String TAG = RotationPersist1Fragment.class.toString(); 30 | 31 | @BindView(R.id.list_threading_log) 32 | ListView _logList; 33 | 34 | private LogAdapter _adapter; 35 | private List _logs; 36 | private Unbinder unbinder; 37 | 38 | private CompositeDisposable _disposables = new CompositeDisposable(); 39 | 40 | // ----------------------------------------------------------------------------------- 41 | 42 | @OnClick(R.id.btn_rotate_persist) 43 | public void startOperationFromWorkerFrag() { 44 | _logs = new ArrayList<>(); 45 | _adapter.clear(); 46 | 47 | FragmentManager fm = getActivity().getSupportFragmentManager(); 48 | RotationPersist1WorkerFragment frag = 49 | (RotationPersist1WorkerFragment) fm.findFragmentByTag(RotationPersist1WorkerFragment.TAG); 50 | 51 | if (frag == null) { 52 | frag = new RotationPersist1WorkerFragment(); 53 | fm.beginTransaction().add(frag, RotationPersist1WorkerFragment.TAG).commit(); 54 | } else { 55 | Timber.d("Worker frag already spawned"); 56 | } 57 | } 58 | 59 | @Override 60 | public void observeResults(Flowable intsFlowable) { 61 | 62 | DisposableSubscriber d = 63 | new DisposableSubscriber() { 64 | @Override 65 | public void onNext(Integer integer) { 66 | _log(String.format("Worker frag spits out - %d", integer)); 67 | } 68 | 69 | @Override 70 | public void onError(Throwable e) { 71 | Timber.e(e, "Error in worker demo frag observable"); 72 | _log("Dang! something went wrong."); 73 | } 74 | 75 | @Override 76 | public void onComplete() { 77 | _log("Observable is complete"); 78 | } 79 | }; 80 | 81 | intsFlowable 82 | .doOnSubscribe( 83 | subscription -> { 84 | _log("Subscribing to intsObservable"); 85 | }) 86 | .subscribe(d); 87 | 88 | _disposables.add(d); 89 | } 90 | 91 | // ----------------------------------------------------------------------------------- 92 | // Boilerplate 93 | // ----------------------------------------------------------------------------------- 94 | 95 | @Override 96 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 97 | super.onActivityCreated(savedInstanceState); 98 | _setupLogger(); 99 | } 100 | 101 | @Override 102 | public View onCreateView( 103 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 104 | View layout = inflater.inflate(R.layout.fragment_rotation_persist, container, false); 105 | unbinder = ButterKnife.bind(this, layout); 106 | return layout; 107 | } 108 | 109 | @Override 110 | public void onPause() { 111 | super.onPause(); 112 | _disposables.clear(); 113 | } 114 | 115 | @Override 116 | public void onDestroyView() { 117 | super.onDestroyView(); 118 | unbinder.unbind(); 119 | } 120 | 121 | private void _setupLogger() { 122 | _logs = new ArrayList<>(); 123 | _adapter = new LogAdapter(getActivity(), new ArrayList<>()); 124 | _logList.setAdapter(_adapter); 125 | } 126 | 127 | private void _log(String logMsg) { 128 | _logs.add(0, logMsg); 129 | 130 | // You can only do below stuff on main thread. 131 | new Handler(getMainLooper()) 132 | .post( 133 | () -> { 134 | _adapter.clear(); 135 | _adapter.addAll(_logs); 136 | }); 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist1WorkerFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v4.app.Fragment; 6 | import com.morihacky.android.rxjava.MainActivity; 7 | import io.reactivex.Flowable; 8 | import io.reactivex.disposables.Disposable; 9 | import io.reactivex.flowables.ConnectableFlowable; 10 | import java.util.concurrent.TimeUnit; 11 | 12 | public class RotationPersist1WorkerFragment extends Fragment { 13 | 14 | public static final String TAG = RotationPersist1WorkerFragment.class.toString(); 15 | 16 | private IAmYourMaster _masterFrag; 17 | private ConnectableFlowable _storedIntsFlowable; 18 | private Disposable _storedIntsDisposable; 19 | 20 | /** 21 | * Hold a reference to the activity -> caller fragment this way when the worker frag kicks off we 22 | * can talk back to the master and send results 23 | */ 24 | @Override 25 | public void onAttach(Context context) { 26 | super.onAttach(context); 27 | 28 | _masterFrag = 29 | (RotationPersist1Fragment) 30 | ((MainActivity) context) 31 | .getSupportFragmentManager() 32 | .findFragmentByTag(RotationPersist1Fragment.TAG); 33 | 34 | if (_masterFrag == null) { 35 | throw new ClassCastException("We did not find a master who can understand us :("); 36 | } 37 | } 38 | 39 | /** This method will only be called once when the retained Fragment is first created. */ 40 | @Override 41 | public void onCreate(Bundle savedInstanceState) { 42 | super.onCreate(savedInstanceState); 43 | 44 | // Retain this fragment across configuration changes. 45 | setRetainInstance(true); 46 | 47 | if (_storedIntsFlowable != null) { 48 | return; 49 | } 50 | 51 | Flowable intsObservable = 52 | Flowable.interval(1, TimeUnit.SECONDS).map(Long::intValue).take(20); 53 | 54 | _storedIntsFlowable = intsObservable.publish(); 55 | _storedIntsDisposable = _storedIntsFlowable.connect(); 56 | } 57 | 58 | /** The Worker fragment has started doing it's thing */ 59 | @Override 60 | public void onResume() { 61 | super.onResume(); 62 | _masterFrag.observeResults(_storedIntsFlowable); 63 | } 64 | 65 | @Override 66 | public void onDestroy() { 67 | super.onDestroy(); 68 | _storedIntsDisposable.dispose(); 69 | } 70 | 71 | /** Set the callback to null so we don't accidentally leak the Activity instance. */ 72 | @Override 73 | public void onDetach() { 74 | super.onDetach(); 75 | _masterFrag = null; 76 | } 77 | 78 | public interface IAmYourMaster { 79 | void observeResults(Flowable intsObservable); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist2Fragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import static android.os.Looper.getMainLooper; 4 | 5 | import android.os.Bundle; 6 | import android.os.Handler; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.app.FragmentManager; 9 | import android.view.LayoutInflater; 10 | import android.view.View; 11 | import android.view.ViewGroup; 12 | import android.widget.ListView; 13 | import butterknife.BindView; 14 | import butterknife.ButterKnife; 15 | import butterknife.OnClick; 16 | import com.morihacky.android.rxjava.R; 17 | import com.morihacky.android.rxjava.wiring.LogAdapter; 18 | import io.reactivex.Flowable; 19 | import io.reactivex.disposables.CompositeDisposable; 20 | import io.reactivex.subscribers.DisposableSubscriber; 21 | import java.util.ArrayList; 22 | import java.util.List; 23 | import timber.log.Timber; 24 | 25 | public class RotationPersist2Fragment extends BaseFragment 26 | implements RotationPersist2WorkerFragment.IAmYourMaster { 27 | 28 | public static final String TAG = RotationPersist2Fragment.class.toString(); 29 | 30 | @BindView(R.id.list_threading_log) 31 | ListView _logList; 32 | 33 | private LogAdapter _adapter; 34 | private List _logs; 35 | 36 | private CompositeDisposable _disposables = new CompositeDisposable(); 37 | 38 | // ----------------------------------------------------------------------------------- 39 | 40 | @OnClick(R.id.btn_rotate_persist) 41 | public void startOperationFromWorkerFrag() { 42 | _logs = new ArrayList<>(); 43 | _adapter.clear(); 44 | 45 | FragmentManager fm = getActivity().getSupportFragmentManager(); 46 | RotationPersist2WorkerFragment frag = 47 | (RotationPersist2WorkerFragment) fm.findFragmentByTag(RotationPersist2WorkerFragment.TAG); 48 | 49 | if (frag == null) { 50 | frag = new RotationPersist2WorkerFragment(); 51 | fm.beginTransaction().add(frag, RotationPersist2WorkerFragment.TAG).commit(); 52 | } else { 53 | Timber.d("Worker frag already spawned"); 54 | } 55 | } 56 | 57 | @Override 58 | public void setStream(Flowable intStream) { 59 | DisposableSubscriber d = 60 | new DisposableSubscriber() { 61 | @Override 62 | public void onNext(Integer integer) { 63 | _log(String.format("Worker frag spits out - %d", integer)); 64 | } 65 | 66 | @Override 67 | public void onError(Throwable e) { 68 | Timber.e(e, "Error in worker demo frag observable"); 69 | _log("Dang! something went wrong."); 70 | } 71 | 72 | @Override 73 | public void onComplete() { 74 | _log("Observable is complete"); 75 | } 76 | }; 77 | 78 | intStream.doOnSubscribe(subscription -> _log("Subscribing to intsObservable")).subscribe(d); 79 | 80 | _disposables.add(d); 81 | } 82 | 83 | // ----------------------------------------------------------------------------------- 84 | // Boilerplate 85 | // ----------------------------------------------------------------------------------- 86 | 87 | @Override 88 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 89 | super.onActivityCreated(savedInstanceState); 90 | _setupLogger(); 91 | } 92 | 93 | @Override 94 | public View onCreateView( 95 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 96 | View layout = inflater.inflate(R.layout.fragment_rotation_persist, container, false); 97 | ButterKnife.bind(this, layout); 98 | return layout; 99 | } 100 | 101 | @Override 102 | public void onPause() { 103 | super.onPause(); 104 | _disposables.clear(); 105 | } 106 | 107 | private void _setupLogger() { 108 | _logs = new ArrayList<>(); 109 | _adapter = new LogAdapter(getActivity(), new ArrayList<>()); 110 | _logList.setAdapter(_adapter); 111 | } 112 | 113 | private void _log(String logMsg) { 114 | _logs.add(0, logMsg); 115 | 116 | // You can only do below stuff on main thread. 117 | new Handler(getMainLooper()) 118 | .post( 119 | () -> { 120 | _adapter.clear(); 121 | _adapter.addAll(_logs); 122 | }); 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist2WorkerFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.content.Context; 4 | import android.os.Bundle; 5 | import android.support.v4.app.Fragment; 6 | import com.morihacky.android.rxjava.MainActivity; 7 | import io.reactivex.Flowable; 8 | import io.reactivex.processors.PublishProcessor; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | public class RotationPersist2WorkerFragment extends Fragment { 12 | 13 | public static final String TAG = RotationPersist2WorkerFragment.class.toString(); 14 | 15 | private PublishProcessor _intStream; 16 | private PublishProcessor _lifeCycleStream; 17 | 18 | private IAmYourMaster _masterFrag; 19 | 20 | /** 21 | * Since we're holding a reference to the Master a.k.a Activity/Master Frag remember to explicitly 22 | * remove the worker fragment or you'll have a mem leak in your hands. 23 | * 24 | *

See {@link MainActivity#onBackPressed()} 25 | */ 26 | @Override 27 | public void onAttach(Context context) { 28 | super.onAttach(context); 29 | 30 | _masterFrag = 31 | (RotationPersist2Fragment) 32 | ((MainActivity) context) 33 | .getSupportFragmentManager() 34 | .findFragmentByTag(RotationPersist2Fragment.TAG); 35 | 36 | if (_masterFrag == null) { 37 | throw new ClassCastException("We did not find a master who can understand us :("); 38 | } 39 | } 40 | 41 | /** This method will only be called once when the retained Fragment is first created. */ 42 | @Override 43 | public void onCreate(Bundle savedInstanceState) { 44 | super.onCreate(savedInstanceState); 45 | 46 | _intStream = PublishProcessor.create(); 47 | _lifeCycleStream = PublishProcessor.create(); 48 | 49 | // Retain this fragment across configuration changes. 50 | setRetainInstance(true); 51 | 52 | _intStream.takeUntil(_lifeCycleStream); 53 | 54 | Flowable.interval(1, TimeUnit.SECONDS).map(Long::intValue).take(20).subscribe(_intStream); 55 | } 56 | 57 | /** The Worker fragment has started doing it's thing */ 58 | @Override 59 | public void onResume() { 60 | super.onResume(); 61 | _masterFrag.setStream(_intStream); 62 | } 63 | 64 | @Override 65 | public void onDestroy() { 66 | super.onDestroy(); 67 | _lifeCycleStream.onComplete(); 68 | } 69 | 70 | /** Set the callback to null so we don't accidentally leak the Activity instance. */ 71 | @Override 72 | public void onDetach() { 73 | super.onDetach(); 74 | _masterFrag = null; 75 | } 76 | 77 | public interface IAmYourMaster { 78 | void setStream(Flowable intStream); 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/RotationPersist3Fragment.kt: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import android.arch.lifecycle.ViewModelProviders 5 | import android.os.Bundle 6 | import android.os.Handler 7 | import android.os.Looper.getMainLooper 8 | import android.view.LayoutInflater 9 | import android.view.View 10 | import android.view.ViewGroup 11 | import android.widget.ListView 12 | import butterknife.BindView 13 | import butterknife.ButterKnife 14 | import butterknife.OnClick 15 | import com.morihacky.android.rxjava.MyApp 16 | import com.morihacky.android.rxjava.R 17 | import com.morihacky.android.rxjava.ext.plus 18 | import com.morihacky.android.rxjava.wiring.LogAdapter 19 | import io.reactivex.Flowable 20 | import io.reactivex.disposables.CompositeDisposable 21 | import io.reactivex.disposables.Disposable 22 | import timber.log.Timber 23 | import java.util.concurrent.TimeUnit 24 | 25 | class RotationPersist3Fragment : BaseFragment() { 26 | 27 | @BindView(R.id.list_threading_log) 28 | lateinit var logList: ListView 29 | lateinit var adapter: LogAdapter 30 | lateinit var sharedViewModel: SharedViewModel 31 | 32 | private var logs: MutableList = ArrayList() 33 | private var disposables = CompositeDisposable() 34 | 35 | // ----------------------------------------------------------------------------------- 36 | 37 | override fun onCreate(savedInstanceState: Bundle?) { 38 | super.onCreate(savedInstanceState) 39 | sharedViewModel = ViewModelProviders.of(activity).get(SharedViewModel::class.java) 40 | } 41 | 42 | override fun onCreateView( 43 | inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { 44 | val layout = inflater!!.inflate(R.layout.fragment_rotation_persist, container, false) 45 | ButterKnife.bind(this, layout) 46 | return layout 47 | } 48 | 49 | @OnClick(R.id.btn_rotate_persist) 50 | fun startOperationFromWorkerFrag() { 51 | logs = ArrayList() 52 | adapter.clear() 53 | 54 | disposables += 55 | sharedViewModel 56 | .sourceStream() 57 | .subscribe({ l -> 58 | _log("Received element $l") 59 | }) 60 | } 61 | 62 | // ----------------------------------------------------------------------------------- 63 | // Boilerplate 64 | // ----------------------------------------------------------------------------------- 65 | 66 | override fun onActivityCreated(savedInstanceState: Bundle?) { 67 | super.onActivityCreated(savedInstanceState) 68 | _setupLogger() 69 | } 70 | 71 | override fun onPause() { 72 | super.onPause() 73 | disposables.clear() 74 | } 75 | 76 | private fun _setupLogger() { 77 | logs = ArrayList() 78 | adapter = LogAdapter(activity, ArrayList()) 79 | logList.adapter = adapter 80 | } 81 | 82 | private fun _log(logMsg: String) { 83 | logs.add(0, logMsg) 84 | 85 | // You can only do below stuff on main thread. 86 | Handler(getMainLooper()) 87 | .post { 88 | adapter.clear() 89 | adapter.addAll(logs) 90 | } 91 | } 92 | } 93 | 94 | class SharedViewModel : ViewModel() { 95 | var disposable: Disposable? = null 96 | 97 | var sharedObservable: Flowable = 98 | Flowable.interval(1, TimeUnit.SECONDS) 99 | .take(20) 100 | .doOnNext { l -> Timber.tag("KG").d("onNext $l") } 101 | // .replayingShare() 102 | .replay(1) 103 | .autoConnect(1) { t -> disposable = t } 104 | 105 | fun sourceStream(): Flowable { 106 | return sharedObservable 107 | } 108 | 109 | override fun onCleared() { 110 | super.onCleared() 111 | Timber.tag("KG").d("Clearing ViewModel") 112 | disposable?.dispose() 113 | MyApp.getRefWatcher().watch(this) 114 | } 115 | } 116 | 117 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/TimeoutDemoFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.support.annotation.Nullable; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ListView; 11 | import butterknife.BindView; 12 | import butterknife.ButterKnife; 13 | import butterknife.OnClick; 14 | import com.morihacky.android.rxjava.R; 15 | import com.morihacky.android.rxjava.wiring.LogAdapter; 16 | import io.reactivex.Observable; 17 | import io.reactivex.ObservableEmitter; 18 | import io.reactivex.ObservableOnSubscribe; 19 | import io.reactivex.android.schedulers.AndroidSchedulers; 20 | import io.reactivex.observers.DisposableObserver; 21 | import io.reactivex.schedulers.Schedulers; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.concurrent.TimeUnit; 25 | import timber.log.Timber; 26 | 27 | public class TimeoutDemoFragment extends BaseFragment { 28 | 29 | @BindView(R.id.list_threading_log) 30 | ListView _logsList; 31 | 32 | private LogAdapter _adapter; 33 | private DisposableObserver _disposable; 34 | private List _logs; 35 | 36 | @Override 37 | public void onDestroy() { 38 | super.onDestroy(); 39 | 40 | if (_disposable == null) { 41 | return; 42 | } 43 | 44 | _disposable.dispose(); 45 | } 46 | 47 | @Override 48 | public View onCreateView( 49 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 50 | View layout = inflater.inflate(R.layout.fragment_subject_timeout, container, false); 51 | ButterKnife.bind(this, layout); 52 | return layout; 53 | } 54 | 55 | @Override 56 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 57 | super.onActivityCreated(savedInstanceState); 58 | _setupLogger(); 59 | } 60 | 61 | @OnClick(R.id.btn_demo_timeout_1_2s) 62 | public void onStart2sTask() { 63 | _disposable = _getEventCompletionObserver(); 64 | 65 | _getObservableTask_2sToComplete() 66 | .timeout(3, TimeUnit.SECONDS) 67 | .subscribeOn(Schedulers.computation()) 68 | .observeOn(AndroidSchedulers.mainThread()) 69 | .subscribe(_disposable); 70 | } 71 | 72 | @OnClick(R.id.btn_demo_timeout_1_5s) 73 | public void onStart5sTask() { 74 | _disposable = _getEventCompletionObserver(); 75 | 76 | _getObservableTask_5sToComplete() 77 | .timeout(3, TimeUnit.SECONDS, _onTimeoutObservable()) 78 | .subscribeOn(Schedulers.computation()) 79 | .observeOn(AndroidSchedulers.mainThread()) 80 | .subscribe(_disposable); 81 | } 82 | 83 | // ----------------------------------------------------------------------------------- 84 | // Main Rx entities 85 | 86 | private Observable _getObservableTask_5sToComplete() { 87 | return Observable.create( 88 | new ObservableOnSubscribe() { 89 | @Override 90 | public void subscribe(ObservableEmitter subscriber) throws Exception { 91 | _log(String.format("Starting a 5s task")); 92 | subscriber.onNext("5 s"); 93 | try { 94 | Thread.sleep(5_000); 95 | } catch (InterruptedException e) { 96 | e.printStackTrace(); 97 | } 98 | subscriber.onComplete(); 99 | } 100 | }); 101 | } 102 | 103 | private Observable _getObservableTask_2sToComplete() { 104 | return Observable.create( 105 | new ObservableOnSubscribe() { 106 | @Override 107 | public void subscribe(ObservableEmitter subscriber) throws Exception { 108 | _log(String.format("Starting a 2s task")); 109 | subscriber.onNext("2 s"); 110 | try { 111 | Thread.sleep(2_000); 112 | } catch (InterruptedException e) { 113 | e.printStackTrace(); 114 | } 115 | subscriber.onComplete(); 116 | } 117 | }); 118 | } 119 | 120 | private Observable _onTimeoutObservable() { 121 | return Observable.create( 122 | new ObservableOnSubscribe() { 123 | 124 | @Override 125 | public void subscribe(ObservableEmitter subscriber) throws Exception { 126 | _log("Timing out this task ..."); 127 | subscriber.onError(new Throwable("Timeout Error")); 128 | } 129 | }); 130 | } 131 | 132 | private DisposableObserver _getEventCompletionObserver() { 133 | return new DisposableObserver() { 134 | @Override 135 | public void onNext(String taskType) { 136 | _log(String.format("onNext %s task", taskType)); 137 | } 138 | 139 | @Override 140 | public void onError(Throwable e) { 141 | _log(String.format("Dang a task timeout")); 142 | Timber.e(e, "Timeout Demo exception"); 143 | } 144 | 145 | @Override 146 | public void onComplete() { 147 | _log(String.format("task was completed")); 148 | } 149 | }; 150 | } 151 | 152 | // ----------------------------------------------------------------------------------- 153 | // Method that help wiring up the example (irrelevant to RxJava) 154 | 155 | private void _setupLogger() { 156 | _logs = new ArrayList<>(); 157 | _adapter = new LogAdapter(getActivity(), new ArrayList<>()); 158 | _logsList.setAdapter(_adapter); 159 | } 160 | 161 | private void _log(String logMsg) { 162 | 163 | if (_isCurrentlyOnMainThread()) { 164 | _logs.add(0, logMsg + " (main thread) "); 165 | _adapter.clear(); 166 | _adapter.addAll(_logs); 167 | } else { 168 | _logs.add(0, logMsg + " (NOT main thread) "); 169 | 170 | // You can only do below stuff on main thread. 171 | new Handler(Looper.getMainLooper()) 172 | .post( 173 | () -> { 174 | _adapter.clear(); 175 | _adapter.addAll(_logs); 176 | }); 177 | } 178 | } 179 | 180 | private boolean _isCurrentlyOnMainThread() { 181 | return Looper.myLooper() == Looper.getMainLooper(); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/fragments/TimingDemoFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.support.annotation.Nullable; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.ListView; 10 | import butterknife.BindView; 11 | import butterknife.ButterKnife; 12 | import butterknife.OnClick; 13 | import com.morihacky.android.rxjava.R; 14 | import com.morihacky.android.rxjava.wiring.LogAdapter; 15 | 16 | import butterknife.Unbinder; 17 | import io.reactivex.Flowable; 18 | import io.reactivex.subscribers.DefaultSubscriber; 19 | import io.reactivex.subscribers.DisposableSubscriber; 20 | import java.text.SimpleDateFormat; 21 | import java.util.ArrayList; 22 | import java.util.Date; 23 | import java.util.List; 24 | import java.util.Locale; 25 | import java.util.concurrent.TimeUnit; 26 | import timber.log.Timber; 27 | 28 | import static android.os.Looper.getMainLooper; 29 | import static android.os.Looper.myLooper; 30 | 31 | public class TimingDemoFragment extends BaseFragment { 32 | 33 | @BindView(R.id.list_threading_log) 34 | ListView _logsList; 35 | 36 | private LogAdapter _adapter; 37 | private List _logs; 38 | 39 | private DisposableSubscriber _subscriber1; 40 | private DisposableSubscriber _subscriber2; 41 | private Unbinder unbinder; 42 | 43 | @Override 44 | public View onCreateView( 45 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 46 | View layout = inflater.inflate(R.layout.fragment_demo_timing, container, false); 47 | unbinder = ButterKnife.bind(this, layout); 48 | return layout; 49 | } 50 | 51 | @Override 52 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 53 | super.onActivityCreated(savedInstanceState); 54 | _setupLogger(); 55 | } 56 | 57 | @Override 58 | public void onDestroyView() { 59 | super.onDestroyView(); 60 | unbinder.unbind(); 61 | } 62 | // ----------------------------------------------------------------------------------- 63 | 64 | @OnClick(R.id.btn_demo_timing_1) 65 | public void btn1_RunSingleTaskAfter2s() { 66 | _log(String.format("A1 [%s] --- BTN click", _getCurrentTimestamp())); 67 | 68 | Flowable.timer(2, TimeUnit.SECONDS) // 69 | .subscribe( 70 | new DefaultSubscriber() { 71 | @Override 72 | public void onNext(Long number) { 73 | _log(String.format("A1 [%s] NEXT", _getCurrentTimestamp())); 74 | } 75 | 76 | @Override 77 | public void onError(Throwable e) { 78 | Timber.e(e, "something went wrong in TimingDemoFragment example"); 79 | } 80 | 81 | @Override 82 | public void onComplete() { 83 | _log(String.format("A1 [%s] XXX COMPLETE", _getCurrentTimestamp())); 84 | } 85 | }); 86 | } 87 | 88 | @OnClick(R.id.btn_demo_timing_2) 89 | public void btn2_RunTask_IntervalOf1s() { 90 | if (_subscriber1 != null && !_subscriber1.isDisposed()) { 91 | _subscriber1.dispose(); 92 | _log(String.format("B2 [%s] XXX BTN KILLED", _getCurrentTimestamp())); 93 | return; 94 | } 95 | 96 | _log(String.format("B2 [%s] --- BTN click", _getCurrentTimestamp())); 97 | 98 | _subscriber1 = 99 | new DisposableSubscriber() { 100 | @Override 101 | public void onComplete() { 102 | _log(String.format("B2 [%s] XXXX COMPLETE", _getCurrentTimestamp())); 103 | } 104 | 105 | @Override 106 | public void onError(Throwable e) { 107 | Timber.e(e, "something went wrong in TimingDemoFragment example"); 108 | } 109 | 110 | @Override 111 | public void onNext(Long number) { 112 | _log(String.format("B2 [%s] NEXT", _getCurrentTimestamp())); 113 | } 114 | }; 115 | 116 | Flowable.interval(1, TimeUnit.SECONDS).subscribe(_subscriber1); 117 | } 118 | 119 | @OnClick(R.id.btn_demo_timing_3) 120 | public void btn3_RunTask_IntervalOf1s_StartImmediately() { 121 | if (_subscriber2 != null && !_subscriber2.isDisposed()) { 122 | _subscriber2.dispose(); 123 | _log(String.format("C3 [%s] XXX BTN KILLED", _getCurrentTimestamp())); 124 | return; 125 | } 126 | 127 | _log(String.format("C3 [%s] --- BTN click", _getCurrentTimestamp())); 128 | 129 | _subscriber2 = 130 | new DisposableSubscriber() { 131 | @Override 132 | public void onNext(Long number) { 133 | _log(String.format("C3 [%s] NEXT", _getCurrentTimestamp())); 134 | } 135 | 136 | @Override 137 | public void onComplete() { 138 | _log(String.format("C3 [%s] XXXX COMPLETE", _getCurrentTimestamp())); 139 | } 140 | 141 | @Override 142 | public void onError(Throwable e) { 143 | Timber.e(e, "something went wrong in TimingDemoFragment example"); 144 | } 145 | }; 146 | 147 | Flowable.interval(0, 1, TimeUnit.SECONDS).subscribe(_subscriber2); 148 | } 149 | 150 | @OnClick(R.id.btn_demo_timing_4) 151 | public void btn4_RunTask5Times_IntervalOf3s() { 152 | _log(String.format("D4 [%s] --- BTN click", _getCurrentTimestamp())); 153 | 154 | Flowable.interval(3, TimeUnit.SECONDS) 155 | .take(5) 156 | .subscribe( 157 | new DefaultSubscriber() { 158 | @Override 159 | public void onNext(Long number) { 160 | _log(String.format("D4 [%s] NEXT", _getCurrentTimestamp())); 161 | } 162 | 163 | @Override 164 | public void onError(Throwable e) { 165 | Timber.e(e, "something went wrong in TimingDemoFragment example"); 166 | } 167 | 168 | @Override 169 | public void onComplete() { 170 | _log(String.format("D4 [%s] XXX COMPLETE", _getCurrentTimestamp())); 171 | } 172 | }); 173 | } 174 | 175 | @OnClick(R.id.btn_demo_timing_5) 176 | public void btn5_RunTask5Times_IntervalOf3s() { 177 | _log(String.format("D5 [%s] --- BTN click", _getCurrentTimestamp())); 178 | 179 | Flowable.just("Do task A right away") 180 | .doOnNext(input -> _log(String.format("D5 %s [%s]", input, _getCurrentTimestamp()))) 181 | .delay(1, TimeUnit.SECONDS) 182 | .doOnNext( 183 | oldInput -> 184 | _log( 185 | String.format( 186 | "D5 %s [%s]", "Doing Task B after a delay", _getCurrentTimestamp()))) 187 | .subscribe( 188 | new DefaultSubscriber() { 189 | @Override 190 | public void onComplete() { 191 | _log(String.format("D5 [%s] XXX COMPLETE", _getCurrentTimestamp())); 192 | } 193 | 194 | @Override 195 | public void onError(Throwable e) { 196 | Timber.e(e, "something went wrong in TimingDemoFragment example"); 197 | } 198 | 199 | @Override 200 | public void onNext(String number) { 201 | _log(String.format("D5 [%s] NEXT", _getCurrentTimestamp())); 202 | } 203 | }); 204 | } 205 | 206 | // ----------------------------------------------------------------------------------- 207 | // Method that help wiring up the example (irrelevant to RxJava) 208 | 209 | @OnClick(R.id.btn_clr) 210 | public void OnClearLog() { 211 | _logs = new ArrayList<>(); 212 | _adapter.clear(); 213 | } 214 | 215 | private void _setupLogger() { 216 | _logs = new ArrayList<>(); 217 | _adapter = new LogAdapter(getActivity(), new ArrayList<>()); 218 | _logsList.setAdapter(_adapter); 219 | } 220 | 221 | private void _log(String logMsg) { 222 | _logs.add(0, String.format(logMsg + " [MainThread: %b]", getMainLooper() == myLooper())); 223 | 224 | // You can only do below stuff on main thread. 225 | new Handler(getMainLooper()) 226 | .post( 227 | () -> { 228 | _adapter.clear(); 229 | _adapter.addAll(_logs); 230 | }); 231 | } 232 | 233 | private String _getCurrentTimestamp() { 234 | return new SimpleDateFormat("k:m:s:S a", Locale.getDefault()).format(new Date()); 235 | } 236 | } 237 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationAdapter.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.pagination; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.Button; 8 | import android.widget.TextView; 9 | 10 | import com.morihacky.android.rxjava.R; 11 | import com.morihacky.android.rxjava.rxbus.RxBus; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | /** There isn't anything specific to Pagination here. Just wiring for the example */ 17 | class PaginationAdapter extends RecyclerView.Adapter { 18 | 19 | private static final int ITEM_LOG = 0; 20 | private static final int ITEM_BTN = 1; 21 | 22 | private final List _items = new ArrayList<>(); 23 | private final RxBus _bus; 24 | 25 | PaginationAdapter(RxBus bus) { 26 | _bus = bus; 27 | } 28 | 29 | void addItems(List items) { 30 | _items.addAll(items); 31 | } 32 | 33 | @Override 34 | public int getItemViewType(int position) { 35 | if (position == _items.size()) { 36 | return ITEM_BTN; 37 | } 38 | 39 | return ITEM_LOG; 40 | } 41 | 42 | @Override 43 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 44 | switch (viewType) { 45 | case ITEM_BTN: 46 | return ItemBtnViewHolder.create(parent); 47 | default: 48 | return ItemLogViewHolder.create(parent); 49 | } 50 | } 51 | 52 | @Override 53 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 54 | switch (getItemViewType(position)) { 55 | case ITEM_LOG: 56 | ((ItemLogViewHolder) holder).bindContent(_items.get(position)); 57 | return; 58 | case ITEM_BTN: 59 | ((ItemBtnViewHolder) holder).bindContent(_bus); 60 | } 61 | } 62 | 63 | @Override 64 | public int getItemCount() { 65 | return _items.size() + 1; // add 1 for paging button 66 | } 67 | 68 | private static class ItemLogViewHolder extends RecyclerView.ViewHolder { 69 | ItemLogViewHolder(View itemView) { 70 | super(itemView); 71 | } 72 | 73 | static ItemLogViewHolder create(ViewGroup parent) { 74 | return new ItemLogViewHolder( 75 | LayoutInflater.from(parent.getContext()).inflate(R.layout.item_log, parent, false)); 76 | } 77 | 78 | void bindContent(String content) { 79 | ((TextView) itemView).setText(content); 80 | } 81 | } 82 | 83 | static class ItemBtnViewHolder extends RecyclerView.ViewHolder { 84 | ItemBtnViewHolder(View itemView) { 85 | super(itemView); 86 | } 87 | 88 | static ItemBtnViewHolder create(ViewGroup parent) { 89 | return new ItemBtnViewHolder( 90 | LayoutInflater.from(parent.getContext()).inflate(R.layout.item_btn, parent, false)); 91 | } 92 | 93 | void bindContent(RxBus bus) { 94 | ((Button) itemView).setText(R.string.btn_demo_pagination_more); 95 | itemView.setOnClickListener(v -> bus.send(new ItemBtnViewHolder.PageEvent())); 96 | } 97 | 98 | static class PageEvent {} 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationAutoAdapter.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.pagination; 2 | 3 | import android.support.v7.widget.RecyclerView; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | import com.morihacky.android.rxjava.R; 9 | import com.morihacky.android.rxjava.rxbus.RxBus; 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | class PaginationAutoAdapter extends RecyclerView.Adapter { 14 | 15 | private static final int ITEM_LOG = 0; 16 | 17 | private final List _items = new ArrayList<>(); 18 | private final RxBus _bus; 19 | 20 | PaginationAutoAdapter(RxBus bus) { 21 | _bus = bus; 22 | } 23 | 24 | @Override 25 | public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 26 | return ItemLogViewHolder.create(parent); 27 | } 28 | 29 | @Override 30 | public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { 31 | ((ItemLogViewHolder) holder).bindContent(_items.get(position)); 32 | 33 | boolean lastPositionReached = position == _items.size() - 1; 34 | if (lastPositionReached) { 35 | _bus.send(new PageEvent()); 36 | } 37 | } 38 | 39 | @Override 40 | public int getItemViewType(int position) { 41 | return ITEM_LOG; 42 | } 43 | 44 | @Override 45 | public int getItemCount() { 46 | return _items.size(); 47 | } 48 | 49 | void addItems(List items) { 50 | _items.addAll(items); 51 | } 52 | 53 | private static class ItemLogViewHolder extends RecyclerView.ViewHolder { 54 | ItemLogViewHolder(View itemView) { 55 | super(itemView); 56 | } 57 | 58 | static ItemLogViewHolder create(ViewGroup parent) { 59 | return new ItemLogViewHolder( 60 | LayoutInflater.from(parent.getContext()).inflate(R.layout.item_log, parent, false)); 61 | } 62 | 63 | void bindContent(String content) { 64 | ((TextView) itemView).setText(content); 65 | } 66 | } 67 | 68 | static class PageEvent {} 69 | } 70 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationAutoFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.pagination; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ProgressBar; 11 | import butterknife.BindView; 12 | import butterknife.ButterKnife; 13 | import com.morihacky.android.rxjava.MainActivity; 14 | import com.morihacky.android.rxjava.R; 15 | import com.morihacky.android.rxjava.fragments.BaseFragment; 16 | import com.morihacky.android.rxjava.rxbus.RxBus; 17 | import io.reactivex.Flowable; 18 | import io.reactivex.android.schedulers.AndroidSchedulers; 19 | import io.reactivex.disposables.CompositeDisposable; 20 | import io.reactivex.disposables.Disposable; 21 | import io.reactivex.processors.PublishProcessor; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | public class PaginationAutoFragment extends BaseFragment { 27 | 28 | @BindView(R.id.list_paging) 29 | RecyclerView _pagingList; 30 | 31 | @BindView(R.id.progress_paging) 32 | ProgressBar _progressBar; 33 | 34 | private PaginationAutoAdapter _adapter; 35 | private RxBus _bus; 36 | private CompositeDisposable _disposables; 37 | private PublishProcessor _paginator; 38 | private boolean _requestUnderWay = false; 39 | 40 | @Override 41 | public View onCreateView( 42 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 43 | View layout = inflater.inflate(R.layout.fragment_pagination, container, false); 44 | ButterKnife.bind(this, layout); 45 | return layout; 46 | } 47 | 48 | @Override 49 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 50 | super.onActivityCreated(savedInstanceState); 51 | 52 | _bus = ((MainActivity) getActivity()).getRxBusSingleton(); 53 | 54 | LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); 55 | layoutManager.setOrientation(LinearLayoutManager.VERTICAL); 56 | _pagingList.setLayoutManager(layoutManager); 57 | 58 | _adapter = new PaginationAutoAdapter(_bus); 59 | _pagingList.setAdapter(_adapter); 60 | 61 | _paginator = PublishProcessor.create(); 62 | } 63 | 64 | @Override 65 | public void onStart() { 66 | super.onStart(); 67 | _disposables = new CompositeDisposable(); 68 | 69 | Disposable d2 = 70 | _paginator 71 | .onBackpressureDrop() 72 | .doOnNext( 73 | i -> { 74 | _requestUnderWay = true; 75 | _progressBar.setVisibility(View.VISIBLE); 76 | }) 77 | .concatMap(this::_itemsFromNetworkCall) 78 | .observeOn(AndroidSchedulers.mainThread()) 79 | .map( 80 | items -> { 81 | _adapter.addItems(items); 82 | _adapter.notifyDataSetChanged(); 83 | 84 | return items; 85 | }) 86 | .doOnNext( 87 | i -> { 88 | _requestUnderWay = false; 89 | _progressBar.setVisibility(View.INVISIBLE); 90 | }) 91 | .subscribe(); 92 | 93 | // I'm using an RxBus purely to hear from a nested button click 94 | // we don't really need Rx for this part. it's just easy ¯\_(ツ)_/¯ 95 | 96 | Disposable d1 = 97 | _bus.asFlowable() 98 | .filter(o -> !_requestUnderWay) 99 | .subscribe( 100 | event -> { 101 | if (event instanceof PaginationAutoAdapter.PageEvent) { 102 | 103 | // trigger the paginator for the next event 104 | int nextPage = _adapter.getItemCount(); 105 | _paginator.onNext(nextPage); 106 | } 107 | }); 108 | 109 | _disposables.add(d1); 110 | _disposables.add(d2); 111 | 112 | _paginator.onNext(0); 113 | } 114 | 115 | @Override 116 | public void onStop() { 117 | super.onStop(); 118 | _disposables.clear(); 119 | } 120 | 121 | /** Fake Observable that simulates a network call and then sends down a list of items */ 122 | private Flowable> _itemsFromNetworkCall(int pageStart) { 123 | return Flowable.just(true) 124 | .observeOn(AndroidSchedulers.mainThread()) 125 | .delay(2, TimeUnit.SECONDS) 126 | .map( 127 | dummy -> { 128 | List items = new ArrayList<>(); 129 | for (int i = 0; i < 10; i++) { 130 | items.add("Item " + (pageStart + i)); 131 | } 132 | return items; 133 | }); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/pagination/PaginationFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.pagination; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v7.widget.LinearLayoutManager; 6 | import android.support.v7.widget.RecyclerView; 7 | import android.view.LayoutInflater; 8 | import android.view.View; 9 | import android.view.ViewGroup; 10 | import android.widget.ProgressBar; 11 | import butterknife.BindView; 12 | import butterknife.ButterKnife; 13 | import com.morihacky.android.rxjava.MainActivity; 14 | import com.morihacky.android.rxjava.R; 15 | import com.morihacky.android.rxjava.fragments.BaseFragment; 16 | import com.morihacky.android.rxjava.rxbus.RxBus; 17 | import io.reactivex.Flowable; 18 | import io.reactivex.android.schedulers.AndroidSchedulers; 19 | import io.reactivex.disposables.CompositeDisposable; 20 | import io.reactivex.disposables.Disposable; 21 | import io.reactivex.processors.PublishProcessor; 22 | import java.util.ArrayList; 23 | import java.util.List; 24 | import java.util.concurrent.TimeUnit; 25 | 26 | public class PaginationFragment extends BaseFragment { 27 | 28 | @BindView(R.id.list_paging) 29 | RecyclerView _pagingList; 30 | 31 | @BindView(R.id.progress_paging) 32 | ProgressBar _progressBar; 33 | 34 | private PaginationAdapter _adapter; 35 | private RxBus _bus; 36 | private CompositeDisposable _disposables; 37 | private PublishProcessor _paginator; 38 | 39 | @Override 40 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 41 | super.onActivityCreated(savedInstanceState); 42 | 43 | _bus = ((MainActivity) getActivity()).getRxBusSingleton(); 44 | 45 | LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity()); 46 | layoutManager.setOrientation(LinearLayoutManager.VERTICAL); 47 | _pagingList.setLayoutManager(layoutManager); 48 | 49 | _adapter = new PaginationAdapter(_bus); 50 | _pagingList.setAdapter(_adapter); 51 | 52 | _paginator = PublishProcessor.create(); 53 | } 54 | 55 | @Override 56 | public void onStart() { 57 | super.onStart(); 58 | _disposables = new CompositeDisposable(); 59 | 60 | Disposable d2 = 61 | _paginator 62 | .onBackpressureDrop() 63 | .concatMap(nextPage -> _itemsFromNetworkCall(nextPage + 1, 10)) 64 | .observeOn(AndroidSchedulers.mainThread()) 65 | .map( 66 | items -> { 67 | int start = _adapter.getItemCount() - 1; 68 | 69 | _adapter.addItems(items); 70 | _adapter.notifyItemRangeInserted(start, 10); 71 | 72 | _progressBar.setVisibility(View.INVISIBLE); 73 | 74 | return items; 75 | }) 76 | .subscribe(); 77 | 78 | // I'm using an Rxbus purely to hear from a nested button click 79 | // we don't really need Rx for this part. it's just easy ¯\_(ツ)_/¯ 80 | Disposable d1 = 81 | _bus.asFlowable() 82 | .subscribe( 83 | event -> { 84 | if (event instanceof PaginationAdapter.ItemBtnViewHolder.PageEvent) { 85 | 86 | // trigger the paginator for the next event 87 | int nextPage = _adapter.getItemCount() - 1; 88 | _paginator.onNext(nextPage); 89 | } 90 | }); 91 | 92 | _disposables.add(d1); 93 | _disposables.add(d2); 94 | } 95 | 96 | @Override 97 | public void onStop() { 98 | super.onStop(); 99 | _disposables.clear(); 100 | } 101 | 102 | /** Fake Observable that simulates a network call and then sends down a list of items */ 103 | private Flowable> _itemsFromNetworkCall(int start, int count) { 104 | return Flowable.just(true) 105 | .observeOn(AndroidSchedulers.mainThread()) 106 | .doOnNext(dummy -> _progressBar.setVisibility(View.VISIBLE)) 107 | .delay(2, TimeUnit.SECONDS) 108 | .map( 109 | dummy -> { 110 | List items = new ArrayList<>(); 111 | for (int i = 0; i < count; i++) { 112 | items.add("Item " + (start + i)); 113 | } 114 | return items; 115 | }); 116 | } 117 | 118 | // ----------------------------------------------------------------------------------- 119 | // WIRING up the views required for this example 120 | 121 | @Override 122 | public View onCreateView( 123 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 124 | View layout = inflater.inflate(R.layout.fragment_pagination, container, false); 125 | ButterKnife.bind(this, layout); 126 | return layout; 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/retrofit/Contributor.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.retrofit; 2 | 3 | public class Contributor { 4 | public String login; 5 | public long contributions; 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/retrofit/GithubApi.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.retrofit; 2 | 3 | import java.util.List; 4 | 5 | import io.reactivex.Observable; 6 | import retrofit2.http.GET; 7 | import retrofit2.http.Path; 8 | 9 | public interface GithubApi { 10 | 11 | /** See https://developer.github.com/v3/repos/#list-contributors */ 12 | @GET("/repos/{owner}/{repo}/contributors") 13 | Observable> contributors( 14 | @Path("owner") String owner, @Path("repo") String repo); 15 | 16 | @GET("/repos/{owner}/{repo}/contributors") 17 | List getContributors(@Path("owner") String owner, @Path("repo") String repo); 18 | 19 | /** See https://developer.github.com/v3/users/ */ 20 | @GET("/users/{user}") 21 | Observable user(@Path("user") String user); 22 | 23 | /** See https://developer.github.com/v3/users/ */ 24 | @GET("/users/{user}") 25 | User getUser(@Path("user") String user); 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/retrofit/GithubService.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.retrofit; 2 | 3 | import android.text.TextUtils; 4 | 5 | import com.jakewharton.retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 6 | 7 | import okhttp3.OkHttpClient; 8 | import okhttp3.Request; 9 | import retrofit2.Retrofit; 10 | import retrofit2.converter.gson.GsonConverterFactory; 11 | 12 | import static java.lang.String.format; 13 | 14 | public class GithubService { 15 | 16 | private GithubService() {} 17 | 18 | public static GithubApi createGithubService(final String githubToken) { 19 | Retrofit.Builder builder = 20 | new Retrofit.Builder() 21 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 22 | .addConverterFactory(GsonConverterFactory.create()) 23 | .baseUrl("https://api.github.com"); 24 | 25 | if (!TextUtils.isEmpty(githubToken)) { 26 | 27 | OkHttpClient client = 28 | new OkHttpClient.Builder() 29 | .addInterceptor( 30 | chain -> { 31 | Request request = chain.request(); 32 | Request newReq = 33 | request 34 | .newBuilder() 35 | .addHeader("Authorization", format("token %s", githubToken)) 36 | .build(); 37 | return chain.proceed(newReq); 38 | }) 39 | .build(); 40 | 41 | builder.client(client); 42 | } 43 | 44 | return builder.build().create(GithubApi.class); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/retrofit/User.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.retrofit; 2 | 3 | public class User { 4 | public String name; 5 | public String email; 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBus.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.rxbus; 2 | 3 | import com.jakewharton.rxrelay2.PublishRelay; 4 | import com.jakewharton.rxrelay2.Relay; 5 | 6 | import io.reactivex.BackpressureStrategy; 7 | import io.reactivex.Flowable; 8 | 9 | /** courtesy: https://gist.github.com/benjchristensen/04eef9ca0851f3a5d7bf */ 10 | public class RxBus { 11 | 12 | private final Relay _bus = PublishRelay.create().toSerialized(); 13 | 14 | public void send(Object o) { 15 | _bus.accept(o); 16 | } 17 | 18 | public Flowable asFlowable() { 19 | return _bus.toFlowable(BackpressureStrategy.LATEST); 20 | } 21 | 22 | public boolean hasObservers() { 23 | return _bus.hasObservers(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemoFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.rxbus; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import butterknife.ButterKnife; 9 | import com.morihacky.android.rxjava.R; 10 | import com.morihacky.android.rxjava.fragments.BaseFragment; 11 | 12 | public class RxBusDemoFragment extends BaseFragment { 13 | 14 | @Override 15 | public View onCreateView( 16 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 17 | View layout = inflater.inflate(R.layout.fragment_rxbus_demo, container, false); 18 | ButterKnife.bind(this, layout); 19 | return layout; 20 | } 21 | 22 | @Override 23 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 24 | super.onActivityCreated(savedInstanceState); 25 | 26 | getActivity() 27 | .getSupportFragmentManager() 28 | .beginTransaction() 29 | .replace(R.id.demo_rxbus_frag_1, new RxBusDemo_TopFragment()) 30 | .replace(R.id.demo_rxbus_frag_2, new RxBusDemo_Bottom3Fragment()) 31 | //.replace(R.id.demo_rxbus_frag_2, new RxBusDemo_Bottom2Fragment()) 32 | //.replace(R.id.demo_rxbus_frag_2, new RxBusDemo_Bottom1Fragment()) 33 | .commit(); 34 | } 35 | 36 | public static class TapEvent {} 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom1Fragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.rxbus; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.view.ViewCompat; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | import butterknife.BindView; 11 | import butterknife.ButterKnife; 12 | import com.morihacky.android.rxjava.MainActivity; 13 | import com.morihacky.android.rxjava.R; 14 | import com.morihacky.android.rxjava.fragments.BaseFragment; 15 | import io.reactivex.disposables.CompositeDisposable; 16 | 17 | public class RxBusDemo_Bottom1Fragment extends BaseFragment { 18 | 19 | @BindView(R.id.demo_rxbus_tap_txt) 20 | TextView _tapEventTxtShow; 21 | 22 | private CompositeDisposable _disposables; 23 | private RxBus _rxBus; 24 | 25 | @Override 26 | public View onCreateView( 27 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 28 | View layout = inflater.inflate(R.layout.fragment_rxbus_bottom, container, false); 29 | ButterKnife.bind(this, layout); 30 | return layout; 31 | } 32 | 33 | @Override 34 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 35 | super.onActivityCreated(savedInstanceState); 36 | _rxBus = ((MainActivity) getActivity()).getRxBusSingleton(); 37 | } 38 | 39 | @Override 40 | public void onStart() { 41 | super.onStart(); 42 | _disposables = new CompositeDisposable(); 43 | 44 | _disposables.add( 45 | _rxBus 46 | .asFlowable() 47 | .subscribe( 48 | event -> { 49 | if (event instanceof RxBusDemoFragment.TapEvent) { 50 | _showTapText(); 51 | } 52 | })); 53 | } 54 | 55 | @Override 56 | public void onStop() { 57 | super.onStop(); 58 | _disposables.clear(); 59 | } 60 | 61 | private void _showTapText() { 62 | _tapEventTxtShow.setVisibility(View.VISIBLE); 63 | _tapEventTxtShow.setAlpha(1f); 64 | ViewCompat.animate(_tapEventTxtShow).alphaBy(-1f).setDuration(400); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom2Fragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.rxbus; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.view.ViewCompat; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | import butterknife.BindView; 11 | import butterknife.ButterKnife; 12 | import com.morihacky.android.rxjava.MainActivity; 13 | import com.morihacky.android.rxjava.R; 14 | import com.morihacky.android.rxjava.fragments.BaseFragment; 15 | import io.reactivex.Flowable; 16 | import io.reactivex.android.schedulers.AndroidSchedulers; 17 | import io.reactivex.disposables.CompositeDisposable; 18 | import java.util.List; 19 | import java.util.concurrent.TimeUnit; 20 | 21 | public class RxBusDemo_Bottom2Fragment extends BaseFragment { 22 | 23 | @BindView(R.id.demo_rxbus_tap_txt) 24 | TextView _tapEventTxtShow; 25 | 26 | @BindView(R.id.demo_rxbus_tap_count) 27 | TextView _tapEventCountShow; 28 | 29 | private RxBus _rxBus; 30 | private CompositeDisposable _disposables; 31 | 32 | @Override 33 | public View onCreateView( 34 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 35 | View layout = inflater.inflate(R.layout.fragment_rxbus_bottom, container, false); 36 | ButterKnife.bind(this, layout); 37 | return layout; 38 | } 39 | 40 | @Override 41 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 42 | super.onActivityCreated(savedInstanceState); 43 | _rxBus = ((MainActivity) getActivity()).getRxBusSingleton(); 44 | } 45 | 46 | @Override 47 | public void onStart() { 48 | super.onStart(); 49 | _disposables = new CompositeDisposable(); 50 | 51 | Flowable tapEventEmitter = _rxBus.asFlowable().share(); 52 | 53 | _disposables.add( 54 | tapEventEmitter.subscribe( 55 | event -> { 56 | if (event instanceof RxBusDemoFragment.TapEvent) { 57 | _showTapText(); 58 | } 59 | })); 60 | 61 | Flowable debouncedEmitter = tapEventEmitter.debounce(1, TimeUnit.SECONDS); 62 | Flowable> debouncedBufferEmitter = tapEventEmitter.buffer(debouncedEmitter); 63 | 64 | _disposables.add( 65 | debouncedBufferEmitter 66 | .observeOn(AndroidSchedulers.mainThread()) 67 | .subscribe( 68 | taps -> { 69 | _showTapCount(taps.size()); 70 | })); 71 | } 72 | 73 | @Override 74 | public void onStop() { 75 | super.onStop(); 76 | _disposables.clear(); 77 | } 78 | 79 | // ----------------------------------------------------------------------------------- 80 | // Helper to show the text via an animation 81 | 82 | private void _showTapText() { 83 | _tapEventTxtShow.setVisibility(View.VISIBLE); 84 | _tapEventTxtShow.setAlpha(1f); 85 | ViewCompat.animate(_tapEventTxtShow).alphaBy(-1f).setDuration(400); 86 | } 87 | 88 | private void _showTapCount(int size) { 89 | _tapEventCountShow.setText(String.valueOf(size)); 90 | _tapEventCountShow.setVisibility(View.VISIBLE); 91 | _tapEventCountShow.setScaleX(1f); 92 | _tapEventCountShow.setScaleY(1f); 93 | ViewCompat.animate(_tapEventCountShow) 94 | .scaleXBy(-1f) 95 | .scaleYBy(-1f) 96 | .setDuration(800) 97 | .setStartDelay(100); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_Bottom3Fragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.rxbus; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.support.v4.view.ViewCompat; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | import butterknife.BindView; 11 | import butterknife.ButterKnife; 12 | import com.morihacky.android.rxjava.MainActivity; 13 | import com.morihacky.android.rxjava.R; 14 | import com.morihacky.android.rxjava.fragments.BaseFragment; 15 | import io.reactivex.android.schedulers.AndroidSchedulers; 16 | import io.reactivex.disposables.CompositeDisposable; 17 | import io.reactivex.flowables.ConnectableFlowable; 18 | import java.util.concurrent.TimeUnit; 19 | 20 | public class RxBusDemo_Bottom3Fragment extends BaseFragment { 21 | 22 | @BindView(R.id.demo_rxbus_tap_txt) 23 | TextView _tapEventTxtShow; 24 | 25 | @BindView(R.id.demo_rxbus_tap_count) 26 | TextView _tapEventCountShow; 27 | 28 | private RxBus _rxBus; 29 | private CompositeDisposable _disposables; 30 | 31 | @Override 32 | public View onCreateView( 33 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 34 | View layout = inflater.inflate(R.layout.fragment_rxbus_bottom, container, false); 35 | ButterKnife.bind(this, layout); 36 | return layout; 37 | } 38 | 39 | @Override 40 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 41 | super.onActivityCreated(savedInstanceState); 42 | _rxBus = ((MainActivity) getActivity()).getRxBusSingleton(); 43 | } 44 | 45 | @Override 46 | public void onStart() { 47 | super.onStart(); 48 | _disposables = new CompositeDisposable(); 49 | 50 | ConnectableFlowable tapEventEmitter = _rxBus.asFlowable().publish(); 51 | 52 | _disposables // 53 | .add( 54 | tapEventEmitter.subscribe( 55 | event -> { 56 | if (event instanceof RxBusDemoFragment.TapEvent) { 57 | _showTapText(); 58 | } 59 | })); 60 | 61 | _disposables.add( 62 | tapEventEmitter 63 | .publish(stream -> stream.buffer(stream.debounce(1, TimeUnit.SECONDS))) 64 | .observeOn(AndroidSchedulers.mainThread()) 65 | .subscribe( 66 | taps -> { 67 | _showTapCount(taps.size()); 68 | })); 69 | 70 | _disposables.add(tapEventEmitter.connect()); 71 | } 72 | 73 | @Override 74 | public void onStop() { 75 | super.onStop(); 76 | _disposables.clear(); 77 | } 78 | 79 | // ----------------------------------------------------------------------------------- 80 | // Helper to show the text via an animation 81 | 82 | private void _showTapText() { 83 | _tapEventTxtShow.setVisibility(View.VISIBLE); 84 | _tapEventTxtShow.setAlpha(1f); 85 | ViewCompat.animate(_tapEventTxtShow).alphaBy(-1f).setDuration(400); 86 | } 87 | 88 | private void _showTapCount(int size) { 89 | _tapEventCountShow.setText(String.valueOf(size)); 90 | _tapEventCountShow.setVisibility(View.VISIBLE); 91 | _tapEventCountShow.setScaleX(1f); 92 | _tapEventCountShow.setScaleY(1f); 93 | ViewCompat.animate(_tapEventCountShow) 94 | .scaleXBy(-1f) 95 | .scaleYBy(-1f) 96 | .setDuration(800) 97 | .setStartDelay(100); 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/rxbus/RxBusDemo_TopFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.rxbus; 2 | 3 | import android.os.Bundle; 4 | import android.support.annotation.Nullable; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | import butterknife.ButterKnife; 9 | import butterknife.OnClick; 10 | import com.morihacky.android.rxjava.MainActivity; 11 | import com.morihacky.android.rxjava.R; 12 | import com.morihacky.android.rxjava.fragments.BaseFragment; 13 | 14 | public class RxBusDemo_TopFragment extends BaseFragment { 15 | 16 | private RxBus _rxBus; 17 | 18 | @Override 19 | public View onCreateView( 20 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 21 | View layout = inflater.inflate(R.layout.fragment_rxbus_top, container, false); 22 | ButterKnife.bind(this, layout); 23 | return layout; 24 | } 25 | 26 | @Override 27 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 28 | super.onActivityCreated(savedInstanceState); 29 | _rxBus = ((MainActivity) getActivity()).getRxBusSingleton(); 30 | } 31 | 32 | @OnClick(R.id.btn_demo_rxbus_tap) 33 | public void onTapButtonClicked() { 34 | if (_rxBus.hasObservers()) { 35 | _rxBus.send(new RxBusDemoFragment.TapEvent()); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/volley/MyVolley.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.volley; 2 | 3 | import android.content.Context; 4 | import com.android.volley.RequestQueue; 5 | import com.android.volley.toolbox.Volley; 6 | 7 | /** 8 | * Helper class that is used to provide references to initialized RequestQueue(s) and ImageLoader(s) 9 | */ 10 | public class MyVolley { 11 | private static RequestQueue mRequestQueue; 12 | 13 | private MyVolley() { 14 | // no instances 15 | } 16 | 17 | public static void init(Context context) { 18 | mRequestQueue = Volley.newRequestQueue(context); 19 | } 20 | 21 | static RequestQueue getRequestQueue() { 22 | if (mRequestQueue != null) { 23 | return mRequestQueue; 24 | } else { 25 | throw new IllegalStateException("RequestQueue not initialized"); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/volley/VolleyDemoFragment.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.volley; 2 | 3 | import android.os.Bundle; 4 | import android.os.Handler; 5 | import android.os.Looper; 6 | import android.support.annotation.Nullable; 7 | import android.util.Log; 8 | import android.view.LayoutInflater; 9 | import android.view.View; 10 | import android.view.ViewGroup; 11 | import android.widget.ListView; 12 | import butterknife.BindView; 13 | import butterknife.ButterKnife; 14 | import butterknife.OnClick; 15 | import com.android.volley.Request; 16 | import com.android.volley.VolleyError; 17 | import com.android.volley.toolbox.JsonObjectRequest; 18 | import com.android.volley.toolbox.RequestFuture; 19 | import com.morihacky.android.rxjava.R; 20 | import com.morihacky.android.rxjava.fragments.BaseFragment; 21 | import com.morihacky.android.rxjava.wiring.LogAdapter; 22 | 23 | import butterknife.Unbinder; 24 | import io.reactivex.Flowable; 25 | import io.reactivex.android.schedulers.AndroidSchedulers; 26 | import io.reactivex.disposables.CompositeDisposable; 27 | import io.reactivex.schedulers.Schedulers; 28 | import io.reactivex.subscribers.DisposableSubscriber; 29 | import java.nio.charset.Charset; 30 | import java.util.ArrayList; 31 | import java.util.List; 32 | import java.util.concurrent.ExecutionException; 33 | import org.json.JSONObject; 34 | import timber.log.Timber; 35 | 36 | public class VolleyDemoFragment extends BaseFragment { 37 | 38 | public static final String TAG = "VolleyDemoFragment"; 39 | 40 | @BindView(R.id.list_threading_log) 41 | ListView _logsList; 42 | 43 | private List _logs; 44 | private LogAdapter _adapter; 45 | private Unbinder unbinder; 46 | 47 | private CompositeDisposable _disposables = new CompositeDisposable(); 48 | 49 | @Override 50 | public View onCreateView( 51 | LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { 52 | View layout = inflater.inflate(R.layout.fragment_volley, container, false); 53 | unbinder = ButterKnife.bind(this, layout); 54 | return layout; 55 | } 56 | 57 | @Override 58 | public void onActivityCreated(@Nullable Bundle savedInstanceState) { 59 | super.onActivityCreated(savedInstanceState); 60 | _setupLogger(); 61 | } 62 | 63 | @Override 64 | public void onPause() { 65 | super.onPause(); 66 | _disposables.clear(); 67 | } 68 | 69 | @Override 70 | public void onDestroyView() { 71 | super.onDestroyView(); 72 | unbinder.unbind(); 73 | } 74 | 75 | /** 76 | * Creates and returns an observable generated from the Future returned from {@code 77 | * getRouteData()}. The observable can then be subscribed to as shown in {@code 78 | * startVolleyRequest()} 79 | * 80 | * @return Observable 81 | */ 82 | public Flowable newGetRouteData() { 83 | return Flowable.defer( 84 | () -> { 85 | try { 86 | return Flowable.just(getRouteData()); 87 | } catch (InterruptedException | ExecutionException e) { 88 | Log.e("routes", e.getMessage()); 89 | return Flowable.error(e); 90 | } 91 | }); 92 | } 93 | 94 | @OnClick(R.id.btn_start_operation) 95 | void startRequest() { 96 | startVolleyRequest(); 97 | } 98 | 99 | private void startVolleyRequest() { 100 | DisposableSubscriber d = 101 | new DisposableSubscriber() { 102 | @Override 103 | public void onNext(JSONObject jsonObject) { 104 | Log.e(TAG, "onNext " + jsonObject.toString()); 105 | _log("onNext " + jsonObject.toString()); 106 | } 107 | 108 | @Override 109 | public void onError(Throwable e) { 110 | VolleyError cause = (VolleyError) e.getCause(); 111 | String s = new String(cause.networkResponse.data, Charset.forName("UTF-8")); 112 | Log.e(TAG, s); 113 | Log.e(TAG, cause.toString()); 114 | _log("onError " + s); 115 | } 116 | 117 | @Override 118 | public void onComplete() { 119 | Log.e(TAG, "onCompleted"); 120 | Timber.d("----- onCompleted"); 121 | _log("onCompleted "); 122 | } 123 | }; 124 | 125 | newGetRouteData() 126 | .subscribeOn(Schedulers.io()) 127 | .observeOn(AndroidSchedulers.mainThread()) 128 | .subscribe(d); 129 | 130 | _disposables.add(d); 131 | } 132 | 133 | /** 134 | * Converts the Asynchronous Request into a Synchronous Future that can be used to block via 135 | * {@code Future.get()}. Observables require blocking/synchronous functions 136 | * 137 | * @return JSONObject 138 | * @throws ExecutionException 139 | * @throws InterruptedException 140 | */ 141 | private JSONObject getRouteData() throws ExecutionException, InterruptedException { 142 | RequestFuture future = RequestFuture.newFuture(); 143 | String url = "http://www.weather.com.cn/adat/sk/101010100.html"; 144 | JsonObjectRequest req = new JsonObjectRequest(Request.Method.GET, url, future, future); 145 | MyVolley.getRequestQueue().add(req); 146 | return future.get(); 147 | } 148 | 149 | // ----------------------------------------------------------------------------------- 150 | // Methods that help wiring up the example (irrelevant to RxJava) 151 | 152 | private void _setupLogger() { 153 | _logs = new ArrayList<>(); 154 | _adapter = new LogAdapter(getActivity(), new ArrayList<>()); 155 | _logsList.setAdapter(_adapter); 156 | } 157 | 158 | private void _log(String logMsg) { 159 | 160 | if (_isCurrentlyOnMainThread()) { 161 | _logs.add(0, logMsg + " (main thread) "); 162 | _adapter.clear(); 163 | _adapter.addAll(_logs); 164 | } else { 165 | _logs.add(0, logMsg + " (NOT main thread) "); 166 | 167 | // You can only do below stuff on main thread. 168 | new Handler(Looper.getMainLooper()) 169 | .post( 170 | () -> { 171 | _adapter.clear(); 172 | _adapter.addAll(_logs); 173 | }); 174 | } 175 | } 176 | 177 | private boolean _isCurrentlyOnMainThread() { 178 | return Looper.myLooper() == Looper.getMainLooper(); 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /app/src/main/java/com/morihacky/android/rxjava/wiring/LogAdapter.java: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.wiring; 2 | 3 | import android.content.Context; 4 | import android.widget.ArrayAdapter; 5 | import com.morihacky.android.rxjava.R; 6 | import java.util.List; 7 | 8 | public class LogAdapter extends ArrayAdapter { 9 | 10 | public LogAdapter(Context context, List logs) { 11 | super(context, R.layout.item_log, R.id.item_log, logs); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/morihacky/android/rxjava/ext/RxExt.kt: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.ext 2 | 3 | import io.reactivex.disposables.CompositeDisposable 4 | import io.reactivex.disposables.Disposable 5 | 6 | operator fun CompositeDisposable.plus(disposable: Disposable): CompositeDisposable { 7 | add(disposable) 8 | return this 9 | } 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/morihacky/android/rxjava/fragments/MulticastPlaygroundFragment.kt: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.os.Handler 6 | import android.os.Looper 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.* 11 | import butterknife.BindView 12 | import butterknife.ButterKnife 13 | import butterknife.OnClick 14 | import com.jakewharton.rx.replayingShare 15 | import com.morihacky.android.rxjava.R 16 | import io.reactivex.Observable 17 | import io.reactivex.disposables.Disposable 18 | import java.util.concurrent.TimeUnit 19 | 20 | class MulticastPlaygroundFragment : BaseFragment() { 21 | 22 | @BindView(R.id.list_threading_log) lateinit var logList: ListView 23 | @BindView(R.id.dropdown) lateinit var pickOperatorDD: Spinner 24 | @BindView(R.id.msg_text) lateinit var messageText: TextView 25 | 26 | private lateinit var sharedObservable: Observable 27 | private lateinit var adapter: LogAdapter 28 | 29 | private var logs: MutableList = ArrayList() 30 | private var disposable1: Disposable? = null 31 | private var disposable2: Disposable? = null 32 | 33 | override fun onCreateView(inflater: LayoutInflater?, 34 | container: ViewGroup?, 35 | savedInstanceState: Bundle?): View? { 36 | val layout = inflater!!.inflate(R.layout.fragment_multicast_playground, container, false) 37 | ButterKnife.bind(this, layout) 38 | 39 | _setupLogger() 40 | _setupDropdown() 41 | 42 | return layout 43 | } 44 | 45 | @OnClick(R.id.btn_1) 46 | fun onBtn1Click() { 47 | 48 | disposable1?.let { 49 | it.dispose() 50 | _log("subscriber 1 disposed") 51 | disposable1 = null 52 | return 53 | } 54 | 55 | disposable1 = 56 | sharedObservable 57 | .doOnSubscribe { _log("subscriber 1 (subscribed)") } 58 | .subscribe({ long -> _log("subscriber 1: onNext $long") }) 59 | 60 | } 61 | 62 | @OnClick(R.id.btn_2) 63 | fun onBtn2Click() { 64 | disposable2?.let { 65 | it.dispose() 66 | _log("subscriber 2 disposed") 67 | disposable2 = null 68 | return 69 | } 70 | 71 | disposable2 = 72 | sharedObservable 73 | .doOnSubscribe { _log("subscriber 2 (subscribed)") } 74 | .subscribe({ long -> _log("subscriber 2: onNext $long") }) 75 | } 76 | 77 | @OnClick(R.id.btn_3) 78 | fun onBtn3Click() { 79 | logs = ArrayList() 80 | adapter.clear() 81 | } 82 | 83 | // ----------------------------------------------------------------------------------- 84 | // Method that help wiring up the example (irrelevant to RxJava) 85 | 86 | private fun _log(logMsg: String) { 87 | 88 | if (_isCurrentlyOnMainThread()) { 89 | logs.add(0, logMsg + " (main thread) ") 90 | adapter.clear() 91 | adapter.addAll(logs) 92 | } else { 93 | logs.add(0, logMsg + " (NOT main thread) ") 94 | 95 | // You can only do below stuff on main thread. 96 | Handler(Looper.getMainLooper()).post { 97 | adapter.clear() 98 | adapter.addAll(logs) 99 | } 100 | } 101 | } 102 | 103 | private fun _setupLogger() { 104 | logs = ArrayList() 105 | adapter = LogAdapter(activity, ArrayList()) 106 | logList.adapter = adapter 107 | } 108 | 109 | private fun _setupDropdown() { 110 | pickOperatorDD.adapter = ArrayAdapter(context, 111 | android.R.layout.simple_spinner_dropdown_item, 112 | arrayOf(".publish().refCount()", 113 | ".publish().autoConnect(2)", 114 | ".replay(1).autoConnect(2)", 115 | ".replay(1).refCount()", 116 | ".replayingShare()")) 117 | 118 | 119 | pickOperatorDD.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { 120 | 121 | override fun onItemSelected(p0: AdapterView<*>?, p1: View?, index: Int, p3: Long) { 122 | 123 | val sourceObservable = Observable.interval(0L, 3, TimeUnit.SECONDS) 124 | .doOnSubscribe { _log("observer (subscribed)") } 125 | .doOnDispose { _log("observer (disposed)") } 126 | .doOnTerminate { _log("observer (terminated)") } 127 | 128 | sharedObservable = 129 | when (index) { 130 | 0 -> { 131 | messageText.setText(R.string.msg_demo_multicast_publishRefCount) 132 | sourceObservable.publish().refCount() 133 | } 134 | 1 -> { 135 | messageText.setText(R.string.msg_demo_multicast_publishAutoConnect) 136 | sourceObservable.publish().autoConnect(2) 137 | } 138 | 2 -> { 139 | messageText.setText(R.string.msg_demo_multicast_replayAutoConnect) 140 | sourceObservable.replay(1).autoConnect(2) 141 | } 142 | 3 -> { 143 | messageText.setText(R.string.msg_demo_multicast_replayRefCount) 144 | sourceObservable.replay(1).refCount() 145 | } 146 | 4 -> { 147 | messageText.setText(R.string.msg_demo_multicast_replayingShare) 148 | sourceObservable.replayingShare() 149 | } 150 | else -> throw RuntimeException("got to pick an op yo!") 151 | } 152 | } 153 | 154 | override fun onNothingSelected(p0: AdapterView<*>?) {} 155 | } 156 | } 157 | 158 | private fun _isCurrentlyOnMainThread(): Boolean { 159 | return Looper.myLooper() == Looper.getMainLooper() 160 | } 161 | 162 | private inner class LogAdapter(context: Context, logs: List) : 163 | ArrayAdapter(context, R.layout.item_log, R.id.item_log, logs) 164 | 165 | } 166 | 167 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/morihacky/android/rxjava/fragments/PlaygroundFragment.kt: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.os.Handler 6 | import android.os.Looper 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.ArrayAdapter 11 | import android.widget.ListView 12 | import com.morihacky.android.rxjava.R 13 | 14 | class PlaygroundFragment : BaseFragment() { 15 | 16 | private var _logsList: ListView? = null 17 | private var _adapter: LogAdapter? = null 18 | 19 | private var _logs: MutableList = ArrayList() 20 | 21 | override fun onCreateView(inflater: LayoutInflater?, 22 | container: ViewGroup?, 23 | savedInstanceState: Bundle?): View? { 24 | val view = inflater?.inflate(R.layout.fragment_concurrency_schedulers, container, false) 25 | 26 | _logsList = view?.findViewById(R.id.list_threading_log) as ListView 27 | _setupLogger() 28 | 29 | view.findViewById(R.id.btn_start_operation).setOnClickListener { _ -> 30 | _log("Button clicked") 31 | } 32 | 33 | return view 34 | } 35 | 36 | // ----------------------------------------------------------------------------------- 37 | // Method that help wiring up the example (irrelevant to RxJava) 38 | 39 | private fun _log(logMsg: String) { 40 | 41 | if (_isCurrentlyOnMainThread()) { 42 | _logs.add(0, logMsg + " (main thread) ") 43 | _adapter?.clear() 44 | _adapter?.addAll(_logs) 45 | } else { 46 | _logs.add(0, logMsg + " (NOT main thread) ") 47 | 48 | // You can only do below stuff on main thread. 49 | Handler(Looper.getMainLooper()).post { 50 | _adapter?.clear() 51 | _adapter?.addAll(_logs) 52 | } 53 | } 54 | } 55 | 56 | private fun _setupLogger() { 57 | _logs = ArrayList() 58 | _adapter = LogAdapter(activity, ArrayList()) 59 | _logsList?.adapter = _adapter 60 | } 61 | 62 | private fun _isCurrentlyOnMainThread(): Boolean { 63 | return Looper.myLooper() == Looper.getMainLooper() 64 | } 65 | 66 | private inner class LogAdapter(context: Context, logs: List) : ArrayAdapter(context, R.layout.item_log, R.id.item_log, logs) 67 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/morihacky/android/rxjava/fragments/UsingFragment.kt: -------------------------------------------------------------------------------- 1 | package com.morihacky.android.rxjava.fragments 2 | 3 | import android.content.Context 4 | import android.os.Bundle 5 | import android.os.Handler 6 | import android.os.Looper 7 | import android.view.LayoutInflater 8 | import android.view.View 9 | import android.view.ViewGroup 10 | import android.widget.ArrayAdapter 11 | import android.widget.ListView 12 | import android.widget.TextView 13 | import com.morihacky.android.rxjava.R 14 | import io.reactivex.Flowable 15 | import io.reactivex.functions.Consumer 16 | import io.reactivex.functions.Function 17 | import org.reactivestreams.Publisher 18 | import java.util.* 19 | import java.util.concurrent.Callable 20 | 21 | class UsingFragment : BaseFragment() { 22 | 23 | private lateinit var _logs: MutableList 24 | private lateinit var _logsList: ListView 25 | private lateinit var _adapter: UsingFragment.LogAdapter 26 | 27 | override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View? { 28 | val view = inflater?.inflate(R.layout.fragment_buffer, container, false) 29 | _logsList = view?.findViewById(R.id.list_threading_log) as ListView 30 | 31 | (view.findViewById(R.id.text_description) as TextView).setText(R.string.msg_demo_using) 32 | 33 | _setupLogger() 34 | view.findViewById(R.id.btn_start_operation).setOnClickListener { executeUsingOperation() } 35 | return view 36 | } 37 | 38 | private fun executeUsingOperation() { 39 | val resourceSupplier = Callable { Realm() } 40 | val sourceSupplier = Function> { realm -> 41 | Flowable.just(true) 42 | .map { 43 | realm.doSomething() 44 | // i would use the copyFromRealm and change it to a POJO 45 | Random().nextInt(50) 46 | } 47 | } 48 | val disposer = Consumer { realm -> 49 | realm.clear() 50 | } 51 | 52 | Flowable.using(resourceSupplier, sourceSupplier, disposer) 53 | .subscribe({ i -> 54 | _log("got a value $i - (look at the logs)") 55 | }) 56 | } 57 | 58 | inner class Realm { 59 | init { 60 | _log("initializing Realm instance") 61 | } 62 | 63 | fun doSomething() { 64 | _log("do something with Realm instance") 65 | } 66 | 67 | fun clear() { 68 | // notice how this is called even before you manually "dispose" 69 | _log("cleaning up the resources (happens before a manual 'dispose'") 70 | } 71 | } 72 | 73 | // ----------------------------------------------------------------------------------- 74 | // Method that help wiring up the example (irrelevant to RxJava) 75 | 76 | private fun _log(logMsg: String) { 77 | _logs.add(0, logMsg) 78 | 79 | // You can only do below stuff on main thread. 80 | Handler(Looper.getMainLooper()).post { 81 | _adapter.clear() 82 | _adapter.addAll(_logs) 83 | } 84 | } 85 | 86 | private fun _setupLogger() { 87 | _logs = ArrayList() 88 | _adapter = LogAdapter(activity, ArrayList()) 89 | _logsList.adapter = _adapter 90 | } 91 | 92 | private class LogAdapter(context: Context, logs: List) : ArrayAdapter(context, R.layout.item_log, R.id.item_log, logs) 93 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaushikgopal/RxJava-Android-Samples/b84865252c203498c1f0a2fa0f751f3c9127a781/app/src/main/res/drawable-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaushikgopal/RxJava-Android-Samples/b84865252c203498c1f0a2fa0f751f3c9127a781/app/src/main/res/drawable-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaushikgopal/RxJava-Android-Samples/b84865252c203498c1f0a2fa0f751f3c9127a781/app/src/main/res/drawable-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kaushikgopal/RxJava-Android-Samples/b84865252c203498c1f0a2fa0f751f3c9127a781/app/src/main/res/drawable-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/btn_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_buffer.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 17 | 18 |