(
-                Chat.class,
-                R.layout.message,
-                ChatHolder.class,
-                mChatIndicesRef.limitToLast(50),
-                mChatRef,
-                this) {
-            @Override
-            public void populateViewHolder(ChatHolder holder, Chat chat, int position) {
-                holder.bind(chat);
-            }
-
-            @Override
-            public void onDataChanged() {
-                // If there are no chat messages, show a view that invites the user to add a message.
-                mEmptyListMessage.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE);
-            }
-        };
-    }
-}
diff --git a/app/src/main/java/com/firebase/uidemo/database/firestore/Chat.java b/app/src/main/java/com/firebase/uidemo/database/firestore/Chat.java
new file mode 100644
index 000000000..d24b095b8
--- /dev/null
+++ b/app/src/main/java/com/firebase/uidemo/database/firestore/Chat.java
@@ -0,0 +1,59 @@
+package com.firebase.uidemo.database.firestore;
+
+import com.firebase.uidemo.database.AbstractChat;
+import com.google.firebase.firestore.IgnoreExtraProperties;
+import com.google.firebase.firestore.ServerTimestamp;
+
+import java.util.Date;
+
+@IgnoreExtraProperties
+public class Chat extends AbstractChat {
+
+    private String mName;
+    private String mMessage;
+    private String mUid;
+    private Date mTimestamp;
+
+    public Chat() {
+        // Needed for Firebase
+    }
+
+    public Chat(String name, String message, String uid) {
+        mName = name;
+        mMessage = message;
+        mUid = uid;
+    }
+
+    public String getName() {
+        return mName;
+    }
+
+    public void setName(String name) {
+        mName = name;
+    }
+
+    public String getMessage() {
+        return mMessage;
+    }
+
+    public void setMessage(String message) {
+        mMessage = message;
+    }
+
+    public String getUid() {
+        return mUid;
+    }
+
+    public void setUid(String uid) {
+        mUid = uid;
+    }
+
+    @ServerTimestamp
+    public Date getTimestamp() {
+        return mTimestamp;
+    }
+
+    public void setTimestamp(Date timestamp) {
+        mTimestamp = timestamp;
+    }
+}
diff --git a/app/src/main/java/com/firebase/uidemo/database/firestore/FirestoreChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/firestore/FirestoreChatActivity.java
new file mode 100644
index 000000000..271342ce1
--- /dev/null
+++ b/app/src/main/java/com/firebase/uidemo/database/firestore/FirestoreChatActivity.java
@@ -0,0 +1,173 @@
+package com.firebase.uidemo.database.firestore;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.firebase.ui.auth.ui.ImeHelper;
+import com.firebase.ui.firestore.FirestoreRecyclerAdapter;
+import com.firebase.ui.firestore.FirestoreRecyclerOptions;
+import com.firebase.uidemo.R;
+import com.firebase.uidemo.database.ChatHolder;
+import com.firebase.uidemo.database.realtime.Chat;
+import com.firebase.uidemo.util.LifecycleActivity;
+import com.firebase.uidemo.util.SignInResultNotifier;
+import com.google.android.gms.tasks.OnFailureListener;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.firestore.CollectionReference;
+import com.google.firebase.firestore.FirebaseFirestore;
+import com.google.firebase.firestore.Query;
+
+import butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+
+/**
+ * Class demonstrating how to setup a {@link RecyclerView} with an adapter while taking sign-in
+ * states into consideration. Also demonstrates adding data to a ref and then reading it back using
+ * the {@link FirestoreRecyclerAdapter} to build a simple chat app.
+ * 
+ * For a general intro to the RecyclerView, see Creating
+ * Lists.
+ */
+public class FirestoreChatActivity extends LifecycleActivity
+        implements FirebaseAuth.AuthStateListener {
+    private static final String TAG = "FirestoreChatActivity";
+
+    private static final CollectionReference sChatCollection =
+            FirebaseFirestore.getInstance().collection("chats");
+    /** Get the last 50 chat messages ordered by timestamp . */
+    private static final Query sChatQuery = sChatCollection.orderBy("timestamp").limit(50);
+
+    static {
+        FirebaseFirestore.setLoggingEnabled(true);
+    }
+
+    @BindView(R.id.messagesList)
+    RecyclerView mRecyclerView;
+
+    @BindView(R.id.sendButton)
+    Button mSendButton;
+
+    @BindView(R.id.messageEdit)
+    EditText mMessageEdit;
+
+    @BindView(R.id.emptyTextView)
+    TextView mEmptyListMessage;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_chat);
+        ButterKnife.bind(this);
+
+        mRecyclerView.setHasFixedSize(true);
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+
+        ImeHelper.setImeOnDoneListener(mMessageEdit, new ImeHelper.DonePressedListener() {
+            @Override
+            public void onDonePressed() {
+                onSendClick();
+            }
+        });
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        if (isSignedIn()) { attachRecyclerViewAdapter(); }
+        FirebaseAuth.getInstance().addAuthStateListener(this);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        FirebaseAuth.getInstance().removeAuthStateListener(this);
+    }
+
+    @Override
+    public void onAuthStateChanged(@NonNull FirebaseAuth auth) {
+        mSendButton.setEnabled(isSignedIn());
+        mMessageEdit.setEnabled(isSignedIn());
+
+        if (isSignedIn()) {
+            attachRecyclerViewAdapter();
+        } else {
+            Toast.makeText(this, R.string.signing_in, Toast.LENGTH_SHORT).show();
+            auth.signInAnonymously().addOnCompleteListener(new SignInResultNotifier(this));
+        }
+    }
+
+    private boolean isSignedIn() {
+        return FirebaseAuth.getInstance().getCurrentUser() != null;
+    }
+
+    private void attachRecyclerViewAdapter() {
+        final RecyclerView.Adapter adapter = newAdapter();
+
+        // Scroll to bottom on new messages
+        adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
+            @Override
+            public void onItemRangeInserted(int positionStart, int itemCount) {
+                mRecyclerView.smoothScrollToPosition(adapter.getItemCount());
+            }
+        });
+
+        mRecyclerView.setAdapter(adapter);
+    }
+
+    @OnClick(R.id.sendButton)
+    public void onSendClick() {
+        String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
+        String name = "User " + uid.substring(0, 6);
+
+        onAddMessage(new Chat(name, mMessageEdit.getText().toString(), uid));
+
+        mMessageEdit.setText("");
+    }
+
+    protected RecyclerView.Adapter newAdapter() {
+        FirestoreRecyclerOptions options =
+                new FirestoreRecyclerOptions.Builder()
+                        .setQuery(sChatQuery, Chat.class)
+                        .setLifecycleOwner(this)
+                        .build();
+
+        return new FirestoreRecyclerAdapter(options) {
+            @Override
+            public ChatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+                return new ChatHolder(LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.message, parent, false));
+            }
+
+            @Override
+            protected void onBindViewHolder(ChatHolder holder, int position, Chat model) {
+                holder.bind(model);
+            }
+
+            @Override
+            public void onDataChanged() {
+                // If there are no chat messages, show a view that invites the user to add a message.
+                mEmptyListMessage.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE);
+            }
+        };
+    }
+
+    protected void onAddMessage(Chat chat) {
+        sChatCollection.add(chat).addOnFailureListener(this, new OnFailureListener() {
+            @Override
+            public void onFailure(@NonNull Exception e) {
+                Log.e(TAG, "Failed to write message", e);
+            }
+        });
+    }
+}
diff --git a/app/src/main/java/com/firebase/uidemo/database/Chat.java b/app/src/main/java/com/firebase/uidemo/database/realtime/Chat.java
similarity index 75%
rename from app/src/main/java/com/firebase/uidemo/database/Chat.java
rename to app/src/main/java/com/firebase/uidemo/database/realtime/Chat.java
index c4fc6efdb..3e37eb98a 100644
--- a/app/src/main/java/com/firebase/uidemo/database/Chat.java
+++ b/app/src/main/java/com/firebase/uidemo/database/realtime/Chat.java
@@ -1,6 +1,11 @@
-package com.firebase.uidemo.database;
+package com.firebase.uidemo.database.realtime;
+
+import com.firebase.uidemo.database.AbstractChat;
+import com.google.firebase.database.IgnoreExtraProperties;
+
+@IgnoreExtraProperties
+public class Chat extends AbstractChat {
 
-public class Chat {
     private String mName;
     private String mMessage;
     private String mUid;
diff --git a/app/src/main/java/com/firebase/uidemo/database/realtime/RealtimeDbChatActivity.java b/app/src/main/java/com/firebase/uidemo/database/realtime/RealtimeDbChatActivity.java
new file mode 100644
index 000000000..131990997
--- /dev/null
+++ b/app/src/main/java/com/firebase/uidemo/database/realtime/RealtimeDbChatActivity.java
@@ -0,0 +1,171 @@
+package com.firebase.uidemo.database.realtime;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.v7.widget.LinearLayoutManager;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.firebase.ui.auth.ui.ImeHelper;
+import com.firebase.ui.database.FirebaseRecyclerAdapter;
+import com.firebase.ui.database.FirebaseRecyclerOptions;
+import com.firebase.uidemo.R;
+import com.firebase.uidemo.database.ChatHolder;
+import com.firebase.uidemo.util.LifecycleActivity;
+import com.firebase.uidemo.util.SignInResultNotifier;
+import com.google.firebase.auth.FirebaseAuth;
+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 butterknife.BindView;
+import butterknife.ButterKnife;
+import butterknife.OnClick;
+
+/**
+ * Class demonstrating how to setup a {@link RecyclerView} with an adapter while taking sign-in
+ * states into consideration. Also demonstrates adding data to a ref and then reading it back using
+ * the {@link FirebaseRecyclerAdapter} to build a simple chat app.
+ * 
+ * For a general intro to the RecyclerView, see Creating
+ * Lists.
+ */
+public class RealtimeDbChatActivity extends LifecycleActivity
+        implements FirebaseAuth.AuthStateListener {
+    private static final String TAG = "RealtimeDatabaseDemo";
+
+    /**
+     * Get the last 50 chat messages.
+     */
+    protected static final Query sChatQuery =
+            FirebaseDatabase.getInstance().getReference().child("chats").limitToLast(50);
+
+    @BindView(R.id.messagesList)
+    RecyclerView mRecyclerView;
+
+    @BindView(R.id.sendButton)
+    Button mSendButton;
+
+    @BindView(R.id.messageEdit)
+    EditText mMessageEdit;
+
+    @BindView(R.id.emptyTextView)
+    TextView mEmptyListMessage;
+
+    @Override
+    protected void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_chat);
+        ButterKnife.bind(this);
+
+        mRecyclerView.setHasFixedSize(true);
+        mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
+
+        ImeHelper.setImeOnDoneListener(mMessageEdit, new ImeHelper.DonePressedListener() {
+            @Override
+            public void onDonePressed() {
+                onSendClick();
+            }
+        });
+    }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        if (isSignedIn()) { attachRecyclerViewAdapter(); }
+        FirebaseAuth.getInstance().addAuthStateListener(this);
+    }
+
+    @Override
+    protected void onStop() {
+        super.onStop();
+        FirebaseAuth.getInstance().removeAuthStateListener(this);
+    }
+
+    @Override
+    public void onAuthStateChanged(@NonNull FirebaseAuth auth) {
+        mSendButton.setEnabled(isSignedIn());
+        mMessageEdit.setEnabled(isSignedIn());
+
+        if (isSignedIn()) {
+            attachRecyclerViewAdapter();
+        } else {
+            Toast.makeText(this, R.string.signing_in, Toast.LENGTH_SHORT).show();
+            auth.signInAnonymously().addOnCompleteListener(new SignInResultNotifier(this));
+        }
+    }
+
+    private boolean isSignedIn() {
+        return FirebaseAuth.getInstance().getCurrentUser() != null;
+    }
+
+    private void attachRecyclerViewAdapter() {
+        final RecyclerView.Adapter adapter = newAdapter();
+
+        // Scroll to bottom on new messages
+        adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
+            @Override
+            public void onItemRangeInserted(int positionStart, int itemCount) {
+                mRecyclerView.smoothScrollToPosition(adapter.getItemCount());
+            }
+        });
+
+        mRecyclerView.setAdapter(adapter);
+    }
+
+    @OnClick(R.id.sendButton)
+    public void onSendClick() {
+        String uid = FirebaseAuth.getInstance().getCurrentUser().getUid();
+        String name = "User " + uid.substring(0, 6);
+
+        onAddMessage(new Chat(name, mMessageEdit.getText().toString(), uid));
+
+        mMessageEdit.setText("");
+    }
+
+    protected RecyclerView.Adapter newAdapter() {
+        FirebaseRecyclerOptions options =
+                new FirebaseRecyclerOptions.Builder()
+                        .setQuery(sChatQuery, Chat.class)
+                        .setLifecycleOwner(this)
+                        .build();
+
+        return new FirebaseRecyclerAdapter(options) {
+            @Override
+            public ChatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+                return new ChatHolder(LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.message, parent, false));
+            }
+
+            @Override
+            protected void onBindViewHolder(ChatHolder holder, int position, Chat model) {
+                holder.bind(model);
+            }
+
+            @Override
+            public void onDataChanged() {
+                // If there are no chat messages, show a view that invites the user to add a message.
+                mEmptyListMessage.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE);
+            }
+        };
+    }
+
+    protected void onAddMessage(Chat chat) {
+        sChatQuery.getRef().push().setValue(chat, new DatabaseReference.CompletionListener() {
+            @Override
+            public void onComplete(DatabaseError error, DatabaseReference reference) {
+                if (error != null) {
+                    Log.e(TAG, "Failed to write message", error.toException());
+                }
+            }
+        });
+    }
+}
diff --git a/app/src/main/java/com/firebase/uidemo/database/realtime/RealtimeDbChatIndexActivity.java b/app/src/main/java/com/firebase/uidemo/database/realtime/RealtimeDbChatIndexActivity.java
new file mode 100644
index 000000000..b6c57498c
--- /dev/null
+++ b/app/src/main/java/com/firebase/uidemo/database/realtime/RealtimeDbChatIndexActivity.java
@@ -0,0 +1,58 @@
+package com.firebase.uidemo.database.realtime;
+
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import com.firebase.ui.database.FirebaseRecyclerAdapter;
+import com.firebase.ui.database.FirebaseRecyclerOptions;
+import com.firebase.uidemo.R;
+import com.firebase.uidemo.database.ChatHolder;
+import com.google.firebase.auth.FirebaseAuth;
+import com.google.firebase.database.DatabaseReference;
+import com.google.firebase.database.FirebaseDatabase;
+
+public class RealtimeDbChatIndexActivity extends RealtimeDbChatActivity {
+    private DatabaseReference mChatIndicesRef;
+
+    @Override
+    protected FirebaseRecyclerAdapter newAdapter() {
+        mChatIndicesRef = FirebaseDatabase.getInstance()
+                .getReference()
+                .child("chatIndices")
+                .child(FirebaseAuth.getInstance().getCurrentUser().getUid());
+
+        FirebaseRecyclerOptions options =
+                new FirebaseRecyclerOptions.Builder()
+                        .setIndexedQuery(
+                                mChatIndicesRef.limitToFirst(50), sChatQuery.getRef(), Chat.class)
+                        .setLifecycleOwner(this)
+                        .build();
+
+        return new FirebaseRecyclerAdapter(options) {
+            @Override
+            public ChatHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+                return new ChatHolder(LayoutInflater.from(parent.getContext())
+                        .inflate(R.layout.message, parent, false));
+            }
+
+            @Override
+            protected void onBindViewHolder(ChatHolder holder, int position, Chat model) {
+                holder.bind(model);
+            }
+
+            @Override
+            public void onDataChanged() {
+                // If there are no chat messages, show a view that invites the user to add a message.
+                mEmptyListMessage.setVisibility(getItemCount() == 0 ? View.VISIBLE : View.GONE);
+            }
+        };
+    }
+
+    @Override
+    protected void onAddMessage(Chat chat) {
+        DatabaseReference chatRef = sChatQuery.getRef().push();
+        mChatIndicesRef.child(chatRef.getKey()).setValue(true);
+        chatRef.setValue(chat);
+    }
+}
diff --git a/app/src/main/java/com/firebase/uidemo/storage/ImageActivity.java b/app/src/main/java/com/firebase/uidemo/storage/ImageActivity.java
index 6f39bf783..71949baf8 100644
--- a/app/src/main/java/com/firebase/uidemo/storage/ImageActivity.java
+++ b/app/src/main/java/com/firebase/uidemo/storage/ImageActivity.java
@@ -13,8 +13,7 @@
 import android.widget.ImageView;
 import android.widget.Toast;
 
-import com.bumptech.glide.Glide;
-import com.firebase.ui.storage.images.FirebaseImageLoader;
+import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions;
 import com.firebase.uidemo.R;
 import com.firebase.uidemo.util.SignInResultNotifier;
 import com.google.android.gms.tasks.OnFailureListener;
@@ -131,11 +130,11 @@ public void onFailure(@NonNull Exception e) {
     @OnClick(R.id.button_download_direct)
     protected void downloadDirect() {
         // Download directly from StorageReference using Glide
-        Glide.with(this)
-                .using(new FirebaseImageLoader())
+        // (See MyAppGlideModule for Loader registration)
+        GlideApp.with(this)
                 .load(mImageRef)
                 .centerCrop()
-                .crossFade()
+                .transition(DrawableTransitionOptions.withCrossFade())
                 .into(mImageView);
     }
 
diff --git a/app/src/main/java/com/firebase/uidemo/storage/MyAppGlideModule.java b/app/src/main/java/com/firebase/uidemo/storage/MyAppGlideModule.java
new file mode 100644
index 000000000..ccbffbbf8
--- /dev/null
+++ b/app/src/main/java/com/firebase/uidemo/storage/MyAppGlideModule.java
@@ -0,0 +1,28 @@
+package com.firebase.uidemo.storage;
+
+import android.content.Context;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.Registry;
+import com.bumptech.glide.annotation.GlideModule;
+import com.bumptech.glide.module.AppGlideModule;
+import com.firebase.ui.storage.images.FirebaseImageLoader;
+import com.google.firebase.storage.StorageReference;
+
+import java.io.InputStream;
+
+/**
+ * Glide module to register {@link com.firebase.ui.storage.images.FirebaseImageLoader}.
+ * See: http://bumptech.github.io/glide/doc/generatedapi.html
+ */
+@GlideModule
+public class MyAppGlideModule extends AppGlideModule {
+
+    @Override
+    public void registerComponents(Context context, Glide glide, Registry registry) {
+        // Register FirebaseImageLoader to handle StorageReference
+        registry.append(StorageReference.class, InputStream.class,
+                new FirebaseImageLoader.Factory());
+    }
+
+}
diff --git a/app/src/main/java/com/firebase/uidemo/util/LifecycleActivity.java b/app/src/main/java/com/firebase/uidemo/util/LifecycleActivity.java
new file mode 100644
index 000000000..808cb071d
--- /dev/null
+++ b/app/src/main/java/com/firebase/uidemo/util/LifecycleActivity.java
@@ -0,0 +1,16 @@
+package com.firebase.uidemo.util;
+
+import android.arch.lifecycle.LifecycleRegistry;
+import android.arch.lifecycle.LifecycleRegistryOwner;
+import android.support.v7.app.AppCompatActivity;
+
+// TODO remove once arch components are merged into support lib
+@SuppressWarnings("Registered")
+public class LifecycleActivity extends AppCompatActivity implements LifecycleRegistryOwner {
+    private final LifecycleRegistry mRegistry = new LifecycleRegistry(this);
+
+    @Override
+    public LifecycleRegistry getLifecycle() {
+        return mRegistry;
+    }
+}
diff --git a/app/src/main/res/layout/activity_chat.xml b/app/src/main/res/layout/activity_chat.xml
index 7c0171e08..fc6a78ae4 100644
--- a/app/src/main/res/layout/activity_chat.xml
+++ b/app/src/main/res/layout/activity_chat.xml
@@ -4,7 +4,7 @@
     xmlns:tools="http://schemas.android.com/tools"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    tools:context=".database.ChatActivity">
+    tools:context=".database.realtime.RealtimeDbChatActivity">