diff --git a/database/README.md b/database/README.md index 79a4c105d..540cc70f5 100644 --- a/database/README.md +++ b/database/README.md @@ -269,3 +269,22 @@ recycler.setAdapter(mAdapter); ``` Like before, we get a custom RecyclerView populated with data from Firebase by setting the properties to the correct fields. + +## Using FirebaseUI with indexed data +If your data is [properly indexed](https://firebase.google.com/docs/database/android/structure-data#best_practices_for_data_structure), change your adapter initalization like so: + +For a `RecyclerView`, use `FirebaseIndexRecyclerAdapter` instead of `FirebaseRecyclerAdapter`: +```java +new FirebaseIndexRecyclerAdapter(Chat.class, + android.R.layout.two_line_list_item, + ChatHolder.class, + keyRef, // The Firebase location containing the list of keys to be found in dataRef. + dataRef) //The Firebase location to watch for data changes. Each key key found at keyRef's location represents a list item in the RecyclerView. +``` + +And for a `ListView`, use `FirebaseIndexListAdapter`; +```java +new FirebaseIndexListAdapter(this, Chat.class, android.R.layout.two_line_list_item, keyRef, dataRef) +``` + +`keyRef` is the location of your keys, and `dataRef` is the location of your data. diff --git a/database/src/androidTest/java/com/firebase/ui/database/ApplicationTest.java b/database/src/androidTest/java/com/firebase/ui/database/ApplicationTest.java deleted file mode 100644 index 95d7b3d78..000000000 --- a/database/src/androidTest/java/com/firebase/ui/database/ApplicationTest.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2016 Google Inc. All Rights Reserved. - * - * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software distributed under the - * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either - * express or implied. See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.firebase.ui.database; - -import android.content.Context; - -import com.google.firebase.FirebaseApp; -import com.google.firebase.FirebaseOptions; - -/** - * Helpers for testing. - */ -public class ApplicationTest { - - private static final String APP_NAME = "firebaseui-tests"; - - public ApplicationTest() {} - - public static FirebaseApp getAppInstance(Context context) { - try { - return FirebaseApp.getInstance(APP_NAME); - } catch (IllegalStateException e) { - return initializeApp(context); - } - } - - public static FirebaseApp initializeApp(Context context) { - return FirebaseApp.initializeApp(context, new FirebaseOptions.Builder() - .setApplicationId("fir-ui-tests") - .setDatabaseUrl("https://fir-ui-tests.firebaseio.com/") - .build(), APP_NAME); - } -} diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java index b094b8d30..f7420eee7 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayOfObjectsTest.java @@ -18,13 +18,10 @@ import android.test.InstrumentationTestCase; import android.test.suitebuilder.annotation.SmallTest; +import com.firebase.ui.database.utils.Bean; import com.google.firebase.FirebaseApp; -import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; -import com.google.firebase.database.Query; - -import junit.framework.AssertionFailedError; import org.junit.After; import org.junit.Before; @@ -32,65 +29,37 @@ import org.junit.runner.RunWith; import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; + +import static com.firebase.ui.database.TestUtils.getAppInstance; +import static com.firebase.ui.database.TestUtils.getBean; +import static com.firebase.ui.database.TestUtils.runAndWaitUntil; @RunWith(AndroidJUnit4.class) @SmallTest public class FirebaseArrayOfObjectsTest extends InstrumentationTestCase { - public static class Bean { - int number; - String text; - boolean bool; - - public Bean() { - // necessary for Jackson - } - - public Bean(int number, String text, boolean bool) { - this.number = number; - this.text = text; - this.bool = bool; - } - public Bean(int index) { - this(index, "Text "+index, index % 2 == 0); - } - - public int getNumber() { - return number; - } - - public String getText() { - return text; - } - - public boolean isBool() { - return bool; - } - } - private DatabaseReference mRef; private FirebaseArray mArray; @Before public void setUp() throws Exception { - FirebaseApp app = ApplicationTest.getAppInstance(getInstrumentation().getContext()); + FirebaseApp app = getAppInstance(getInstrumentation().getContext()); mRef = FirebaseDatabase.getInstance(app).getReference() .child("firebasearray").child("objects"); mArray = new FirebaseArray(mRef); mRef.removeValue(); - runAndWaitUntil(mArray, mRef, new Runnable() { - @Override - public void run() { - for (int i = 1; i <= 3; i++) { - mRef.push().setValue(new Bean(i, "Text " + i, i % 2 == 0 ? true : false), i); + runAndWaitUntil(mArray, new Runnable() { + @Override + public void run() { + for (int i = 1; i <= 3; i++) { + mRef.push().setValue(new Bean(i, "Text " + i, i % 2 == 0), i); + } + } + }, new Callable() { + @Override + public Boolean call() throws Exception { + return mArray.getCount() == 3; + } } - } - }, new Callable() { - @Override - public Boolean call() throws Exception { - return mArray.getCount() == 3; - } - } ); } @@ -113,7 +82,7 @@ public void testSize() throws Exception { @Test public void testPushIncreasesSize() throws Exception { assertEquals(3, mArray.getCount()); - runAndWaitUntil(mArray, mRef, new Runnable() { + runAndWaitUntil(mArray, new Runnable() { public void run() { mRef.push().setValue(new Bean(4)); } @@ -124,9 +93,10 @@ public Boolean call() throws Exception { } }); } + @Test public void testPushAppends() throws Exception { - runAndWaitUntil(mArray, mRef, new Runnable() { + runAndWaitUntil(mArray, new Runnable() { public void run() { mRef.push().setValue(new Bean(4), 4); } @@ -140,71 +110,32 @@ public Boolean call() throws Exception { @Test public void testAddValueWithPriority() throws Exception { - runAndWaitUntil(mArray, mRef, new Runnable() { + runAndWaitUntil(mArray, new Runnable() { public void run() { mRef.push().setValue(new Bean(4), 0.5); } }, new Callable() { public Boolean call() throws Exception { - return mArray.getItem(3).getValue(Bean.class).getNumber() == 3 && mArray.getItem(0).getValue(Bean.class).getNumber() == 4; + return mArray.getItem(3).getValue(Bean.class).getNumber() == 3 && mArray.getItem(0) + .getValue(Bean.class) + .getNumber() == 4; } }); } @Test public void testChangePriorities() throws Exception { - runAndWaitUntil(mArray, mRef, new Runnable() { + runAndWaitUntil(mArray, new Runnable() { public void run() { mArray.getItem(2).getRef().setPriority(0.5); } }, new Callable() { public Boolean call() throws Exception { - return getBean(mArray, 0).getNumber() == 3 && getBean(mArray, 1).getNumber() == 1 && getBean(mArray, 2).getNumber() == 2; + return getBean(mArray, 0).getNumber() == 3 + && getBean(mArray, 1).getNumber() == 1 + && getBean(mArray, 2).getNumber() == 2; //return isValuesEqual(mArray, new int[]{3, 1, 2}); } }); } - - private static boolean isValuesEqual(FirebaseArray array, int[] expected) { - if (array.getCount() != expected.length) return false; - for (int i=0; i < array.getCount(); i++) { - if (!array.getItem(i).getValue(Integer.class).equals(expected[i])) { - return false; - } - } - return true; - } - - private Bean getBean(FirebaseArray array, int index) { - return array.getItem(index).getValue(Bean.class); - } - - public static void runAndWaitUntil(final FirebaseArray array, Query ref, Runnable task, Callable done) throws InterruptedException { - final java.util.concurrent.Semaphore semaphore = new java.util.concurrent.Semaphore(0); - array.setOnChangedListener(new FirebaseArray.OnChangedListener() { - public void onChanged(EventType type, int index, int oldIndex) { - semaphore.release(); - } - - @Override - public void onCancelled(DatabaseError databaseError) { - throw new IllegalStateException(databaseError.toException()); - } - }); - task.run(); - boolean isDone = false; - long startedAt = System.currentTimeMillis(); - while (!isDone && System.currentTimeMillis() - startedAt < 5000) { - semaphore.tryAcquire(1, TimeUnit.SECONDS); - try { - isDone = done.call(); - } catch (Exception e) { - e.printStackTrace(); - // and we're not done - } - } - if (!isDone) { - throw new AssertionFailedError(); - } - array.setOnChangedListener(null); - }} +} diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java index 67ddbea4d..b79a4fcf3 100644 --- a/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseArrayTest.java @@ -19,12 +19,8 @@ import android.test.suitebuilder.annotation.SmallTest; import com.google.firebase.FirebaseApp; -import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.FirebaseDatabase; -import com.google.firebase.database.Query; - -import junit.framework.AssertionFailedError; import org.junit.After; import org.junit.Before; @@ -32,24 +28,24 @@ import org.junit.runner.RunWith; import java.util.concurrent.Callable; -import java.util.concurrent.TimeUnit; + +import static com.firebase.ui.database.TestUtils.getAppInstance; +import static com.firebase.ui.database.TestUtils.isValuesEqual; +import static com.firebase.ui.database.TestUtils.runAndWaitUntil; @RunWith(AndroidJUnit4.class) @SmallTest public class FirebaseArrayTest extends InstrumentationTestCase { - - private static final int TIMEOUT = 5000; - private DatabaseReference mRef; private FirebaseArray mArray; @Before public void setUp() throws Exception { - FirebaseApp app = ApplicationTest.getAppInstance(getInstrumentation().getContext()); + FirebaseApp app = getAppInstance(getInstrumentation().getContext()); mRef = FirebaseDatabase.getInstance(app).getReference().child("firebasearray"); mArray = new FirebaseArray(mRef); mRef.removeValue(); - runAndWaitUntil(mArray, mRef, new Runnable() { + runAndWaitUntil(mArray, new Runnable() { public void run() { for (int i = 1; i <= 3; i++) { mRef.push().setValue(i, i); @@ -81,7 +77,7 @@ public void testSize() throws Exception { @Test public void testPushIncreasesSize() throws Exception { assertEquals(3, mArray.getCount()); - runAndWaitUntil(mArray, mRef, new Runnable() { + runAndWaitUntil(mArray, new Runnable() { public void run() { mRef.push().setValue(4); } @@ -92,9 +88,10 @@ public Boolean call() throws Exception { } }); } + @Test public void testPushAppends() throws Exception { - runAndWaitUntil(mArray, mRef, new Runnable() { + runAndWaitUntil(mArray, new Runnable() { public void run() { mRef.push().setValue(4, 4); } @@ -108,20 +105,21 @@ public Boolean call() throws Exception { @Test public void testAddValueWithPriority() throws Exception { - runAndWaitUntil(mArray, mRef, new Runnable() { + runAndWaitUntil(mArray, new Runnable() { public void run() { mRef.push().setValue(4, 0.5); } }, new Callable() { public Boolean call() throws Exception { - return mArray.getItem(3).getValue(Integer.class).equals(3) && mArray.getItem(0).getValue(Integer.class).equals(4); + return mArray.getItem(3).getValue(Integer.class).equals(3) + && mArray.getItem(0).getValue(Integer.class).equals(4); } }); } @Test public void testChangePriorities() throws Exception { - runAndWaitUntil(mArray, mRef, new Runnable() { + runAndWaitUntil(mArray, new Runnable() { public void run() { mArray.getItem(2).getRef().setPriority(0.5); } @@ -131,55 +129,4 @@ public Boolean call() throws Exception { } }); } - - private static boolean isValuesEqual(FirebaseArray array, int[] expected) { - if (array.getCount() != expected.length) return false; - for (int i=0; i < array.getCount(); i++) { - if (!array.getItem(i).getValue(Integer.class).equals(expected[i])) { - return false; - } - } - return true; - } - - private Integer getIntValue(FirebaseArray array, int index) { - return array.getItem(index).getValue(Integer.class); - } - - private static void print(FirebaseArray array) { - for (int i=0; i < array.getCount(); i++) { - System.out.println(i+": key="+array.getItem(i).getKey()+" value="+array.getItem(i).getValue()); - - } - } - - public static void runAndWaitUntil(final FirebaseArray array, Query ref, Runnable task, Callable done) throws InterruptedException { - final java.util.concurrent.Semaphore semaphore = new java.util.concurrent.Semaphore(0); - array.setOnChangedListener(new FirebaseArray.OnChangedListener() { - public void onChanged(EventType type, int index, int oldIndex) { - semaphore.release(); - } - - @Override - public void onCancelled(DatabaseError databaseError) { - throw new IllegalStateException(databaseError.toException()); - } - }); - task.run(); - boolean isDone = false; - long startedAt = System.currentTimeMillis(); - while (!isDone && System.currentTimeMillis() - startedAt < TIMEOUT) { - semaphore.tryAcquire(1, TimeUnit.SECONDS); - try { - isDone = done.call(); - } catch (Exception e) { - e.printStackTrace(); - // and we're not done - } - } - if (!isDone) { - throw new AssertionFailedError(); - } - array.setOnChangedListener(null); - } } diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java new file mode 100644 index 000000000..ce833f74b --- /dev/null +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayOfObjectsTest.java @@ -0,0 +1,157 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.database; + +import android.support.test.runner.AndroidJUnit4; +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import com.firebase.ui.database.utils.Bean; +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.Callable; + +import static com.firebase.ui.database.TestUtils.getAppInstance; +import static com.firebase.ui.database.TestUtils.getBean; +import static com.firebase.ui.database.TestUtils.runAndWaitUntil; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class FirebaseIndexArrayOfObjectsTest extends InstrumentationTestCase { + private DatabaseReference mRef; + private DatabaseReference mKeyRef; + private FirebaseArray mArray; + + @Before + public void setUp() throws Exception { + FirebaseDatabase databaseInstance = + FirebaseDatabase.getInstance(getAppInstance(getInstrumentation().getContext())); + mRef = databaseInstance.getReference().child("firebasearray").child("objects"); + mKeyRef = databaseInstance.getReference().child("firebaseindexarray").child("objects"); + + mArray = new FirebaseIndexArray(mKeyRef, mRef); + mRef.removeValue(); + mKeyRef.removeValue(); + + runAndWaitUntil(mArray, new Runnable() { + @Override + public void run() { + for (int i = 1; i <= 3; i++) { + setValue(new Bean(i, "Text " + i, i % 2 == 0), i); + } + } + }, new Callable() { + @Override + public Boolean call() throws Exception { + return mArray.getCount() == 3; + } + } + ); + } + + @After + public void tearDown() throws Exception { + if (mRef != null) { + mRef.getRoot().removeValue(); + } + + if (mArray != null) { + mArray.cleanup(); + } + } + + @Test + public void testSize() throws Exception { + assertEquals(3, mArray.getCount()); + } + + @Test + public void testPushIncreasesSize() throws Exception { + assertEquals(3, mArray.getCount()); + runAndWaitUntil(mArray, new Runnable() { + public void run() { + setValue(new Bean(4), null); + } + }, new Callable() { + @Override + public Boolean call() throws Exception { + return mArray.getCount() == 4; + } + }); + } + + @Test + public void testPushAppends() throws Exception { + runAndWaitUntil(mArray, new Runnable() { + public void run() { + setValue(new Bean(4), 4); + } + }, new Callable() { + @Override + public Boolean call() throws Exception { + return mArray.getItem(3).getValue(Bean.class).getNumber() == 4; + } + }); + } + + @Test + public void testAddValueWithPriority() throws Exception { + runAndWaitUntil(mArray, new Runnable() { + public void run() { + setValue(new Bean(4), 0.5); + } + }, new Callable() { + public Boolean call() throws Exception { + return mArray.getItem(3).getValue(Bean.class).getNumber() == 3 && mArray.getItem(0) + .getValue(Bean.class) + .getNumber() == 4; + } + }); + } + + @Test + public void testChangePriorities() throws Exception { + runAndWaitUntil(mArray, new Runnable() { + public void run() { + mKeyRef.child(mArray.getItem(2).getKey()).setPriority(0.5); + } + }, new Callable() { + public Boolean call() throws Exception { + return getBean(mArray, 0).getNumber() == 3 + && getBean(mArray, 1).getNumber() == 1 + && getBean(mArray, 2).getNumber() == 2; + //return isValuesEqual(mArray, new int[]{3, 1, 2}); + } + }); + } + + private void setValue(Object value, Object priority) { + String key = mKeyRef.push().getKey(); + + if (priority != null) { + mKeyRef.child(key).setValue(true, priority); + mRef.child(key).setValue(value, priority); + } else { + mKeyRef.child(key).setValue(true); + mRef.child(key).setValue(value); + } + } +} diff --git a/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java new file mode 100644 index 000000000..71d62f96e --- /dev/null +++ b/database/src/androidTest/java/com/firebase/ui/database/FirebaseIndexArrayTest.java @@ -0,0 +1,151 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.database; + +import android.support.test.runner.AndroidJUnit4; +import android.test.InstrumentationTestCase; +import android.test.suitebuilder.annotation.SmallTest; + +import com.google.firebase.database.DatabaseReference; +import com.google.firebase.database.FirebaseDatabase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.util.concurrent.Callable; + +import static com.firebase.ui.database.TestUtils.getAppInstance; +import static com.firebase.ui.database.TestUtils.isValuesEqual; +import static com.firebase.ui.database.TestUtils.runAndWaitUntil; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class FirebaseIndexArrayTest extends InstrumentationTestCase { + private DatabaseReference mRef; + private DatabaseReference mKeyRef; + private FirebaseIndexArray mArray; + + @Before + public void setUp() throws Exception { + FirebaseDatabase databaseInstance = + FirebaseDatabase.getInstance(getAppInstance(getInstrumentation().getContext())); + mRef = databaseInstance.getReference().child("firebasearray"); + mKeyRef = databaseInstance.getReference().child("firebaseindexarray"); + + mArray = new FirebaseIndexArray(mKeyRef, mRef); + mRef.removeValue(); + mKeyRef.removeValue(); + + runAndWaitUntil(mArray, new Runnable() { + @Override + public void run() { + for (int i = 1; i <= 3; i++) { + setValue(i, i); + } + } + }, new Callable() { + @Override + public Boolean call() throws Exception { + return mArray.getCount() == 3; + } + }); + } + + @After + public void tearDown() throws Exception { + if (mRef != null) { + mRef.getRoot().removeValue(); + } + + if (mArray != null) { + mArray.cleanup(); + } + } + + @Test + public void testSize() throws Exception { + assertEquals(3, mArray.getCount()); + } + + @Test + public void testPushIncreasesSize() throws Exception { + assertEquals(3, mArray.getCount()); + runAndWaitUntil(mArray, new Runnable() { + public void run() { + setValue(4, null); + } + }, new Callable() { + @Override + public Boolean call() throws Exception { + return mArray.getCount() == 4; + } + }); + } + + @Test + public void testPushAppends() throws Exception { + runAndWaitUntil(mArray, new Runnable() { + public void run() { + setValue(4, 4); + } + }, new Callable() { + @Override + public Boolean call() throws Exception { + return mArray.getItem(3).getValue(Integer.class).equals(4); + } + }); + } + + @Test + public void testAddValueWithPriority() throws Exception { + runAndWaitUntil(mArray, new Runnable() { + public void run() { + setValue(4, 0.5); + } + }, new Callable() { + public Boolean call() throws Exception { + return mArray.getItem(3).getValue(Integer.class).equals(3) + && mArray.getItem(0).getValue(Integer.class).equals(4); + } + }); + } + + @Test + public void testChangePriorities() throws Exception { + runAndWaitUntil(mArray, new Runnable() { + public void run() { + mKeyRef.child(mArray.getItem(2).getKey()).setPriority(0.5); + } + }, new Callable() { + public Boolean call() throws Exception { + return isValuesEqual(mArray, new int[]{3, 1, 2}); + } + }); + } + + private void setValue(Object value, Object priority) { + String key = mKeyRef.push().getKey(); + + if (priority != null) { + mKeyRef.child(key).setValue(true, priority); + mRef.child(key).setValue(value, priority); + } else { + mKeyRef.child(key).setValue(true); + mRef.child(key).setValue(value); + } + } +} diff --git a/database/src/androidTest/java/com/firebase/ui/database/utils/Bean.java b/database/src/androidTest/java/com/firebase/ui/database/utils/Bean.java new file mode 100644 index 000000000..26aaeadc7 --- /dev/null +++ b/database/src/androidTest/java/com/firebase/ui/database/utils/Bean.java @@ -0,0 +1,32 @@ +package com.firebase.ui.database.utils; + +public class Bean { + private int number; + private String text; + private boolean bool; + + public Bean() { + } + + public Bean(int number, String text, boolean bool) { + this.number = number; + this.text = text; + this.bool = bool; + } + + public Bean(int index) { + this(index, "Text " + index, index % 2 == 0); + } + + public int getNumber() { + return number; + } + + public String getText() { + return text; + } + + public boolean isBool() { + return bool; + } +} \ No newline at end of file diff --git a/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java b/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java new file mode 100644 index 000000000..7e920561f --- /dev/null +++ b/database/src/androidTest/java/com/firebase/ui/database/utils/TestUtils.java @@ -0,0 +1,81 @@ +package com.firebase.ui.database; + +import android.content.Context; + +import com.firebase.ui.database.utils.Bean; +import com.google.firebase.FirebaseApp; +import com.google.firebase.FirebaseOptions; +import com.google.firebase.database.DatabaseError; + +import junit.framework.AssertionFailedError; + +import java.util.concurrent.Callable; +import java.util.concurrent.Semaphore; +import java.util.concurrent.TimeUnit; + +public class TestUtils { + private static final String APP_NAME = "firebaseui-tests"; + private static final int TIMEOUT = 10000; + + + public static FirebaseApp getAppInstance(Context context) { + try { + return FirebaseApp.getInstance(APP_NAME); + } catch (IllegalStateException e) { + return initializeApp(context); + } + } + + public static FirebaseApp initializeApp(Context context) { + return FirebaseApp.initializeApp(context, new FirebaseOptions.Builder() + .setApplicationId("fir-ui-tests") + .setDatabaseUrl("https://fir-ui-tests.firebaseio.com/") + .build(), APP_NAME); + } + + public static void runAndWaitUntil(FirebaseArray array, + Runnable task, + Callable done) throws InterruptedException { + final Semaphore semaphore = new Semaphore(0); + array.setOnChangedListener(new FirebaseArray.OnChangedListener() { + public void onChanged(EventType type, int index, int oldIndex) { + semaphore.release(); + } + + @Override + public void onCancelled(DatabaseError databaseError) { + throw new IllegalStateException(databaseError.toException()); + } + }); + task.run(); + boolean isDone = false; + long startedAt = System.currentTimeMillis(); + while (!isDone && System.currentTimeMillis() - startedAt < TIMEOUT) { + semaphore.tryAcquire(1, TimeUnit.SECONDS); + try { + isDone = done.call(); + } catch (Exception e) { + e.printStackTrace(); + // and we're not done + } + } + if (!isDone) { + throw new AssertionFailedError(); + } + array.setOnChangedListener(null); + } + + public static boolean isValuesEqual(FirebaseArray array, int[] expected) { + if (array.getCount() != expected.length) return false; + for (int i = 0; i < array.getCount(); i++) { + if (!array.getItem(i).getValue(Integer.class).equals(expected[i])) { + return false; + } + } + return true; + } + + public static Bean getBean(FirebaseArray array, int index) { + return array.getItem(index).getValue(Bean.class); + } +} diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java index 775e3b659..1bb5fa4fd 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseArray.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseArray.java @@ -20,6 +20,7 @@ import com.google.firebase.database.Query; import java.util.ArrayList; +import java.util.List; /** * This class implements an array-like collection on top of a Firebase location. @@ -27,17 +28,18 @@ class FirebaseArray implements ChildEventListener { public interface OnChangedListener { enum EventType {ADDED, CHANGED, REMOVED, MOVED} + void onChanged(EventType type, int index, int oldIndex); + void onCancelled(DatabaseError databaseError); } private Query mQuery; private OnChangedListener mListener; - private ArrayList mSnapshots; + private List mSnapshots = new ArrayList<>(); public FirebaseArray(Query ref) { mQuery = ref; - mSnapshots = new ArrayList(); mQuery.addChildEventListener(this); } @@ -47,8 +49,8 @@ public void cleanup() { public int getCount() { return mSnapshots.size(); - } + public DataSnapshot getItem(int index) { return mSnapshots.get(index); } @@ -65,7 +67,7 @@ private int getIndexForKey(String key) { throw new IllegalArgumentException("Key not found"); } - // Start of ChildEventListener methods + @Override public void onChildAdded(DataSnapshot snapshot, String previousChildKey) { int index = 0; if (previousChildKey != null) { @@ -75,18 +77,21 @@ public void onChildAdded(DataSnapshot snapshot, String previousChildKey) { notifyChangedListeners(OnChangedListener.EventType.ADDED, index); } + @Override public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { int index = getIndexForKey(snapshot.getKey()); mSnapshots.set(index, snapshot); notifyChangedListeners(OnChangedListener.EventType.CHANGED, index); } + @Override public void onChildRemoved(DataSnapshot snapshot) { int index = getIndexForKey(snapshot.getKey()); mSnapshots.remove(index); notifyChangedListeners(OnChangedListener.EventType.REMOVED, index); } + @Override public void onChildMoved(DataSnapshot snapshot, String previousChildKey) { int oldIndex = getIndexForKey(snapshot.getKey()); mSnapshots.remove(oldIndex); @@ -95,25 +100,25 @@ public void onChildMoved(DataSnapshot snapshot, String previousChildKey) { notifyChangedListeners(OnChangedListener.EventType.MOVED, newIndex, oldIndex); } - public void onCancelled(DatabaseError databaseError) { - notifyCancelledListeners(databaseError); + @Override + public void onCancelled(DatabaseError error) { + notifyCancelledListeners(error); } - // End of ChildEventListener methods public void setOnChangedListener(OnChangedListener listener) { mListener = listener; } - + protected void notifyChangedListeners(OnChangedListener.EventType type, int index) { notifyChangedListeners(type, index, -1); } - + protected void notifyChangedListeners(OnChangedListener.EventType type, int index, int oldIndex) { if (mListener != null) { mListener.onChanged(type, index, oldIndex); } } - + protected void notifyCancelledListeners(DatabaseError databaseError) { if (mListener != null) { mListener.onCancelled(databaseError); diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java new file mode 100644 index 000000000..62935351b --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexArray.java @@ -0,0 +1,171 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.database; + +import android.util.Log; + +import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.Query; +import com.google.firebase.database.ValueEventListener; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +class FirebaseIndexArray extends FirebaseArray { + private static final String TAG = FirebaseIndexArray.class.getSimpleName(); + + private Query mQuery; + private Map mRefs = new HashMap<>(); + private List mDataSnapshots = new ArrayList<>(); + private OnChangedListener mListener; + + FirebaseIndexArray(Query keyRef, Query dataRef) { + super(keyRef); + mQuery = dataRef; + } + + @Override + public void cleanup() { + super.cleanup(); + Set refs = new HashSet<>(mRefs.keySet()); + for (Query ref : refs) { + ref.removeEventListener(mRefs.remove(ref)); + } + } + + @Override + public int getCount() { + return mDataSnapshots.size(); + } + + @Override + public DataSnapshot getItem(int index) { + return mDataSnapshots.get(index); + } + + private int getIndexForKey(String key) { + int dataCount = getCount(); + int index = 0; + for (int keyIndex = 0; index < dataCount; keyIndex++) { + String superKey = super.getItem(keyIndex).getKey(); + if (key.equals(superKey)) { + break; + } else if (getItem(index).getKey().equals(superKey)) { + index++; + } + } + return index; + } + + private boolean isMatch(int index, String key) { + return index >= 0 && index < getCount() && getItem(index).getKey().equals(key); + } + + @Override + public void onChildAdded(DataSnapshot keySnapshot, String previousChildKey) { + super.setOnChangedListener(null); + super.onChildAdded(keySnapshot, previousChildKey); + super.setOnChangedListener(mListener); + + Query ref = mQuery.getRef().child(keySnapshot.getKey()); + mRefs.put(ref, ref.addValueEventListener(new DataRefListener())); + } + + @Override + public void onChildChanged(DataSnapshot snapshot, String previousChildKey) { + super.setOnChangedListener(null); + super.onChildChanged(snapshot, previousChildKey); + super.setOnChangedListener(mListener); + } + + @Override + public void onChildRemoved(DataSnapshot keySnapshot) { + String key = keySnapshot.getKey(); + int index = getIndexForKey(key); + mQuery.getRef().child(key).removeEventListener(mRefs.remove(mQuery.getRef().child(key))); + + super.setOnChangedListener(null); + super.onChildRemoved(keySnapshot); + super.setOnChangedListener(mListener); + + if (isMatch(index, key)) { + mDataSnapshots.remove(index); + notifyChangedListeners(OnChangedListener.EventType.REMOVED, index); + } + } + + @Override + public void onChildMoved(DataSnapshot keySnapshot, String previousChildKey) { + String key = keySnapshot.getKey(); + int oldIndex = getIndexForKey(key); + + super.setOnChangedListener(null); + super.onChildMoved(keySnapshot, previousChildKey); + super.setOnChangedListener(mListener); + + if (isMatch(oldIndex, key)) { + DataSnapshot snapshot = mDataSnapshots.remove(oldIndex); + int newIndex = getIndexForKey(key); + mDataSnapshots.add(newIndex, snapshot); + notifyChangedListeners(OnChangedListener.EventType.MOVED, newIndex, oldIndex); + } + } + + @Override + public void onCancelled(DatabaseError error) { + Log.e(TAG, "A fatal error occurred retrieving the necessary keys to populate your adapter."); + super.onCancelled(error); + } + + @Override + public void setOnChangedListener(OnChangedListener listener) { + super.setOnChangedListener(listener); + mListener = listener; + } + + private class DataRefListener implements ValueEventListener { + @Override + public void onDataChange(DataSnapshot snapshot) { + String key = snapshot.getKey(); + int index = getIndexForKey(key); + + if (snapshot.getValue() != null) { + if (!isMatch(index, key)) { + mDataSnapshots.add(index, snapshot); + notifyChangedListeners(OnChangedListener.EventType.ADDED, index); + } else { + mDataSnapshots.set(index, snapshot); + notifyChangedListeners(OnChangedListener.EventType.CHANGED, index); + } + } else { + Log.w(TAG, "Key not found at ref: " + snapshot.getRef()); + if (isMatch(index, key)) { + mDataSnapshots.remove(index); + notifyChangedListeners(OnChangedListener.EventType.REMOVED, index); + } + } + } + + @Override + public void onCancelled(DatabaseError error) { + notifyCancelledListeners(error); + } + } +} diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java new file mode 100644 index 000000000..0a4e1b3f0 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexListAdapter.java @@ -0,0 +1,49 @@ +package com.firebase.ui.database; + +import android.app.Activity; + +import com.google.firebase.database.Query; + +/** + * This class is a generic way of backing an Android ListView with a Firebase location. + * It handles all of the child events at the given Firebase location. It marshals received data into the given + * class type. Extend this class and provide an implementation of {@code populateView}, which will be given an + * instance of your list item mLayout and an instance your class that holds your data. Simply populate the view however + * you like and this class will handle updating the list as the data changes. + *

+ * If your data is not indexed: + *

+ *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
+ *     ListAdapter adapter = new FirebaseListAdapter(this, ChatMessage.class, android.R.layout.two_line_list_item, keyRef, dataRef)
+ *     {
+ *         protected void populateView(View view, ChatMessage chatMessage, int position)
+ *         {
+ *             ((TextView)view.findViewById(android.R.id.text1)).setText(chatMessage.getName());
+ *             ((TextView)view.findViewById(android.R.id.text2)).setText(chatMessage.getMessage());
+ *         }
+ *     };
+ *     listView.setListAdapter(adapter);
+ * 
+ * + * @param The class type to use as a model for the data contained in the children of the given Firebase location + */ +public abstract class FirebaseIndexListAdapter extends FirebaseListAdapter { + /** + * @param activity The activity containing the ListView + * @param modelClass Firebase will marshall the data at a location into an instance of a class that you provide + * @param modelLayout This is the layout used to represent a single list item. You will be responsible for populating an + * instance of the corresponding view with the data from an instance of modelClass. + * @param keyRef The Firebase location containing the list of keys to be found in {@code dataRef}. + * Can also be a slice of a location, using some + * combination of {@code limit()}, {@code startAt()}, and {@code endAt()}. + * @param dataRef The Firebase location to watch for data changes. + * Each key key found in {@code keyRef}'s location represents a list item in the {@code ListView}. + */ + public FirebaseIndexListAdapter(Activity activity, + Class modelClass, + int modelLayout, + Query keyRef, + Query dataRef) { + super(activity, modelClass, modelLayout, new FirebaseIndexArray(keyRef, dataRef)); + } +} diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java new file mode 100644 index 000000000..0d8facc14 --- /dev/null +++ b/database/src/main/java/com/firebase/ui/database/FirebaseIndexRecyclerAdapter.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the + * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either + * express or implied. See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.firebase.ui.database; + +import android.support.v7.widget.RecyclerView; + +import com.google.firebase.database.Query; + +/** + * This class is a generic way of backing an RecyclerView with a Firebase location. + * It handles all of the child events at the given Firebase location. It marshals received data into the given + * class type. + *

+ * To use this class in your app, subclass it passing in all required parameters and implement the + * populateViewHolder method. + *

+ *

+ *     private static class ChatMessageViewHolder extends RecyclerView.ViewHolder {
+ *         TextView messageText;
+ *         TextView nameText;
+ *
+ *         public ChatMessageViewHolder(View itemView) {
+ *             super(itemView);
+ *             nameText = (TextView)itemView.findViewById(android.R.id.text1);
+ *             messageText = (TextView) itemView.findViewById(android.R.id.text2);
+ *         }
+ *     }
+ *
+ *     FirebaseIndexRecyclerAdapter adapter;
+ *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
+ *
+ *     RecyclerView recycler = (RecyclerView) findViewById(R.id.messages_recycler);
+ *     recycler.setHasFixedSize(true);
+ *     recycler.setLayoutManager(new LinearLayoutManager(this));
+ *
+ *     adapter = new FirebaseIndexRecyclerAdapter(ChatMessage.class, android.R.layout.two_line_list_item, ChatMessageViewHolder.class, keyRef, dataRef) {
+ *         public void populateViewHolder(ChatMessageViewHolder chatMessageViewHolder, ChatMessage chatMessage, int position) {
+ *             chatMessageViewHolder.nameText.setText(chatMessage.getName());
+ *             chatMessageViewHolder.messageText.setText(chatMessage.getMessage());
+ *         }
+ *     };
+ *     recycler.setAdapter(mAdapter);
+ * 
+ * + * @param The Java class that maps to the type of objects stored in the Firebase location. + * @param The ViewHolder class that contains the Views in the layout that is shown for each object. + */ +public abstract class FirebaseIndexRecyclerAdapter extends FirebaseRecyclerAdapter { + /** + * @param modelClass Firebase will marshall the data at a location into an instance of a class that you provide + * @param modelLayout This is the layout used to represent a single item in the list. You will be responsible for populating an + * instance of the corresponding view with the data from an instance of modelClass. + * @param viewHolderClass The class that hold references to all sub-views in an instance modelLayout. + * @param keyRef The Firebase location containing the list of keys to be found in {@code dataRef}. + * Can also be a slice of a location, using some + * combination of {@code limit()}, {@code startAt()}, and {@code endAt()}. + * @param dataRef The Firebase location to watch for data changes. + * Each key key found at {@code keyRef}'s location represents a list item in the {@code RecyclerView}. + */ + public FirebaseIndexRecyclerAdapter(Class modelClass, + int modelLayout, + Class viewHolderClass, + Query keyRef, + Query dataRef) { + super(modelClass, modelLayout, viewHolderClass, new FirebaseIndexArray(keyRef, dataRef)); + } +} diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java index 5cc780966..6b8d0743d 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseListAdapter.java @@ -20,22 +20,21 @@ import android.view.ViewGroup; import android.widget.BaseAdapter; -import com.google.firebase.database.DatabaseReference; -import com.google.firebase.database.DatabaseError; import com.google.firebase.database.DataSnapshot; +import com.google.firebase.database.DatabaseError; +import com.google.firebase.database.DatabaseReference; import com.google.firebase.database.Query; /** * This class is a generic way of backing an Android ListView with a Firebase location. * It handles all of the child events at the given Firebase location. It marshals received data into the given - * class type. Extend this class and provide an implementation of populateView, which will be given an + * class type. Extend this class and provide an implementation of {@code populateView}, which will be given an * instance of your list item mLayout and an instance your class that holds your data. Simply populate the view however * you like and this class will handle updating the list as the data changes. - * - *
- * {@code
+ * 

+ *

  *     DatabaseReference ref = FirebaseDatabase.getInstance().getReference();
- *     ListAdapter adapter = new FirebaseListAdapter(this, ChatMessage.class, android.R.layout.two_line_list_item, mRef)
+ *     ListAdapter adapter = new FirebaseListAdapter(this, ChatMessage.class, android.R.layout.two_line_list_item, ref)
  *     {
  *         protected void populateView(View view, ChatMessage chatMessage, int position)
  *         {
@@ -44,32 +43,27 @@
  *         }
  *     };
  *     listView.setListAdapter(adapter);
- * }
- * 
+ * * * @param The class type to use as a model for the data contained in the children of the given Firebase location */ public abstract class FirebaseListAdapter extends BaseAdapter { + private static final String TAG = FirebaseListAdapter.class.getSimpleName(); private final Class mModelClass; protected int mLayout; protected Activity mActivity; FirebaseArray mSnapshots; - - /** - * @param activity The activity containing the ListView - * @param modelClass Firebase will marshall the data at a location into an instance of a class that you provide - * @param modelLayout This is the layout used to represent a single list item. You will be responsible for populating an - * instance of the corresponding view with the data from an instance of modelClass. - * @param ref The Firebase location to watch for data changes. Can also be a slice of a location, using some - * combination of limit(), startAt(), and endAt(), - */ - public FirebaseListAdapter(Activity activity, Class modelClass, int modelLayout, Query ref) { + FirebaseListAdapter(Activity activity, + Class modelClass, + int modelLayout, + FirebaseArray snapshots) { mModelClass = modelClass; mLayout = modelLayout; mActivity = activity; - mSnapshots = new FirebaseArray(ref); + mSnapshots = snapshots; + mSnapshots.setOnChangedListener(new FirebaseArray.OnChangedListener() { @Override public void onChanged(EventType type, int index, int oldIndex) { @@ -82,20 +76,23 @@ public void onCancelled(DatabaseError databaseError) { } }); } + /** * @param activity The activity containing the ListView * @param modelClass Firebase will marshall the data at a location into an instance of a class that you provide * @param modelLayout This is the layout used to represent a single list item. You will be responsible for populating an * instance of the corresponding view with the data from an instance of modelClass. * @param ref The Firebase location to watch for data changes. Can also be a slice of a location, using some - * combination of limit(), startAt(), and endAt(), + * combination of {@code limit()}, {@code startAt()}, and {@code endAt()}. */ - public FirebaseListAdapter(Activity activity, Class modelClass, int modelLayout, DatabaseReference ref) { - this(activity, modelClass, modelLayout, (Query) ref); + public FirebaseListAdapter(Activity activity, + Class modelClass, + int modelLayout, + Query ref) { + this(activity, modelClass, modelLayout, new FirebaseArray(ref)); } public void cleanup() { - // We're being destroyed, let go of our mListener and forget about all of the mModels mSnapshots.cleanup(); } @@ -120,7 +117,9 @@ protected T parseSnapshot(DataSnapshot snapshot) { return snapshot.getValue(mModelClass); } - public DatabaseReference getRef(int position) { return mSnapshots.getItem(position).getRef(); } + public DatabaseReference getRef(int position) { + return mSnapshots.getItem(position).getRef(); + } @Override public long getItemId(int i) { @@ -140,15 +139,15 @@ public View getView(int position, View view, ViewGroup viewGroup) { populateView(view, model, position); return view; } - + /** * This method will be triggered in the event that this listener either failed at the server, * or is removed as a result of the security and Firebase Database rules. * - * @param databaseError A description of the error that occurred + * @param error A description of the error that occurred */ - protected void onCancelled(DatabaseError databaseError) { - Log.w("FirebaseListAdapter", databaseError.toException()); + protected void onCancelled(DatabaseError error) { + Log.w(TAG, error.toException()); } /** @@ -158,9 +157,9 @@ protected void onCancelled(DatabaseError databaseError) { *

* Your implementation should populate the view using the data contained in the model. * - * @param v The view to populate - * @param model The object containing the data used to populate the view - * @param position The position in the list of the view being populated + * @param v The view to populate + * @param model The object containing the data used to populate the view + * @param position The position in the list of the view being populated */ abstract protected void populateView(View v, T model, int position); } diff --git a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java index 8dfc287b9..7e41802e6 100644 --- a/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java +++ b/database/src/main/java/com/firebase/ui/database/FirebaseRecyclerAdapter.java @@ -32,12 +32,11 @@ * This class is a generic way of backing an RecyclerView with a Firebase location. * It handles all of the child events at the given Firebase location. It marshals received data into the given * class type. - * + *

* To use this class in your app, subclass it passing in all required parameters and implement the * populateViewHolder method. - * - *

- * {@code
+ * 

+ *

  *     private static class ChatMessageViewHolder extends RecyclerView.ViewHolder {
  *         TextView messageText;
  *         TextView nameText;
@@ -63,32 +62,27 @@
  *         }
  *     };
  *     recycler.setAdapter(mAdapter);
- * }
- * 
+ * * - * @param The Java class that maps to the type of objects stored in the Firebase location. + * @param The Java class that maps to the type of objects stored in the Firebase location. * @param The ViewHolder class that contains the Views in the layout that is shown for each object. */ public abstract class FirebaseRecyclerAdapter extends RecyclerView.Adapter { + private static final String TAG = FirebaseRecyclerAdapter.class.getSimpleName(); - Class mModelClass; protected int mModelLayout; + Class mModelClass; Class mViewHolderClass; FirebaseArray mSnapshots; - /** - * @param modelClass Firebase will marshall the data at a location into an instance of a class that you provide - * @param modelLayout This is the layout used to represent a single item in the list. You will be responsible for populating an - * instance of the corresponding view with the data from an instance of modelClass. - * @param viewHolderClass The class that hold references to all sub-views in an instance modelLayout. - * @param ref The Firebase location to watch for data changes. Can also be a slice of a location, using some - * combination of limit(), startAt(), and endAt() - */ - public FirebaseRecyclerAdapter(Class modelClass, int modelLayout, Class viewHolderClass, Query ref) { + FirebaseRecyclerAdapter(Class modelClass, + int modelLayout, + Class viewHolderClass, + FirebaseArray snapshots) { mModelClass = modelClass; mModelLayout = modelLayout; mViewHolderClass = viewHolderClass; - mSnapshots = new FirebaseArray(ref); + mSnapshots = snapshots; mSnapshots.setOnChangedListener(new FirebaseArray.OnChangedListener() { @Override @@ -110,7 +104,7 @@ public void onChanged(EventType type, int index, int oldIndex) { throw new IllegalStateException("Incomplete case statement"); } } - + @Override public void onCancelled(DatabaseError databaseError) { FirebaseRecyclerAdapter.this.onCancelled(databaseError); @@ -119,15 +113,18 @@ public void onCancelled(DatabaseError databaseError) { } /** - * @param modelClass Firebase will marshall the data at a location into an instance of a class that you provide - * @param modelLayout This is the layout used to represent a single item in the list. You will be responsible for populating an - * instance of the corresponding view with the data from an instance of modelClass. + * @param modelClass Firebase will marshall the data at a location into an instance of a class that you provide + * @param modelLayout This is the layout used to represent a single item in the list. You will be responsible for populating an + * instance of the corresponding view with the data from an instance of modelClass. * @param viewHolderClass The class that hold references to all sub-views in an instance modelLayout. - * @param ref The Firebase location to watch for data changes. Can also be a slice of a location, using some - * combination of limit(), startAt(), and endAt() + * @param ref The Firebase location to watch for data changes. Can also be a slice of a location, using some + * combination of {@code limit()}, {@code startAt()}, and {@code endAt()}. */ - public FirebaseRecyclerAdapter(Class modelClass, int modelLayout, Class viewHolderClass, DatabaseReference ref) { - this(modelClass, modelLayout, viewHolderClass, (Query) ref); + public FirebaseRecyclerAdapter(Class modelClass, + int modelLayout, + Class viewHolderClass, + Query ref) { + this(modelClass, modelLayout, viewHolderClass, new FirebaseArray(ref)); } public void cleanup() { @@ -154,7 +151,9 @@ protected T parseSnapshot(DataSnapshot snapshot) { return snapshot.getValue(mModelClass); } - public DatabaseReference getRef(int position) { return mSnapshots.getItem(position).getRef(); } + public DatabaseReference getRef(int position) { + return mSnapshots.getItem(position).getRef(); + } @Override public long getItemId(int position) { @@ -178,6 +177,7 @@ public VH onCreateViewHolder(ViewGroup parent, int viewType) { throw new RuntimeException(e); } } + @Override public void onBindViewHolder(VH viewHolder, int position) { T model = getItem(position); @@ -188,15 +188,15 @@ public void onBindViewHolder(VH viewHolder, int position) { public int getItemViewType(int position) { return mModelLayout; } - + /** * This method will be triggered in the event that this listener either failed at the server, * or is removed as a result of the security and Firebase Database rules. * - * @param databaseError A description of the error that occurred + * @param error A description of the error that occurred */ - protected void onCancelled(DatabaseError databaseError) { - Log.w("FirebaseRecyclerAdapter", databaseError.toException()); + protected void onCancelled(DatabaseError error) { + Log.w(TAG, error.toException()); } /** @@ -208,7 +208,7 @@ protected void onCancelled(DatabaseError databaseError) { * * @param viewHolder The view to populate * @param model The object containing the data used to populate the view - * @param position The position in the list of the view being populated + * @param position The position in the list of the view being populated */ abstract protected void populateViewHolder(VH viewHolder, T model, int position); }