A few days ago I started developing the simplest chat for Android, I decided to use Firebase - an easy-to-use real-time database that stores my data in JSON format. Although Firebase provides a full API and documentation for use, I found that it lacked the details when trying to apply it to an architecture pattern, such as MVP, so I decided to try and explain how I understood the Android implementation. I also expanded the MVP pattern with a new layer, obviously for Firebase - Interactors.
Creating a basic Firebase template
I will not go into details on creating an account in Firebase, as well as on the “5-minute quick launch”, I’ll just go straight to the implementation.
First we consider which directories we need in our Firebase template, for example - Firebase creates users in a separate database, and when creating stored information, this is an email, a password (which you cannot directly observe) and a unique UID (randomly generated key, which the user adheres to throughout his life cycle), therefore, if we want to save the user name, we would not have succeeded. This is why we need “Users” as a directory in our template, which will contain the username and, possibly, the avatar so that we can store certain specific information.
We may also have a directory called curentUsers, which will contain all the users who are currently logged into our chat application. We definitely need a “Messages” folder to store our messages.
So our three directories are Users, currentUsers, Messages ...
')
Links to them are as follows:
"Https: // <your-firebase> / currentUsers /"
"Https: // <your-firebase> / Users /"
"Https: // <your-firebase> / messages /"
These are links to directories that we use when we want to add / extract data, and basically everything we need for the system of users and messages.
Let's turn to a real conversation with Android. If you imported a Firebase dependency into Gradle, you should have all the features of the Firebase client available ... In our chat application there will be four screens:
- Main screen for selecting the login option (Login or Register) and display the number of registered users
- Login for user authentication
- Registration screen in which we create a new “Users - Chat” screen (which can display a chat snippet or a ListOfUsers snippet)
Main screen
Here we are looking for the entry point that the user wants (to register or log in), and display the number of current users in TextView.
MainActivityPresenter :
public class MainActivityPresenterImpl implements MainPresenter { private final MainView mainView; private final MainInteractor interactor; public MainActivityPresenterImpl(MainView view) { this.mainView = view; interactor = new MainInteractor(this); } @Override public void receiveRequest() { interactor.receiveRequest(); } @Override public String getNumberOfUsers(long numberOfUsers) { return "Online users: " + String.valueOf(numberOfUsers); } @Override public void sendNumberOfChildren(long number) { mainView.setNumberOfUsersTextView(getNumberOfUsers(number)); } }
The MainInteractor:
public class MainInteractor implements MInteractor { private final Firebase mainRef = new Firebase("https://<your-firebase>/currentUsers"); private final MainPresenter presenter; public MainInteractor(MainPresenter pre) { this.presenter = pre; } @Override public void receiveRequest() { mainRef.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot dataSnapshot) { presenter.sendNumberOfChildren(dataSnapshot.getChildrenCount()); }); } }
What's going on here? In the interaction, we have a Firebase link, whose parameter constructor is a link (currentUsers directory), and we add a listener to a link that sends one request to the currentUsers directory and receives DataSnapshot — a special Firebase function ... The snapshot is essentially a list of all objects data in the specified directory, so if we make dataSnapshot.getChildrenCount (), we simply get the number of objects currently in the directory, which is equal to the number of users online! We show it in TextView, and the user sees how many of his peers are online. Pretty simple, but powerful, as we use this principle of data queries in all aspects of communication with our Firebase.
Registration screen
We saw the code for the main screen in the previous section, but here’s how it looks. In addition, by pressing Register, we will go through a three-step process, first select a Username, which, if it appears, displays an error, otherwise we will go to the Emoji fragment, in which we will select our own avatar, then we’ll go to the detailed account a screen where we complete our registration if no email is sent, in which case we also get an error, so here are the screens:

We have several simple EditTexts, one for the Username, one for the Email, and one for the Password. A grid of emoji to choose from (currently one line will add more) and a progress bar for displaying the rotation animation during authentication. The "Register" button takes the values ​​combined into "Fragments" and sends them to the presenter:
public class FirebaseUserRegisterPresenterImpl implements FirebaseUserRegisterPresenter { private final RegisterView registerView; private final RegisterInteractor interactor; public FirebaseUserRegisterPresenterImpl(RegisterView view) { this.registerView = view; this.interactor = new RegisterInteractor(this); } @Override public void receiveRegisterRequest(String username, String email, String password, String emoji) { interactor.receiveRegisterRequest(username, email, password, emoji); registerView.spinProgressBar(); } @Override public void onFailure() { registerView.onFailure(); registerView.stopProgressBar(); } @Override public void onSuccess() { registerView.onSuccess(); registerView.stopProgressBar(); } }
Interactor:
public class RegisterInteractor implements RInteractor { private Firebase userRef = new Firebase("https://<your-firebase>/Users/"); private final FirebaseUserRegisterPresenter presenter; public RegisterInteractor(FirebaseUserRegisterPresenter pre) { this.presenter = pre; } @Override public void receiveRegisterRequest(final String username, String email, String password, final String emoji) { userRef.createUser(email, password, new Firebase.ValueResultHandler<Map<String, Object>>() { @Override public void onSuccess(Map<String, Object> stringObjectMap) { String uid = stringObjectMap.get("uid").toString(); userRef = new Firebase("https://<your-firebase>/Users/" + uid); userRef.setValue(createUser(username, emoji)); presenter.onSuccess(); } @Override public void onError(FirebaseError firebaseError) { presenter.onFailure(); } }); } @Override public Map<String, Object> createUser(String username, String emoji) { Map<String, Object> user = new HashMap<>(); user.put("username", username); user.put("emoji", emoji); return user; } }
Here we have several new features:
- .createUser (), .push () and .setValue () methods
- user UID
.createUser () - creates users! In a separate database, so when we create a user, we also need to create his object in the / Users directory (to view it).
This is done by pressing "Pushing". The specified .push () “pushes” deeper into the directory, creating a subdirectory with a randomly generated key for its name, but before that we attach the UID to the link, so we can compare the directories with the users UID. The UID is a randomly generated key and, using it as the name of a subdirectory (and a parameter in the User object), we can later determine which username corresponds to a specific UID and get the username after logging in or even delete Child of currentUsers (displays user from the system).
The .setValue () method adds an object (or objects) to a directory, so we can simply store any data we want.
Login screen
The interface of the login screen is fairly simple: two EditTexts (email address and password) and a login button, as well as a progress bar, to revitalize the process a bit.
What happens when a user clicks Login?
This part is complicated, we know that our users are in a separate database, so when we register a user, how do we know which user name he or she is using?
Here is the whole purpose of the / Users directory, as mentioned earlier. Also, calling it after the user UID, we can simply search for a directory with the corresponding UID (if, for example, we want to extrapolate certain pieces of information from a specific user). Also, if we call the objects UID, we can enter an object with the specified UID and delete it in onTestroy () chat activity - a very simple way to register a user.
Login Presenter:
public class FirebaseLoginPresenterImpl implements FirebaseLoginPresenter { private final LoginView loginView; private final LoginInteractor interactor; public FirebaseLoginPresenterImpl(LoginView view) { this.loginView = view; interactor = new LoginInteractor(this); } @Override public void receiveUserLogin(String email, String password) { loginView.spinProgressBar(); interactor.attemptToLogIn(email, password); } @Override public void onFailure() { loginView.stopProgressBar(); loginView.onFailure(); } @Override public void onSuccess(String user, String uid) { loginView.stopProgressBar(); loginView.logTheUserIn(user, uid); } }
He receives an email and password, shows a scroll bar until the request is completed, and calls the “View” methods, given the result:
- A successful login sends the username of the user, and the UID sends the intent to the ChatActivity login — Failed warns the user with Toast.
If user authentication is successful, we get the username for the specified user and send it to the chat screen, but before that we add the user to the / currentUsers directory so that we can just see who is logged in. AuthData is obtained by default and is used to display some specific Firebase user data (for example, UID, special key generated by authentication ..)
Chat screen
ChatActivity uses 2 snippets, one for messaging services, and one to display a list of active users. By clicking on the menu menu icon once, we replace the message fragment with a list fragment, and by clicking it again, we will set BackStack (and come back!).

The problem here is that we get all our data from Firebase, that is, we cannot implement Firebase in our views, but the ListView / RecyclerView adapters are also components of the Android View, so how do we continue here?
Once again, the answer is MVP (+ Interactors)! A good architecture reflects itself in the components that it implements, which means that we can also write our adapters to MVP, which are a “View” component, in which there is a presenter who sends new values ​​to the ListView elements (and requests the specified values ​​from Interactor) Because the values ​​are generated by an Interactor that has a link to Firebase - we can separate Android from Java - from Backend.
Adapter
:
public class CustomMessageRecyclerAdapter extends RecyclerView.Adapter<CustomMessageRecyclerAdapter.ViewHolder> implements MessageAdapterView { private final ArrayList<Message> mMessageList = new ArrayList<>(); private final String user; private final MessagePresenterImpl presenter; public CustomMessageRecyclerAdapter(String username) { this.user = username; presenter = new MessagePresenterImpl(this); } @Override public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.chat_message, parent, false); return new ViewHolder(v); } @Override public void onBindViewHolder(ViewHolder holder, int position) { Message current = mMessageList.get(position); if (current.getAuthor().equals(user)) { holder.mAuthorTextView.setText("You"); } else { holder.mAuthorTextView.setText(current.getAuthor()); } holder.mMessageTextView.setText(current.getMessage()); holder.mEmojiTextView.setText(current.getEmoji()); } @Override public int getItemCount() { return mMessageList.size(); } @Override public void addItem(Message message) { mMessageList.add(message); notifyDataSetChanged(); } @Override public void request() { presenter.requestMessages(); } public class ViewHolder extends RecyclerView.ViewHolder { private TextView mAuthorTextView; private TextView mMessageTextView; private TextView mEmojiTextView; public ViewHolder(View itemView) { super(itemView); mAuthorTextView = (TextView) itemView.findViewById(R.id.message_author); mMessageTextView = (TextView) itemView.findViewById(R.id.message_value); mEmojiTextView = (TextView) itemView.findViewById(R.id.message_emoji); } } }
It's very simple, we have a method that inflates our ViewHolder, which fills the holder, a method for requesting messages from Firebase and one that adds a message to an ArrayList if there is a new message to display.
Presenter:
public class MessagePresenterImpl implements MessagePresenter { private final MessageAdapterView adapterView; private final MessageInteractor interactor; public MessagePresenterImpl(MessageAdapterView view) { this.adapterView = view; this.interactor = new MessageInteractor(this); } @Override public void sendMessageToAdapter(Message message) { adapterView.addItem(message); } @Override public void requestMessages() { interactor.request(); } }
Interactor :
public class MessageInteractor { private final MessagePresenter presenter; private final Firebase mMessagesRef = new Firebase("https://<your-firebase>/messages"); private final Query mMessageQuery; public MessageInteractor(MessagePresenter pre) { this.presenter = pre; this.mMessageQuery = mMessagesRef.orderByValue().limitToLast(100); } public void request() { mMessageQuery.addChildEventListener(new ChildEventListener() { @Override public void onChildAdded(DataSnapshot dataSnapshot, String s) { presenter.sendMessageToAdapter(dataSnapshot.getValue(Message.class)); }
The adapter needs a new message, it tells Presenter to request messages, but this is not Presenter's work, so it tells Interactor to request them from Firebase, having done this, we have a clean structure and data stream that is completely independent, so by changing the view, we don’t you need to change everything, we just set up data POJO, speakers and integrators do not need to change what they do, requests remain the same! Therefore, if we switch the amount of data, just add more fields to the POJO, if we want to display them differently, just change the view (by adding more widgets).
A request simply means a request, .orderByValue () means that we receive objects (values) there, .limitToLast (100) means that we always receive the last 100 messages. Although if the chat is active for some time, all messages (even after 100) will be displayed until the message fragment is destroyed / restarted.
Also in our onDestroy ChatActivity, we send the UID of the integrator (through the Presentator) to remove the user from currentUsers (log off).
public class ChatLoginInteractor implements CLoginInteractor { @Override public void logTheUserOut(String uid) { Firebase userRef = new Firebase("https://<your-firebase>/currentUsers/" + uid); userRef.removeValue();
How it works, step by step .
The Firebase for Android library is very well built, the documentation is a bit difficult to understand, but the basics are easy to get if you dig and try to combine things.
- A link to Firebase is simply a link to the directory in which you want to make changes, requests or just add new data.
Listeners give us “Rx-like” functions, they constantly monitor the addition of new users (each object in the directory is a child), and we can work with their data.
DataSnapshot is a list of current values ​​in one directory.
AuthData is similar to the Bundle of all data for a specific user / request, UID, unique key ...
- Firebase uses Jackson parsing, so your POJO needs empty constructors, and setters to generate
- you really don't need special REST clients, since the DataSnapshot function can perform all data parsing using .getValue (POJO.class)
- In real time ... All requests and clicks on Firebase are extremely fast, since all data is formatted as JSON objects.
Before using any links to your Firebase, you must call Firebase.setAndroidContext (this) in each onCreate () method
Conclusion :
Firebase is an extremely powerful tool for simple Backend databases, it is very fast and easy to use on small projects, but can be used even for more complex applications like this chat application.
This is a cross platform, so you can create Firebase applications for Android, iOS and JS with full support (JS supports Angular, React and Node, I think) and use one Firebase template on all three main platforms.
PS I do not guarantee full performance for a long time, since FireBase is constantly updated and changed. Making multiple edits will change the situation.