├── .gitignore ├── LICENSE.md ├── README.md ├── app ├── .gitignore ├── build.gradle ├── consumer-proguard-rules.pro └── src │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── durdinapps │ │ │ └── rxfirebase2 │ │ │ ├── DataSnapshotMapper.java │ │ │ ├── DocumentSnapshotMapper.java │ │ │ ├── RxCompletableHandler.java │ │ │ ├── RxFirebaseAuth.java │ │ │ ├── RxFirebaseChildEvent.java │ │ │ ├── RxFirebaseDatabase.java │ │ │ ├── RxFirebaseFunctions.java │ │ │ ├── RxFirebaseQuery.java │ │ │ ├── RxFirebaseRemote.java │ │ │ ├── RxFirebaseStorage.java │ │ │ ├── RxFirebaseUser.java │ │ │ ├── RxFirestore.java │ │ │ ├── RxFirestoreOfflineHandler.java │ │ │ ├── RxHandler.java │ │ │ └── exceptions │ │ │ ├── RxFirebaseDataCastException.java │ │ │ ├── RxFirebaseDataException.java │ │ │ └── RxFirebaseNullDataException.java │ └── res │ │ └── values │ │ └── strings.xml │ └── test │ └── java │ └── durdinapps │ └── rxfirebase2 │ ├── RxFirebaseAuthTest.java │ ├── RxFirebaseDatabaseTest.java │ ├── RxFirebaseRemoteTest.java │ ├── RxFirebaseStorageTest.java │ ├── RxFirebaseUserTest.java │ ├── RxFirestoreTest.java │ └── RxTestUtil.java ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── projectFilesBackup └── .idea │ └── workspace.xml └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | /.idea/vcs.xml 7 | /.idea/misc.xml 8 | /.idea/modules.xml 9 | .DS_Store 10 | /build 11 | /captures 12 | .externalNativeBuild 13 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Francisco García Sierra 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Rx2Firebase 2 | [Rxjava 2.0](https://github.com/ReactiveX/RxJava/tree/2.x) wrapper on Google's [Android Firebase](https://firebase.google.com/docs/android/setup?hl=es) library. 3 | 4 | This repository started as a personal usage of [Nick Moskalenko](https://github.com/nmoskalenko) RxFirebase library. You can check his work [here](https://github.com/nmoskalenko/RxFirebase). 5 | 6 | 7 | ## Download 8 | 9 | ##### Gradle: 10 | 11 | ```groovy 12 | dependencies { 13 | compile 'com.github.FrangSierra:RxFirebase:1.5.6' 14 | } 15 | ``` 16 | ``` 17 | allprojects { 18 | repositories { 19 | ... 20 | maven { url "https://jitpack.io" } 21 | } 22 | } 23 | ``` 24 | 25 | ## Usage 26 | Library provides set of static methods of classes: 27 | * RxFirebaseAuth 28 | * RxFirebaseUser 29 | * RxFirebaseDatabase 30 | * RxFirebaseStorage 31 | * RxFirestore 32 | * RxFirebaseFunctions 33 | 34 | ### Authentication: 35 | Sign in with email and password: 36 | 37 | ```java 38 | RxFirebaseAuth.signInWithEmailAndPassword(auth, email, password) 39 | .map(authResult -> authResult.getUser() != null) 40 | .take(1) 41 | .subscribe(logged -> { 42 | Log.i("Rxfirebase2", "User logged " + logged); 43 | }); 44 | ``` 45 | ### Firestore: 46 | 47 | You can observe values providing the Class of expected data like: 48 | 49 | ```java 50 | DocumentReference document = firestore.collection("Users").document("UserId_1"); 51 | RxFirestore.observeDocumentRef(document) 52 | .subscribe( userDoc -> { 53 | //Do something with my snapshot 54 | }); 55 | ``` 56 | 57 | Get and set documents on a specific reference: 58 | 59 | ```java 60 | DocumentReference document = firestore.collection("Users").document("UserId_1"); 61 | User mynewUser = User("newUserName", 24); 62 | //Set data 63 | RxFirestore.setDocument(document, myNewUser).subscribe(); 64 | //Get and map data 65 | RxFirestore.getDocument(document) 66 | .map( userDoc -> { return userDoc.toObject(User.class); }) 67 | .subscribe( casterUser -> { 68 | //Do something with my already casted user 69 | }); 70 | ``` 71 | 72 | Finally you can do sync operations on the database using `runTransaction` and if you wanna realize multiple 73 | operations at once, you should use the method `atomicOperation` which wraps the `WriteBatch` related methods from Firestore. 74 | 75 | ### Database: 76 | 77 | You can observe values providing the Class of expected data like: 78 | 79 | ```java 80 | RxFirebaseDatabase.observeSingleValueEvent(getPostsRef().child("posts"), Post.class) 81 | .subscribe(post -> { 82 | //Do something with yourpost 83 | }); 84 | ``` 85 | 86 | or providing your own mapper between DataSnapshot and your data type: 87 | 88 | ```java 89 | RxFirebaseDatabase.observeSingleValueEvent(getPostsRef().child("posts"), 90 | dataSnapshot -> { 91 | // do your own mapping here 92 | return new Author(); 93 | }) 94 | .subscribe(author -> { 95 | // process author value 96 | }); 97 | ``` 98 | 99 | There are some pre-defined mappers to make things easier: 100 | 101 | ##### Observing list values 102 | 103 | ```java 104 | RxFirebaseDatabase.observeSingleValueEvent(getPostsRef().child("posts"), DataSnapshotMapper.listOf(PostComment.class)) 105 | .subscribe(blogPost -> { 106 | // process postcomment list item 107 | }); 108 | ``` 109 | 110 | ##### Observing map values 111 | 112 | ```java 113 | RxFirebaseDatabase.observeSingleValueEvent(getPostsRef().child("posts"), DataSnapshotMapper.mapOf(PostComment.class)) 114 | .subscribe(PostCommentAsMapItem -> { 115 | // process blogPost as key-value pair 116 | }); 117 | ``` 118 | 119 | ### Storage: 120 | 121 | Download file from Firebase storage 122 | 123 | ```java 124 | RxFirebaseStorage.getFile(getStorageRef(), targetFile) 125 | .subscribe(taskSnapshot -> { 126 | Log.i("RxFirebaseSample", "transferred: " + snapshot.getBytesTransferred() + " bytes"); 127 | }, throwable -> { 128 | Log.e("RxFirebaseSample", throwable.toString()); 129 | }); 130 | ``` 131 | 132 | or download file as bytes array 133 | 134 | ```java 135 | RxFirebaseStorage.getBytes(getStorageRef(), 1024 * 100) 136 | .subscribe(bytes -> { 137 | Log.i("RxFirebaseSample", "downloaded: " + new String(bytes)); 138 | }, throwable -> { 139 | Log.e("RxFirebaseSample", throwable.toString()); 140 | }); 141 | ``` 142 | ### RxFirebaseQuery 143 | 144 | RxFirebaseQuery is a builder class used to work together with methods from RxFirebaseDatabase that allow you to retrieve data from multiple databaseReferences. Doing this allow you to build and create dynamic queries to retrieve database objects from references retrieved from different tables easily. 145 | At the moment RxFirebaseQuery just allow the user to create the queries and retrieve the data. Filters should be done with the `DatabaseReference` items that you pass to the constructor. In other hand for update and delete data you should use `Firebase` method `updateChildren()` 146 | ```java 147 | DatabaseReference reference = FirebaseDatabase.getInstance().getReference(); 148 | DatabaseReference from = reference.child("tweets"); 149 | Query where = reference.child("users").child(userId).child("feedReferences"); 150 | RxFirebaseQuery.getInstance() 151 | .filterByRefs(from, where) 152 | .asList() 153 | .subscribe(dataSnapshots -> { 154 | Log.i("RxFirebase", "Retrieved a total of " + dataSnapshots.size() + " tweets"); 155 | for (DataSnapshot dataSnapshot : dataSnapshots) { 156 | Tweet tweet = dataSnapshot.getValue(Tweet.class); 157 | Log.i("RxFirebase", "New tweet for user feed: " + tweet.getDescription()); 158 | } 159 | }); 160 | 161 | ## RxJava and RxJava 2.0 162 | One of the differences between RxJava and RxJava 2 is that RxJava 2 no longer accepts `null` values. Throwing a `NullPointerException` immediately. For this reason some of the methods of the library as been redesigned to return a `Completable` instead of a `Observable`. For example: 163 | 164 | #### RxFirebase 165 | 166 | ```java 167 | @NonNull 168 | public static Observable updateEmail(@NonNull final FirebaseUser firebaseUser, @NonNull final String email) { 169 | return Observable.create(new Observable.OnSubscribe() { 170 | @Override 171 | public void call(final Subscriber subscriber) { 172 | RxHandler.assignOnTask(subscriber, firebaseUser.updateEmail(email)); 173 | } 174 | }); 175 | } 176 | ``` 177 | 178 | #### Rx2Firebase 179 | 180 | ```java 181 | @NonNull 182 | public static Completable updateEmail(@NonNull final FirebaseUser firebaseUser, @NonNull final String email) { 183 | return Completable.create(new CompletableOnSubscribe() { 184 | @Override 185 | public void subscribe(CompletableEmitter emitter) throws Exception { 186 | RxCompletableHandler.assignOnTask(emitter, firebaseUser.updateEmail(email)); 187 | } 188 | }); 189 | } 190 | ``` 191 | 192 | `RxCompletableHandler` manages the CompletableEmitters in the same way that `RxHandler` manages the `Subscriber`. 193 | You can check all the differences between RxJava and RxJava 2.0 in the next [Link](https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0) 194 | 195 | ## License 196 | 197 | MIT License 198 | 199 | Copyright (c) 2016 Francisco García Sierra 200 | 201 | Permission is hereby granted, free of charge, to any person obtaining a 202 | copy of this software and associated documentation files (the "Software"), 203 | to deal in the Software without restriction, including without limitation 204 | the rights to use, copy, modify, merge, publish, distribute, sublicense, 205 | and/or sell copies of the Software, and to permit persons to whom the 206 | Software is furnished to do so, subject to the following conditions: 207 | 208 | The above copyright notice and this permission notice shall be included 209 | in all copies or substantial portions of the Software. 210 | 211 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS 212 | OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 213 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 214 | THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 215 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 216 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 217 | OTHER DEALINGS IN THE SOFTWARE. 218 | 219 | 220 | ## Support on Beerpay 221 | Hey dude! Help me out for a couple of :beers:! 222 | 223 | [![Beerpay](https://beerpay.io/FrangSierra/RxFirebase/badge.svg?style=beer-square)](https://beerpay.io/FrangSierra/RxFirebase) [![Beerpay](https://beerpay.io/FrangSierra/RxFirebase/make-wish.svg?style=flat-square)](https://beerpay.io/FrangSierra/RxFirebase?focus=wish) 224 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext { 3 | rx_version = "2.1.10" 4 | rx_android_version = "2.1.1" 5 | firebase_auth_version = '19.2.0' 6 | firebase_database_version = '19.2.1' 7 | firebase_storage_version = '19.1.1' 8 | firebase_firestore_version = '21.4.1' 9 | firebase_functions_version = '19.0.2' 10 | firebase_remote_version = '19.1.2' 11 | support_version = "28.0.3" 12 | } 13 | } 14 | apply plugin: 'com.android.library' 15 | 16 | android { 17 | 18 | packagingOptions { 19 | exclude 'META-INF/LICENSE' 20 | exclude 'META-INF/LICENSE-FIREBASE.txt' 21 | exclude 'META-INF/NOTICE' 22 | } 23 | 24 | compileSdkVersion 28 25 | buildToolsVersion "28.0.3" 26 | 27 | defaultConfig { 28 | minSdkVersion 14 29 | targetSdkVersion 28 30 | versionCode 6 31 | versionName "1.5.5" 32 | consumerProguardFiles 'consumer-proguard-rules.pro' 33 | } 34 | } 35 | 36 | repositories{ 37 | google() 38 | } 39 | 40 | dependencies { 41 | implementation fileTree(dir: 'libs', include: ['*.jar']) 42 | compileOnly "com.google.firebase:firebase-auth:$firebase_auth_version" 43 | compileOnly "com.google.firebase:firebase-database:$firebase_database_version" 44 | compileOnly "com.google.firebase:firebase-storage:$firebase_storage_version" 45 | compileOnly "com.google.firebase:firebase-firestore:$firebase_firestore_version" 46 | compileOnly "com.google.firebase:firebase-functions:$firebase_functions_version" 47 | compileOnly "com.google.firebase:firebase-config:$firebase_remote_version" 48 | 49 | implementation "io.reactivex.rxjava2:rxjava:$rx_version" 50 | implementation "io.reactivex.rxjava2:rxandroid:$rx_android_version" 51 | 52 | testImplementation "com.google.firebase:firebase-auth:$firebase_auth_version" 53 | testImplementation "com.google.firebase:firebase-database:$firebase_database_version" 54 | testImplementation "com.google.firebase:firebase-storage:$firebase_storage_version" 55 | testImplementation "com.google.firebase:firebase-firestore:$firebase_firestore_version" 56 | testImplementation "com.google.firebase:firebase-config:$firebase_remote_version" 57 | testImplementation 'junit:junit:4.13' 58 | testImplementation "org.mockito:mockito-core:3.3.1" 59 | } 60 | -------------------------------------------------------------------------------- /app/consumer-proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Firebase uses Gson annotations and Gson uses generic type information stored in a class file when working with fields. 2 | # Proguard removes such information by default, so configure it to keep all of it. 3 | -keepattributes Signature -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/DataSnapshotMapper.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import com.google.firebase.database.DataSnapshot; 4 | import com.google.firebase.database.GenericTypeIndicator; 5 | 6 | import java.util.ArrayList; 7 | import java.util.LinkedHashMap; 8 | import java.util.List; 9 | 10 | import androidx.annotation.NonNull; 11 | import durdinapps.rxfirebase2.exceptions.RxFirebaseDataCastException; 12 | import io.reactivex.exceptions.Exceptions; 13 | import io.reactivex.functions.Function; 14 | import io.reactivex.functions.Predicate; 15 | 16 | public abstract class DataSnapshotMapper implements Function { 17 | 18 | private DataSnapshotMapper() { 19 | } 20 | 21 | public static DataSnapshotMapper of(Class clazz) { 22 | return new TypedDataSnapshotMapper(clazz); 23 | } 24 | 25 | public static DataSnapshotMapper> listOf(Class clazz) { 26 | return new TypedListDataSnapshotMapper<>(clazz); 27 | } 28 | 29 | public static DataSnapshotMapper> listOf(Class clazz, Function mapper) { 30 | return new TypedListDataSnapshotMapper<>(clazz, mapper); 31 | } 32 | 33 | public static DataSnapshotMapper> mapOf(Class clazz) { 34 | return new TypedMapDataSnapshotMapper<>(clazz); 35 | } 36 | 37 | public static DataSnapshotMapper of(GenericTypeIndicator genericTypeIndicator) { 38 | return new GenericTypedDataSnapshotMapper(genericTypeIndicator); 39 | } 40 | 41 | public static DataSnapshotMapper, RxFirebaseChildEvent> ofChildEvent(Class clazz) { 42 | return new ChildEventDataSnapshotMapper(clazz); 43 | } 44 | 45 | private static U getDataSnapshotTypedValue(DataSnapshot dataSnapshot, Class clazz) { 46 | U value; 47 | try { 48 | value = dataSnapshot.getValue(clazz); 49 | } catch (Exception ex) { 50 | throw Exceptions.propagate(new RxFirebaseDataCastException( 51 | "There was a problem trying to cast " + dataSnapshot.toString() + " to " + clazz.getSimpleName(), ex)); 52 | } 53 | if (value == null) { 54 | throw Exceptions.propagate(new RxFirebaseDataCastException( 55 | "The value after cast " + dataSnapshot.toString() + " to " + clazz.getSimpleName() + "is null.")); 56 | } 57 | return value; 58 | } 59 | 60 | private static class TypedDataSnapshotMapper extends DataSnapshotMapper { 61 | 62 | private final Class clazz; 63 | 64 | public TypedDataSnapshotMapper(final Class clazz) { 65 | this.clazz = clazz; 66 | } 67 | 68 | @Override 69 | public U apply(final DataSnapshot dataSnapshot) { 70 | return getDataSnapshotTypedValue(dataSnapshot, clazz); 71 | } 72 | } 73 | 74 | private static class TypedListDataSnapshotMapper extends DataSnapshotMapper> { 75 | 76 | private final Class clazz; 77 | private final Function mapper; 78 | 79 | TypedListDataSnapshotMapper(final Class clazz) { 80 | this(clazz, null); 81 | } 82 | 83 | TypedListDataSnapshotMapper(final Class clazz, Function mapper) { 84 | this.clazz = clazz; 85 | this.mapper = mapper; 86 | } 87 | 88 | @Override 89 | public List apply(final DataSnapshot dataSnapshot) throws Exception { 90 | List items = new ArrayList<>(); 91 | for (DataSnapshot childSnapshot : dataSnapshot.getChildren()) { 92 | items.add(mapper != null 93 | ? mapper.apply(childSnapshot) 94 | : getDataSnapshotTypedValue(childSnapshot, clazz)); 95 | } 96 | return items; 97 | } 98 | } 99 | 100 | private static class TypedMapDataSnapshotMapper extends DataSnapshotMapper> { 101 | 102 | private final Class clazz; 103 | 104 | TypedMapDataSnapshotMapper(final Class clazz) { 105 | this.clazz = clazz; 106 | } 107 | 108 | @Override 109 | public LinkedHashMap apply(final DataSnapshot dataSnapshot) { 110 | LinkedHashMap items = new LinkedHashMap<>(); 111 | for (DataSnapshot childSnapshot : dataSnapshot.getChildren()) { 112 | items.put(childSnapshot.getKey(), getDataSnapshotTypedValue(childSnapshot, clazz)); 113 | } 114 | return items; 115 | } 116 | } 117 | 118 | private static class GenericTypedDataSnapshotMapper extends DataSnapshotMapper { 119 | 120 | private final GenericTypeIndicator genericTypeIndicator; 121 | 122 | public GenericTypedDataSnapshotMapper(GenericTypeIndicator genericTypeIndicator) { 123 | this.genericTypeIndicator = genericTypeIndicator; 124 | } 125 | 126 | @Override 127 | public U apply(DataSnapshot dataSnapshot) { 128 | U value = dataSnapshot.getValue(genericTypeIndicator); 129 | if (value == null) { 130 | throw Exceptions.propagate(new RxFirebaseDataCastException( 131 | "unable to cast firebase data response to generic type")); 132 | } 133 | return value; 134 | } 135 | } 136 | 137 | private static class ChildEventDataSnapshotMapper 138 | extends DataSnapshotMapper, RxFirebaseChildEvent> { 139 | 140 | private final Class clazz; 141 | 142 | public ChildEventDataSnapshotMapper(final Class clazz) { 143 | this.clazz = clazz; 144 | } 145 | 146 | @Override 147 | public RxFirebaseChildEvent apply(final RxFirebaseChildEvent rxFirebaseChildEvent) { 148 | DataSnapshot dataSnapshot = rxFirebaseChildEvent.getValue(); 149 | if (dataSnapshot.exists()) { 150 | return new RxFirebaseChildEvent( 151 | dataSnapshot.getKey(), 152 | getDataSnapshotTypedValue(dataSnapshot, clazz), 153 | rxFirebaseChildEvent.getPreviousChildName(), 154 | rxFirebaseChildEvent.getEventType()); 155 | } else { 156 | throw Exceptions.propagate(new RuntimeException("child dataSnapshot doesn't exist")); 157 | } 158 | } 159 | } 160 | 161 | static final Predicate DATA_SNAPSHOT_EXISTENCE_PREDICATE = new Predicate() { 162 | @Override 163 | public boolean test(@NonNull DataSnapshot dataSnapshot) throws Exception { 164 | return dataSnapshot.exists(); 165 | } 166 | }; 167 | } -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/DocumentSnapshotMapper.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | 4 | import com.google.firebase.firestore.DocumentSnapshot; 5 | import com.google.firebase.firestore.QuerySnapshot; 6 | 7 | import java.util.ArrayList; 8 | import java.util.LinkedHashMap; 9 | import java.util.List; 10 | 11 | import androidx.annotation.NonNull; 12 | import io.reactivex.functions.Function; 13 | import io.reactivex.functions.Predicate; 14 | 15 | public abstract class DocumentSnapshotMapper implements Function { 16 | 17 | private DocumentSnapshotMapper() { 18 | } 19 | 20 | public static DocumentSnapshotMapper of(Class clazz) { 21 | return new TypedDocumentSnapshotMapper(clazz); 22 | } 23 | 24 | public static DocumentSnapshotMapper> listOf(Class clazz) { 25 | return new TypedListQuerySnapshotMapper<>(clazz); 26 | } 27 | 28 | public static DocumentSnapshotMapper> listOf(Class clazz, Function mapper) { 29 | return new TypedListQuerySnapshotMapper<>(clazz, mapper); 30 | } 31 | 32 | public static TypedMapQuerySnapshotMapper mapOf(Class clazz) { 33 | return new TypedMapQuerySnapshotMapper<>(clazz); 34 | } 35 | 36 | private static U getDataSnapshotTypedValue(DocumentSnapshot documentSnapshot, Class clazz) { 37 | return documentSnapshot.toObject(clazz); 38 | } 39 | 40 | private static class TypedDocumentSnapshotMapper extends DocumentSnapshotMapper { 41 | 42 | private final Class clazz; 43 | 44 | public TypedDocumentSnapshotMapper(final Class clazz) { 45 | this.clazz = clazz; 46 | } 47 | 48 | @Override 49 | public U apply(final DocumentSnapshot documentSnapshot) { 50 | return getDataSnapshotTypedValue(documentSnapshot, clazz); 51 | } 52 | } 53 | 54 | private static class TypedListQuerySnapshotMapper extends DocumentSnapshotMapper> { 55 | 56 | private final Class clazz; 57 | private final Function mapper; 58 | 59 | TypedListQuerySnapshotMapper(final Class clazz) { 60 | this(clazz, null); 61 | } 62 | 63 | TypedListQuerySnapshotMapper(final Class clazz, Function mapper) { 64 | this.clazz = clazz; 65 | this.mapper = mapper; 66 | } 67 | 68 | @Override 69 | public List apply(final QuerySnapshot querySnapshot) throws Exception { 70 | List items = new ArrayList<>(); 71 | for (DocumentSnapshot documentSnapshot : querySnapshot) { 72 | items.add(mapper != null 73 | ? mapper.apply(documentSnapshot) 74 | : getDataSnapshotTypedValue(documentSnapshot, clazz)); 75 | } 76 | return items; 77 | } 78 | } 79 | 80 | 81 | private static class TypedMapQuerySnapshotMapper extends DocumentSnapshotMapper> { 82 | 83 | private final Class clazz; 84 | 85 | TypedMapQuerySnapshotMapper(final Class clazz) { 86 | this.clazz = clazz; 87 | } 88 | 89 | @Override 90 | public LinkedHashMap apply(final QuerySnapshot querySnapshot) { 91 | LinkedHashMap items = new LinkedHashMap<>(); 92 | for (DocumentSnapshot documentSnapshot : querySnapshot) { 93 | items.put(documentSnapshot.getId(), getDataSnapshotTypedValue(documentSnapshot, clazz)); 94 | } 95 | return items; 96 | } 97 | } 98 | 99 | static final Predicate QUERY_EXISTENCE_PREDICATE = new Predicate() { 100 | @Override 101 | public boolean test(@NonNull QuerySnapshot querySnapshot) throws Exception { 102 | return !querySnapshot.isEmpty(); 103 | } 104 | }; 105 | 106 | static final Predicate DOCUMENT_EXISTENCE_PREDICATE = new Predicate() { 107 | @Override 108 | public boolean test(@NonNull DocumentSnapshot documentSnapshot) throws Exception { 109 | return documentSnapshot.exists(); 110 | } 111 | }; 112 | } 113 | -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/RxCompletableHandler.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.google.android.gms.tasks.OnCompleteListener; 6 | import com.google.android.gms.tasks.OnFailureListener; 7 | import com.google.android.gms.tasks.OnSuccessListener; 8 | import com.google.android.gms.tasks.Task; 9 | 10 | import io.reactivex.CompletableEmitter; 11 | 12 | 13 | public class RxCompletableHandler implements OnFailureListener, OnSuccessListener, OnCompleteListener { 14 | 15 | private final CompletableEmitter completableEmitter; 16 | 17 | private RxCompletableHandler(CompletableEmitter completableEmitter) { 18 | this.completableEmitter = completableEmitter; 19 | } 20 | 21 | public static void assignOnTask(CompletableEmitter completableEmitter, Task task) { 22 | RxCompletableHandler handler = new RxCompletableHandler(completableEmitter); 23 | task.addOnFailureListener(handler); 24 | task.addOnSuccessListener(handler); 25 | try { 26 | task.addOnCompleteListener(handler); 27 | } catch (Throwable t) { 28 | // ignore 29 | } 30 | } 31 | 32 | 33 | @Override 34 | public void onFailure(@NonNull Exception e) { 35 | if (!completableEmitter.isDisposed()) 36 | completableEmitter.onError(e); 37 | } 38 | 39 | @Override 40 | public void onComplete(@NonNull Task task) { 41 | completableEmitter.onComplete(); 42 | } 43 | 44 | @Override 45 | public void onSuccess(Object o) { 46 | completableEmitter.onComplete(); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/RxFirebaseAuth.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.google.firebase.auth.ActionCodeResult; 6 | import com.google.firebase.auth.AuthCredential; 7 | import com.google.firebase.auth.AuthResult; 8 | import com.google.firebase.auth.FirebaseAuth; 9 | import com.google.firebase.auth.FirebaseUser; 10 | import com.google.firebase.auth.SignInMethodQueryResult; 11 | 12 | import io.reactivex.Completable; 13 | import io.reactivex.CompletableEmitter; 14 | import io.reactivex.CompletableOnSubscribe; 15 | import io.reactivex.Maybe; 16 | import io.reactivex.MaybeEmitter; 17 | import io.reactivex.MaybeOnSubscribe; 18 | import io.reactivex.Observable; 19 | import io.reactivex.ObservableEmitter; 20 | import io.reactivex.ObservableOnSubscribe; 21 | import io.reactivex.functions.Cancellable; 22 | 23 | public class RxFirebaseAuth { 24 | 25 | /** 26 | * Asynchronously signs in as an anonymous user. 27 | * If there is already an anonymous user signed in, that user will be returned; otherwise, a new anonymous user identity will be created and returned. 28 | * 29 | * @param firebaseAuth firebaseAuth instance. 30 | * @return a {@link Maybe} which emits an {@link AuthResult} if success. 31 | * @see Firebase Auth API 32 | */ 33 | @NonNull 34 | public static Maybe signInAnonymously(@NonNull final FirebaseAuth firebaseAuth) { 35 | return Maybe.create(new MaybeOnSubscribe() { 36 | @Override 37 | public void subscribe(MaybeEmitter emitter) throws Exception { 38 | RxHandler.assignOnTask(emitter, firebaseAuth.signInAnonymously()); 39 | } 40 | }); 41 | } 42 | 43 | /** 44 | * Asynchronously signs in using an email and password. 45 | * Fails with an error if the email address and password do not match. 46 | *

47 | * Note: The user's password is NOT the password used to access the user's email account. 48 | * The email address serves as a unique identifier for the user, and the password is used to access the user's account in your Firebase project. 49 | * 50 | * @param firebaseAuth firebaseAuth instance. 51 | * @param email email to login. 52 | * @param password password to login. 53 | * @return a {@link Maybe} which emits an {@link AuthResult} if success. 54 | * @see Firebase Auth API 55 | */ 56 | @NonNull 57 | public static Maybe signInWithEmailAndPassword(@NonNull final FirebaseAuth firebaseAuth, 58 | @NonNull final String email, 59 | @NonNull final String password) { 60 | return Maybe.create(new MaybeOnSubscribe() { 61 | @Override 62 | public void subscribe(MaybeEmitter emitter) throws Exception { 63 | RxHandler.assignOnTask(emitter, firebaseAuth.signInWithEmailAndPassword(email, password)); 64 | } 65 | }); 66 | } 67 | 68 | /** 69 | * Asynchronously signs in with the given credentials. 70 | * 71 | * @param firebaseAuth firebaseAuth instance. 72 | * @param credential The auth credential. Value must not be null. 73 | * @return a {@link Maybe} which emits an {@link AuthResult} if success. 74 | * @see Firebase Auth API 75 | */ 76 | @NonNull 77 | public static Maybe signInWithCredential(@NonNull final FirebaseAuth firebaseAuth, 78 | @NonNull final AuthCredential credential) { 79 | return Maybe.create(new MaybeOnSubscribe() { 80 | @Override 81 | public void subscribe(MaybeEmitter emitter) throws Exception { 82 | RxHandler.assignOnTask(emitter, firebaseAuth.signInWithCredential(credential)); 83 | } 84 | }); 85 | } 86 | 87 | /** 88 | * Asynchronously signs in using a custom token. 89 | *

90 | * Custom tokens are used to integrate Firebase Auth with existing auth systems, and must be generated by the auth backend. 91 | *

92 | * Fails with an error if the token is invalid, expired, or not accepted by the Firebase Auth service. 93 | * 94 | * @param firebaseAuth firebaseAuth instance. 95 | * @param token The custom token to sign in with. 96 | * @return a {@link Maybe} which emits an {@link AuthResult} if success. 97 | * @see Firebase Auth API 98 | */ 99 | @NonNull 100 | public static Maybe signInWithCustomToken(@NonNull final FirebaseAuth firebaseAuth, 101 | @NonNull final String token) { 102 | return Maybe.create(new MaybeOnSubscribe() { 103 | @Override 104 | public void subscribe(MaybeEmitter emitter) throws Exception { 105 | RxHandler.assignOnTask(emitter, firebaseAuth.signInWithCustomToken(token)); 106 | } 107 | }); 108 | } 109 | 110 | /** 111 | * Creates a new user account associated with the specified email address and password. 112 | *

113 | * On successful creation of the user account, this user will also be signed in to your application. 114 | *

115 | * User account creation can fail if the account already exists or the password is invalid. 116 | *

117 | * Note: The email address acts as a unique identifier for the user and enables an email-based password reset. 118 | * This function will create a new user account and set the initial user password. 119 | *

120 | * Custom tokens are used to integrate Firebase Auth with existing auth systems, and must be generated by the auth backend. 121 | *

122 | * Fails with an error if the token is invalid, expired, or not accepted by the Firebase Auth service. 123 | * 124 | * @param firebaseAuth firebaseAuth instance. 125 | * @param email The user's email address. 126 | * @param password The user's chosen password. 127 | * @return a {@link Maybe} which emits an {@link AuthResult} if success. 128 | * @see Firebase Auth API 129 | */ 130 | @NonNull 131 | public static Maybe createUserWithEmailAndPassword(@NonNull final FirebaseAuth firebaseAuth, 132 | @NonNull final String email, 133 | @NonNull final String password) { 134 | return Maybe.create(new MaybeOnSubscribe() { 135 | @Override 136 | public void subscribe(MaybeEmitter emitter) throws Exception { 137 | RxHandler.assignOnTask(emitter, firebaseAuth.createUserWithEmailAndPassword(email, password)); 138 | } 139 | }); 140 | } 141 | 142 | /** 143 | * Gets the list of provider IDs that can be used to sign in for the given email address. Useful for an "identifier-first" sign-in flow. 144 | * 145 | * @param firebaseAuth firebaseAuth instance. 146 | * @param email An email address. 147 | * @return a {@link Maybe} which emits an {@link SignInMethodQueryResult} if success. 148 | * @see Firebase Auth API 149 | */ 150 | @NonNull 151 | public static Maybe fetchSignInMethodsForEmail(@NonNull final FirebaseAuth firebaseAuth, 152 | @NonNull final String email) { 153 | return Maybe.create(new MaybeOnSubscribe() { 154 | @Override 155 | public void subscribe(MaybeEmitter emitter) { 156 | RxHandler.assignOnTask(emitter, firebaseAuth.fetchSignInMethodsForEmail(email)); 157 | } 158 | }); 159 | } 160 | 161 | /** 162 | * Sends a password reset email to the given email address. 163 | *

164 | * To complete the password reset, call {@link FirebaseAuth#confirmPasswordReset(String, String)} with the code supplied in 165 | * the email sent to the user, along with the new password specified by the user. 166 | * 167 | * @param firebaseAuth firebaseAuth instance. 168 | * @param email The email address with the password to be reset. 169 | * @return a {@link Completable} which emits when the action is completed. 170 | * @see Firebase Auth API 171 | */ 172 | @NonNull 173 | public static Completable sendPasswordResetEmail(@NonNull final FirebaseAuth firebaseAuth, 174 | @NonNull final String email) { 175 | return Completable.create(new CompletableOnSubscribe() { 176 | @Override 177 | public void subscribe(CompletableEmitter emitter) throws Exception { 178 | RxCompletableHandler.assignOnTask(emitter, firebaseAuth.sendPasswordResetEmail(email)); 179 | } 180 | }); 181 | } 182 | 183 | /** 184 | * Asynchronously sets the provided user as currentUser on the current Auth instance. A new instance copy of the user provided will be made and set as currentUser. 185 | *

186 | * This will trigger firebase.auth.Auth#onAuthStateChanged and firebase.auth.Auth#onIdTokenChanged listeners like other sign in methods. 187 | * @param firebaseAuth firebaseAuth instance. 188 | * @param newUser The new user to update the current instance. 189 | * @return a {@link Completable} which emits when the action is completed. 190 | * @see Firebase Auth API 191 | */ 192 | @NonNull 193 | public static Completable updateCurrentUser(@NonNull final FirebaseAuth firebaseAuth, 194 | @NonNull final FirebaseUser newUser) { 195 | return Completable.create(new CompletableOnSubscribe() { 196 | @Override 197 | public void subscribe(CompletableEmitter emitter) throws Exception { 198 | RxCompletableHandler.assignOnTask(emitter, firebaseAuth.updateCurrentUser(newUser)); 199 | } 200 | }); 201 | } 202 | 203 | /** 204 | * Observable which track the auth changes of {@link FirebaseAuth} to listen when an user is logged or not. 205 | * 206 | * @param firebaseAuth firebaseAuth instance. 207 | * @return an {@link Observable} which emits every time that the {@link FirebaseAuth} state change. 208 | */ 209 | @NonNull 210 | public static Observable observeAuthState(@NonNull final FirebaseAuth firebaseAuth) { 211 | 212 | return Observable.create(new ObservableOnSubscribe() { 213 | @Override 214 | public void subscribe(final ObservableEmitter emitter) throws Exception { 215 | final FirebaseAuth.AuthStateListener authStateListener = new FirebaseAuth.AuthStateListener() { 216 | @Override 217 | public void onAuthStateChanged(@NonNull FirebaseAuth firebaseAuth) { 218 | emitter.onNext(firebaseAuth); 219 | } 220 | }; 221 | firebaseAuth.addAuthStateListener(authStateListener); 222 | emitter.setCancellable(new Cancellable() { 223 | @Override 224 | public void cancel() throws Exception { 225 | firebaseAuth.removeAuthStateListener(authStateListener); 226 | } 227 | }); 228 | } 229 | }); 230 | } 231 | 232 | /** 233 | * Checks that the code given is valid. This code will have been generated 234 | * by {@link FirebaseAuth#sendPasswordResetEmail(String)} or {@link com.google.firebase.auth.FirebaseUser#sendEmailVerification()} valid for a single use. 235 | * 236 | * @param firebaseAuth firebaseAuth instance. 237 | * @param code generated code by firebase. 238 | * @return a {@link Maybe} which emits when the action is completed. 239 | */ 240 | @NonNull 241 | public static Maybe checkActionCode(@NonNull final FirebaseAuth firebaseAuth, 242 | @NonNull final String code) { 243 | return Maybe.create(new MaybeOnSubscribe() { 244 | @Override 245 | public void subscribe(MaybeEmitter emitter) throws Exception { 246 | RxHandler.assignOnTask(emitter, firebaseAuth.checkActionCode(code)); 247 | } 248 | }); 249 | } 250 | 251 | /** 252 | * Changes the user's password to newPassword for the account for which the code is valid. 253 | * Code validity can be checked with {@link FirebaseAuth#verifyPasswordResetCode(String)}. 254 | * This use case is only valid for signed-out users, and behavior is undefined for signed-in users. 255 | * Password changes for signed-in users should be made using {@link com.google.firebase.auth.FirebaseUser#updatePassword(String)}. 256 | * 257 | * @param firebaseAuth firebaseAuth instance. 258 | * @param code generated code by firebase. 259 | * @param newPassword new password for the user. 260 | * @return a {@link Completable} which emits when the action is completed. 261 | */ 262 | @NonNull 263 | public static Completable confirmPasswordReset(@NonNull final FirebaseAuth firebaseAuth, 264 | @NonNull final String code, 265 | @NonNull final String newPassword) { 266 | return Completable.create(new CompletableOnSubscribe() { 267 | @Override 268 | public void subscribe(CompletableEmitter emitter) throws Exception { 269 | RxCompletableHandler.assignOnTask(emitter, firebaseAuth.confirmPasswordReset(code, newPassword)); 270 | } 271 | }); 272 | } 273 | 274 | /** 275 | * Applies the given code, which can be any out of band code which is valid according 276 | * to {@link FirebaseAuth#checkActionCode(String)} that does not also pass {@link FirebaseAuth#{verifyPasswordResetCode(String)}, 277 | * which requires an additional parameter. 278 | * 279 | * @param firebaseAuth firebaseAuth instance. 280 | * @param code generated code by firebase. 281 | * @return a {@link Completable} which emits when the action is completed. 282 | */ 283 | @NonNull 284 | public static Completable applyActionCode(@NonNull final FirebaseAuth firebaseAuth, 285 | @NonNull final String code) { 286 | return Completable.create(new CompletableOnSubscribe() { 287 | @Override 288 | public void subscribe(CompletableEmitter emitter) throws Exception { 289 | RxCompletableHandler.assignOnTask(emitter, firebaseAuth.applyActionCode(code)); 290 | } 291 | }); 292 | } 293 | 294 | /** 295 | * Checks that the code is a valid password reset out of band code. 296 | * This code will have been generated by a call to {@link FirebaseAuth#sendPasswordResetEmail(String)}, and is valid for a single use. 297 | * 298 | * @param firebaseAuth firebaseAuth instance. 299 | * @param code generated code by firebase. 300 | * @return a {@link Maybe} which emits when the action is completed. 301 | */ 302 | @NonNull 303 | public static Maybe verifyPasswordResetCode(@NonNull final FirebaseAuth firebaseAuth, 304 | @NonNull final String code) { 305 | return Maybe.create(new MaybeOnSubscribe() { 306 | @Override 307 | public void subscribe(MaybeEmitter emitter) throws Exception { 308 | RxHandler.assignOnTask(emitter, firebaseAuth.verifyPasswordResetCode(code)); 309 | } 310 | }); 311 | } 312 | 313 | /** 314 | * Registers a listener to changes in the ID token state. 315 | * There can be more than one listener registered at the same time. The listeners are called asynchronously, possibly on a different thread. 316 | *

317 | * Authentication state changes are: 318 | *

319 | * -When a user signs in 320 | * -When the current user signs out 321 | * -When the current user changes 322 | * -When there is a change in the current user's token 323 | *

324 | * Use RemoveIdTokenListener to unregister a listener. 325 | * 326 | * @param firebaseAuth firebaseAuth instance. 327 | * @param idTokenListener given token listener. 328 | * @return a {@link Completable} which emits when the action is completed. 329 | */ 330 | @NonNull 331 | public static Completable addIdTokenListener(@NonNull final FirebaseAuth firebaseAuth, 332 | @NonNull final FirebaseAuth.IdTokenListener idTokenListener) { 333 | return Completable.create(new CompletableOnSubscribe() { 334 | @Override 335 | public void subscribe(CompletableEmitter emitter) throws Exception { 336 | firebaseAuth.addIdTokenListener(idTokenListener); 337 | emitter.onComplete(); 338 | } 339 | }); 340 | } 341 | 342 | /** 343 | * Unregisters a listener of ID token changes. 344 | * Listener must previously been added with AddIdTokenListener. 345 | * 346 | * @param firebaseAuth firebaseAuth instance. 347 | * @param idTokenListener given token listener. 348 | * @return a {@link Completable} which emits when the action is completed. 349 | */ 350 | @NonNull 351 | public static Completable removeIdTokenListener(@NonNull final FirebaseAuth firebaseAuth, 352 | @NonNull final FirebaseAuth.IdTokenListener idTokenListener) { 353 | return Completable.create(new CompletableOnSubscribe() { 354 | @Override 355 | public void subscribe(CompletableEmitter emitter) throws Exception { 356 | firebaseAuth.removeIdTokenListener(idTokenListener); 357 | emitter.onComplete(); 358 | } 359 | }); 360 | } 361 | } -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/RxFirebaseChildEvent.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | public class RxFirebaseChildEvent { 7 | 8 | private EventType eventType; 9 | private String key; 10 | private T value; 11 | private String previousChildName; 12 | 13 | public RxFirebaseChildEvent(@NonNull String key, 14 | @NonNull T value, 15 | @NonNull String previousChildName, 16 | @NonNull EventType eventType) { 17 | this.key = key; 18 | this.value = value; 19 | this.previousChildName = previousChildName; 20 | this.eventType = eventType; 21 | } 22 | 23 | 24 | public RxFirebaseChildEvent(@NonNull String key, @NonNull T data, @NonNull EventType eventType) { 25 | this.key = key; 26 | this.value = data; 27 | this.eventType = eventType; 28 | } 29 | 30 | /** 31 | * @return the key associate to this {@link RxFirebaseChildEvent}; 32 | */ 33 | @NonNull 34 | public String getKey() { 35 | return key; 36 | } 37 | 38 | /** 39 | * @return the value associate to this {@link RxFirebaseChildEvent}; 40 | */ 41 | @NonNull 42 | public T getValue() { 43 | return value; 44 | } 45 | 46 | /** 47 | * @return the previous child name at this position; 48 | */ 49 | @NonNull 50 | public String getPreviousChildName() { 51 | return previousChildName; 52 | } 53 | 54 | /** 55 | * @return the kind of event of this event. This is used to different them when an item is added, changed, moved or removed. 56 | */ 57 | @NonNull 58 | public EventType getEventType() { 59 | return eventType; 60 | } 61 | 62 | @Override 63 | public boolean equals(Object o) { 64 | if (this == o) return true; 65 | if (o == null || getClass() != o.getClass()) return false; 66 | 67 | RxFirebaseChildEvent that = (RxFirebaseChildEvent) o; 68 | 69 | if (eventType != that.eventType) return false; 70 | if (value != null ? !value.equals(that.value) : that.value != null) return false; 71 | return previousChildName != null ? previousChildName.equals(that.previousChildName) : that.previousChildName == null; 72 | 73 | } 74 | 75 | @Override 76 | public int hashCode() { 77 | int result = eventType != null ? eventType.hashCode() : 0; 78 | result = 31 * result + (value != null ? value.hashCode() : 0); 79 | result = 31 * result + (previousChildName != null ? previousChildName.hashCode() : 0); 80 | return result; 81 | } 82 | 83 | /** 84 | * Enum used to different when an item is added, changed, moved or removed. 85 | */ 86 | public enum EventType { 87 | ADDED, 88 | CHANGED, 89 | REMOVED, 90 | MOVED 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/RxFirebaseDatabase.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.google.android.gms.tasks.OnFailureListener; 6 | import com.google.android.gms.tasks.OnSuccessListener; 7 | import com.google.firebase.database.ChildEventListener; 8 | import com.google.firebase.database.DataSnapshot; 9 | import com.google.firebase.database.DatabaseError; 10 | import com.google.firebase.database.DatabaseReference; 11 | import com.google.firebase.database.MutableData; 12 | import com.google.firebase.database.Query; 13 | import com.google.firebase.database.Transaction; 14 | import com.google.firebase.database.ValueEventListener; 15 | 16 | import java.util.Iterator; 17 | import java.util.Map; 18 | 19 | import durdinapps.rxfirebase2.exceptions.RxFirebaseDataException; 20 | import io.reactivex.BackpressureStrategy; 21 | import io.reactivex.Completable; 22 | import io.reactivex.CompletableEmitter; 23 | import io.reactivex.CompletableOnSubscribe; 24 | import io.reactivex.Flowable; 25 | import io.reactivex.FlowableEmitter; 26 | import io.reactivex.FlowableOnSubscribe; 27 | import io.reactivex.Maybe; 28 | import io.reactivex.MaybeEmitter; 29 | import io.reactivex.MaybeOnSubscribe; 30 | import io.reactivex.MaybeSource; 31 | import io.reactivex.Single; 32 | import io.reactivex.SingleEmitter; 33 | import io.reactivex.SingleOnSubscribe; 34 | import io.reactivex.functions.Cancellable; 35 | import io.reactivex.functions.Function; 36 | 37 | import static durdinapps.rxfirebase2.DataSnapshotMapper.DATA_SNAPSHOT_EXISTENCE_PREDICATE; 38 | 39 | public class RxFirebaseDatabase { 40 | 41 | /** 42 | * Listener for changes in te data at the given query location. 43 | * 44 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 45 | * @param strategy {@link BackpressureStrategy} associated to this {@link Flowable} 46 | * @return a {@link Flowable} which emits when a value of the database change in the given query. 47 | */ 48 | @NonNull 49 | public static Flowable observeValueEvent(@NonNull final Query query, 50 | @NonNull BackpressureStrategy strategy) { 51 | return Flowable.create(new FlowableOnSubscribe() { 52 | @Override 53 | public void subscribe(final FlowableEmitter emitter) throws Exception { 54 | final ValueEventListener valueEventListener = new ValueEventListener() { 55 | @Override 56 | public void onDataChange(DataSnapshot dataSnapshot) { 57 | emitter.onNext(dataSnapshot); 58 | } 59 | 60 | @Override 61 | public void onCancelled(final DatabaseError error) { 62 | if (!emitter.isCancelled()) 63 | emitter.onError(new RxFirebaseDataException(error)); 64 | } 65 | }; 66 | emitter.setCancellable(new Cancellable() { 67 | @Override 68 | public void cancel() throws Exception { 69 | query.removeEventListener(valueEventListener); 70 | } 71 | }); 72 | query.addValueEventListener(valueEventListener); 73 | } 74 | }, strategy); 75 | } 76 | 77 | /** 78 | * Listener for a single change in te data at the given query location. 79 | * 80 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 81 | * @return a {@link Maybe} which emits the actual state of the database for the given query. onSuccess will be only call when 82 | * the given {@link DataSnapshot} exists onComplete will only called when the data doesn't exist. 83 | */ 84 | @NonNull 85 | public static Maybe observeSingleValueEvent(@NonNull final Query query) { 86 | return Maybe.create(new MaybeOnSubscribe() { 87 | @Override 88 | public void subscribe(final MaybeEmitter emitter) throws Exception { 89 | query.addListenerForSingleValueEvent(new ValueEventListener() { 90 | @Override 91 | public void onDataChange(DataSnapshot dataSnapshot) { 92 | if (dataSnapshot.exists()) { 93 | emitter.onSuccess(dataSnapshot); 94 | } else { 95 | emitter.onComplete(); 96 | } 97 | } 98 | 99 | @Override 100 | public void onCancelled(DatabaseError error) { 101 | if (!emitter.isDisposed()) 102 | emitter.onError(new RxFirebaseDataException(error)); 103 | } 104 | }); 105 | } 106 | }); 107 | } 108 | 109 | /** 110 | * Run a transaction on the data at this location. For more information on running transactions, see 111 | * 112 | * @param ref reference represents a particular location in your database. 113 | * @param fireLocalEvents boolean which allow to receive calls of your transaction in your local device. 114 | * @param transactionValue value of the transaction. 115 | * @return a {@link Single} which emits the final {@link DataSnapshot} value if the transaction success. 116 | */ 117 | @NonNull 118 | public static Single runTransaction(@NonNull final DatabaseReference ref, 119 | @NonNull final boolean fireLocalEvents, 120 | @NonNull final long transactionValue) { 121 | return Single.create(new SingleOnSubscribe() { 122 | @Override 123 | public void subscribe(final SingleEmitter emitter) throws Exception { 124 | ref.runTransaction(new Transaction.Handler() { 125 | @Override 126 | public Transaction.Result doTransaction(MutableData mutableData) { 127 | Integer currentValue = mutableData.getValue(Integer.class); 128 | if (currentValue == null) { 129 | mutableData.setValue(transactionValue); 130 | } else { 131 | mutableData.setValue(currentValue + transactionValue); 132 | } 133 | return Transaction.success(mutableData); 134 | } 135 | 136 | @Override 137 | public void onComplete(DatabaseError databaseError, boolean b, DataSnapshot dataSnapshot) { 138 | if (databaseError != null && !emitter.isDisposed()) { 139 | emitter.onError(new RxFirebaseDataException(databaseError)); 140 | } else { 141 | emitter.onSuccess(dataSnapshot); 142 | } 143 | } 144 | }, fireLocalEvents); 145 | } 146 | }); 147 | } 148 | 149 | 150 | /** 151 | * Set the given value on the specified {@link DatabaseReference}. 152 | * 153 | * @param ref reference represents a particular location in your database. 154 | * @param value value to update. 155 | * @return a {@link Completable} which is complete when the set value call finish successfully. 156 | */ 157 | @NonNull 158 | public static Completable setValue(@NonNull final DatabaseReference ref, 159 | final Object value) { 160 | return Completable.create(new CompletableOnSubscribe() { 161 | @Override 162 | public void subscribe(@NonNull final CompletableEmitter e) throws Exception { 163 | ref.setValue(value).addOnSuccessListener(new OnSuccessListener() { 164 | @Override 165 | public void onSuccess(Void aVoid) { 166 | e.onComplete(); 167 | } 168 | }).addOnFailureListener(new OnFailureListener() { 169 | @Override 170 | public void onFailure(@NonNull Exception exception) { 171 | if (!e.isDisposed()) 172 | e.onError(exception); 173 | } 174 | }); 175 | } 176 | }); 177 | } 178 | 179 | /** 180 | * Update the specific child keys to the specified values. 181 | * 182 | * @param ref reference represents a particular location in your database. 183 | * @param updateData The paths to update and their new values 184 | * @return a {@link Completable} which is complete when the update children call finish successfully. 185 | */ 186 | @NonNull 187 | public static Completable updateChildren(@NonNull final DatabaseReference ref, 188 | @NonNull final Map updateData) { 189 | return Completable.create(new CompletableOnSubscribe() { 190 | @Override 191 | public void subscribe(final CompletableEmitter emitter) throws Exception { 192 | ref.updateChildren(updateData, new DatabaseReference.CompletionListener() { 193 | @Override 194 | public void onComplete(DatabaseError error, DatabaseReference databaseReference) { 195 | if (error != null && !emitter.isDisposed()) { 196 | emitter.onError(new RxFirebaseDataException(error)); 197 | } else { 198 | emitter.onComplete(); 199 | } 200 | } 201 | }); 202 | } 203 | }); 204 | } 205 | 206 | /** 207 | * Listener for for child events occurring at the given query location. 208 | * 209 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 210 | * @param strategy {@link BackpressureStrategy} associated to this {@link Flowable} 211 | * @return a {@link Flowable} which emits when a value of a child int the database change on the given query. 212 | */ 213 | @NonNull 214 | public static Flowable> observeChildEvent( 215 | @NonNull final Query query, @NonNull BackpressureStrategy strategy) { 216 | return Flowable.create(new FlowableOnSubscribe>() { 217 | @Override 218 | public void subscribe(final FlowableEmitter> emitter) throws Exception { 219 | final ChildEventListener childEventListener = new ChildEventListener() { 220 | 221 | @Override 222 | public void onChildAdded(DataSnapshot dataSnapshot, String previousChildName) { 223 | emitter.onNext( 224 | new RxFirebaseChildEvent<>(dataSnapshot.getKey(), dataSnapshot, previousChildName, 225 | RxFirebaseChildEvent.EventType.ADDED)); 226 | } 227 | 228 | @Override 229 | public void onChildChanged(DataSnapshot dataSnapshot, String previousChildName) { 230 | emitter.onNext( 231 | new RxFirebaseChildEvent<>(dataSnapshot.getKey(), dataSnapshot, previousChildName, 232 | RxFirebaseChildEvent.EventType.CHANGED)); 233 | } 234 | 235 | @Override 236 | public void onChildRemoved(DataSnapshot dataSnapshot) { 237 | emitter.onNext(new RxFirebaseChildEvent<>(dataSnapshot.getKey(), dataSnapshot, 238 | RxFirebaseChildEvent.EventType.REMOVED)); 239 | } 240 | 241 | @Override 242 | public void onChildMoved(DataSnapshot dataSnapshot, String previousChildName) { 243 | emitter.onNext( 244 | new RxFirebaseChildEvent<>(dataSnapshot.getKey(), dataSnapshot, previousChildName, 245 | RxFirebaseChildEvent.EventType.MOVED)); 246 | } 247 | 248 | @Override 249 | public void onCancelled(DatabaseError error) { 250 | if (!emitter.isCancelled()) 251 | emitter.onError(new RxFirebaseDataException(error)); 252 | } 253 | }; 254 | emitter.setCancellable(new Cancellable() { 255 | @Override 256 | public void cancel() throws Exception { 257 | query.removeEventListener(childEventListener); 258 | } 259 | }); 260 | query.addChildEventListener(childEventListener); 261 | 262 | } 263 | }, strategy); 264 | } 265 | 266 | /** 267 | * Method which retrieve a list of DataSnapshot from multiple {@link DatabaseReference}. 268 | * 269 | * @param whereRefs array of {@link DatabaseReference references.} 270 | * @return a {@link Flowable} which emmit {@link DataSnapshot} from the given queries. 271 | */ 272 | @NonNull 273 | public static Flowable observeMultipleSingleValueEvent(@NonNull DatabaseReference... whereRefs) { 274 | return Maybe.merge(Flowable.fromArray(whereRefs) 275 | .map(new Function>() { 276 | @Override 277 | public MaybeSource apply(@NonNull DatabaseReference databaseReference) throws 278 | Exception { 279 | return observeSingleValueEvent(databaseReference); 280 | } 281 | }) 282 | ); 283 | } 284 | 285 | /** 286 | * Retrieve the child {@link DatabaseReference references} from an specific parent which equals to the 287 | * references retrieved from another query. Which allow to make a "where" clause on a no relational table. 288 | *

289 | * Example: 290 | * DatabaseReference from = reference.child("Tweets"); 291 | * Query where = reference.child("favorited").child(userA); 292 | * requestFilteredReferenceKeys(from, where).subscribe... 293 | *

294 | * This last method will return the key references(/tweets/tweetId) which the userA mark as favorited. 295 | * With the given list we can work together with {@link RxFirebaseDatabase#observeMultipleSingleValueEvent(DatabaseReference...)} 296 | * to retrieve the Datasnapshots from the desired {@link DatabaseReference} based on other {@link DatabaseReference} values. 297 | * 298 | * @param from base reference where you want to retrieve the original references. 299 | * @param whereRef reference that you use as a filter to create your from references. 300 | * @return a {@link Maybe} which contain the list of the given DatabaseReferences. 301 | */ 302 | @NonNull 303 | public static Maybe requestFilteredReferenceKeys(@NonNull final DatabaseReference from, 304 | @NonNull Query whereRef) { 305 | return observeSingleValueEvent(whereRef, new Function() { 306 | @Override 307 | public DatabaseReference[] apply(@NonNull DataSnapshot dataSnapshot) throws Exception { 308 | int childrenCount = (int) dataSnapshot.getChildrenCount(); 309 | DatabaseReference[] filterRefs = new DatabaseReference[childrenCount]; 310 | final Iterator iterator = dataSnapshot.getChildren().iterator(); 311 | for (int i = 0; i < childrenCount; i++) { 312 | filterRefs[i] = from.child(iterator.next().getKey()); 313 | } 314 | return filterRefs; 315 | } 316 | }); 317 | } 318 | 319 | /** 320 | * Listener for changes in te data at the given query location. 321 | * 322 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 323 | * @param clazz class type for the {@link DataSnapshot} items. 324 | * @param strategy {@link BackpressureStrategy} associated to this {@link Flowable} 325 | * @return a {@link Flowable} which emits when a value of the database change in the given query. 326 | */ 327 | @NonNull 328 | public static Flowable observeValueEvent(@NonNull final Query query, 329 | @NonNull final Class clazz, 330 | @NonNull BackpressureStrategy strategy) { 331 | return observeValueEvent(query, DataSnapshotMapper.of(clazz), strategy); 332 | } 333 | 334 | /** 335 | * Listener for a single change in te data at the given query location. 336 | * 337 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 338 | * @param clazz class type for the {@link DataSnapshot} items. 339 | * @return a {@link Maybe} which emits the actual state of the database for the given query. 340 | */ 341 | @NonNull 342 | public static Maybe observeSingleValueEvent(@NonNull final Query query, 343 | @NonNull final Class clazz) { 344 | return observeSingleValueEvent(query, DataSnapshotMapper.of(clazz)); 345 | } 346 | 347 | /** 348 | * Listener for for child events occurring at the given query location. 349 | * 350 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 351 | * @param clazz class type for the {@link DataSnapshot} items. 352 | * @param strategy {@link BackpressureStrategy} associated to this {@link Flowable} 353 | * @return a {@link Flowable} which emits when a value of a child int the database change on the given query. 354 | */ 355 | @NonNull 356 | public static Flowable> observeChildEvent( 357 | @NonNull final Query query, @NonNull final Class clazz, 358 | @NonNull BackpressureStrategy strategy) { 359 | return observeChildEvent(query, DataSnapshotMapper.ofChildEvent(clazz), strategy); 360 | } 361 | 362 | /** 363 | * Listener for changes in te data at the given query location. 364 | * 365 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 366 | * @param mapper specific function to map the dispatched events. 367 | * @param strategy {@link BackpressureStrategy} associated to this {@link Flowable} 368 | * @return a {@link Flowable} which emits when a value of the database change in the given query. 369 | */ 370 | @NonNull 371 | public static Flowable observeValueEvent(@NonNull final Query query, 372 | @NonNull final Function mapper, 373 | @NonNull BackpressureStrategy strategy) { 374 | return observeValueEvent(query, strategy).map(mapper); 375 | } 376 | 377 | /** 378 | * Listener for a single change in te data at the given query location. 379 | * 380 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 381 | * @param mapper specific function to map the dispatched events. 382 | * @return a {@link Maybe} which emits the actual state of the database for the given query. 383 | */ 384 | @NonNull 385 | public static Maybe observeSingleValueEvent(@NonNull final Query query, 386 | @NonNull final Function mapper) { 387 | return observeSingleValueEvent(query) 388 | .filter(DATA_SNAPSHOT_EXISTENCE_PREDICATE) 389 | .map(mapper); 390 | } 391 | 392 | /** 393 | * Listener for for child events occurring at the given query location. 394 | * 395 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 396 | * @param mapper specific function to map the dispatched events. 397 | * @param strategy {@link BackpressureStrategy} associated to this {@link Flowable} 398 | * @return a {@link Flowable} which emits when a value of a child int the database change on the given query. 399 | */ 400 | @NonNull 401 | public static Flowable> observeChildEvent( 402 | @NonNull final Query query, @NonNull final Function, 403 | ? extends RxFirebaseChildEvent> mapper, @NonNull BackpressureStrategy strategy) { 404 | return observeChildEvent(query, strategy).map(mapper); 405 | } 406 | 407 | /** 408 | * Listener for changes in the data at the given query location. 409 | * 410 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 411 | * @return a {@link Flowable} which emits when a value of the database change in the given query. 412 | */ 413 | @NonNull 414 | public static Flowable observeValueEvent(@NonNull final Query query) { 415 | return observeValueEvent(query, BackpressureStrategy.DROP); 416 | } 417 | 418 | 419 | /** 420 | * Listener for for child events occurring at the given query location. 421 | * 422 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 423 | * @return a {@link Flowable} which emits when a value of a child int the database change on the given query. 424 | */ 425 | @NonNull 426 | public static Flowable> observeChildEvent( 427 | @NonNull final Query query) { 428 | return observeChildEvent(query, BackpressureStrategy.DROP); 429 | } 430 | 431 | /** 432 | * Run a transaction on the data at this location. For more information on running transactions, see 433 | * 434 | * @param ref reference represents a particular location in your database. 435 | * @param transactionValue value of the transaction. 436 | * @return a {@link Single} which emits the final {@link DataSnapshot} value if the transaction success. 437 | */ 438 | @NonNull 439 | public static Single runTransaction(@NonNull final DatabaseReference ref, 440 | @NonNull final long transactionValue) { 441 | return runTransaction(ref, true, transactionValue); 442 | } 443 | 444 | /** 445 | * Listener for changes in te data at the given query location. 446 | * 447 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 448 | * @param clazz class type for the {@link DataSnapshot} items. 449 | * @return a {@link Flowable} which emits when a value of the database change in the given query. 450 | */ 451 | @NonNull 452 | public static Flowable observeValueEvent(@NonNull final Query query, 453 | @NonNull final Class clazz) { 454 | return observeValueEvent(query, DataSnapshotMapper.of(clazz), BackpressureStrategy.DROP); 455 | } 456 | 457 | /** 458 | * Listener for for child events occurring at the given query location. 459 | * 460 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 461 | * @param clazz class type for the {@link DataSnapshot} items. 462 | * @return a {@link Flowable} which emits when a value of a child int the database change on the given query. 463 | */ 464 | @NonNull 465 | public static Flowable> observeChildEvent( 466 | @NonNull final Query query, @NonNull final Class clazz) { 467 | return observeChildEvent(query, DataSnapshotMapper.ofChildEvent(clazz), BackpressureStrategy.DROP); 468 | } 469 | 470 | /** 471 | * Listener for changes in te data at the given query location. 472 | * 473 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 474 | * @return a {@link Flowable} which emits when a value of the database change in the given query. 475 | */ 476 | @NonNull 477 | public static Flowable observeValueEvent(@NonNull final Query query, 478 | @NonNull final Function mapper) { 479 | return observeValueEvent(query, BackpressureStrategy.DROP).map(mapper); 480 | } 481 | 482 | /** 483 | * Listener for for child events occurring at the given query location. 484 | * 485 | * @param query reference represents a particular location in your Database and can be used for reading or writing data to that Database location. 486 | * @param mapper specific function to map the dispatched events. 487 | * @return a {@link Flowable} which emits when a value of a child int the database change on the given query. 488 | */ 489 | @NonNull 490 | public static Flowable> observeChildEvent( 491 | @NonNull final Query query, @NonNull final Function, 492 | ? extends RxFirebaseChildEvent> mapper) { 493 | return observeChildEvent(query, BackpressureStrategy.DROP).map(mapper); 494 | } 495 | } -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/RxFirebaseFunctions.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import androidx.annotation.NonNull; 4 | import androidx.annotation.Nullable; 5 | 6 | import com.google.android.gms.tasks.OnFailureListener; 7 | import com.google.android.gms.tasks.OnSuccessListener; 8 | import com.google.firebase.functions.FirebaseFunctions; 9 | import com.google.firebase.functions.HttpsCallableResult; 10 | 11 | import io.reactivex.Single; 12 | import io.reactivex.SingleEmitter; 13 | import io.reactivex.SingleOnSubscribe; 14 | 15 | public class RxFirebaseFunctions { 16 | 17 | /** 18 | * Calls a {@link HttpsCallableResult} which represents a reference to a Google Cloud Functions HTTPS callable function. 19 | * 20 | * @param functions Instance of {@link FirebaseFunctions} 21 | * @param name Name of the Google Cloud function 22 | * @param data Params for the request. 23 | * @return a {@link Single} which will emit the result of the given function. 24 | */ 25 | @NonNull 26 | public static Single getHttpsCallable(@NonNull final FirebaseFunctions functions, 27 | @NonNull final String name, 28 | @Nullable final Object data) { 29 | return Single.create(new SingleOnSubscribe() { 30 | @Override 31 | public void subscribe(final SingleEmitter emitter) { 32 | functions.getHttpsCallable(name) 33 | .call(data) 34 | .addOnSuccessListener(new OnSuccessListener() { 35 | @Override 36 | public void onSuccess(HttpsCallableResult httpsCallableResult) { 37 | emitter.onSuccess(httpsCallableResult); 38 | } 39 | }) 40 | .addOnFailureListener(new OnFailureListener() { 41 | @Override 42 | public void onFailure(@NonNull Exception e) { 43 | emitter.onError(e); 44 | } 45 | }); 46 | } 47 | }); 48 | } 49 | 50 | 51 | /** 52 | * Calls a {@link HttpsCallableResult} which represents a reference to a Google Cloud Functions HTTPS callable function. 53 | * 54 | * @param functions Instance of {@link FirebaseFunctions} 55 | * @param name Name of the Google Cloud function 56 | * @return a {@link Single} which will emit the result of the given function. 57 | */ 58 | @NonNull 59 | public static Single getHttpsCallable(@NonNull final FirebaseFunctions functions, 60 | @NonNull final String name) { 61 | return getHttpsCallable(functions, name, null); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/RxFirebaseQuery.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.google.firebase.database.DataSnapshot; 6 | import com.google.firebase.database.DatabaseReference; 7 | import com.google.firebase.database.Query; 8 | 9 | import java.util.List; 10 | 11 | import io.reactivex.Flowable; 12 | import io.reactivex.Maybe; 13 | import io.reactivex.Single; 14 | import io.reactivex.functions.Function; 15 | 16 | import static durdinapps.rxfirebase2.RxFirebaseDatabase.observeMultipleSingleValueEvent; 17 | import static durdinapps.rxfirebase2.RxFirebaseDatabase.observeSingleValueEvent; 18 | import static durdinapps.rxfirebase2.RxFirebaseDatabase.requestFilteredReferenceKeys; 19 | 20 | /** 21 | * Basic builder to create Firebase queries based on filters from different {@link DatabaseReference references}. 22 | */ 23 | public class RxFirebaseQuery { 24 | private Maybe whereMaybe; 25 | 26 | private RxFirebaseQuery() { 27 | } 28 | 29 | /** 30 | * Retrieve a new instance for {@link RxFirebaseQuery}. 31 | */ 32 | public static RxFirebaseQuery getInstance() { 33 | return new RxFirebaseQuery(); 34 | } 35 | 36 | 37 | /** 38 | * Generate a filter based on the method {@link RxFirebaseDatabase#requestFilteredReferenceKeys(DatabaseReference, Query)}. 39 | * 40 | * @param from base reference where you want to retrieve the original references. 41 | * @param whereRef reference that you use as a filter to create your from references. 42 | * @return the current instance of {@link RxFirebaseQuery}. 43 | */ 44 | @NonNull 45 | public RxFirebaseQuery filterByRefs(@NonNull DatabaseReference from, 46 | @NonNull Query whereRef) { 47 | whereMaybe = requestFilteredReferenceKeys(from, whereRef); 48 | return this; 49 | } 50 | 51 | /** 52 | * Generate a filter based on a given function. 53 | * 54 | * @param whereRef reference that you use as a filter to create your from references. 55 | * @param mapper Custom mapper to map the retrieved where references to new {@link DatabaseReference}. 56 | * @return the current instance of {@link RxFirebaseQuery}. 57 | */ 58 | @NonNull 59 | public RxFirebaseQuery filter(@NonNull Query whereRef, 60 | @NonNull final Function mapper) { 61 | whereMaybe = observeSingleValueEvent(whereRef, mapper); 62 | return this; 63 | } 64 | 65 | /** 66 | * Retrieve the final result as a {@link Single} which emmit the final result of the event as a {@link List} of {@link DataSnapshot}. 67 | */ 68 | @NonNull 69 | public Single> asList() { 70 | return create().toList(); 71 | } 72 | 73 | /** 74 | * Retrieve the final result as a {@link Single} which emmit the final result of the event as a {@link List} of a given type. 75 | * 76 | * @param mapper specific function to map the dispatched events. 77 | */ 78 | @NonNull 79 | public Single> asList(@NonNull final Function, ? extends List> mapper) { 80 | return create().toList().map(mapper); 81 | } 82 | 83 | /** 84 | * Retrieve the final result of the query as a {@link Flowable} which emmit mapped values. 85 | * 86 | * @param mapper specific function to map the dispatched events. 87 | */ 88 | @NonNull 89 | public Flowable create(@NonNull final Function mapper) { 90 | return create().map(mapper); 91 | } 92 | 93 | /** 94 | * Retrieve the final result of the query as a {@link Flowable} which emmit {@link DataSnapshot}. 95 | */ 96 | @NonNull 97 | public Flowable create() { 98 | if (whereMaybe == null) 99 | throw new IllegalArgumentException("It's necessary define a where function to retrieve data"); 100 | 101 | return whereMaybe.toFlowable().flatMap(new Function>() { 102 | @Override 103 | public Flowable apply(@io.reactivex.annotations.NonNull DatabaseReference[] keys) throws Exception { 104 | return observeMultipleSingleValueEvent(keys); 105 | } 106 | }); 107 | } 108 | } 109 | 110 | -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/RxFirebaseRemote.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.google.firebase.remoteconfig.FirebaseRemoteConfig; 6 | 7 | import io.reactivex.Completable; 8 | import io.reactivex.CompletableEmitter; 9 | import io.reactivex.CompletableOnSubscribe; 10 | 11 | public class RxFirebaseRemote { 12 | 13 | /** 14 | * Fetches parameter values for your app. 15 | * Configuration values may be served from the Default Config (local cache) or from the Remote Config Server, 16 | * depending on how much time has elapsed since parameter values were last fetched from the Remote Config server. 17 | * This method lets the caller specify the cache expiration in seconds. 18 | *

19 | * To identify the current app instance, the fetch request creates a Firebase Instance ID token, 20 | * which periodically sends data to the Firebase backend. To stop the periodic sync, call deleteInstanceId(); 21 | * calling fetchConfig again creates a new token and resumes the periodic sync. 22 | * 23 | * @param config firebase remote config instance. 24 | * @param cacheLifeTime If the data in the cache was fetched no longer than this many seconds ago, this method will return the cached data. 25 | * If not, a fetch from the Remote Config Server will be attempted. 26 | * @return a {@link Completable} which emits when the action is completed. 27 | */ 28 | @NonNull 29 | public static Completable fetch(@NonNull final FirebaseRemoteConfig config, 30 | @NonNull final long cacheLifeTime) { 31 | return Completable.create(new CompletableOnSubscribe() { 32 | @Override 33 | public void subscribe(CompletableEmitter emitter) { 34 | RxCompletableHandler.assignOnTask(emitter, config.fetch(cacheLifeTime)); 35 | } 36 | }); 37 | } 38 | 39 | /** 40 | * Fetches parameter values for your app. 41 | * Configuration values may be served from the Default Config (local cache) or from the Remote Config Server, 42 | * depending on how much time has elapsed since parameter values were last fetched from the Remote Config server. 43 | * This method lets the caller specify the cache expiration in seconds. 44 | *

45 | * To identify the current app instance, the fetch request creates a Firebase Instance ID token, 46 | * which periodically sends data to the Firebase backend. To stop the periodic sync, call deleteInstanceId(); 47 | * calling fetchConfig again creates a new token and resumes the periodic sync. 48 | * 49 | * @param config firebase remote config instance. 50 | * @return a {@link Completable} which emits when the action is completed. 51 | */ 52 | @NonNull 53 | public static Completable fetch(@NonNull final FirebaseRemoteConfig config) { 54 | return Completable.create(new CompletableOnSubscribe() { 55 | @Override 56 | public void subscribe(CompletableEmitter emitter) { 57 | RxCompletableHandler.assignOnTask(emitter, config.fetch(43200L)); 58 | } 59 | }); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/RxFirebaseStorage.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import android.net.Uri; 4 | import androidx.annotation.NonNull; 5 | 6 | import com.google.android.gms.tasks.OnFailureListener; 7 | import com.google.android.gms.tasks.OnSuccessListener; 8 | import com.google.firebase.storage.FileDownloadTask; 9 | import com.google.firebase.storage.StorageMetadata; 10 | import com.google.firebase.storage.StorageReference; 11 | import com.google.firebase.storage.StorageTask; 12 | import com.google.firebase.storage.StreamDownloadTask; 13 | import com.google.firebase.storage.UploadTask; 14 | 15 | import java.io.File; 16 | import java.io.InputStream; 17 | 18 | import io.reactivex.Completable; 19 | import io.reactivex.CompletableEmitter; 20 | import io.reactivex.CompletableOnSubscribe; 21 | import io.reactivex.Maybe; 22 | import io.reactivex.MaybeEmitter; 23 | import io.reactivex.MaybeOnSubscribe; 24 | import io.reactivex.Single; 25 | import io.reactivex.SingleEmitter; 26 | import io.reactivex.SingleOnSubscribe; 27 | import io.reactivex.functions.Cancellable; 28 | 29 | public class RxFirebaseStorage { 30 | 31 | /** 32 | * Asynchronously downloads the object from this {@link StorageReference} a byte array will be allocated large enough to hold the entire file in memory. 33 | * 34 | * @param storageRef represents a reference to a Google Cloud Storage object. 35 | * @param maxDownloadSizeBytes the maximum allowed size in bytes that will be allocated. Set this parameter to prevent out of memory conditions from occurring. 36 | * If the download exceeds this limit, the task will fail and an IndexOutOfBoundsException will be returned. 37 | * @return a {@link Single} which emits an byte[] if success. 38 | */ 39 | @NonNull 40 | public static Maybe getBytes(@NonNull final StorageReference storageRef, 41 | final long maxDownloadSizeBytes) { 42 | return Maybe.create(new MaybeOnSubscribe() { 43 | @Override 44 | public void subscribe(MaybeEmitter emitter) throws Exception { 45 | RxHandler.assignOnTask(emitter, storageRef.getBytes(maxDownloadSizeBytes)); 46 | } 47 | }); 48 | } 49 | 50 | /** 51 | * Asynchronously retrieves a long lived download URL with a revocable token. 52 | * 53 | * @param storageRef represents a reference to a Google Cloud Storage object. 54 | * @return a {@link Single} which emits an {@link Uri} if success. 55 | */ 56 | @NonNull 57 | public static Maybe getDownloadUrl(@NonNull final StorageReference storageRef) { 58 | return Maybe.create(new MaybeOnSubscribe() { 59 | @Override 60 | public void subscribe(MaybeEmitter emitter) throws Exception { 61 | RxHandler.assignOnTask(emitter, storageRef.getDownloadUrl()); 62 | } 63 | }); 64 | } 65 | 66 | /** 67 | * Asynchronously downloads the object at this {@link StorageReference} to a specified system filepath. 68 | * 69 | * @param storageRef represents a reference to a Google Cloud Storage object. 70 | * @param destinationFile a File representing the path the object should be downloaded to. 71 | * @return a {@link Single} which emits an {@link FileDownloadTask.TaskSnapshot} if success. 72 | */ 73 | @NonNull 74 | public static Single getFile(@NonNull final StorageReference storageRef, 75 | @NonNull final File destinationFile) { 76 | return Single.create(new SingleOnSubscribe() { 77 | public void subscribe(final SingleEmitter emitter) throws Exception { 78 | final StorageTask taskSnapshotStorageTask = 79 | storageRef.getFile(destinationFile).addOnSuccessListener(new OnSuccessListener() { 80 | @Override 81 | public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) { 82 | emitter.onSuccess(taskSnapshot); 83 | } 84 | }).addOnFailureListener(new OnFailureListener() { 85 | @Override 86 | public void onFailure(@NonNull Exception e) { 87 | if (!emitter.isDisposed()) 88 | emitter.onError(e); 89 | } 90 | }); 91 | 92 | emitter.setCancellable(new Cancellable() { 93 | @Override 94 | public void cancel() throws Exception { 95 | taskSnapshotStorageTask.cancel(); 96 | } 97 | }); 98 | } 99 | }); 100 | } 101 | 102 | /** 103 | * Asynchronously downloads the object at this {@link StorageReference} to a specified system filepath. 104 | * 105 | * @param storageRef represents a reference to a Google Cloud Storage object. 106 | * @param destinationUri a file system URI representing the path the object should be downloaded to. 107 | * @return a {@link Single} which emits an {@link FileDownloadTask.TaskSnapshot} if success. 108 | */ 109 | @NonNull 110 | public static Single getFile(@NonNull final StorageReference storageRef, 111 | @NonNull final Uri destinationUri) { 112 | return Single.create(new SingleOnSubscribe() { 113 | public void subscribe(final SingleEmitter emitter) throws Exception { 114 | final StorageTask taskSnapshotStorageTask = 115 | storageRef.getFile(destinationUri).addOnSuccessListener(new OnSuccessListener() { 116 | @Override 117 | public void onSuccess(FileDownloadTask.TaskSnapshot taskSnapshot) { 118 | emitter.onSuccess(taskSnapshot); 119 | } 120 | }).addOnFailureListener(new OnFailureListener() { 121 | @Override 122 | public void onFailure(@NonNull Exception e) { 123 | if (!emitter.isDisposed()) 124 | emitter.onError(e); 125 | } 126 | }); 127 | 128 | emitter.setCancellable(new Cancellable() { 129 | @Override 130 | public void cancel() throws Exception { 131 | taskSnapshotStorageTask.cancel(); 132 | } 133 | }); 134 | } 135 | }); 136 | } 137 | 138 | /** 139 | * Retrieves metadata associated with an object at this {@link StorageReference}. 140 | * 141 | * @param storageRef represents a reference to a Google Cloud Storage object. 142 | * @return a {@link Single} which emits an {@link StorageMetadata} if success. 143 | */ 144 | @NonNull 145 | public static Maybe getMetadata(@NonNull final StorageReference storageRef) { 146 | return Maybe.create(new MaybeOnSubscribe() { 147 | @Override 148 | public void subscribe(MaybeEmitter emitter) throws Exception { 149 | RxHandler.assignOnTask(emitter, storageRef.getMetadata()); 150 | } 151 | }); 152 | } 153 | 154 | /** 155 | * Asynchronously downloads the object at this {@link StorageReference} via a InputStream. 156 | * 157 | * @param storageRef represents a reference to a Google Cloud Storage object. 158 | * @return a {@link Single} which emits an {@link StreamDownloadTask.TaskSnapshot} if success. 159 | */ 160 | @NonNull 161 | public static Single getStream(@NonNull final StorageReference storageRef) { 162 | return Single.create(new SingleOnSubscribe() { 163 | public void subscribe(final SingleEmitter emitter) throws Exception { 164 | final StorageTask taskSnapshotStorageTask = 165 | storageRef.getStream().addOnSuccessListener(new OnSuccessListener() { 166 | @Override 167 | public void onSuccess(StreamDownloadTask.TaskSnapshot taskSnapshot) { 168 | emitter.onSuccess(taskSnapshot); 169 | } 170 | }).addOnFailureListener(new OnFailureListener() { 171 | @Override 172 | public void onFailure(@NonNull Exception e) { 173 | if (!emitter.isDisposed()) 174 | emitter.onError(e); 175 | } 176 | }); 177 | 178 | emitter.setCancellable(new Cancellable() { 179 | @Override 180 | public void cancel() throws Exception { 181 | taskSnapshotStorageTask.cancel(); 182 | } 183 | }); 184 | } 185 | }); 186 | } 187 | 188 | /** 189 | * Asynchronously downloads the object at this {@link StorageReference} via a InputStream. 190 | * 191 | * @param storageRef represents a reference to a Google Cloud Storage object. 192 | * @param processor A StreamDownloadTask.StreamProcessor that is responsible for reading data from the InputStream. 193 | * The StreamDownloadTask.StreamProcessor is called on a background thread and checked exceptions thrown 194 | * from this object will be returned as a failure to the OnFailureListener registered on the StreamDownloadTask. 195 | * @return a {@link Single} which emits an {@link StreamDownloadTask.TaskSnapshot} if success. 196 | */ 197 | @NonNull 198 | public static Single getStream(@NonNull final StorageReference storageRef, 199 | @NonNull final StreamDownloadTask.StreamProcessor processor) { 200 | return Single.create(new SingleOnSubscribe() { 201 | public void subscribe(final SingleEmitter emitter) throws Exception { 202 | final StorageTask taskSnapshotStorageTask = 203 | storageRef.getStream(processor).addOnSuccessListener(new OnSuccessListener() { 204 | @Override 205 | public void onSuccess(StreamDownloadTask.TaskSnapshot taskSnapshot) { 206 | emitter.onSuccess(taskSnapshot); 207 | } 208 | }).addOnFailureListener(new OnFailureListener() { 209 | @Override 210 | public void onFailure(@NonNull Exception e) { 211 | if (!emitter.isDisposed()) 212 | emitter.onError(e); 213 | } 214 | }); 215 | 216 | emitter.setCancellable(new Cancellable() { 217 | @Override 218 | public void cancel() throws Exception { 219 | taskSnapshotStorageTask.cancel(); 220 | } 221 | }); 222 | } 223 | }); 224 | } 225 | 226 | /** 227 | * Asynchronously uploads byte data to this {@link StorageReference}. 228 | * 229 | * @param storageRef represents a reference to a Google Cloud Storage object. 230 | * @param bytes The byte[] to upload. 231 | * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. 232 | */ 233 | @NonNull 234 | public static Single putBytes(@NonNull final StorageReference storageRef, 235 | @NonNull final byte[] bytes) { 236 | return Single.create(new SingleOnSubscribe() { 237 | public void subscribe(final SingleEmitter emitter) throws Exception { 238 | final StorageTask taskSnapshotStorageTask = 239 | storageRef.putBytes(bytes).addOnSuccessListener(new OnSuccessListener() { 240 | @Override 241 | public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { 242 | emitter.onSuccess(taskSnapshot); 243 | } 244 | }).addOnFailureListener(new OnFailureListener() { 245 | @Override 246 | public void onFailure(@NonNull Exception e) { 247 | if (!emitter.isDisposed()) 248 | emitter.onError(e); 249 | } 250 | }); 251 | emitter.setCancellable(new Cancellable() { 252 | @Override 253 | public void cancel() throws Exception { 254 | taskSnapshotStorageTask.cancel(); 255 | } 256 | }); 257 | } 258 | }); 259 | } 260 | 261 | /** 262 | * Asynchronously uploads byte data to this {@link StorageReference}. 263 | * 264 | * @param storageRef represents a reference to a Google Cloud Storage object. 265 | * @param bytes The byte[] to upload. 266 | * @param metadata {@link StorageMetadata} containing additional information (MIME type, etc.) about the object being uploaded. 267 | * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. 268 | */ 269 | @NonNull 270 | public static Single putBytes(@NonNull final StorageReference storageRef, 271 | @NonNull final byte[] bytes, 272 | @NonNull final StorageMetadata metadata) { 273 | return Single.create(new SingleOnSubscribe() { 274 | public void subscribe(final SingleEmitter emitter) throws Exception { 275 | final StorageTask taskSnapshotStorageTask = 276 | storageRef.putBytes(bytes, metadata).addOnSuccessListener(new OnSuccessListener() { 277 | @Override 278 | public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { 279 | emitter.onSuccess(taskSnapshot); 280 | } 281 | }).addOnFailureListener(new OnFailureListener() { 282 | @Override 283 | public void onFailure(@NonNull Exception e) { 284 | if (!emitter.isDisposed()) 285 | emitter.onError(e); 286 | } 287 | }); 288 | 289 | emitter.setCancellable(new Cancellable() { 290 | @Override 291 | public void cancel() throws Exception { 292 | taskSnapshotStorageTask.cancel(); 293 | } 294 | }); 295 | } 296 | }); 297 | } 298 | 299 | /** 300 | * Asynchronously uploads from a content URI to this {@link StorageReference}. 301 | * 302 | * @param storageRef represents a reference to a Google Cloud Storage object. 303 | * @param uri The source of the upload. This can be a file:// scheme or any content URI. A content resolver will be used to load the data. 304 | * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. 305 | */ 306 | @NonNull 307 | public static Single putFile(@NonNull final StorageReference storageRef, 308 | @NonNull final Uri uri) { 309 | return Single.create(new SingleOnSubscribe() { 310 | public void subscribe(final SingleEmitter emitter) throws Exception { 311 | final StorageTask taskSnapshotStorageTask = 312 | storageRef.putFile(uri).addOnSuccessListener(new OnSuccessListener() { 313 | @Override 314 | public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { 315 | emitter.onSuccess(taskSnapshot); 316 | } 317 | }).addOnFailureListener(new OnFailureListener() { 318 | @Override 319 | public void onFailure(@NonNull Exception e) { 320 | if (!emitter.isDisposed()) 321 | emitter.onError(e); 322 | } 323 | }); 324 | 325 | emitter.setCancellable(new Cancellable() { 326 | @Override 327 | public void cancel() throws Exception { 328 | taskSnapshotStorageTask.cancel(); 329 | } 330 | }); 331 | } 332 | }); 333 | } 334 | 335 | /** 336 | * Asynchronously uploads from a content URI to this {@link StorageReference}. 337 | * 338 | * @param storageRef represents a reference to a Google Cloud Storage object. 339 | * @param uri The source of the upload. This can be a file:// scheme or any content URI. A content resolver will be used to load the data. 340 | * @param metadata {@link StorageMetadata} containing additional information (MIME type, etc.) about the object being uploaded. 341 | * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. 342 | */ 343 | @NonNull 344 | public static Single putFile(@NonNull final StorageReference storageRef, 345 | @NonNull final Uri uri, 346 | @NonNull final StorageMetadata metadata) { 347 | return Single.create(new SingleOnSubscribe() { 348 | public void subscribe(final SingleEmitter emitter) throws Exception { 349 | final StorageTask taskSnapshotStorageTask = 350 | storageRef.putFile(uri, metadata) 351 | .addOnSuccessListener(new OnSuccessListener() { 352 | @Override 353 | public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { 354 | emitter.onSuccess(taskSnapshot); 355 | } 356 | }).addOnFailureListener(new OnFailureListener() { 357 | @Override 358 | public void onFailure(@NonNull Exception e) { 359 | if (!emitter.isDisposed()) 360 | emitter.onError(e); 361 | } 362 | }); 363 | 364 | emitter.setCancellable(new Cancellable() { 365 | @Override 366 | public void cancel() throws Exception { 367 | taskSnapshotStorageTask.cancel(); 368 | } 369 | }); 370 | } 371 | }); 372 | } 373 | 374 | /** 375 | * Asynchronously uploads from a content URI to this {@link StorageReference}. 376 | * 377 | * @param storageRef represents a reference to a Google Cloud Storage object. 378 | * @param uri The source of the upload. This can be a file:// scheme or any content URI. A content resolver will be used to load the data. 379 | * @param metadata {@link StorageMetadata} containing additional information (MIME type, etc.) about the object being uploaded. 380 | * @param existingUploadUri If set, an attempt is made to resume an existing upload session as defined by getUploadSessionUri(). 381 | * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. 382 | */ 383 | @NonNull 384 | public static Single putFile(@NonNull final StorageReference storageRef, 385 | @NonNull final Uri uri, 386 | @NonNull final StorageMetadata metadata, 387 | @NonNull final Uri existingUploadUri) { 388 | return Single.create(new SingleOnSubscribe() { 389 | public void subscribe(final SingleEmitter emitter) throws Exception { 390 | final StorageTask taskSnapshotStorageTask = 391 | storageRef.putFile(uri, metadata, existingUploadUri) 392 | .addOnSuccessListener(new OnSuccessListener() { 393 | @Override 394 | public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { 395 | emitter.onSuccess(taskSnapshot); 396 | } 397 | }).addOnFailureListener(new OnFailureListener() { 398 | @Override 399 | public void onFailure(@NonNull Exception e) { 400 | if (!emitter.isDisposed()) 401 | emitter.onError(e); 402 | } 403 | }); 404 | 405 | emitter.setCancellable(new Cancellable() { 406 | @Override 407 | public void cancel() throws Exception { 408 | taskSnapshotStorageTask.cancel(); 409 | } 410 | }); 411 | } 412 | }); 413 | } 414 | 415 | /** 416 | * @param storageRef represents a reference to a Google Cloud Storage object. 417 | * @param stream The InputStream to upload. 418 | * @param metadata {@link StorageMetadata} containing additional information (MIME type, etc.) about the object being uploaded. 419 | * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. 420 | */ 421 | @NonNull 422 | public static Single putStream(@NonNull final StorageReference storageRef, 423 | @NonNull final InputStream stream, 424 | @NonNull final StorageMetadata metadata) { 425 | return Single.create(new SingleOnSubscribe() { 426 | public void subscribe(final SingleEmitter emitter) throws Exception { 427 | final StorageTask taskSnapshotStorageTask = 428 | storageRef.putStream(stream, metadata) 429 | .addOnSuccessListener(new OnSuccessListener() { 430 | @Override 431 | public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { 432 | emitter.onSuccess(taskSnapshot); 433 | } 434 | }).addOnFailureListener(new OnFailureListener() { 435 | @Override 436 | public void onFailure(@NonNull Exception e) { 437 | if (!emitter.isDisposed()) 438 | emitter.onError(e); 439 | } 440 | }); 441 | 442 | emitter.setCancellable(new Cancellable() { 443 | @Override 444 | public void cancel() throws Exception { 445 | taskSnapshotStorageTask.cancel(); 446 | } 447 | }); 448 | } 449 | }); 450 | } 451 | 452 | /** 453 | * Asynchronously uploads a stream of data to this {@link StorageReference}. 454 | * 455 | * @param storageRef represents a reference to a Google Cloud Storage object. 456 | * @param stream The InputStream to upload. 457 | * @return a {@link Single} which emits an {@link UploadTask.TaskSnapshot} if success. 458 | */ 459 | @NonNull 460 | public static Single putStream(@NonNull final StorageReference storageRef, 461 | @NonNull final InputStream stream) { 462 | return Single.create(new SingleOnSubscribe() { 463 | public void subscribe(final SingleEmitter emitter) throws Exception { 464 | final StorageTask taskSnapshotStorageTask = 465 | storageRef.putStream(stream).addOnSuccessListener(new OnSuccessListener() { 466 | @Override 467 | public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) { 468 | emitter.onSuccess(taskSnapshot); 469 | } 470 | }).addOnFailureListener(new OnFailureListener() { 471 | @Override 472 | public void onFailure(@NonNull Exception e) { 473 | if (!emitter.isDisposed()) 474 | emitter.onError(e); 475 | } 476 | }); 477 | 478 | emitter.setCancellable(new Cancellable() { 479 | @Override 480 | public void cancel() throws Exception { 481 | taskSnapshotStorageTask.cancel(); 482 | } 483 | }); 484 | } 485 | }); 486 | } 487 | 488 | /** 489 | * Asynchronously uploads a stream of data to this {@link StorageReference}. 490 | * 491 | * @param storageRef represents a reference to a Google Cloud Storage object. 492 | * @param metadata {@link StorageMetadata} containing additional information (MIME type, etc.) about the object being uploaded. 493 | * @return a {@link Maybe} which emits an {@link StorageMetadata} if success. 494 | */ 495 | @NonNull 496 | public static Maybe updateMetadata(@NonNull final StorageReference storageRef, 497 | @NonNull final StorageMetadata metadata) { 498 | return Maybe.create(new MaybeOnSubscribe() { 499 | @Override 500 | public void subscribe(MaybeEmitter emitter) throws Exception { 501 | RxHandler.assignOnTask(emitter, storageRef.updateMetadata(metadata)); 502 | } 503 | }); 504 | } 505 | 506 | /** 507 | * Deletes the object at this {@link StorageReference}. 508 | * 509 | * @param storageRef represents a reference to a Google Cloud Storage object. 510 | * @return a {@link Completable} if the task is complete successfully. 511 | */ 512 | @NonNull 513 | public static Completable delete(@NonNull final StorageReference storageRef) { 514 | return Completable.create(new CompletableOnSubscribe() { 515 | @Override 516 | public void subscribe(CompletableEmitter emitter) throws Exception { 517 | RxCompletableHandler.assignOnTask(emitter, storageRef.delete()); 518 | } 519 | }); 520 | } 521 | } -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/RxFirebaseUser.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.google.firebase.auth.AuthCredential; 6 | import com.google.firebase.auth.AuthResult; 7 | import com.google.firebase.auth.FirebaseUser; 8 | import com.google.firebase.auth.GetTokenResult; 9 | import com.google.firebase.auth.PhoneAuthCredential; 10 | import com.google.firebase.auth.UserProfileChangeRequest; 11 | 12 | import io.reactivex.Completable; 13 | import io.reactivex.CompletableEmitter; 14 | import io.reactivex.CompletableOnSubscribe; 15 | import io.reactivex.Maybe; 16 | import io.reactivex.MaybeEmitter; 17 | import io.reactivex.MaybeOnSubscribe; 18 | 19 | public class RxFirebaseUser { 20 | 21 | /** 22 | * Fetches a Firebase Auth ID Token for the user; useful when authenticating against your own backend. 23 | * 24 | * @param firebaseUser current firebaseUser instance. 25 | * @param forceRefresh force to refresh the token ID. 26 | * @return a {@link Maybe} which emits an {@link GetTokenResult} if success. 27 | */ 28 | @NonNull 29 | public static Maybe getIdToken(@NonNull final FirebaseUser firebaseUser, 30 | final boolean forceRefresh) { 31 | return Maybe.create(new MaybeOnSubscribe() { 32 | @Override 33 | public void subscribe(MaybeEmitter emitter) throws Exception { 34 | RxHandler.assignOnTask(emitter, firebaseUser.getIdToken(forceRefresh)); 35 | } 36 | }); 37 | } 38 | 39 | /** 40 | * Updates the email address of the user. 41 | * 42 | * @param firebaseUser current firebaseUser instance. 43 | * @param email new email. 44 | * @return a {@link Completable} if the task is complete successfully. 45 | */ 46 | @NonNull 47 | public static Completable updateEmail(@NonNull final FirebaseUser firebaseUser, 48 | @NonNull final String email) { 49 | return Completable.create(new CompletableOnSubscribe() { 50 | @Override 51 | public void subscribe(CompletableEmitter emitter) throws Exception { 52 | RxCompletableHandler.assignOnTask(emitter, firebaseUser.updateEmail(email)); 53 | } 54 | }); 55 | } 56 | 57 | /** 58 | * Updates the password of the user. 59 | * 60 | * @param firebaseUser current firebaseUser instance. 61 | * @param password new password. 62 | * @return a {@link Completable} if the task is complete successfully. 63 | */ 64 | @NonNull 65 | public static Completable updatePassword(@NonNull final FirebaseUser firebaseUser, 66 | @NonNull final String password) { 67 | return Completable.create(new CompletableOnSubscribe() { 68 | @Override 69 | public void subscribe(CompletableEmitter emitter) throws Exception { 70 | RxCompletableHandler.assignOnTask(emitter, firebaseUser.updatePassword(password)); 71 | } 72 | }); 73 | } 74 | 75 | /** 76 | * Updates the user profile information. 77 | * 78 | * @param firebaseUser current firebaseUser instance. 79 | * @param request {@link UserProfileChangeRequest} request for this user. 80 | * @return a {@link Completable} if the task is complete successfully. 81 | */ 82 | @NonNull 83 | public static Completable updateProfile(@NonNull final FirebaseUser firebaseUser, 84 | @NonNull final UserProfileChangeRequest request) { 85 | return Completable.create(new CompletableOnSubscribe() { 86 | @Override 87 | public void subscribe(CompletableEmitter emitter) throws Exception { 88 | RxCompletableHandler.assignOnTask(emitter, firebaseUser.updateProfile(request)); 89 | } 90 | }); 91 | } 92 | 93 | /** 94 | * Deletes the user record from your Firebase project's database. 95 | * 96 | * @param firebaseUser current firebaseUser instance. 97 | * @return a {@link Completable} if the task is complete successfully. 98 | */ 99 | @NonNull 100 | public static Completable delete(@NonNull final FirebaseUser firebaseUser) { 101 | return Completable.create(new CompletableOnSubscribe() { 102 | @Override 103 | public void subscribe(CompletableEmitter emitter) throws Exception { 104 | RxCompletableHandler.assignOnTask(emitter, firebaseUser.delete()); 105 | } 106 | }); 107 | } 108 | 109 | /** 110 | * Reauthenticates the user with the given credential. 111 | * 112 | * @param firebaseUser current firebaseUser instance. 113 | * @param credential {@link AuthCredential} to re-authenticate. 114 | * @return a {@link Completable} if the task is complete successfully. 115 | */ 116 | @NonNull 117 | public static Completable reAuthenticate(@NonNull final FirebaseUser firebaseUser, 118 | @NonNull final AuthCredential credential) { 119 | return Completable.create(new CompletableOnSubscribe() { 120 | @Override 121 | public void subscribe(CompletableEmitter emitter) throws Exception { 122 | RxCompletableHandler.assignOnTask(emitter, firebaseUser.reauthenticate(credential)); 123 | } 124 | }); 125 | } 126 | 127 | /** 128 | * Manually refreshes the data of the current user (for example, attached providers, display name, and so on). 129 | * 130 | * @param firebaseUser current firebaseUser instance. 131 | * @return a {@link Completable} if the task is complete successfully. 132 | */ 133 | @NonNull 134 | public static Completable reload(@NonNull final FirebaseUser firebaseUser) { 135 | return Completable.create(new CompletableOnSubscribe() { 136 | @Override 137 | public void subscribe(CompletableEmitter emitter) throws Exception { 138 | RxCompletableHandler.assignOnTask(emitter, firebaseUser.reload()); 139 | } 140 | }); 141 | } 142 | 143 | /** 144 | * Initiates email verification for the user. 145 | * 146 | * @param firebaseUser current firebaseUser instance. 147 | * @return a {@link Completable} if the task is complete successfully. 148 | */ 149 | @NonNull 150 | public static Completable sendEmailVerification(@NonNull final FirebaseUser firebaseUser) { 151 | return Completable.create(new CompletableOnSubscribe() { 152 | @Override 153 | public void subscribe(CompletableEmitter emitter) throws Exception { 154 | RxCompletableHandler.assignOnTask(emitter, firebaseUser.sendEmailVerification()); 155 | } 156 | }); 157 | } 158 | 159 | /** 160 | * Attaches the given {@link AuthCredential} to the user. 161 | * 162 | * @param firebaseUser current firebaseUser instance. 163 | * @param credential new {@link AuthCredential} to link. 164 | * @return a {@link Maybe} which emits an {@link AuthResult} if success. 165 | */ 166 | @NonNull 167 | public static Maybe linkWithCredential(@NonNull final FirebaseUser firebaseUser, 168 | @NonNull final AuthCredential credential) { 169 | return Maybe.create(new MaybeOnSubscribe() { 170 | @Override 171 | public void subscribe(MaybeEmitter emitter) throws Exception { 172 | RxHandler.assignOnTask(emitter, firebaseUser.linkWithCredential(credential)); 173 | } 174 | }); 175 | } 176 | 177 | /** 178 | * Detaches credentials from a given provider type from this user. 179 | * 180 | * @param firebaseUser current firebaseUser instance. 181 | * @param provider a unique identifier of the type of provider to be unlinked, for example, {@link com.google.firebase.auth.FacebookAuthProvider#PROVIDER_ID}. 182 | * @return a {@link Maybe} which emits an {@link AuthResult} if success. 183 | */ 184 | @NonNull 185 | public static Maybe unlink(@NonNull final FirebaseUser firebaseUser, 186 | @NonNull final String provider) { 187 | return Maybe.create(new MaybeOnSubscribe() { 188 | @Override 189 | public void subscribe(MaybeEmitter emitter) throws Exception { 190 | RxHandler.assignOnTask(emitter, firebaseUser.unlink(provider)); 191 | } 192 | }); 193 | } 194 | 195 | /** 196 | * updates the current phone number for the given user. 197 | * 198 | * @param firebaseUser current firebaseUser instance. 199 | * @param phoneAuthCredential new phone credential. 200 | * @return a {@link Completable} if the task is complete successfully. 201 | */ 202 | @NonNull 203 | public static Completable updatePhoneNumber(@NonNull final FirebaseUser firebaseUser, 204 | @NonNull final PhoneAuthCredential phoneAuthCredential) { 205 | return Completable.create(new CompletableOnSubscribe() { 206 | @Override 207 | public void subscribe(CompletableEmitter emitter) throws Exception { 208 | RxCompletableHandler.assignOnTask(emitter, firebaseUser.updatePhoneNumber(phoneAuthCredential)); 209 | } 210 | }); 211 | } 212 | 213 | /** 214 | * Reauthenticates the user with the given credential, and returns the profile data for that account. 215 | * This is useful for operations that require a recent sign-in, to prevent or resolve a {@link com.google.firebase.auth.FirebaseAuthRecentLoginRequiredException} 216 | * 217 | * @param firebaseUser current firebaseUser instance. 218 | * @param credential Authcredential used for reauthenticate. 219 | * @return a {@link Maybe} which emits an {@link AuthResult} if success. 220 | */ 221 | @NonNull 222 | public static Maybe reauthenticateAndRetrieveData(@NonNull final FirebaseUser firebaseUser, 223 | @NonNull final AuthCredential credential) { 224 | return Maybe.create(new MaybeOnSubscribe() { 225 | @Override 226 | public void subscribe(MaybeEmitter emitter) throws Exception { 227 | RxHandler.assignOnTask(emitter, firebaseUser.reauthenticateAndRetrieveData(credential)); 228 | } 229 | }); 230 | } 231 | } -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/RxFirestoreOfflineHandler.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import com.google.firebase.firestore.DocumentReference; 4 | import com.google.firebase.firestore.DocumentSnapshot; 5 | import com.google.firebase.firestore.EventListener; 6 | import com.google.firebase.firestore.FirebaseFirestoreException; 7 | import com.google.firebase.firestore.ListenerRegistration; 8 | import androidx.annotation.Nullable; 9 | import io.reactivex.Completable; 10 | import io.reactivex.CompletableEmitter; 11 | import io.reactivex.CompletableOnSubscribe; 12 | import io.reactivex.functions.Cancellable; 13 | 14 | public class RxFirestoreOfflineHandler { 15 | 16 | /** 17 | * Method that listen a given reference and waits for a single change inside of it. However, this method 18 | * is just called in offline methods of add/set/delete in a new reference. That's why this reference should be 19 | * updated just once after successfully modify the reference with an offline operation. 20 | * 21 | * @param ref Document reference to be listened. 22 | * @return A Completable which emits when the given reference receives an offline update. 23 | */ 24 | public static Completable listenOfflineListener(final DocumentReference ref) { 25 | return Completable.create(new CompletableOnSubscribe() { 26 | @Override 27 | public void subscribe(final CompletableEmitter emitter) { 28 | try { 29 | final ListenerRegistration listener = ref.addSnapshotListener(new EventListener() { 30 | @Override 31 | public void onEvent(@Nullable DocumentSnapshot documentSnapshot, @Nullable FirebaseFirestoreException e) { 32 | if (e != null) { 33 | emitter.onError(e); 34 | } else { 35 | if (documentSnapshot != null) { 36 | emitter.onComplete(); 37 | } else { 38 | emitter.onError(new NullPointerException("Empty Snapshot")); 39 | } 40 | } 41 | } 42 | }); 43 | 44 | emitter.setCancellable(new Cancellable() { 45 | @Override 46 | public void cancel() { 47 | listener.remove(); 48 | } 49 | }); 50 | } catch (Exception e) { 51 | emitter.onError(e); 52 | } 53 | } 54 | }); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/RxHandler.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | 6 | import com.google.android.gms.tasks.OnCompleteListener; 7 | import com.google.android.gms.tasks.OnFailureListener; 8 | import com.google.android.gms.tasks.OnSuccessListener; 9 | import com.google.android.gms.tasks.Task; 10 | 11 | import durdinapps.rxfirebase2.exceptions.RxFirebaseNullDataException; 12 | import io.reactivex.MaybeEmitter; 13 | 14 | public class RxHandler implements OnSuccessListener, OnFailureListener, OnCompleteListener { 15 | 16 | private final MaybeEmitter emitter; 17 | 18 | private RxHandler(MaybeEmitter emitter) { 19 | this.emitter = emitter; 20 | } 21 | 22 | public static void assignOnTask(MaybeEmitter emitter, Task task) { 23 | RxHandler handler = new RxHandler(emitter); 24 | task.addOnSuccessListener(handler); 25 | task.addOnFailureListener(handler); 26 | try { 27 | task.addOnCompleteListener(handler); 28 | } catch (Throwable t) { 29 | // ignore 30 | } 31 | } 32 | 33 | @Override 34 | public void onSuccess(T res) { 35 | if (res != null) { 36 | emitter.onSuccess(res); 37 | } else { 38 | emitter.onError(new RxFirebaseNullDataException("Observables can't emit null values")); 39 | } 40 | } 41 | 42 | @Override 43 | public void onComplete(@NonNull Task task) { 44 | emitter.onComplete(); 45 | } 46 | 47 | @Override 48 | public void onFailure(@NonNull Exception e) { 49 | if (!emitter.isDisposed()) 50 | emitter.onError(e); 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/exceptions/RxFirebaseDataCastException.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2.exceptions; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | public class RxFirebaseDataCastException extends Exception { 6 | 7 | public RxFirebaseDataCastException() { 8 | } 9 | 10 | public RxFirebaseDataCastException(@NonNull String detailMessage) { 11 | super(detailMessage); 12 | } 13 | 14 | public RxFirebaseDataCastException(@NonNull String detailMessage, @NonNull Throwable throwable) { 15 | super(detailMessage, throwable); 16 | } 17 | 18 | public RxFirebaseDataCastException(@NonNull Throwable throwable) { 19 | super(throwable); 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/exceptions/RxFirebaseDataException.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2.exceptions; 2 | 3 | import androidx.annotation.NonNull; 4 | 5 | import com.google.firebase.database.DatabaseError; 6 | 7 | public class RxFirebaseDataException extends Exception { 8 | 9 | protected DatabaseError error; 10 | 11 | public RxFirebaseDataException(@NonNull DatabaseError error) { 12 | this.error = error; 13 | } 14 | 15 | public DatabaseError getError() { 16 | return error; 17 | } 18 | 19 | @Override 20 | public String toString() { 21 | return "RxFirebaseDataException{" + 22 | "error=" + error + 23 | '}'; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/main/java/durdinapps/rxfirebase2/exceptions/RxFirebaseNullDataException.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2.exceptions; 2 | 3 | 4 | import androidx.annotation.NonNull; 5 | import androidx.annotation.Nullable; 6 | 7 | public class RxFirebaseNullDataException extends NullPointerException { 8 | private final static String DEFAULT_MESSAGE = "Task result was successfully but data was empty"; 9 | 10 | public RxFirebaseNullDataException() { 11 | } 12 | 13 | public RxFirebaseNullDataException(@NonNull String detailMessage) { 14 | super(detailMessage); 15 | } 16 | 17 | public RxFirebaseNullDataException(@Nullable Exception resultException) { 18 | super(resultException != null ? resultException.getMessage() : DEFAULT_MESSAGE); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RxFirebase2 3 | 4 | -------------------------------------------------------------------------------- /app/src/test/java/durdinapps/rxfirebase2/RxFirebaseAuthTest.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | 4 | import com.google.android.gms.tasks.Task; 5 | import com.google.firebase.auth.ActionCodeResult; 6 | import com.google.firebase.auth.AuthCredential; 7 | import com.google.firebase.auth.AuthResult; 8 | import com.google.firebase.auth.FirebaseAuth; 9 | import com.google.firebase.auth.FirebaseUser; 10 | import com.google.firebase.auth.SignInMethodQueryResult; 11 | import com.google.firebase.database.DataSnapshot; 12 | 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import org.mockito.ArgumentCaptor; 16 | import org.mockito.Mock; 17 | import org.mockito.MockitoAnnotations; 18 | 19 | import java.util.Collections; 20 | 21 | import io.reactivex.observers.TestObserver; 22 | 23 | import static durdinapps.rxfirebase2.RxTestUtil.ANY_CODE; 24 | import static durdinapps.rxfirebase2.RxTestUtil.ANY_EMAIL; 25 | import static durdinapps.rxfirebase2.RxTestUtil.ANY_PASSWORD; 26 | import static durdinapps.rxfirebase2.RxTestUtil.ANY_TOKEN; 27 | import static durdinapps.rxfirebase2.RxTestUtil.EXCEPTION; 28 | import static durdinapps.rxfirebase2.RxTestUtil.RESULT_CODE; 29 | import static durdinapps.rxfirebase2.RxTestUtil.setupTask; 30 | import static durdinapps.rxfirebase2.RxTestUtil.testOnCompleteListener; 31 | import static durdinapps.rxfirebase2.RxTestUtil.testOnFailureListener; 32 | import static durdinapps.rxfirebase2.RxTestUtil.testOnSuccessListener; 33 | import static org.mockito.ArgumentMatchers.eq; 34 | import static org.mockito.Mockito.verify; 35 | import static org.mockito.Mockito.when; 36 | 37 | public class RxFirebaseAuthTest { 38 | 39 | @Mock 40 | private FirebaseAuth firebaseAuth; 41 | 42 | @Mock 43 | private Task authResultTask; 44 | 45 | @Mock 46 | private Task providerQueryResultTask; 47 | 48 | @Mock 49 | private Task actionCodeResultTask; 50 | 51 | @Mock 52 | private Task checkCodeResultTask; 53 | 54 | @Mock 55 | private Task voidTask; 56 | 57 | @Mock 58 | private AuthResult authResult; 59 | 60 | @Mock 61 | private SignInMethodQueryResult providerQueryResult; 62 | 63 | @Mock 64 | private ActionCodeResult actionCodeResult; 65 | 66 | @Mock 67 | private DataSnapshot dataSnapshot; 68 | 69 | @Mock 70 | private AuthCredential authCredential; 71 | 72 | @Mock 73 | private FirebaseUser firebaseUser; 74 | 75 | 76 | @Before 77 | public void setup() { 78 | MockitoAnnotations.initMocks(this); 79 | 80 | setupTask(authResultTask); 81 | setupTask(providerQueryResultTask); 82 | setupTask(actionCodeResultTask); 83 | setupTask(checkCodeResultTask); 84 | setupTask(voidTask); 85 | 86 | when(firebaseAuth.signInAnonymously()).thenReturn(authResultTask); 87 | when(firebaseAuth.signInWithEmailAndPassword(ANY_EMAIL, ANY_PASSWORD)).thenReturn(authResultTask); 88 | when(firebaseAuth.signInWithCredential(authCredential)).thenReturn(authResultTask); 89 | when(firebaseAuth.signInWithCustomToken(ANY_TOKEN)).thenReturn(authResultTask); 90 | when(firebaseAuth.createUserWithEmailAndPassword(ANY_EMAIL, ANY_PASSWORD)).thenReturn(authResultTask); 91 | when(firebaseAuth.fetchSignInMethodsForEmail(ANY_EMAIL)).thenReturn(providerQueryResultTask); 92 | when(firebaseAuth.checkActionCode(ANY_CODE)).thenReturn(actionCodeResultTask); 93 | when(firebaseAuth.verifyPasswordResetCode(ANY_CODE)).thenReturn(checkCodeResultTask); 94 | when(firebaseAuth.sendPasswordResetEmail(ANY_EMAIL)).thenReturn(voidTask); 95 | when(firebaseAuth.confirmPasswordReset(ANY_CODE, ANY_PASSWORD)).thenReturn(voidTask); 96 | when(firebaseAuth.applyActionCode(ANY_CODE)).thenReturn(voidTask); 97 | 98 | when(firebaseAuth.getCurrentUser()).thenReturn(firebaseUser); 99 | 100 | } 101 | 102 | @Test 103 | public void signInAnonymously() { 104 | 105 | TestObserver authTestObserver = RxFirebaseAuth 106 | .signInAnonymously(firebaseAuth) 107 | .test(); 108 | 109 | testOnSuccessListener.getValue().onSuccess(authResult); 110 | testOnCompleteListener.getValue().onComplete(authResultTask); 111 | 112 | verify(firebaseAuth).signInAnonymously(); 113 | 114 | authTestObserver 115 | .assertNoErrors() 116 | .assertValueCount(1) 117 | .assertValueSet(Collections.singletonList(authResult)) 118 | .assertComplete(); 119 | } 120 | 121 | @Test 122 | public void signInAnonymouslyError() { 123 | 124 | TestObserver authTestObserver = RxFirebaseAuth 125 | .signInAnonymously(firebaseAuth) 126 | .test(); 127 | 128 | testOnFailureListener.getValue().onFailure(EXCEPTION); 129 | 130 | verify(firebaseAuth).signInAnonymously(); 131 | 132 | authTestObserver.assertError(EXCEPTION) 133 | .assertNotComplete(); 134 | } 135 | 136 | @Test 137 | public void createUserWithEmailAndPassword() { 138 | TestObserver authTestObserver = RxFirebaseAuth 139 | .createUserWithEmailAndPassword(firebaseAuth, ANY_EMAIL, ANY_PASSWORD) 140 | .test(); 141 | 142 | testOnSuccessListener.getValue().onSuccess(authResult); 143 | testOnCompleteListener.getValue().onComplete(authResultTask); 144 | 145 | verify(firebaseAuth).createUserWithEmailAndPassword(ANY_EMAIL, ANY_PASSWORD); 146 | 147 | authTestObserver.assertNoErrors() 148 | .assertValueCount(1) 149 | .assertValueSet(Collections.singletonList(authResult)) 150 | .assertComplete() 151 | .dispose(); 152 | } 153 | 154 | @Test 155 | public void createUserWithEmailAndPasswordError() { 156 | 157 | TestObserver authTestObserver = RxFirebaseAuth 158 | .createUserWithEmailAndPassword(firebaseAuth, ANY_EMAIL, ANY_PASSWORD) 159 | .test(); 160 | 161 | testOnFailureListener.getValue().onFailure(EXCEPTION); 162 | 163 | verify(firebaseAuth).createUserWithEmailAndPassword(ANY_EMAIL, ANY_PASSWORD); 164 | 165 | authTestObserver.assertError(EXCEPTION) 166 | .assertNotComplete() 167 | .dispose(); 168 | } 169 | 170 | @Test 171 | public void signInWithEmailAndPassword() { 172 | 173 | TestObserver authTestObserver = RxFirebaseAuth 174 | .signInWithEmailAndPassword(firebaseAuth, ANY_EMAIL, ANY_PASSWORD) 175 | .test(); 176 | 177 | testOnSuccessListener.getValue().onSuccess(authResult); 178 | testOnCompleteListener.getValue().onComplete(authResultTask); 179 | 180 | verify(firebaseAuth).signInWithEmailAndPassword(eq(ANY_EMAIL), eq(ANY_PASSWORD)); 181 | 182 | authTestObserver.assertNoErrors() 183 | .assertValueCount(1) 184 | .assertValueSet(Collections.singletonList(authResult)) 185 | .assertComplete() 186 | .dispose(); 187 | } 188 | 189 | @Test 190 | public void signInWithEmailAndPasswordError() { 191 | 192 | TestObserver authTestObserver = RxFirebaseAuth 193 | .signInWithEmailAndPassword(firebaseAuth, ANY_EMAIL, ANY_PASSWORD) 194 | .test(); 195 | 196 | testOnFailureListener.getValue().onFailure(EXCEPTION); 197 | 198 | verify(firebaseAuth).signInWithEmailAndPassword(eq(ANY_EMAIL), eq(ANY_PASSWORD)); 199 | 200 | authTestObserver.assertError(EXCEPTION) 201 | .assertNotComplete() 202 | .dispose(); 203 | } 204 | 205 | @Test 206 | public void signInWithCredential() { 207 | 208 | TestObserver authTestObserver = RxFirebaseAuth 209 | .signInWithCredential(firebaseAuth, authCredential) 210 | .test(); 211 | 212 | testOnSuccessListener.getValue().onSuccess(authResult); 213 | testOnCompleteListener.getValue().onComplete(authResultTask); 214 | 215 | verify(firebaseAuth).signInWithCredential(authCredential); 216 | 217 | authTestObserver.assertNoErrors() 218 | .assertValueCount(1) 219 | .assertValueSet(Collections.singletonList(authResult)) 220 | .assertComplete() 221 | .dispose(); 222 | } 223 | 224 | @Test 225 | public void signInWithCredentialError() { 226 | 227 | TestObserver authTestObserver = RxFirebaseAuth 228 | .signInWithCredential(firebaseAuth, authCredential) 229 | .test(); 230 | 231 | testOnFailureListener.getValue().onFailure(EXCEPTION); 232 | 233 | verify(firebaseAuth).signInWithCredential(authCredential); 234 | 235 | authTestObserver.assertError(EXCEPTION) 236 | .assertNotComplete() 237 | .dispose(); 238 | } 239 | 240 | @Test 241 | public void signInWithCustomToken() { 242 | 243 | TestObserver authTestObserver = RxFirebaseAuth 244 | .signInWithCustomToken(firebaseAuth, ANY_TOKEN) 245 | .test(); 246 | 247 | testOnSuccessListener.getValue().onSuccess(authResult); 248 | testOnCompleteListener.getValue().onComplete(authResultTask); 249 | 250 | verify(firebaseAuth).signInWithCustomToken(eq(ANY_TOKEN)); 251 | 252 | authTestObserver.assertNoErrors() 253 | .assertValueCount(1) 254 | .assertValueSet(Collections.singletonList(authResult)) 255 | .assertComplete() 256 | .dispose(); 257 | } 258 | 259 | @Test 260 | public void signInWithCustomTokenError() { 261 | 262 | TestObserver authTestObserver = RxFirebaseAuth 263 | .signInWithCustomToken(firebaseAuth, ANY_TOKEN) 264 | .test(); 265 | 266 | testOnFailureListener.getValue().onFailure(EXCEPTION); 267 | 268 | verify(firebaseAuth).signInWithCustomToken(eq(ANY_TOKEN)); 269 | 270 | authTestObserver.assertError(EXCEPTION) 271 | .assertNotComplete() 272 | .dispose(); 273 | } 274 | 275 | @Test 276 | public void fetchProvidersForEmail() { 277 | 278 | TestObserver authTestObserver = RxFirebaseAuth 279 | .fetchSignInMethodsForEmail(firebaseAuth, ANY_EMAIL) 280 | .test(); 281 | 282 | testOnSuccessListener.getValue().onSuccess(providerQueryResult); 283 | testOnCompleteListener.getValue().onComplete(providerQueryResultTask); 284 | 285 | verify(firebaseAuth).fetchSignInMethodsForEmail(eq(ANY_EMAIL)); 286 | 287 | authTestObserver.assertNoErrors() 288 | .assertValueCount(1) 289 | .assertValueSet(Collections.singletonList(providerQueryResult)) 290 | .assertComplete() 291 | .dispose(); 292 | } 293 | 294 | @Test 295 | public void checkActionCode() { 296 | 297 | TestObserver authTestObserver = RxFirebaseAuth 298 | .checkActionCode(firebaseAuth, ANY_CODE) 299 | .test(); 300 | 301 | testOnSuccessListener.getValue().onSuccess(actionCodeResult); 302 | testOnCompleteListener.getValue().onComplete(actionCodeResultTask); 303 | 304 | verify(firebaseAuth).checkActionCode(eq(ANY_CODE)); 305 | 306 | authTestObserver.assertNoErrors() 307 | .assertValueCount(1) 308 | .assertValueSet(Collections.singletonList(actionCodeResult)) 309 | .assertComplete() 310 | .dispose(); 311 | } 312 | 313 | @Test 314 | public void fetchProvidersForEmailError() { 315 | 316 | TestObserver authTestObserver = RxFirebaseAuth 317 | .fetchSignInMethodsForEmail(firebaseAuth, ANY_EMAIL) 318 | .test(); 319 | 320 | testOnFailureListener.getValue().onFailure(EXCEPTION); 321 | 322 | verify(firebaseAuth).fetchSignInMethodsForEmail(ANY_EMAIL); 323 | 324 | authTestObserver.assertError(EXCEPTION) 325 | .assertNotComplete() 326 | .dispose(); 327 | } 328 | 329 | @Test 330 | public void verifyPasswordResetCode() { 331 | 332 | TestObserver authTestObserver = RxFirebaseAuth 333 | .verifyPasswordResetCode(firebaseAuth, ANY_CODE) 334 | .test(); 335 | 336 | testOnSuccessListener.getValue().onSuccess(RESULT_CODE); 337 | testOnCompleteListener.getValue().onComplete(checkCodeResultTask); 338 | 339 | verify(firebaseAuth).verifyPasswordResetCode(ANY_CODE); 340 | 341 | authTestObserver.assertNoErrors() 342 | .assertValueCount(1) 343 | .assertValueSet(Collections.singletonList(RESULT_CODE)) 344 | .assertComplete() 345 | .dispose(); 346 | } 347 | 348 | @Test 349 | public void sendPasswordResetEmail() { 350 | TestObserver authTestObserver = RxFirebaseAuth 351 | .sendPasswordResetEmail(firebaseAuth, ANY_EMAIL) 352 | .test(); 353 | 354 | testOnCompleteListener.getValue().onComplete(voidTask); 355 | 356 | verify(firebaseAuth).sendPasswordResetEmail(eq(ANY_EMAIL)); 357 | 358 | authTestObserver.assertNoErrors() 359 | .assertValueSet(Collections.singletonList(voidTask)) 360 | .assertComplete() 361 | .dispose(); 362 | } 363 | 364 | @Test 365 | public void confirmPasswordReset() { 366 | TestObserver authTestObserver = RxFirebaseAuth 367 | .confirmPasswordReset(firebaseAuth, ANY_CODE, ANY_PASSWORD) 368 | .test(); 369 | 370 | testOnCompleteListener.getValue().onComplete(voidTask); 371 | 372 | verify(firebaseAuth).confirmPasswordReset(eq(ANY_CODE), eq(ANY_PASSWORD)); 373 | 374 | authTestObserver.assertNoErrors() 375 | .assertValueSet(Collections.singletonList(voidTask)) 376 | .assertComplete() 377 | .dispose(); 378 | } 379 | 380 | @Test 381 | public void applyActionCode() { 382 | TestObserver authTestObserver = RxFirebaseAuth 383 | .applyActionCode(firebaseAuth, ANY_CODE) 384 | .test(); 385 | 386 | testOnCompleteListener.getValue().onComplete(voidTask); 387 | 388 | verify(firebaseAuth).applyActionCode(eq(ANY_CODE)); 389 | 390 | authTestObserver.assertNoErrors() 391 | .assertValueSet(Collections.singletonList(voidTask)) 392 | .assertComplete() 393 | .dispose(); 394 | } 395 | 396 | @Test 397 | public void sendPasswordResetEmailError() { 398 | 399 | TestObserver authTestObserver = RxFirebaseAuth 400 | .sendPasswordResetEmail(firebaseAuth, ANY_EMAIL) 401 | .test(); 402 | 403 | testOnFailureListener.getValue().onFailure(EXCEPTION); 404 | 405 | verify(firebaseAuth).sendPasswordResetEmail(eq(ANY_EMAIL)); 406 | 407 | authTestObserver.assertError(EXCEPTION) 408 | .assertNotComplete() 409 | .dispose(); 410 | } 411 | 412 | @Test 413 | public void testObserveAuthState() { 414 | 415 | TestObserver authTestObserver = RxFirebaseAuth 416 | .observeAuthState(firebaseAuth) 417 | .test(); 418 | 419 | ArgumentCaptor argument = ArgumentCaptor.forClass(FirebaseAuth.AuthStateListener.class); 420 | verify(firebaseAuth).addAuthStateListener(argument.capture()); 421 | argument.getValue().onAuthStateChanged(firebaseAuth); 422 | 423 | authTestObserver.assertNoErrors() 424 | .assertValueCount(1) 425 | .assertValueSet(Collections.singletonList(firebaseAuth)) 426 | .assertNotComplete() 427 | .dispose(); 428 | } 429 | } 430 | -------------------------------------------------------------------------------- /app/src/test/java/durdinapps/rxfirebase2/RxFirebaseDatabaseTest.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | 4 | import android.app.DownloadManager; 5 | 6 | import com.google.android.gms.tasks.Task; 7 | import com.google.firebase.database.ChildEventListener; 8 | import com.google.firebase.database.DataSnapshot; 9 | import com.google.firebase.database.DatabaseError; 10 | import com.google.firebase.database.DatabaseReference; 11 | import com.google.firebase.database.Query; 12 | import com.google.firebase.database.ValueEventListener; 13 | 14 | import org.junit.Before; 15 | import org.junit.Test; 16 | import org.mockito.ArgumentCaptor; 17 | import org.mockito.Mock; 18 | import org.mockito.MockitoAnnotations; 19 | 20 | import java.util.ArrayList; 21 | import java.util.Arrays; 22 | import java.util.Collections; 23 | import java.util.HashMap; 24 | import java.util.LinkedHashMap; 25 | import java.util.List; 26 | import java.util.Map; 27 | 28 | import durdinapps.rxfirebase2.exceptions.RxFirebaseDataException; 29 | import io.reactivex.functions.Function; 30 | import io.reactivex.observers.TestObserver; 31 | import io.reactivex.subscribers.TestSubscriber; 32 | 33 | import static durdinapps.rxfirebase2.RxTestUtil.ANY_KEY; 34 | import static durdinapps.rxfirebase2.RxTestUtil.PREVIOUS_CHILD_NAME; 35 | import static org.mockito.ArgumentMatchers.eq; 36 | import static org.mockito.Mockito.doReturn; 37 | import static org.mockito.Mockito.mock; 38 | import static org.mockito.Mockito.verify; 39 | import static org.mockito.Mockito.when; 40 | 41 | public class RxFirebaseDatabaseTest { 42 | 43 | @Mock 44 | private DatabaseReference databaseReference; 45 | @Mock 46 | private DatabaseReference databaseReferenceTwo; 47 | 48 | @Mock 49 | private Query query; 50 | 51 | @Mock 52 | private DataSnapshot dataSnapshot; 53 | @Mock 54 | private DataSnapshot dataSnapshotTwo; 55 | 56 | @Mock 57 | private Task voidTask; 58 | 59 | private ChildData childData = new ChildData(); 60 | private List childDataList = new ArrayList<>(); 61 | private Map childDataMap = new HashMap<>(); 62 | private Map updatedData = new HashMap<>(); 63 | 64 | private RxFirebaseChildEvent childEventAdded; 65 | private RxFirebaseChildEvent childEventChanged; 66 | private RxFirebaseChildEvent childEventRemoved; 67 | private RxFirebaseChildEvent childEventMoved; 68 | 69 | @Before 70 | public void setup() { 71 | MockitoAnnotations.initMocks(this); 72 | 73 | childDataList.add(childData); 74 | childDataMap.put(ANY_KEY, childData); 75 | updatedData.put(databaseReference.toString(), childData); 76 | 77 | childEventAdded = new RxFirebaseChildEvent<>(ANY_KEY, childData, PREVIOUS_CHILD_NAME, RxFirebaseChildEvent.EventType.ADDED); 78 | childEventChanged = new RxFirebaseChildEvent<>(ANY_KEY, childData, PREVIOUS_CHILD_NAME, RxFirebaseChildEvent.EventType.CHANGED); 79 | childEventRemoved = new RxFirebaseChildEvent<>(ANY_KEY, childData, RxFirebaseChildEvent.EventType.REMOVED); 80 | childEventMoved = new RxFirebaseChildEvent<>(ANY_KEY, childData, PREVIOUS_CHILD_NAME, RxFirebaseChildEvent.EventType.MOVED); 81 | 82 | when(dataSnapshot.exists()).thenReturn(true); 83 | when(dataSnapshot.getValue(ChildData.class)).thenReturn(childData); 84 | when(dataSnapshot.getKey()).thenReturn(ANY_KEY); 85 | when(dataSnapshot.getChildren()).thenReturn(Arrays.asList(dataSnapshot)); 86 | 87 | when(dataSnapshotTwo.exists()).thenReturn(true); 88 | when(dataSnapshotTwo.getValue(ChildData.class)).thenReturn(childData); 89 | when(dataSnapshotTwo.getKey()).thenReturn(ANY_KEY); 90 | when(dataSnapshotTwo.getChildren()).thenReturn(Arrays.asList(dataSnapshotTwo)); 91 | 92 | when(databaseReference.updateChildren(updatedData)).thenReturn(voidTask); 93 | } 94 | 95 | @Test 96 | public void testObserveSingleValue() { 97 | TestObserver testObserver = RxFirebaseDatabase 98 | .observeSingleValueEvent(databaseReference, ChildData.class) 99 | .test(); 100 | 101 | ArgumentCaptor argument = ArgumentCaptor.forClass(ValueEventListener.class); 102 | verify(databaseReference).addListenerForSingleValueEvent(argument.capture()); 103 | argument.getValue().onDataChange(dataSnapshot); 104 | 105 | testObserver.assertNoErrors() 106 | .assertValueCount(1) 107 | .assertValueSet(Collections.singletonList(childData)) 108 | .assertComplete() 109 | .dispose(); 110 | } 111 | 112 | @Test 113 | public void testObserveSingleNoData() { 114 | 115 | DataSnapshot mockFirebaseDataSnapshotNoData = mock(DataSnapshot.class); 116 | when(mockFirebaseDataSnapshotNoData.exists()).thenReturn(false); 117 | 118 | TestObserver testObserver = RxFirebaseDatabase 119 | .observeSingleValueEvent(databaseReference, ChildData.class) 120 | .test(); 121 | 122 | ArgumentCaptor argument = ArgumentCaptor.forClass(ValueEventListener.class); 123 | verify(databaseReference).addListenerForSingleValueEvent(argument.capture()); 124 | argument.getValue().onDataChange(mockFirebaseDataSnapshotNoData); 125 | 126 | testObserver.assertValueCount(0) 127 | .assertComplete() 128 | .dispose(); 129 | } 130 | 131 | @Test 132 | public void testObserveSingleWrongType() { 133 | 134 | TestObserver testObserver = RxFirebaseDatabase 135 | .observeSingleValueEvent(databaseReference, WrongType.class) 136 | .test(); 137 | 138 | ArgumentCaptor argument = ArgumentCaptor.forClass(ValueEventListener.class); 139 | verify(databaseReference).addListenerForSingleValueEvent(argument.capture()); 140 | argument.getValue().onDataChange(dataSnapshot); 141 | 142 | testObserver.assertError(RuntimeException.class) 143 | .assertNotComplete() 144 | .dispose(); 145 | } 146 | 147 | @Test 148 | public void testObserveSingleValueDisconnected() { 149 | 150 | TestObserver testObserver = RxFirebaseDatabase 151 | .observeSingleValueEvent(databaseReference, ChildData.class) 152 | .test(); 153 | 154 | ArgumentCaptor argument = ArgumentCaptor.forClass(ValueEventListener.class); 155 | verify(databaseReference).addListenerForSingleValueEvent(argument.capture()); 156 | argument.getValue().onCancelled(DatabaseError.fromCode(DatabaseError.DISCONNECTED)); 157 | 158 | testObserver.assertError(RxFirebaseDataException.class) 159 | .assertNotComplete() 160 | .dispose(); 161 | } 162 | 163 | @Test 164 | public void testObserveSingleValueEventFailed() { 165 | 166 | TestObserver testObserver = RxFirebaseDatabase 167 | .observeSingleValueEvent(databaseReference, ChildData.class) 168 | .test(); 169 | 170 | ArgumentCaptor argument = ArgumentCaptor.forClass(ValueEventListener.class); 171 | verify(databaseReference).addListenerForSingleValueEvent(argument.capture()); 172 | argument.getValue().onCancelled(DatabaseError.fromCode(DatabaseError.OPERATION_FAILED)); 173 | 174 | testObserver.assertError(RxFirebaseDataException.class) 175 | .assertNotComplete() 176 | .dispose(); 177 | } 178 | 179 | @Test 180 | public void testObserveValueEvent() { 181 | 182 | TestSubscriber testObserver = RxFirebaseDatabase 183 | .observeValueEvent(databaseReference, ChildData.class) 184 | .test(); 185 | 186 | ArgumentCaptor argument = ArgumentCaptor.forClass(ValueEventListener.class); 187 | verify(databaseReference).addValueEventListener(argument.capture()); 188 | argument.getValue().onDataChange(dataSnapshot); 189 | 190 | testObserver.assertNoErrors() 191 | .assertValueCount(1) 192 | .assertValueSet(Collections.singletonList(childData)) 193 | .assertNotComplete() 194 | .dispose(); 195 | } 196 | 197 | @Test 198 | public void testMultipleSingleValueEvent() { 199 | 200 | TestSubscriber testObserver = RxFirebaseDatabase 201 | .observeMultipleSingleValueEvent(databaseReference, databaseReferenceTwo) 202 | .test(); 203 | 204 | ArgumentCaptor argument = ArgumentCaptor.forClass(ValueEventListener.class); 205 | verify(databaseReference).addListenerForSingleValueEvent(argument.capture()); 206 | argument.getValue().onDataChange(dataSnapshot); 207 | verify(databaseReferenceTwo).addListenerForSingleValueEvent(argument.capture()); 208 | argument.getValue().onDataChange(dataSnapshotTwo); 209 | 210 | testObserver.assertNoErrors() 211 | .assertValueCount(2) 212 | .assertComplete() 213 | .dispose(); 214 | } 215 | 216 | @Test 217 | public void testSingleValueEvent() { 218 | 219 | 220 | TestObserver testObserver = RxFirebaseDatabase 221 | .observeSingleValueEvent(databaseReference, ChildData.class) 222 | .test(); 223 | 224 | ArgumentCaptor argument = ArgumentCaptor.forClass(ValueEventListener.class); 225 | verify(databaseReference).addListenerForSingleValueEvent(argument.capture()); 226 | argument.getValue().onDataChange(dataSnapshot); 227 | 228 | testObserver.assertNoErrors() 229 | .assertValueCount(1) 230 | .assertValueSet(Collections.singletonList(childData)) 231 | .assertComplete() 232 | .dispose(); 233 | } 234 | 235 | @Test 236 | public void testObserveValueEventList() { 237 | 238 | TestObserver testObserver = RxFirebaseDatabase 239 | .observeSingleValueEvent(databaseReference, ChildData.class) 240 | .test(); 241 | 242 | ArgumentCaptor argument = ArgumentCaptor.forClass(ValueEventListener.class); 243 | verify(databaseReference).addListenerForSingleValueEvent(argument.capture()); 244 | argument.getValue().onDataChange(dataSnapshot); 245 | 246 | testObserver.assertNoErrors() 247 | .assertValueCount(1) 248 | .assertComplete() 249 | .dispose(); 250 | } 251 | 252 | @Test 253 | public void testObserveValuesMap() { 254 | TestObserver> testObserver = RxFirebaseDatabase 255 | .observeSingleValueEvent(databaseReference) 256 | .map(new Function>() { 257 | @Override 258 | public LinkedHashMap apply(DataSnapshot dataSnapshot) { 259 | LinkedHashMap map = new LinkedHashMap<>(); 260 | map.put(dataSnapshot.getKey(), dataSnapshot.getValue(ChildData.class)); 261 | return map; 262 | } 263 | }).test(); 264 | 265 | ArgumentCaptor argument = ArgumentCaptor.forClass(ValueEventListener.class); 266 | verify(databaseReference).addListenerForSingleValueEvent(argument.capture()); 267 | argument.getValue().onDataChange(dataSnapshot); 268 | 269 | testObserver.assertNoErrors() 270 | .assertValueCount(1) 271 | .assertValueSet(Collections.singletonList(childDataMap)) 272 | .dispose(); 273 | } 274 | 275 | @Test 276 | public void testObserveChildEventAdded() { 277 | 278 | TestSubscriber> testObserver = RxFirebaseDatabase 279 | .observeChildEvent(databaseReference, ChildData.class) 280 | .test(); 281 | 282 | ArgumentCaptor argument = ArgumentCaptor.forClass(ChildEventListener.class); 283 | verify(databaseReference).addChildEventListener(argument.capture()); 284 | argument.getValue().onChildAdded(dataSnapshot, PREVIOUS_CHILD_NAME); 285 | 286 | testObserver.assertNoErrors() 287 | .assertValueCount(1) 288 | .assertValueSet(Collections.singletonList(childEventAdded)) 289 | .assertNotComplete() 290 | .dispose(); 291 | } 292 | 293 | @Test 294 | public void testObserveChildEventChanged() { 295 | 296 | TestSubscriber> testObserver = RxFirebaseDatabase 297 | .observeChildEvent(databaseReference, ChildData.class) 298 | .test(); 299 | 300 | ArgumentCaptor argument = ArgumentCaptor.forClass(ChildEventListener.class); 301 | verify(databaseReference).addChildEventListener(argument.capture()); 302 | argument.getValue().onChildChanged(dataSnapshot, PREVIOUS_CHILD_NAME); 303 | 304 | testObserver.assertNoErrors() 305 | .assertValueCount(1) 306 | .assertValueSet(Collections.singletonList(childEventChanged)) 307 | .assertNotComplete() 308 | .dispose(); 309 | } 310 | 311 | @Test 312 | public void testObserveChildEventRemoved() { 313 | 314 | TestSubscriber> testObserver = RxFirebaseDatabase 315 | .observeChildEvent(databaseReference, ChildData.class) 316 | .test(); 317 | 318 | ArgumentCaptor argument = ArgumentCaptor.forClass(ChildEventListener.class); 319 | verify(databaseReference).addChildEventListener(argument.capture()); 320 | argument.getValue().onChildRemoved(dataSnapshot); 321 | 322 | testObserver.assertNoErrors() 323 | .assertValueCount(1) 324 | .assertValueSet(Collections.singletonList(childEventRemoved)) 325 | .assertNotComplete() 326 | .dispose(); 327 | } 328 | 329 | @Test 330 | public void testObserveChildEventMoved() { 331 | 332 | TestSubscriber> testObserver = RxFirebaseDatabase 333 | .observeChildEvent(databaseReference, ChildData.class) 334 | .test(); 335 | 336 | ArgumentCaptor argument = ArgumentCaptor.forClass(ChildEventListener.class); 337 | verify(databaseReference).addChildEventListener(argument.capture()); 338 | argument.getValue().onChildMoved(dataSnapshot, PREVIOUS_CHILD_NAME); 339 | 340 | testObserver.assertNoErrors() 341 | .assertValueCount(1) 342 | .assertValueSet(Collections.singletonList(childEventMoved)) 343 | .assertNotComplete() 344 | .dispose(); 345 | } 346 | 347 | @Test 348 | public void testObserveChildEventCancelled() { 349 | 350 | TestSubscriber> testObserver = RxFirebaseDatabase 351 | .observeChildEvent(databaseReference, ChildData.class) 352 | .test(); 353 | 354 | ArgumentCaptor argument = ArgumentCaptor.forClass(ChildEventListener.class); 355 | verify(databaseReference).addChildEventListener(argument.capture()); 356 | argument.getValue().onCancelled(DatabaseError.fromCode(DatabaseError.DISCONNECTED)); 357 | 358 | testObserver.assertError(RxFirebaseDataException.class) 359 | .assertNotComplete() 360 | .dispose(); 361 | } 362 | 363 | @Test 364 | public void testObserveListWithDataSnapshotMapper() { 365 | TestSubscriber> testObserver = RxFirebaseDatabase 366 | .observeValueEvent(query, DataSnapshotMapper.listOf(ChildData.class)) 367 | .test(); 368 | 369 | ArgumentCaptor argument = ArgumentCaptor.forClass(ValueEventListener.class); 370 | verify(query).addValueEventListener(argument.capture()); 371 | argument.getValue().onDataChange(dataSnapshot); 372 | 373 | testObserver.assertNoErrors() 374 | .assertValueCount(1) 375 | .assertValueSet(Collections.singletonList(childDataList)) 376 | .assertNotComplete() 377 | .dispose(); 378 | } 379 | 380 | @Test 381 | public void testObserveListWithDataSnapshotCustomMapper() throws Exception { 382 | //noinspection unchecked 383 | Function mapper = (Function) mock(Function.class); 384 | doReturn(childData).when(mapper).apply(eq(dataSnapshot)); 385 | 386 | TestSubscriber> testObserver = RxFirebaseDatabase 387 | .observeValueEvent(query, DataSnapshotMapper.listOf(ChildData.class, mapper)) 388 | .test(); 389 | 390 | ArgumentCaptor argument = ArgumentCaptor.forClass(ValueEventListener.class); 391 | verify(query).addValueEventListener(argument.capture()); 392 | argument.getValue().onDataChange(dataSnapshot); 393 | 394 | verify(mapper).apply(dataSnapshot); 395 | 396 | testObserver.assertNoErrors() 397 | .assertValueCount(1) 398 | .assertValueSet(Collections.singletonList(childDataList)) 399 | .assertNotComplete() 400 | .dispose(); 401 | } 402 | 403 | class ChildData { 404 | int id; 405 | String str; 406 | } 407 | 408 | class WrongType { 409 | String somethingWrong; 410 | long more; 411 | } 412 | } 413 | -------------------------------------------------------------------------------- /app/src/test/java/durdinapps/rxfirebase2/RxFirebaseRemoteTest.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import com.google.android.gms.tasks.Task; 4 | import com.google.firebase.auth.AuthResult; 5 | import com.google.firebase.remoteconfig.FirebaseRemoteConfig; 6 | 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.mockito.Mock; 10 | import org.mockito.MockitoAnnotations; 11 | 12 | import java.util.Collections; 13 | 14 | import io.reactivex.observers.TestObserver; 15 | 16 | import static durdinapps.rxfirebase2.RxTestUtil.ANY_EMAIL; 17 | import static durdinapps.rxfirebase2.RxTestUtil.ANY_TIME; 18 | import static durdinapps.rxfirebase2.RxTestUtil.EXCEPTION; 19 | import static durdinapps.rxfirebase2.RxTestUtil.setupTask; 20 | import static durdinapps.rxfirebase2.RxTestUtil.testOnCompleteListener; 21 | import static durdinapps.rxfirebase2.RxTestUtil.testOnFailureListener; 22 | import static durdinapps.rxfirebase2.RxTestUtil.testOnSuccessListener; 23 | import static org.mockito.ArgumentMatchers.eq; 24 | import static org.mockito.Mockito.verify; 25 | import static org.mockito.Mockito.when; 26 | 27 | public class RxFirebaseRemoteTest { 28 | 29 | @Mock 30 | private Task voidTask; 31 | 32 | @Mock 33 | private FirebaseRemoteConfig firebaseConfig; 34 | 35 | @Before 36 | public void setup() { 37 | MockitoAnnotations.initMocks(this); 38 | 39 | setupTask(voidTask); 40 | 41 | when(firebaseConfig.fetch(ANY_TIME)).thenReturn(voidTask); 42 | } 43 | 44 | @Test 45 | public void fetchRemoteConfig() { 46 | TestObserver fetchTestObserver = RxFirebaseRemote 47 | .fetch(firebaseConfig, ANY_TIME) 48 | .test(); 49 | 50 | testOnCompleteListener.getValue().onComplete(voidTask); 51 | 52 | verify(firebaseConfig).fetch(eq(ANY_TIME)); 53 | 54 | fetchTestObserver.assertNoErrors() 55 | .assertValueSet(Collections.singletonList(voidTask)) 56 | .assertComplete() 57 | .dispose(); 58 | } 59 | 60 | @Test 61 | public void fetchRemoteFailure() { 62 | TestObserver fetchTestObserver = RxFirebaseRemote 63 | .fetch(firebaseConfig, ANY_TIME) 64 | .test(); 65 | 66 | testOnFailureListener.getValue().onFailure(EXCEPTION); 67 | 68 | verify(firebaseConfig).fetch(eq(ANY_TIME)); 69 | 70 | fetchTestObserver.assertError(EXCEPTION) 71 | .assertNotComplete() 72 | .dispose(); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/test/java/durdinapps/rxfirebase2/RxFirebaseStorageTest.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | 4 | import android.net.Uri; 5 | 6 | import com.google.android.gms.tasks.Task; 7 | import com.google.firebase.storage.FileDownloadTask; 8 | import com.google.firebase.storage.StorageMetadata; 9 | import com.google.firebase.storage.StorageReference; 10 | import com.google.firebase.storage.StreamDownloadTask; 11 | import com.google.firebase.storage.UploadTask; 12 | 13 | import org.junit.Before; 14 | import org.junit.Test; 15 | import org.mockito.Mock; 16 | import org.mockito.MockitoAnnotations; 17 | 18 | import java.io.File; 19 | import java.io.InputStream; 20 | import java.util.Collections; 21 | 22 | import io.reactivex.observers.TestObserver; 23 | 24 | import static durdinapps.rxfirebase2.RxTestUtil.NULL_FIREBASE_EXCEPTION; 25 | import static durdinapps.rxfirebase2.RxTestUtil.setupTask; 26 | import static durdinapps.rxfirebase2.RxTestUtil.testOnCompleteListener; 27 | import static durdinapps.rxfirebase2.RxTestUtil.testOnFailureListener; 28 | import static durdinapps.rxfirebase2.RxTestUtil.testOnSuccessListener; 29 | import static org.mockito.Mockito.verify; 30 | import static org.mockito.Mockito.when; 31 | 32 | public class RxFirebaseStorageTest { 33 | 34 | @Mock 35 | private StorageReference mockStorageRef; 36 | 37 | @Mock 38 | private Task mockVoidTask; 39 | 40 | @Mock 41 | private Task mockBytesTask; 42 | 43 | @Mock 44 | private Task mockUriTask; 45 | 46 | @Mock 47 | private FileDownloadTask mockFileDownloadTask; 48 | 49 | @Mock 50 | private StreamDownloadTask mockStreamDownloadTask; 51 | 52 | @Mock 53 | private Task mockMetadataTask; 54 | 55 | @Mock 56 | private UploadTask mockUploadTask; 57 | 58 | @Mock 59 | private Uri uri; 60 | 61 | @Mock 62 | private File file; 63 | 64 | @Mock 65 | private StorageMetadata metadata; 66 | 67 | @Mock 68 | private FileDownloadTask.TaskSnapshot fileSnapshot; 69 | 70 | @Mock 71 | private StreamDownloadTask.TaskSnapshot streamSnapshot; 72 | 73 | @Mock 74 | private UploadTask.TaskSnapshot uploadSnapshot; 75 | 76 | private byte[] nullBytes; 77 | 78 | private byte[] notNullbytes = new byte[0]; 79 | 80 | @Mock 81 | private StreamDownloadTask.StreamProcessor processor; 82 | 83 | @Mock 84 | private InputStream stream; 85 | 86 | private Void voidData = null; 87 | 88 | 89 | @Before 90 | public void setup() { 91 | MockitoAnnotations.initMocks(this); 92 | 93 | setupTask(mockBytesTask); 94 | setupTask(mockVoidTask); 95 | setupTask(mockUriTask); 96 | setupTask(mockFileDownloadTask); 97 | setupTask(mockStreamDownloadTask); 98 | setupTask(mockMetadataTask); 99 | setupTask(mockUploadTask); 100 | 101 | when(mockStorageRef.getBytes(20)).thenReturn(mockBytesTask); 102 | when(mockStorageRef.getDownloadUrl()).thenReturn(mockUriTask); 103 | when(mockStorageRef.getFile(file)).thenReturn(mockFileDownloadTask); 104 | when(mockStorageRef.getFile(uri)).thenReturn(mockFileDownloadTask); 105 | when(mockStorageRef.getStream()).thenReturn(mockStreamDownloadTask); 106 | when(mockStorageRef.getStream(processor)).thenReturn(mockStreamDownloadTask); 107 | when(mockStorageRef.getMetadata()).thenReturn(mockMetadataTask); 108 | when(mockStorageRef.putBytes(notNullbytes)).thenReturn(mockUploadTask); 109 | when(mockStorageRef.putBytes(nullBytes)).thenReturn(mockUploadTask); 110 | when(mockStorageRef.putBytes(notNullbytes, metadata)).thenReturn(mockUploadTask); 111 | when(mockStorageRef.putBytes(nullBytes, metadata)).thenReturn(mockUploadTask); 112 | when(mockStorageRef.putFile(uri)).thenReturn(mockUploadTask); 113 | when(mockStorageRef.putFile(uri, metadata)).thenReturn(mockUploadTask); 114 | when(mockStorageRef.putFile(uri, metadata, uri)).thenReturn(mockUploadTask); 115 | when(mockStorageRef.putStream(stream)).thenReturn(mockUploadTask); 116 | when(mockStorageRef.putStream(stream, metadata)).thenReturn(mockUploadTask); 117 | when(mockStorageRef.updateMetadata(metadata)).thenReturn(mockMetadataTask); 118 | when(mockStorageRef.delete()).thenReturn(mockVoidTask); 119 | } 120 | 121 | @Test 122 | public void getBytes() { 123 | TestObserver storageTestObserver = 124 | RxFirebaseStorage.getBytes(mockStorageRef, 20) 125 | .test(); 126 | 127 | testOnSuccessListener.getValue().onSuccess(notNullbytes); 128 | testOnCompleteListener.getValue().onComplete(mockBytesTask); 129 | 130 | verify(mockStorageRef).getBytes(20); 131 | 132 | storageTestObserver.assertNoErrors() 133 | .assertValueCount(1) 134 | .assertValueSet(Collections.singletonList(notNullbytes)) 135 | .assertComplete() 136 | .dispose(); 137 | } 138 | 139 | @Test 140 | public void getBytesNoData() { 141 | TestObserver storageTestObserver = 142 | RxFirebaseStorage.getBytes(mockStorageRef, 20) 143 | .test(); 144 | 145 | testOnFailureListener.getValue().onFailure(NULL_FIREBASE_EXCEPTION); 146 | 147 | verify(mockStorageRef).getBytes(20); 148 | 149 | storageTestObserver.assertError(NULL_FIREBASE_EXCEPTION) 150 | .assertValueSet(Collections.singletonList(nullBytes)) 151 | .assertNotComplete() 152 | .dispose(); 153 | } 154 | 155 | @Test 156 | public void getDownloadUrl() { 157 | TestObserver storageTestObserver = 158 | RxFirebaseStorage.getDownloadUrl(mockStorageRef) 159 | .test(); 160 | 161 | testOnSuccessListener.getValue().onSuccess(uri); 162 | testOnCompleteListener.getValue().onComplete(mockUriTask); 163 | 164 | verify(mockStorageRef).getDownloadUrl(); 165 | 166 | storageTestObserver.assertNoErrors() 167 | .assertValueCount(1) 168 | .assertValueSet(Collections.singletonList(uri)) 169 | .assertComplete() 170 | .dispose(); 171 | } 172 | 173 | @Test 174 | public void getFile() { 175 | 176 | TestObserver storageTestObserver = 177 | RxFirebaseStorage.getFile(mockStorageRef, file) 178 | .test(); 179 | 180 | testOnSuccessListener.getValue().onSuccess(fileSnapshot); 181 | 182 | verify(mockStorageRef).getFile(file); 183 | 184 | storageTestObserver.assertNoErrors() 185 | .assertValueCount(1) 186 | .assertValueSet(Collections.singletonList(fileSnapshot)) 187 | .assertComplete() 188 | .dispose(); 189 | } 190 | 191 | @Test 192 | public void getFileUri() { 193 | 194 | TestObserver storageTestObserver = 195 | RxFirebaseStorage.getFile(mockStorageRef, uri) 196 | .test(); 197 | 198 | testOnSuccessListener.getValue().onSuccess(fileSnapshot); 199 | 200 | verify(mockStorageRef).getFile(uri); 201 | 202 | storageTestObserver.assertNoErrors() 203 | .assertValueCount(1) 204 | .assertValueSet(Collections.singletonList(fileSnapshot)) 205 | .assertComplete() 206 | .dispose(); 207 | } 208 | 209 | 210 | @Test 211 | public void getMetadata() { 212 | 213 | TestObserver storageTestObserver = 214 | RxFirebaseStorage.getMetadata(mockStorageRef) 215 | .test(); 216 | 217 | testOnSuccessListener.getValue().onSuccess(metadata); 218 | testOnCompleteListener.getValue().onComplete(mockMetadataTask); 219 | 220 | verify(mockStorageRef).getMetadata(); 221 | 222 | storageTestObserver.assertNoErrors() 223 | .assertValueCount(1) 224 | .assertValueSet(Collections.singletonList(metadata)) 225 | .assertComplete() 226 | .dispose(); 227 | } 228 | 229 | 230 | @Test 231 | public void getStream() { 232 | 233 | TestObserver storageTestObserver = 234 | RxFirebaseStorage.getStream(mockStorageRef) 235 | .test(); 236 | 237 | testOnSuccessListener.getValue().onSuccess(streamSnapshot); 238 | 239 | verify(mockStorageRef).getStream(); 240 | 241 | storageTestObserver.assertNoErrors(); 242 | storageTestObserver.assertValueCount(1); 243 | storageTestObserver.assertValueSet(Collections.singletonList(streamSnapshot)); 244 | storageTestObserver.assertComplete(); 245 | storageTestObserver.dispose(); 246 | } 247 | 248 | @Test 249 | public void getStreamProcessor() { 250 | 251 | TestObserver storageTestObserver = 252 | RxFirebaseStorage.getStream(mockStorageRef, processor) 253 | .test(); 254 | 255 | testOnSuccessListener.getValue().onSuccess(streamSnapshot); 256 | 257 | verify(mockStorageRef).getStream(processor); 258 | 259 | storageTestObserver.assertNoErrors() 260 | .assertValueCount(1) 261 | .assertValueSet(Collections.singletonList(streamSnapshot)) 262 | .assertComplete() 263 | .dispose(); 264 | } 265 | 266 | @Test 267 | public void putBytes() { 268 | 269 | TestObserver storageTestObserver = 270 | RxFirebaseStorage.putBytes(mockStorageRef, notNullbytes) 271 | .test(); 272 | 273 | testOnSuccessListener.getValue().onSuccess(uploadSnapshot); 274 | 275 | verify(mockStorageRef).putBytes(notNullbytes); 276 | 277 | storageTestObserver.assertNoErrors() 278 | .assertValueCount(1) 279 | .assertValueSet(Collections.singletonList(uploadSnapshot)) 280 | .assertComplete() 281 | .dispose(); 282 | } 283 | 284 | @Test 285 | public void putBytesNoData() { 286 | 287 | TestObserver storageTestObserver = 288 | RxFirebaseStorage.putBytes(mockStorageRef, nullBytes) 289 | .test(); 290 | 291 | testOnFailureListener.getValue().onFailure(NULL_FIREBASE_EXCEPTION); 292 | 293 | verify(mockStorageRef).putBytes(nullBytes); 294 | 295 | storageTestObserver.assertError(NULL_FIREBASE_EXCEPTION) 296 | .assertValueSet(Collections.singletonList(uploadSnapshot)) 297 | .assertNotComplete() 298 | .dispose(); 299 | } 300 | 301 | @Test 302 | public void putBytesMetadata() { 303 | 304 | TestObserver storageTestObserver = 305 | RxFirebaseStorage.putBytes(mockStorageRef, notNullbytes, metadata) 306 | .test(); 307 | 308 | 309 | testOnSuccessListener.getValue().onSuccess(uploadSnapshot); 310 | testOnCompleteListener.getValue().onComplete(mockVoidTask); 311 | 312 | verify(mockStorageRef).putBytes(notNullbytes, metadata); 313 | 314 | storageTestObserver.assertNoErrors() 315 | .assertValueCount(1) 316 | .assertValueSet(Collections.singletonList(uploadSnapshot)) 317 | .assertComplete() 318 | .dispose(); 319 | } 320 | 321 | @Test 322 | public void putFile() { 323 | 324 | TestObserver storageTestObserver = 325 | RxFirebaseStorage.putFile(mockStorageRef, uri) 326 | .test(); 327 | 328 | testOnSuccessListener.getValue().onSuccess(uploadSnapshot); 329 | 330 | verify(mockStorageRef).putFile(uri); 331 | 332 | storageTestObserver.assertNoErrors() 333 | .assertValueCount(1) 334 | .assertValueSet(Collections.singletonList(uploadSnapshot)) 335 | .assertComplete() 336 | .dispose(); 337 | } 338 | 339 | @Test 340 | public void putFileMetadata() { 341 | 342 | TestObserver storageTestObserver = 343 | RxFirebaseStorage.putFile(mockStorageRef, uri, metadata) 344 | .test(); 345 | 346 | testOnSuccessListener.getValue().onSuccess(uploadSnapshot); 347 | 348 | verify(mockStorageRef).putFile(uri, metadata); 349 | 350 | storageTestObserver.assertNoErrors() 351 | .assertValueCount(1) 352 | .assertValueSet(Collections.singletonList(uploadSnapshot)) 353 | .assertComplete() 354 | .dispose(); 355 | } 356 | 357 | @Test 358 | public void putFileMetadataAndUri() { 359 | 360 | TestObserver storageTestObserver = 361 | RxFirebaseStorage.putFile(mockStorageRef, uri, metadata, uri) 362 | .test(); 363 | 364 | testOnSuccessListener.getValue().onSuccess(uploadSnapshot); 365 | 366 | verify(mockStorageRef).putFile(uri, metadata, uri); 367 | 368 | storageTestObserver.assertNoErrors() 369 | .assertValueCount(1) 370 | .assertValueSet(Collections.singletonList(uploadSnapshot)) 371 | .assertComplete() 372 | .dispose(); 373 | } 374 | 375 | @Test 376 | public void putStream() { 377 | 378 | TestObserver storageTestObserver = 379 | RxFirebaseStorage.putStream(mockStorageRef, stream) 380 | .test(); 381 | 382 | testOnSuccessListener.getValue().onSuccess(uploadSnapshot); 383 | 384 | verify(mockStorageRef).putStream(stream); 385 | 386 | storageTestObserver.assertNoErrors() 387 | .assertValueCount(1) 388 | .assertValueSet(Collections.singletonList(uploadSnapshot)) 389 | .assertComplete() 390 | .dispose(); 391 | } 392 | 393 | @Test 394 | public void putStreamMetadata() { 395 | 396 | TestObserver storageTestObserver = 397 | RxFirebaseStorage.putStream(mockStorageRef, stream, metadata) 398 | .test(); 399 | 400 | testOnSuccessListener.getValue().onSuccess(uploadSnapshot); 401 | 402 | verify(mockStorageRef).putStream(stream, metadata); 403 | 404 | storageTestObserver.assertNoErrors() 405 | .assertValueCount(1) 406 | .assertValueSet(Collections.singletonList(uploadSnapshot)) 407 | .assertComplete() 408 | .dispose(); 409 | } 410 | 411 | @Test 412 | public void updateMetadata() { 413 | 414 | TestObserver storageTestObserver = 415 | RxFirebaseStorage.updateMetadata(mockStorageRef, metadata) 416 | .test(); 417 | 418 | testOnSuccessListener.getValue().onSuccess(metadata); 419 | 420 | verify(mockStorageRef).updateMetadata(metadata); 421 | 422 | storageTestObserver.assertNoErrors() 423 | .assertValueCount(1) 424 | .assertValueSet(Collections.singletonList(metadata)) 425 | .assertComplete() 426 | .dispose(); 427 | } 428 | 429 | @Test 430 | public void delete() { 431 | 432 | TestObserver storageTestObserver = 433 | RxFirebaseStorage.delete(mockStorageRef) 434 | .test(); 435 | 436 | testOnSuccessListener.getValue().onSuccess(voidData); 437 | testOnCompleteListener.getValue().onComplete(mockVoidTask); 438 | 439 | verify(mockStorageRef).delete(); 440 | 441 | storageTestObserver.assertNoErrors() 442 | .assertComplete() 443 | .dispose(); 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /app/src/test/java/durdinapps/rxfirebase2/RxFirebaseUserTest.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import com.google.android.gms.tasks.Task; 4 | import com.google.firebase.auth.AuthCredential; 5 | import com.google.firebase.auth.AuthResult; 6 | import com.google.firebase.auth.FirebaseUser; 7 | import com.google.firebase.auth.GetTokenResult; 8 | import com.google.firebase.auth.UserProfileChangeRequest; 9 | 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.mockito.Mock; 13 | import org.mockito.MockitoAnnotations; 14 | 15 | import java.util.Collections; 16 | 17 | import io.reactivex.observers.TestObserver; 18 | 19 | import static durdinapps.rxfirebase2.RxTestUtil.ANY_EMAIL; 20 | import static durdinapps.rxfirebase2.RxTestUtil.ANY_PASSWORD; 21 | import static durdinapps.rxfirebase2.RxTestUtil.EXCEPTION; 22 | import static durdinapps.rxfirebase2.RxTestUtil.setupTask; 23 | import static durdinapps.rxfirebase2.RxTestUtil.testOnCompleteListener; 24 | import static durdinapps.rxfirebase2.RxTestUtil.testOnFailureListener; 25 | import static durdinapps.rxfirebase2.RxTestUtil.testOnSuccessListener; 26 | import static org.mockito.Mockito.verify; 27 | import static org.mockito.Mockito.when; 28 | 29 | public class RxFirebaseUserTest { 30 | 31 | @Mock 32 | FirebaseUser firebaseUser; 33 | 34 | @Mock 35 | GetTokenResult getTokenResult; 36 | 37 | @Mock 38 | Task voidTask; 39 | 40 | @Mock 41 | Task getTokenResultTask; 42 | 43 | @Mock 44 | UserProfileChangeRequest userProfileChangeRequest; 45 | 46 | @Mock 47 | AuthCredential authCredential; 48 | 49 | @Mock 50 | Task authCredentialTask; 51 | 52 | @Mock 53 | AuthResult authResult; 54 | 55 | @Mock 56 | Task authResultTask; 57 | 58 | private boolean ANY_FORCE_REFRESH_VALUE = true; 59 | 60 | 61 | @Before 62 | public void setUp() { 63 | MockitoAnnotations.initMocks(this); 64 | 65 | setupTask(getTokenResultTask); 66 | setupTask(voidTask); 67 | setupTask(authResultTask); 68 | setupTask(authCredentialTask); 69 | 70 | when(firebaseUser.getIdToken(ANY_FORCE_REFRESH_VALUE)).thenReturn(getTokenResultTask); 71 | when(firebaseUser.updateEmail(ANY_EMAIL)).thenReturn(voidTask); 72 | when(firebaseUser.updatePassword(ANY_PASSWORD)).thenReturn(voidTask); 73 | when(firebaseUser.updateProfile(userProfileChangeRequest)).thenReturn(voidTask); 74 | when(firebaseUser.delete()).thenReturn(voidTask); 75 | when(firebaseUser.reauthenticate(authCredential)).thenReturn(voidTask); 76 | when(firebaseUser.reauthenticateAndRetrieveData(authCredential)).thenReturn(authResultTask); 77 | when(firebaseUser.linkWithCredential(authCredential)).thenReturn(authResultTask); 78 | } 79 | 80 | @Test 81 | public void getToken() { 82 | TestObserver userTestObserver = RxFirebaseUser.getIdToken(firebaseUser, ANY_FORCE_REFRESH_VALUE).test(); 83 | 84 | testOnSuccessListener.getValue().onSuccess(getTokenResult); 85 | testOnCompleteListener.getValue().onComplete(getTokenResultTask); 86 | 87 | verify(firebaseUser).getIdToken(ANY_FORCE_REFRESH_VALUE); 88 | 89 | userTestObserver.assertComplete() 90 | .assertNoErrors() 91 | .assertValueCount(1) 92 | .dispose(); 93 | } 94 | 95 | @Test 96 | public void getTokenError() { 97 | TestObserver userTestObserver = RxFirebaseUser.getIdToken(firebaseUser, ANY_FORCE_REFRESH_VALUE).test(); 98 | testOnFailureListener.getValue().onFailure(EXCEPTION); 99 | verify(firebaseUser).getIdToken(ANY_FORCE_REFRESH_VALUE); 100 | 101 | userTestObserver.assertError(EXCEPTION) 102 | .dispose(); 103 | } 104 | 105 | @Test 106 | public void updateEmail() { 107 | TestObserver userTestObserver = RxFirebaseUser.updateEmail(firebaseUser, ANY_EMAIL).test(); 108 | 109 | testOnCompleteListener.getValue().onComplete(voidTask); 110 | testOnSuccessListener.getValue().onSuccess(voidTask); 111 | 112 | verify(firebaseUser).updateEmail(ANY_EMAIL); 113 | 114 | userTestObserver.assertComplete() 115 | .dispose(); 116 | 117 | } 118 | 119 | @Test 120 | public void updateEmailError() { 121 | TestObserver userTestObserver = RxFirebaseUser.updateEmail(firebaseUser, ANY_EMAIL).test(); 122 | 123 | testOnFailureListener.getValue().onFailure(EXCEPTION); 124 | 125 | verify(firebaseUser).updateEmail(ANY_EMAIL); 126 | 127 | userTestObserver.assertError(EXCEPTION) 128 | .dispose(); 129 | 130 | } 131 | 132 | @Test 133 | public void updatePassword() { 134 | TestObserver userTestObserver = RxFirebaseUser.updatePassword(firebaseUser, ANY_PASSWORD).test(); 135 | testOnCompleteListener.getValue().onComplete(voidTask); 136 | testOnSuccessListener.getValue().onSuccess(voidTask); 137 | 138 | verify(firebaseUser).updatePassword(ANY_PASSWORD); 139 | 140 | userTestObserver.assertComplete() 141 | .dispose(); 142 | } 143 | 144 | @Test 145 | public void updatePasswordError() { 146 | TestObserver userTestObserver = RxFirebaseUser.updatePassword(firebaseUser, ANY_PASSWORD).test(); 147 | 148 | testOnFailureListener.getValue().onFailure(EXCEPTION); 149 | 150 | verify(firebaseUser).updatePassword(ANY_PASSWORD); 151 | 152 | userTestObserver.assertError(EXCEPTION) 153 | .dispose(); 154 | 155 | } 156 | 157 | @Test 158 | public void updateProfile() { 159 | TestObserver userTestObserver = RxFirebaseUser.updateProfile(firebaseUser, userProfileChangeRequest).test(); 160 | 161 | testOnCompleteListener.getValue().onComplete(voidTask); 162 | testOnSuccessListener.getValue().onSuccess(voidTask); 163 | 164 | verify(firebaseUser).updateProfile(userProfileChangeRequest); 165 | 166 | userTestObserver.assertComplete() 167 | .dispose(); 168 | 169 | } 170 | 171 | @Test 172 | public void updateProfileError() { 173 | TestObserver userTestObserver = RxFirebaseUser.updateProfile(firebaseUser, userProfileChangeRequest).test(); 174 | 175 | testOnFailureListener.getValue().onFailure(EXCEPTION); 176 | 177 | verify(firebaseUser).updateProfile(userProfileChangeRequest); 178 | 179 | userTestObserver.assertError(EXCEPTION) 180 | .dispose(); 181 | 182 | } 183 | 184 | @Test 185 | public void delete() { 186 | TestObserver userTestObserver = RxFirebaseUser.delete(firebaseUser).test(); 187 | 188 | testOnCompleteListener.getValue().onComplete(voidTask); 189 | testOnSuccessListener.getValue().onSuccess(voidTask); 190 | 191 | verify(firebaseUser).delete(); 192 | 193 | userTestObserver.assertComplete() 194 | .dispose(); 195 | } 196 | 197 | @Test 198 | public void deleteError() { 199 | TestObserver userTestObserver = RxFirebaseUser.delete(firebaseUser).test(); 200 | 201 | testOnFailureListener.getValue().onFailure(EXCEPTION); 202 | verify(firebaseUser).delete(); 203 | 204 | userTestObserver.assertError(EXCEPTION) 205 | .dispose(); 206 | } 207 | 208 | @Test 209 | public void reAuthenticate() { 210 | TestObserver userTestObserver = RxFirebaseUser.reAuthenticate(firebaseUser, authCredential).test(); 211 | 212 | testOnCompleteListener.getValue().onComplete(voidTask); 213 | testOnSuccessListener.getValue().onSuccess(voidTask); 214 | 215 | verify(firebaseUser).reauthenticate(authCredential); 216 | 217 | userTestObserver.assertComplete() 218 | .dispose(); 219 | 220 | } 221 | 222 | @Test 223 | public void reauthenticateAndRetrieveData() { 224 | TestObserver userTestObserver = RxFirebaseUser.reauthenticateAndRetrieveData(firebaseUser, authCredential).test(); 225 | 226 | testOnSuccessListener.getValue().onSuccess(authResult); 227 | testOnCompleteListener.getValue().onComplete(authResultTask); 228 | 229 | verify(firebaseUser).reauthenticateAndRetrieveData(authCredential); 230 | 231 | userTestObserver.assertNoErrors() 232 | .assertValueCount(1) 233 | .assertComplete() 234 | .assertValueSet(Collections.singletonList(authResult)) 235 | .dispose(); 236 | 237 | } 238 | 239 | @Test 240 | public void reAuthenticateError() { 241 | TestObserver userTestObserver = RxFirebaseUser.reAuthenticate(firebaseUser, authCredential).test(); 242 | 243 | testOnFailureListener.getValue().onFailure(EXCEPTION); 244 | 245 | verify(firebaseUser).reauthenticate(authCredential); 246 | 247 | userTestObserver.assertError(EXCEPTION) 248 | .dispose(); 249 | } 250 | 251 | @Test 252 | public void linkWithCredentials() { 253 | TestObserver userTestObserver = RxFirebaseUser.linkWithCredential(firebaseUser, authCredential).test(); 254 | 255 | testOnSuccessListener.getValue().onSuccess(authResult); 256 | testOnCompleteListener.getValue().onComplete(authResultTask); 257 | 258 | verify(firebaseUser).linkWithCredential(authCredential); 259 | 260 | 261 | userTestObserver.assertNoErrors() 262 | .assertValueCount(1) 263 | .assertComplete() 264 | .assertValueSet(Collections.singletonList(authResult)) 265 | .dispose(); 266 | } 267 | 268 | @Test 269 | public void linkWithCredentialsError() { 270 | TestObserver userTestObserver = RxFirebaseUser.linkWithCredential(firebaseUser, authCredential).test(); 271 | 272 | testOnFailureListener.getValue().onFailure(EXCEPTION); 273 | 274 | verify(firebaseUser).linkWithCredential(authCredential); 275 | 276 | userTestObserver.assertError(EXCEPTION) 277 | .dispose(); 278 | 279 | 280 | } 281 | } -------------------------------------------------------------------------------- /app/src/test/java/durdinapps/rxfirebase2/RxFirestoreTest.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import com.google.android.gms.tasks.Task; 4 | import com.google.firebase.firestore.CollectionReference; 5 | import com.google.firebase.firestore.DocumentReference; 6 | import com.google.firebase.firestore.DocumentSnapshot; 7 | import com.google.firebase.firestore.ListenerRegistration; 8 | import com.google.firebase.firestore.Query; 9 | import com.google.firebase.firestore.QuerySnapshot; 10 | 11 | import org.junit.Before; 12 | import org.junit.Test; 13 | import org.mockito.Mock; 14 | import org.mockito.MockitoAnnotations; 15 | import org.mockito.invocation.InvocationOnMock; 16 | import org.mockito.stubbing.Answer; 17 | 18 | import java.util.ArrayList; 19 | import java.util.Collections; 20 | import java.util.HashMap; 21 | import java.util.Iterator; 22 | import java.util.List; 23 | 24 | import io.reactivex.observers.TestObserver; 25 | 26 | import static durdinapps.rxfirebase2.RxTestUtil.eventSnapshotListener; 27 | import static durdinapps.rxfirebase2.RxTestUtil.setupOfflineTask; 28 | import static durdinapps.rxfirebase2.RxTestUtil.setupTask; 29 | import static durdinapps.rxfirebase2.RxTestUtil.testOnCompleteListener; 30 | import static durdinapps.rxfirebase2.RxTestUtil.testOnSuccessListener; 31 | import static org.mockito.Mockito.verify; 32 | import static org.mockito.Mockito.when; 33 | 34 | public class RxFirestoreTest { 35 | 36 | @Mock 37 | private Task mockVoidTask; 38 | 39 | @Mock 40 | private DocumentReference documentReference; 41 | 42 | @Mock 43 | private DocumentReference emptyDocumentReference; 44 | 45 | @Mock 46 | private CollectionReference collectionReference; 47 | 48 | @Mock 49 | private CollectionReference emptyCollectionReference; 50 | 51 | @Mock 52 | private Query queryReference; 53 | 54 | @Mock 55 | private Query emptyQueryReference; 56 | 57 | @Mock 58 | private DocumentSnapshot documentSnapshot; 59 | 60 | @Mock 61 | private DocumentSnapshot emptyDocumentSnapshot; 62 | 63 | @Mock 64 | private QuerySnapshot querySnapshot; 65 | @Mock 66 | private QuerySnapshot emptyQuerySnapshot; 67 | 68 | @Mock 69 | private Task emptyDocumentSnapshotTask; 70 | 71 | @Mock 72 | private Task documentSnapshotTask; 73 | 74 | @Mock 75 | private Task documentRefTask; 76 | 77 | @Mock 78 | private Task queryResultTask; 79 | 80 | @Mock 81 | private Task emptyQueryResultTask; 82 | 83 | @Mock 84 | private ListenerRegistration registration; 85 | 86 | 87 | private HashMap updateMap = new HashMap<>(); 88 | private ChildDocData setData = new ChildDocData(); 89 | private ChildDocData childData = new ChildDocData(); 90 | private List childDataList = new ArrayList<>(); 91 | 92 | @Before 93 | public void setup() { 94 | MockitoAnnotations.initMocks(this); 95 | 96 | setupTask(documentSnapshotTask); 97 | setupTask(emptyDocumentSnapshotTask); 98 | setupTask(queryResultTask); 99 | setupTask(emptyQueryResultTask); 100 | setupTask(mockVoidTask); 101 | setupOfflineTask(documentReference, registration); 102 | 103 | when(documentReference.get()).thenReturn(documentSnapshotTask); 104 | when(emptyDocumentReference.get()).thenReturn(emptyDocumentSnapshotTask); 105 | when(collectionReference.get()).thenReturn(queryResultTask); 106 | when(emptyCollectionReference.get()).thenReturn(emptyQueryResultTask); 107 | when(queryReference.get()).thenReturn(queryResultTask); 108 | when(emptyQueryReference.get()).thenReturn(emptyQueryResultTask); 109 | when(documentReference.delete()).thenReturn(mockVoidTask); 110 | when(documentReference.update(updateMap)).thenReturn(mockVoidTask); 111 | when(collectionReference.add(setData)).thenReturn(documentRefTask); 112 | when(documentSnapshot.toObject(ChildDocData.class)).thenReturn(childData); 113 | when(documentSnapshot.exists()).thenReturn(true); //This snapshots exist 114 | when(documentSnapshot.exists()).thenReturn(true); //This snapshots exist 115 | when(emptyDocumentSnapshot.exists()).thenReturn(false); //This snapshots exist 116 | when(querySnapshot.isEmpty()).thenReturn(false); 117 | when(querySnapshot.toObjects(ChildDocData.class)).thenReturn(childDataList); 118 | when(querySnapshot.iterator()).thenAnswer(new Answer>() { 119 | @Override 120 | public Iterator answer(InvocationOnMock invocation) { 121 | return Collections.singletonList(documentSnapshot).iterator(); 122 | } 123 | }); 124 | when(emptyQuerySnapshot.isEmpty()).thenReturn(true); 125 | } 126 | 127 | @Test 128 | public void testGetDocument() { 129 | TestObserver testObserver = RxFirestore 130 | .getDocument(documentReference) 131 | .test(); 132 | 133 | testOnSuccessListener.getValue().onSuccess(documentSnapshot); 134 | 135 | verify(documentReference).get(); 136 | 137 | testObserver 138 | .assertNoErrors() 139 | .assertValueCount(1) 140 | .assertValueSet(Collections.singletonList(documentSnapshot)) 141 | .assertComplete(); 142 | } 143 | 144 | @Test 145 | public void testGetEmptyDocument() { 146 | TestObserver testObserver = RxFirestore 147 | .getDocument(emptyDocumentReference) 148 | .test(); 149 | 150 | testOnSuccessListener.getValue().onSuccess(emptyDocumentSnapshot); 151 | 152 | verify(emptyDocumentReference).get(); 153 | 154 | testObserver 155 | .assertNoErrors() 156 | .assertValueCount(0) 157 | .assertComplete(); 158 | } 159 | 160 | @Test 161 | public void testMappedGetDocument() { 162 | TestObserver testObserver = RxFirestore 163 | .getDocument(documentReference, ChildDocData.class) 164 | .test(); 165 | 166 | testOnSuccessListener.getValue().onSuccess(documentSnapshot); 167 | 168 | verify(documentReference).get(); 169 | 170 | testObserver 171 | .assertNoErrors() 172 | .assertValueCount(1) 173 | .assertValueSet(Collections.singletonList(childData)) 174 | .assertComplete(); 175 | } 176 | 177 | @Test 178 | public void testMappedGetEmptyDocument() { 179 | TestObserver testObserver = RxFirestore 180 | .getDocument(emptyDocumentReference, ChildDocData.class) 181 | .test(); 182 | 183 | testOnSuccessListener.getValue().onSuccess(emptyDocumentSnapshot); 184 | 185 | verify(emptyDocumentReference).get(); 186 | 187 | testObserver 188 | .assertNoErrors() 189 | .assertValueCount(0) 190 | .assertComplete(); 191 | } 192 | 193 | @Test 194 | public void testGetCollection() { 195 | TestObserver testObserver = RxFirestore 196 | .getCollection(collectionReference) 197 | .test(); 198 | 199 | testOnSuccessListener.getValue().onSuccess(querySnapshot); 200 | 201 | verify(collectionReference).get(); 202 | 203 | testObserver 204 | .assertNoErrors() 205 | .assertValueCount(1) 206 | .assertValueSet(Collections.singletonList(querySnapshot)) 207 | .assertComplete(); 208 | } 209 | 210 | @Test 211 | public void testGetEmptyCollection() { 212 | TestObserver testObserver = RxFirestore 213 | .getCollection(emptyCollectionReference) 214 | .test(); 215 | 216 | testOnSuccessListener.getValue().onSuccess(emptyQuerySnapshot); 217 | 218 | verify(emptyCollectionReference).get(); 219 | 220 | testObserver 221 | .assertNoErrors() 222 | .assertValueCount(0) 223 | .assertComplete(); 224 | } 225 | 226 | @Test 227 | public void testMappedGetCollection() { 228 | TestObserver> testObserver = RxFirestore 229 | .getCollection(collectionReference, ChildDocData.class) 230 | .test(); 231 | 232 | testOnSuccessListener.getValue().onSuccess(querySnapshot); 233 | 234 | verify(collectionReference).get(); 235 | 236 | testObserver 237 | .assertNoErrors() 238 | .assertValueCount(1) 239 | .assertComplete(); 240 | } 241 | 242 | @Test 243 | public void testMappedGetEmptyCollection() { 244 | TestObserver> testObserver = RxFirestore 245 | .getCollection(emptyCollectionReference, ChildDocData.class) 246 | .test(); 247 | 248 | testOnSuccessListener.getValue().onSuccess(emptyQuerySnapshot); 249 | 250 | verify(emptyCollectionReference).get(); 251 | 252 | testObserver 253 | .assertNoErrors() 254 | .assertValueCount(0) 255 | .assertComplete(); 256 | } 257 | 258 | @Test 259 | public void testGetQuery() { 260 | TestObserver testObserver = RxFirestore 261 | .getCollection(queryReference) 262 | .test(); 263 | 264 | testOnSuccessListener.getValue().onSuccess(querySnapshot); 265 | 266 | verify(queryReference).get(); 267 | 268 | testObserver 269 | .assertNoErrors() 270 | .assertValueCount(1) 271 | .assertValueSet(Collections.singletonList(querySnapshot)) 272 | .assertComplete(); 273 | } 274 | 275 | @Test 276 | public void testGetEmptyQuery() { 277 | TestObserver testObserver = RxFirestore 278 | .getCollection(emptyQueryReference) 279 | .test(); 280 | 281 | testOnSuccessListener.getValue().onSuccess(emptyQuerySnapshot); 282 | 283 | verify(emptyQueryReference).get(); 284 | 285 | testObserver 286 | .assertNoErrors() 287 | .assertValueCount(0) 288 | .assertComplete(); 289 | } 290 | 291 | @Test 292 | public void testMappedGetQuery() { 293 | TestObserver> testObserver = RxFirestore 294 | .getCollection(queryReference, ChildDocData.class) 295 | .test(); 296 | 297 | testOnSuccessListener.getValue().onSuccess(querySnapshot); 298 | 299 | verify(queryReference).get(); 300 | 301 | testObserver 302 | .assertNoErrors() 303 | .assertValueCount(1) 304 | .assertComplete(); 305 | } 306 | 307 | @Test 308 | public void testMappedGetEmptyQuery() { 309 | TestObserver> testObserver = RxFirestore 310 | .getCollection(emptyQueryReference, ChildDocData.class) 311 | .test(); 312 | 313 | testOnSuccessListener.getValue().onSuccess(emptyQuerySnapshot); 314 | 315 | verify(emptyQueryReference).get(); 316 | 317 | testObserver 318 | .assertNoErrors() 319 | .assertValueCount(0) 320 | .assertComplete(); 321 | } 322 | 323 | @Test 324 | public void testSetDocumentOffline() { 325 | TestObserver testObserver = RxFirestore 326 | .setDocumentOffline(documentReference, setData) 327 | .test(); 328 | 329 | eventSnapshotListener.getValue().onEvent(documentSnapshot, null); 330 | 331 | verify(documentReference).set(setData); 332 | 333 | testObserver 334 | .assertNoErrors() 335 | .assertComplete(); 336 | } 337 | 338 | @Test 339 | public void testUpdateDocument() { 340 | 341 | TestObserver storageTestObserver = 342 | RxFirestore.updateDocument(documentReference, updateMap) 343 | .test(); 344 | 345 | testOnCompleteListener.getValue().onComplete(mockVoidTask); 346 | 347 | verify(documentReference).update(updateMap); 348 | 349 | storageTestObserver.assertNoErrors() 350 | .assertComplete() 351 | .dispose(); 352 | } 353 | 354 | 355 | @Test 356 | public void testUpdateDocumentOffline() { 357 | TestObserver testObserver = RxFirestore 358 | .updateDocumentOffline(documentReference, updateMap) 359 | .test(); 360 | 361 | eventSnapshotListener.getValue().onEvent(documentSnapshot, null); 362 | 363 | verify(documentReference).update(updateMap); 364 | 365 | testObserver 366 | .assertNoErrors() 367 | .assertComplete(); 368 | } 369 | 370 | 371 | @Test 372 | public void testDeleteDocument() { 373 | 374 | TestObserver storageTestObserver = 375 | RxFirestore.deleteDocument(documentReference) 376 | .test(); 377 | 378 | testOnCompleteListener.getValue().onComplete(mockVoidTask); 379 | 380 | verify(documentReference).delete(); 381 | 382 | storageTestObserver.assertNoErrors() 383 | .assertComplete() 384 | .dispose(); 385 | } 386 | 387 | @Test 388 | public void testDeleteDocumentOffline() { 389 | TestObserver testObserver = RxFirestore 390 | .deleteDocumentOffline(documentReference) 391 | .test(); 392 | 393 | eventSnapshotListener.getValue().onEvent(documentSnapshot, null); 394 | verify(documentReference).delete(); 395 | 396 | testObserver 397 | .assertNoErrors() 398 | .assertComplete(); 399 | } 400 | 401 | 402 | class ChildDocData { 403 | int id; 404 | String str; 405 | } 406 | } 407 | -------------------------------------------------------------------------------- /app/src/test/java/durdinapps/rxfirebase2/RxTestUtil.java: -------------------------------------------------------------------------------- 1 | package durdinapps.rxfirebase2; 2 | 3 | import com.google.android.gms.tasks.OnCompleteListener; 4 | import com.google.android.gms.tasks.OnFailureListener; 5 | import com.google.android.gms.tasks.OnSuccessListener; 6 | import com.google.android.gms.tasks.Task; 7 | import com.google.firebase.firestore.DocumentReference; 8 | import com.google.firebase.firestore.DocumentSnapshot; 9 | import com.google.firebase.firestore.EventListener; 10 | import com.google.firebase.firestore.ListenerRegistration; 11 | 12 | import org.mockito.ArgumentCaptor; 13 | 14 | import durdinapps.rxfirebase2.exceptions.RxFirebaseNullDataException; 15 | 16 | import static org.mockito.Mockito.when; 17 | 18 | class RxTestUtil { 19 | static final String ANY_EMAIL = "email@email.com"; 20 | static final String RESULT_CODE = "ABC"; 21 | static final String ANY_CODE = "ABCDE"; 22 | static final String ANY_PASSWORD = "ANY_PASSWORD"; 23 | static final String ANY_TOKEN = "ANY_KEY"; 24 | static final String ANY_KEY = "_token_"; 25 | static final String PREVIOUS_CHILD_NAME = "NONE"; 26 | static final Exception EXCEPTION = new Exception("Something bad happen"); 27 | static final Exception NULL_FIREBASE_EXCEPTION = new RxFirebaseNullDataException(); 28 | static final long ANY_TIME = 12000; 29 | 30 | static ArgumentCaptor testOnCompleteListener = ArgumentCaptor.forClass(OnCompleteListener.class); 31 | static ArgumentCaptor testOnSuccessListener = ArgumentCaptor.forClass(OnSuccessListener.class); 32 | static ArgumentCaptor testOnFailureListener = ArgumentCaptor.forClass(OnFailureListener.class); 33 | static ArgumentCaptor> eventSnapshotListener = ArgumentCaptor.forClass(EventListener.class); 34 | 35 | static void setupTask(Task task) { 36 | when(task.addOnCompleteListener(testOnCompleteListener.capture())).thenReturn(task); 37 | when(task.addOnSuccessListener(testOnSuccessListener.capture())).thenReturn(task); 38 | when(task.addOnFailureListener(testOnFailureListener.capture())).thenReturn(task); 39 | } 40 | 41 | static void setupOfflineTask(DocumentReference documentReference, ListenerRegistration registration) { 42 | when(documentReference.addSnapshotListener(eventSnapshotListener.capture())).thenReturn(registration); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.2.1' 10 | classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' 11 | // NOTE: Do not place your application dependencies here; they belong 12 | // in the individual module build.gradle files 13 | } 14 | } 15 | 16 | allprojects { 17 | repositories { 18 | google() 19 | jcenter() 20 | } 21 | } 22 | 23 | task clean(type: Delete) { 24 | delete rootProject.buildDir 25 | } 26 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/FrangSierra/RxFirebase/f2cb917d088e4320a8e44d83daff5d98447c8eff/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Fri Nov 30 11:51:14 CET 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 10 | DEFAULT_JVM_OPTS="" 11 | 12 | APP_NAME="Gradle" 13 | APP_BASE_NAME=`basename "$0"` 14 | 15 | # Use the maximum available, or set MAX_FD != -1 to use that value. 16 | MAX_FD="maximum" 17 | 18 | warn ( ) { 19 | echo "$*" 20 | } 21 | 22 | die ( ) { 23 | echo 24 | echo "$*" 25 | echo 26 | exit 1 27 | } 28 | 29 | # OS specific support (must be 'true' or 'false'). 30 | cygwin=false 31 | msys=false 32 | darwin=false 33 | case "`uname`" in 34 | CYGWIN* ) 35 | cygwin=true 36 | ;; 37 | Darwin* ) 38 | darwin=true 39 | ;; 40 | MINGW* ) 41 | msys=true 42 | ;; 43 | esac 44 | 45 | # Attempt to set APP_HOME 46 | # Resolve links: $0 may be a link 47 | PRG="$0" 48 | # Need this for relative symlinks. 49 | while [ -h "$PRG" ] ; do 50 | ls=`ls -ld "$PRG"` 51 | link=`expr "$ls" : '.*-> \(.*\)$'` 52 | if expr "$link" : '/.*' > /dev/null; then 53 | PRG="$link" 54 | else 55 | PRG=`dirname "$PRG"`"/$link" 56 | fi 57 | done 58 | SAVED="`pwd`" 59 | cd "`dirname \"$PRG\"`/" >/dev/null 60 | APP_HOME="`pwd -P`" 61 | cd "$SAVED" >/dev/null 62 | 63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 64 | 65 | # Determine the Java command to use to start the JVM. 66 | if [ -n "$JAVA_HOME" ] ; then 67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 68 | # IBM's JDK on AIX uses strange locations for the executables 69 | JAVACMD="$JAVA_HOME/jre/sh/java" 70 | else 71 | JAVACMD="$JAVA_HOME/bin/java" 72 | fi 73 | if [ ! -x "$JAVACMD" ] ; then 74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 75 | 76 | Please set the JAVA_HOME variable in your environment to match the 77 | location of your Java installation." 78 | fi 79 | else 80 | JAVACMD="java" 81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 82 | 83 | Please set the JAVA_HOME variable in your environment to match the 84 | location of your Java installation." 85 | fi 86 | 87 | # Increase the maximum file descriptors if we can. 88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then 89 | MAX_FD_LIMIT=`ulimit -H -n` 90 | if [ $? -eq 0 ] ; then 91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 92 | MAX_FD="$MAX_FD_LIMIT" 93 | fi 94 | ulimit -n $MAX_FD 95 | if [ $? -ne 0 ] ; then 96 | warn "Could not set maximum file descriptor limit: $MAX_FD" 97 | fi 98 | else 99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 100 | fi 101 | fi 102 | 103 | # For Darwin, add options to specify how the application appears in the dock 104 | if $darwin; then 105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 106 | fi 107 | 108 | # For Cygwin, switch paths to Windows format before running java 109 | if $cygwin ; then 110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 112 | JAVACMD=`cygpath --unix "$JAVACMD"` 113 | 114 | # We build the pattern for arguments to be converted via cygpath 115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 116 | SEP="" 117 | for dir in $ROOTDIRSRAW ; do 118 | ROOTDIRS="$ROOTDIRS$SEP$dir" 119 | SEP="|" 120 | done 121 | OURCYGPATTERN="(^($ROOTDIRS))" 122 | # Add a user-defined pattern to the cygpath arguments 123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 125 | fi 126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 127 | i=0 128 | for arg in "$@" ; do 129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 131 | 132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 134 | else 135 | eval `echo args$i`="\"$arg\"" 136 | fi 137 | i=$((i+1)) 138 | done 139 | case $i in 140 | (0) set -- ;; 141 | (1) set -- "$args0" ;; 142 | (2) set -- "$args0" "$args1" ;; 143 | (3) set -- "$args0" "$args1" "$args2" ;; 144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 150 | esac 151 | fi 152 | 153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules 154 | function splitJvmOpts() { 155 | JVM_OPTS=("$@") 156 | } 157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS 158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" 159 | 160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" 161 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------