Firebase provides a real-time database and handles authentication among many other interesting features. It allows developers to focus on building amazing apps and not worry about the backend. In this 360AnDev talk, we will review how to make the most out of these tools in Android, from the first steps all the way to building an app that synchronizes data and authenticates with several major providers.
Introduction (0:00)
My name is Adrián. I’m here all the way from Guatemala. Back at home, we have beautiful landscapes, delicious food, and of course, Android developers. I’m here to talk to you about Firebase. At the moment I will introduce myself as a traveler and as a learner. I have always liked to build, and that’s why, I think, I really enjoy writing code. As coders or developers, we are always building different things. Today I would like to introduce you to some of the building blocks that are available in Firebase.
Firebase (2:08)
Firebase used to be only a real-time database with a couple of other features added afterward, like static hosting and authentication. However, since the last Google I/O they have launched a lot of new features. Now Firebase is a complete suite for mobile development, and it’s quite nice for a lot of reasons.
On slide 5 you can see you can see all the things that Firebase provides. Some things might be quite familiar to a lot of you, like analytics or notifications. These have been around for quite a while. From the left-hand side of the graphic, Realtime Database, Authentication, Cloud Messaging, Storage, and Hosting is available for mobile and also on the web. There are also other features, mainly for mobile, available for both Android and iOS.
In this talk, I’m going to focus on Realtime Database and Authentication, and I’m also going to show you a library called FirebaseUI.
For authentication keep in mind that the idea is that Firebase is going to handle all the flow for us. There is a console available where you can manage the users, and enable or disable different providers like Facebook, Google, Twitter, GitHub, email password, or even anonymous login. It also includes other things, like setting a template email when the user forgets their password, etc.
The Realtime Database is the other thing that we’re going to discuss during this session. Two things are really important to have in mind: the first one is that the database is a real-time database, so instead of doing a lot of queries that we usually do for our backend, we work mostly with subscriptions to have an update when there are changes to the data.
The second thing is that it’s a NoSQL database, so we have to structure the data in a way that makes sense for this. We don’t have to think in terms of tables with rows and columns. Instead, we’re going to think in terms of nodes with children. That is really important. Many of you may have already worked with NoSQL databases, but I’ve noticed a lot of newcomers to Firebase during the last couple of months that are struggling with how to structure the data. We’re going to see some code examples of how this works.
Besides that, we’re going to use a library called FirebaseUI that assists with both things: authentication, and Realtime Database. It will help us with the adapter, when we’re going to want to show the data, and also with all the flow and the layout for the authentication on Android. Let’s start by creating an app.
Creating an app (6:55)
This is the simplest thing on Firebase. I have several screenshots with URLs on the top that you can see on starting on Slide 10. That is Firebase’s main screen where you can create a new project.
Note: If you have a Firebase project before this new launch at this year at IO, you need to migrate those projects for them to appear on this console.
After creating a new project, we see on slide 12 this menu on the left, and a “Get started here” icon for Android, iOS, and web. We’re going to work with Android. Next, it asks us for a package name, and for some features, we need an SHA-1 signing certificate. We’re going to work with Google Authentication, so we need to set this up. Afterward, on slide 14, it lets us know that we need to add some things to our Gradle file.
After setting up our new project, on slide 15, we see that on the top left there is Auth and Database. These are the two things that we’re going to work with right now. On slide 16, that is the basic view for the database. It’s nothing fancy, and it’s not intended to be a full client to manage the data. But it’s quite useful to see what is stored in Firebase, make some modifications, or even add some data.
That is important because sometimes you’re going to use Firebase with a set of data that’s already defined, that does not require any input from the user. Using this console to input that data, it’s not as comfortable as it would be using a client. Usually what I do is write a script, using Ruby or JavaScript, to get the data into Firebase and also to manage during the lifetime of the app.
On slide 17 we can see another tab for the rules. By default, the rules are required for users to be authenticated. There’s a simple language for setting the rules, but the documentation is not that clear on how you can use this language. For now, we’re going to change this, mostly because we are learning how to write data to Firebase. Instead of asking the user to be authenticated, we’re just going to allow any read and write in the database (that is a bad idea if we were releasing into production).
Adding Firebase to a project (11:06)
We changed the rules to allow read and write, so now we’re going to add Firebase to our Android project:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bitandik.labs.simplefirebase" >
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme" >
<activity android:name=".MainActivity" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
First, we need to set the Internet permission on the manifest. That might also appear like common sense, but I’ve seen several projects, and it also happens to me every once in a while, where things are not working because we forgot to do it.
buildscript {
repositories {
jcenter()
}
dependencies {
classpath 'com.android.tools.build:gradle:2.2.0-alpha3'
classpath 'com.google.gms:google-services:3.0.0'
}
}
Afterward, we need to modify the root level build of the Gradle file, and we add the reference to Google services. That is exactly what the wizard on the main page explained. Then we can choose which features we want to use. We are going to work with a Realtime Database:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.4.0'
compile 'com.google.firebase:firebase-database:9.0.2'
}
apply plugin: 'com.google.gms.google-services'
Now on the app level Gradle file we call this reference to the database and also add the plugin for the Google services. The idea with this is that there are different kinds of features and we’re going to work with only a small set of them. We only add what we need. When I was a little kid, I used to play a lot with Legos that didn’t come with a manual. Now there are a lot of different shiny sets, and you can build really nice things. I actually just got a new set of Volkswagen, the Beetle. It’s fun to build these sets, but also, it’s fun to build without instructions. It’s also fun to build without any restrictions, so we’re going to do that. We’re going to build something that is really simple, just to know how to work with Firebase.
Save and retrieve data (12:51)
We’re going to start with saving and retrieving data. This is going to be the structure for this minimal, simple example:
// Root
// https://android-to-do-list.firebaseio.com/
{
// https://android-to-do-list.firebaseio.com/message
"message" : "Hello, World!",
// https://android-to-do-list.firebaseio.com/words
"words" : {
// https://android-to-do-list.firebasio.com/words/-KKGDzmrcC8o5EunCptc
"-KKGDzmrcC8o5EunCptc" : "test!",
// https://android-to-do-list.firebasio.com/words/-KKGEIIqvk-R0yitCXIc
"-KKGEIIqvk-R0yitCXIc" : "another item"
}
}
I’m going to have two nodes; there is a URL attached to each one of them. We have the first one called message
, and I’m going to write only a message in that, like Hello, World!
. Also, I have another node called words
, and under that, I have several children with IDs. On the left-hand side, you can see the ID. Firebase generates that. It’s a unique identifier based on the timestamp. In a few slides, we’re going to see how to do this.
On slide 31 you can see our app is going to have an EditText
and a Button
to input whatever we want to send to the database. On the console, we’re going to see something similar to slide 32. The message is going to be hardcoded, so every time the app boots and starts it will write Hello, World!
. We’re going to use the input to add words to this list.
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent" android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin" tools:context=".MainActivity">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/editText"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true" />
<Button
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/txtBtnSend"
android:id="@+id/button"
android:layout_below="@+id/editText"
android:layout_centerHorizontal="true" />
</RelativeLayout>
That is an XML file. Writing to the database needs to work with a reference. There’s a root reference, and there’s a reference for each one of the available children. We’re going to write using said value, and or push. Why is that? Because said value only writes the value to the node, and push also generates the unique identifier.
public class MainActivity extends AppCompatActivity {
private FirebaseDatabase database;
private String MESSAGE_CHILD = "message";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
database = FirebaseDatabase.getInstance();
DatabaseReference messageReference =
database.getReference().child(MESSAGE_CHILD);
messageReference.setValue("Hello, World!");
...
}
}
We first need a reference. We’re going to define a couple of variables here. One, a reference to the database. Another one for the node that I’m working with, that’s message
. Usually, you do this at a singleton. I’m doing this in the Activity
because there’s only one Java file that we’re going to have here. We get an instance for the database, get a reference for the MESSAGE_CHILD
, and you set a value to write with. And as easy as that we can write to a database. It will look like this:
public class MainActivity extends AppCompatActivity {
private FirebaseDatabase database;
private String WORDS_CHILD = "words";
@Override
protected void onCreate(Bundle savedInstanceState) {
...
final Button btn = (Button)findViewById(R.id.button);
final EditText editText = (EditText)findViewById(R.id.editText);
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
DatabaseReference newElementReference =
database.getReference().child(WORDS_CHILD).push();
newElementReference.setValue(editText.getText().toString());
editText.setText("");
}
});
}
}
For the other one, we need more things. We have a Button
, an EditText
, and a listener
. We define a different child, in this case, it’s words
. On the onClick
event we’re going to grab the reference to this words
child and setValue
. Notice that on getting the reference, I’m also calling push
at the far right of that line of code. That will allow me to write to different children. Each one with its own unique identifier. Let’s see this in Android Studio. By the way, I know that I forgot to mention we’re going to need a file that’s generated by the console. That is a JSON file that holds our credentials. It contains several different references to the project, so we need to add these to our project in Android Studio. I’m going to execute this and go to the console. Here we can see the database and the emulator. When the app started, this new node of data appears and now I can write anything here, and each one of the messages will appear as a new node under the words
node. I can write anything, and they will keep appearing. That is the unique identifier that I mentioned before. To retrieve the data we use a reference and add a listener. We can also query the database. It’s getting only one value, but it’s more common to use, like, subscriptions, as I mentioned, listeners for changes. These listeners could be for either node and older children or for a new specific value. Here I have a showMessage
method that will help me for that.
private static final String TAG =
MainActivity.class.getSimpleName();
public void showMessage(String msg) {
Toast.makeText(MainActivity.this, msg, Toast.LENGTH_SHORT).show();
Log.d(TAG, "Value is: " + msg);
}
And here is the listener.
@Override
protected void onCreate(Bundle savedInstanceState) {
...
messageReference.addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(DataSnapshot snapshot) {
if (snapshot.getValue() != null) {
String value = snapshot.getValue(String.class);
showMessage(value);
}
}
@Override
public void onCancelled(DatabaseError error) {
Log.w(TAG, "Failed to read value.", error.toException());
}
});
}
I’m using the message reference that I’ve set before and added in listener
for a value event. When the data changes, I’m going to check that I am indeed getting something from the database. Then we will parse that to a string value and call the showMessage
method that writes both at Toast
and also to the log
. If anything happens and the request is canceled, we’re going to show an error message on the log. These two methods are mandatory under the `ValueEventListener’, and for the child of event listener is something similar.
@Override
protected void onCreate(Bundle savedInstanceState) {
...
DatabaseReference wordsReference =
database.getReference().child(WORDS_CHILD);
wordsReference.addChildEventListener(new ChildEventListener() {
@Override
public void onChildAdded(DataSnapshot dataSnapshot, String s) {
showMessage(dataSnapshot.getValue().toString());
}
...
});
}
We have different methods for different events. We’re going to work with it onChildAdded
. This method is called every time the app starts, and there is an established connection. It’s going to grab all the children under that reference and every time that a child is added this method gets called. We define the reference for words
and use this method to get the data.
@Override
protected void onCreate(Bundle savedInstanceState) {
...
wordsReference.addChildEventListener(new ChildEventListener() {
...
@Override
public void onChildChanged(DataSnapshot dataSnapshot, String s) {}
@Override
public void onChildRemoved(DataSnapshot dataSnapshot) {}
@Override
public void onChildMoved(DataSnapshot dataSnapshot, String s) {}
@Override
public void onCancelled(DatabaseError databaseError) {
Log.w(TAG, "Failed to read value.", databaseError.toException());
}
});
}
There are other methods for different kind of events, for example, when a child node changes, or gets removed, or moves to a different node. Or like in the previous case, when the request is canceled. That’s Firebase. That’s the database. That’s working with building blocks like Lego bricks, without instructions.
To-Do list app (21:24)
But also there are these shiny Lego sets where we can follow a few steps and build something pretty nice. Now, we’re going to build a to-do list app. We want it to look like this on the left-hand side, and the data is going to look like the right-hand side. Notice that we have the unique identifier and, under each one, we have three fields. We have the username of the user that’s creating the new item, the item itself, and if it’s completed or not. We’re going to add a way to make it completed, make it finish with a click, and delete the item with a long click.
Before diving into the Gradle file, it’s important to separate two things here. One is how you work with Firebase. The other one is that the app itself includes a lot of my ideas on how to implement a to-do list app. When we are learning something new, sometimes we struggle with the specifics of the person who wrote the tutorial, video, book or whatever we’re learning from. How to store and retrieve data was the first part of the talk. Now we are going to add a little bit of another thing to make an app. A simpler one, but an app. At the end of the day, it includes a lot of my ideas on how to handle it, and they might not be the best ones for you. Here’s our Gradle file:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.1.1'
compile 'com.android.support:design:24.1.1'
compile 'com.android.support:recyclerview-v7:24.1.1'
apt 'com.jakewharton:butterknife-compiler:8.2.1'
compile 'com.jakewharton:butterknife:8.2.1'
compile 'com.google.firebase:firebase-database:9.0.2'
compile 'com.firebaseui:firebase-ui-database:0.4.3'
}
I’m working with ButterKnife. I’m working with the database. Also, I will be working with FirebaseUI, mostly because for this kind of app we need a way to show the data we have stored in our database. That’s usually a ListView
or a RecyclerView
. I’m going to use a RecyclerView
and FirebaseUI is going to handle all the changes to the RecyclerView
made by the data. Adding new data, updating the data, removing the data. All this is going to be handle by FirebaseUI. A couple of things about the layout. We have our RecyclerView
. We have an EditText
and a TextInputLayout
wrapping it. We have a FloatingActionButton
. The RecyclerView
needs a layout for each one of the rows. We have here a TextView
with a large text, another TextView
, and on the right-hand side is an ImageView
that will show you a green check when the item has completed.
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_vertical_margin"
android:paddingTop="@dimen/activity_vertical_margin"
android:paddingBottom="@dimen/activity_vertical_margin"
tools:context=".activities.MainActivity">
<android.support.v7.widget.RecyclerView
android:id="@+id/recycler_view_items"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="2"
android:scrollbars="vertical"
android:layout_above="@+id/txtInputWrapper" />
...
</RelativeLayout>
Here is a relative layout with the RecyclerView
. Then the TextInputLayout
and the EditText
and the FloatingActionButton
.
<RelativeLayout >
<android.support.design.widget.TextInputLayout
android:id="@+id/txtInputWrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentBottom="true"
android:layout_toLeftOf="@+id/fab"
android:layout_toStartOf="@+id/fab">
<EditText
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="@string/main.message.addelement"
android:id="@+id/editTextItem" />
</android.support.design.widget.TextInputLayout>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/ic_add_white_36dp"
android:layout_alignBottom="@+id/txtInputWrapper"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true" />
</RelativeLayout>
All for the main activity layout.
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_margin="4dp"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="@dimen/activity_horizontal_margin"
android:layout_weight="1">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceLarge"
android:text="Large Text"
android:id="@+id/txtItem" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="?android:attr/textAppearanceMedium"
android:text="Medium Text"
android:id="@+id/txtUser" />
</LinearLayout>
Also, the row that includes our linear layout. Then another one for the TextView
to have the item stack.
<LinearLayout>
...
<ImageView
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:id="@+id/imgDone"
android:src="@drawable/ic_done_black_36dp"
android:visibility="invisible"
/>
</LinearLayout>
Finally, the ImageView
, will show when the element is done. That is our layout. We need a model to work with this. That is a simple plain old Java object. It’s not mandatory to do it when using Firebase, but it makes things easier when we’re using FirebaseUI, in which case it is mandatory. Our model is going to include these three elements: the item itself, the username and if it’s finished or not.
public class ToDoItem {
private String item;
private String username;
private boolean completed;
public ToDoItem(){ }
public ToDoItem(String item, String username) {
this.username = username;
this.item = item;
this.completed = false;
}
...
//getters & setters
}
We have an empty constructor for Firebase to use JSON and serialize the data, the JSON data into a Java object. We also have another constructor for us, for building items. There are also getters and setters for the three fields that we have. We’re going to be working with an application class to store the references. We also need RecyclerView
and the RecyclerView
needs a RecyclerAdapter
. That is going to work with FirebaseUI, so we need to define a ViewHolder
. Hopefully, you are familiar with RecyclerViews
. If not, I’m going to try to explain it in a way that everyone can understand. Then we’re going to work with the MainActivity
.
public class ToDoApplication extends Application {
private String ITEMS_CHILD_NAME = "items";
private DatabaseReference itemsReference;
@Override
public void onCreate() {
super.onCreate();
FirebaseDatabase database = FirebaseDatabase.getInstance();
database.setPersistenceEnabled(true);
itemsReference = database.getReference(ITEMS_CHILD_NAME);
}
public DatabaseReference getItemsReference() {
return itemsReference;
}
}
First, the singleton or application class. We have a node called items, and it is referenced
onCreate. We set up the database, just like before, but now we have another feature. We're calling the meta
setPersistentEnabled to allow caching and offline support. It seems a little bit strange at first, but that line of code
setPersistentEnabled set to true allows us to work with data offline. The Firebase library itself stores the data and eventually, when there is a connection available, either Wi-Fi or data, it will send whatever it stored. There is one small caveat. If the application closes, all the data we have will not synchronize. Then we have the items reference, and we have a getter for it. We don't need a setter. We are initializing on the
onCreate` method.
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.bitandik.labs.todolist">
<uses-permission android:name="android.permission.INTERNET" />
<application
android:name=".ToDoApplication"
...>
</application>
</manifest>
The manifest
, much like the first app that we saw, needs the Internet permission. Also, we’re specifying the name of the app to have the application class.
Firebase UI library (28:06)
The FirebaseUI library needs to inherit from the FirebaseRecyclerAdapter
and implement the populateViewHolder
method. We’re going to implement two methods. Also, we’re going to implement onCreateViewHolder
. That is because the library is expecting a static ViewHolder
, but as we are calling non-static methods for the click handling. The ViewHolder
can’t be a static. And if we don’t override the onCreateViewHolder
there will be a crash, and it will compile, but it will not run. Here’s the ViewHolder
:
class ToDoItemViewHolder extends RecyclerView.ViewHolder {
@BindView(R.id.txtItem) TextView txtItem;
@BindView(R.id.txtUser) TextView txtUser;
@BindView(R.id.imgDone) ImageView imgDone;
public ToDoItemViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
}
}
We have the TextView
s and the ImageView
; we’re using ButterKnife
for the binding.
class ToDoItemViewHolder extends RecyclerView.ViewHolder
implements View.OnClickListener,
View.OnLongClickListener {
public ToDoItemViewHolder(View itemView) {
...
itemView.setOnClickListener(this);
itemView.setOnLongClickListener(this);
}
}
We’re setting the click handling.
@Override
public void onClick(View view) {
int position = getAdapterPosition();
ToDoItem currentItem = (ToDoItem)getItem(position);
DatabaseReference reference = getRef(position);
boolean completed = !currentItem.isCompleted();
currentItem.setCompleted(completed);
Map<String, Object> updates = new HashMap<String, Object>();
updates.put("completed", completed);
reference.updateChildren(updates);
}
When there is a single click, we’re going to grab the adapter position. Grab the item with that position and get the reference with a method that’s from the FirebaseUI library. With getRef
, using the position, we get the Firebase reference. This is quite useful because now we can write to it when there is a click on each one of the elements. The way to do it is, we change the POJO. In this case, we’re only changing the finish field, then building a HashMap
with the key being the key also in Firebase and the value, the new value. Finally, we call under the reference that we got from the position of the updateChildren
.
@Override
public boolean onLongClick(View view) {
int position = getAdapterPosition();
DatabaseReference reference = getRef(position);
reference.removeValue();
return true;
}
onLongClick
it’s something similar. We’re calling removeValue
, and this will allow us to remove and update elements.
public class ToDoItemsRecyclerAdapter extends
FirebaseRecyclerAdapter<ToDoItem,
ToDoItemsRecyclerAdapter.ToDoItemViewHolder> {
public ToDoItemsRecyclerAdapter(int modelLayout, DatabaseReference ref) {
super(ToDoItem.class, modelLayout,
ToDoItemsRecyclerAdapter.ToDoItemViewHolder.class, ref);
}
@Override
public ToDoItemViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
ViewGroup view = (ViewGroup)
LayoutInflater.from(parent.getContext())
.inflate(mModelLayout, parent, false);
return new ToDoItemViewHolder(view);
}
...
}
Now the RecyclerAdapter
. There is a constructor that calls the super class and here’s the method that I mentioned onCreateViewHolder
that needs to be implemented, because we are using non-static methods for the click handling. The adapter is a call to the super to the main class. This one is inherited, and this is a method that we need to inflate using the layout for the model.
@Override
protected void populateViewHolder(ToDoItemViewHolder holder,
ToDoItem item, int position) {
String itemDescription = item.getItem();
String username = item.getUsername();
holder.txtItem.setText(itemDescription);
holder.txtUser.setText(username);
if (item.isCompleted()) {
holder.imgDone.setVisibility(View.VISIBLE);
} else {
holder.imgDone.setVisibility(View.INVISIBLE);
}
}
For the populateViewHolder
, that’s the method that you will always be needing to implement when using the FirebaseUI library. We use the elements previously defined: the description, the username, and if it is completed or not. If it’s not the visibility is set to INVISIBLE
, so it will not show anything. For this part we’re only using get item, get username from the model and setting that to the text fields. For this, we’re just checking if it’s completed or not. That’s the adapter. For the main activity, we’re going to set up a username, set up the RecyclerView
and add items when the button is clicked.
public class MainActivity extends AppCompatActivity {
@BindView(R.id.recycler_view_items) RecyclerView recyclerView;
@BindView(R.id.editTextItem) EditText editTextItem;
private DatabaseReference databaseReference;
private FirebaseRecyclerAdapter adapter;
...
}
We use the application class previously defined for the reference.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ButterKnife.bind(this);
setupUsername();
SharedPreferences prefs = getApplication().getSharedPreferences("ToDoPrefs", 0);
String username = prefs.getString("username", null);
setTitle(username);
ToDoApplication app = (ToDoApplication)getApplicationContext();
databaseReference = app.getItemsReference();
adapter = new ToDoItemsRecyclerAdapter(R.layout.row, databaseReference);
recyclerView.setLayoutManager(new LinearLayoutManager(this));
recyclerView.setAdapter(adapter);
}
Here we’re just setting the things for the activity, binding with ButterKnife
. Here we’re setting up the username. How do we do this?
private void setupUsername() {
SharedPreferences prefs =
getApplication().getSharedPreferences("ToDoPrefs", 0);
String username = prefs.getString("username", null);
if (username == null) {
Random r = new Random();
username = "AndroidUser" + r.nextInt(100000);
prefs.edit().putString("username", username).commit();
}
}
We’re just generating a random number and adding that under User. For now, that will be the way the users identify and using the application class we’re going to grab the items reference, use that for the adapter, and the adapter for the RecyclerView
.
@OnClick(R.id.fab)
public void addToDoItem() {
SharedPreferences prefs = getApplication().getSharedPreferences("ToDoPrefs", 0);
String username = prefs.getString("username", null);
String itemText = editTextItem.getText().toString();
editTextItem.setText("");
InputMethodManager inputMethodManager =
(InputMethodManager) getSystemService(Activity.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
if (!itemText.isEmpty()) {
ToDoItem toDoItem = new ToDoItem(itemText.trim(), username);
databaseReference.push().setValue(toDoItem);
}
}
As you see, we have the RecyclerView
set up. For writing to the database, we use ButterKnife
to handle the OnClick
event. We grab the username from SharedPreference
, grab the text that the user enters, hide the keyboard, and write to the database. That’s it.
Adding auth (33:41)
Now we’re going to add authentication using FirebaseUI. What need to do is enable the providers that we want to work with, in this case, we want to work with Facebook, Google and email password.
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:24.1.1'
compile 'com.android.support:design:24.1.1'
compile 'com.android.support:recyclerview-v7:24.1.1'
apt 'com.jakewharton:butterknife-compiler:8.2.1'
compile 'com.jakewharton:butterknife:8.2.1'
compile 'com.google.firebase:firebase-database:9.0.2'
compile 'com.firebaseui:firebase-ui-database:0.4.3'
}
We change a little bit of our Gradle file. Instead of using firebase-ui-database
, we only use firebase-ui
. That includes auth and database.
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'),
'proguard-rules.pro'
resValue "string", "facebook_application_id",
project.property('facebook_application_id')
}
debug {
resValue "string", "facebook_application_id",
project.property('facebook_application_id')
}
}
Also, we’re going to add the Facebook application ID. In this case, I’m reading from the Gradle properties file, where I have defined the application ID.
facebook_application_id=1234567890123456
I also need a Facebook application. Follow their setup and wizard and everything. Once I have the application ID, that’s all I’m going to be needing.
public class ToDoApplication extends Application {
private String ITEMS_CHILD_NAME = "items";
private DatabaseReference itemsReference;
private FirebaseAuth auth;
@Override
public void onCreate() {
super.onCreate();
FirebaseDatabase database = FirebaseDatabase.getInstance();
database.setPersistenceEnabled(true);
itemsReference = database.getReference(ITEMS_CHILD_NAME);
auth = FirebaseAuth.getInstance();
}
//getters
}
Now on to the singleton. On the application class, we’re going to add another element. It’s a Firebase auth reference, a Firebase auth object. Much like the database, we initialize this with a getInstance
, and we’re going to be needing a new activity, the LoginActivity
.
public class LoginActivity extends AppCompatActivity {
private FirebaseAuth auth;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
ToDoApplication app = (ToDoApplication)getApplicationContext();
auth = app.getAuth();
doLogin();
}
...
}
We grab the Firebase auth reference that we just defined on the application class and use a method:
private void doLogin() {
FirebaseUser currentUser = auth.getCurrentUser();
if (currentUser != null) {
String username = currentUser.getDisplayName();
SharedPreferences prefs = getApplication().getSharedPreferences("ToDoPrefs",
0);
prefs.edit().putString("username", username).commit();
Intent i = new Intent(this, MainActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP
| Intent.FLAG_ACTIVITY_NEW_TASK
| Intent.FLAG_ACTIVITY_CLEAR_TASK);
startActivity(i);
} else {
...
}
}
In this case, I named the method doLogin
. In this method, I’m trying to grab the current user that’s authenticated. In case data references exist and it’s not equal to null, I’m going to grab the username and write that to SharedPreference
instead of generating a new random number for the username and start the new activity. Because I don’t want the activity to be on the stack, when the user hits the Back
button it doesn’t show him the activity where the log in is being done.
private void doLogin() {
FirebaseUser currentUser = auth.getCurrentUser();
if (currentUser != null) {
...
} else {
startActivityForResult(
AuthUI.getInstance()
.createSignInIntentBuilder()
.setProviders(
AuthUI.EMAIL_PROVIDER,
AuthUI.GOOGLE_PROVIDER,
AuthUI.FACEBOOK_PROVIDER)
.build(),
RC_SIGN_IN);
}
}
If there is no user, we’re going to startActivityForResult
using AuthUI and the providers that we want to use for our app. And as simple as that we have authentication configure. That is all that we need.
@Override
public void onActivityResult(int requestCode,
int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == RC_SIGN_IN) {
if (resultCode == RESULT_OK) {
doLogin();
finish();
}
}
}
When the activity is over it’s going to check that indeed it’s a sign in and the result is okay. If it’s completed correctly, we’re going to call the doLogin' method again. In this case, the user will be not equal to
null`, and it will take us to the next screen. So, you can see in the bar that the name of the account that I just created. I’m going to add a new item, test item. Now, here in the database, is the new item with the new username. And now I have the option to log out. I’m going to choose here “None of the Above”, and use a different provider.
Let’s try Facebook now. It tells me that I already have an account. Now in the console, we can see on auth that the email is associated with the providers from Google and from Facebook. It’s really nice that Firebase does that for us. It takes care of a lot of the work dealing with both the infrastructure and backend.
Also, security rules. Anyone can change the values of any other users. If we want to restrict this for every user to just be able to change their data, we need to add a new node, including the owner, storing the UID. And in the security rules, we’re going to add a couple of things. We have the rules like the root element, another element for the items, and we have a wildcard indicating the item ID. With this I’m referencing each one of the items. I could use this for the unique identifier generator of Firebase. I check that auth is a value. wWth the write I need to check that, indeed, there is an authenticated user, but also there might be two cases: one is that it’s new data, so the data doesn’t exist before. The other one is that the data exists, but I want to check that the user ID of the data that’s being written to, actually the data it’s already in the database. I’m using this node, user ID; it’s equal to the UID of the authenticated user. And also I’m validating that the new data. That the data I’m trying to write to the database has the child user ID.
Conclusion (41:56)
These are the Lego blocks. These are the building blocks that we’re going to use. I think Firebase its a really nice tool that provides a lot of different features. We can choose what to use and what to ignore or use in the future, and eventually, I think it’s going to grow even bigger.
The other day someone told me that in the New Question in the Stack Overflow, one out of four, or something like that, were on Android, and related to Firebase. A lot of people are using it. I went to a hackathon in Mexico about a month ago, there were around 30 teams and about 23 or 24 used Firebase. It’s nice to know what are the tools available to us and how to work with them.
I want to end the presentation telling you a little bit about what I do. As I mentioned, I’m from Guatemala. One of the many reasons that I wanted to be here was to encourage more people from Latin America to speak at conferences, both in the US and Europe. I do believe there is the same talent everywhere. In every part of the world. But there is a big difference with the skills. There is a gap that a lot of people are working to reduce or close. A gap between talent and skills. The context of working with several talented developers enriches you very much.
On that effort, I started an MOOC, working with the university where I work, Galileo, EdX, MIT and the Harvard platform. We had around 26,000 students. For me, it was a really nice experience, very humbling having that many people. To my knowledge, it’s the first one in Spanish that includes subjects like architecture, MBP, clean, unit testing, dependency injection, and common libraries like Retrofit and ButterKnife. We need to get more people into making better quality Android apps. As I mentioned, I’m really happy to be here, and honored to be in a conference that has so many talented speakers. I would like to encourage everyone to keep growing this amazing Android community. I think it’s very diverse. It’s very inclusive. Let’s keep building this nice community together. Thank you.
Receive news and updates from Realm straight to your inbox