Android chat app using Firebase
Learn how to create an Android chat app using Firebase, a powerful and easy-to-use platform for mobile and web app development. Stay connected with others in real-time for personal and professional communication.
Table of Contents
Prerequisites
- Basic knowledge of Android app development.
- Android Studio installed on your system.
- A Firebase account and a project created on the Firebase Console.
Designing SignUp/Login screen – Android Activity
I am currently using the colors below. However, you can use any color you prefer by adding or changing it in the color.xml file.
open activity_main.xml file and add the cardview in it having below attributes.
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="30dp"
app:cardBackgroundColor="@color/black_transparent"
app:cardCornerRadius="30dp"
android:elevation="50dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
In this cardview add linearlayout with bellow attribute.
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:orientation="vertical"
android:padding="25dp"
android:background="@color/black_transparent"
tools:layout_editor_absoluteX="26dp"
tools:layout_editor_absoluteY="246dp">
Add TextView having below attributes in this linearlayout to show the Login text.
<TextView
android:id="@+id/loginText"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:text="Login"
android:textAlignment="center"
android:textSize="36sp"
android:textStyle="bold" />
Add an EditText to this LinearLayout with the following attributes in order to obtain the username.
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="40dp"
android:background="@drawable/custom_edittext"
android:drawableLeft="@drawable/ic_baseline_person_24"
android:drawablePadding="8dp"
android:hint="Username"
android:textColorHint="@color/white"
android:padding="8dp"
android:textColor="@color/white" />
Create custom_edittext.xml file in the drawable folder.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">
<stroke
android:width="3dp"
android:color="@color/colorPrimary"
/>
<corners android:radius="20dp"/>
</shape>
To get the email.
<EditText
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="40dp"
android:background="@drawable/custom_edittext"
android:drawableLeft="@drawable/ic_baseline_email_24"
android:drawablePadding="8dp"
android:hint="Email"
android:textColorHint="@color/white"
android:padding="8dp"
android:textColor="@color/white" />
And for the password.
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="50dp"
android:layout_marginTop="40dp"
android:background="@drawable/custom_edittext"
android:drawableLeft="@drawable/ic_baseline_lock_24"
android:drawablePadding="8dp"
android:hint="Password"
android:textColorHint="@color/white"
android:padding="8dp"
android:textColor="@color/white" />
</LinearLayout>
</androidx.cardview.widget.CardView>
linearLayout and cardview are closed here.
In the main layout that is constraintlayout add the signup button.
<Button
android:id="@+id/btnSubmit"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Sign up"
android:textAllCaps="false"
android:background="@drawable/button_shape"
android:layout_marginTop="10dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardView" />
Create button_shape.xml file in the drawable folder.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<corners android:radius="20dp"/>
<solid android:color="@color/black_transparent"/>
<stroke
android:width="3dp"
android:color="@color/colorPrimary"
/>
</shape>
and at the end of this screen add below textview.
<TextView
android:id="@+id/loginInfo"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Already have account? Log in"
android:layout_marginTop="10dp"
android:textColor="@color/white"
app:layout_constraintEnd_toEndOf="@+id/btnSubmit"
app:layout_constraintStart_toStartOf="@+id/btnSubmit"
app:layout_constraintTop_toBottomOf="@+id/btnSubmit" />
Firebase for Android – User Authentication
Before using Firebase authentication, we need to set up Firebase in project.
To setup firebase visit : Add Firebase to your Android project | Firebase for Android (google.com)
To integrate authentication, open Android Studio and navigate to the “Tools” menu. From there, select “Firebase” and then choose “Authentication” and type from the list of available options. You will see the below image when you click on Add the Firebase Authentication SDK to your app.
Click on Accept the changes it will add the required dependency to the gradle file.
To able to authenticate a user, we need to enable authentication in our project through Firebase.
open the project in the Firebase console, go to the Authentication tab, and set up the sign-in methods that you want to use.
Use the below code for the sign-up function.
FirebaseAuth.getInstance().createUserWithEmailAndPassword(edtEmail.getText().toString(), edtPassword.getText().toString()).addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isComplete()) {
FirebaseDatabase.getInstance().getReference("user/" + FirebaseAuth.getInstance().getCurrentUser().getUid()).setValue(new User(edtUsername.getText().toString(), edtEmail.getText().toString(), ""));
launchFriendsActivity();
Toast.makeText(MainActivity.this, "SignUp Successfully", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, task.getException().getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
}
});
Use the below code for the login function.
FirebaseAuth.getInstance().signInWithEmailAndPassword(edtEmail.getText().toString(), edtPassword.getText().toString()).addOnCompleteListener(new OnCompleteListener<AuthResult>() {
@Override
public void onComplete(@NonNull Task<AuthResult> task) {
if (task.isComplete()) {
launchFriendsActivity();
Toast.makeText(MainActivity.this, "Logged in Successfully", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(MainActivity.this, task.getException().getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
}
});
If google-services.json is missing error occurs.
To solve this we need this file and to get this file go to the project setting in the Firebase and download file from there.
Copy this file and place it in an app folder.
Ensure to add internet permission in the manifest for using Firebase functions.
Friends Activity
Friend activity is the screen that is visible after the user logged in.
Create a status bar with an option menu as shown below.
Create a layout for a menu with name profile_menu.xml
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto">
<item android:id="@+id/item_profile" android:icon="@drawable/ic_baseline_person_24"
app:showAsAction="always"
android:title="TODO" />
</menu>
Inflate this layout menu.
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.profile_menu, menu);
return true;
}
Performed the action on click of menu item. That is start ProfileActivity on click of profile icon.
@Override
public boolean onOptionsItemSelected(@NonNull MenuItem item) {
if (item.getItemId() == R.id.item_profile) {
startActivity(new Intent(FriendsActivity.this, ProfileActivity.class));
}
return super.onOptionsItemSelected(item);
}
The current UI is not as per the requirement. The status bar is blue. To meet the requirement, make the status bar transparent by adding the below code in the onCreate method.
setContentView(R.layout.activity_friends);
getWindow().setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
Objects.requireNonNull(getSupportActionBar()).setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT));
getSupportActionBar().setTitle("");
Designing profile activity
Add cardview in activity_profile.xml and in that cardview include imageview.
<androidx.cardview.widget.CardView
android:id="@+id/cardView"
android:layout_width="180dp"
android:layout_height="180dp"
android:layout_marginTop="20dp"
android:layout_marginBottom="394dp"
app:cardBackgroundColor="@color/black_transparent"
app:cardCornerRadius="90dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/txtUserMail"
app:layout_constraintVertical_bias="0.2">
<ImageView
android:id="@+id/profile_img"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_baseline_person_24" />
</androidx.cardview.widget.CardView
app:layout_constraintVertical_bias
is used to control the vertical alignment or position of a view within its parent ConstraintLayout. It is specified as a value between 0.0 and 1.0, where
- 0.0 means the view is aligned with the top edge of its parent.
- 1.0 means the view is aligned with the bottom edge of its parent.
- 0.5 means the view is centered vertically within its parent.
Add textview.
<TextView
android:id="@+id/txtUserMail"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:text="Preview"
android:textColor="@color/black"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
Add the Upload photo and log out button with the required attribute.
<Button
android:id="@+id/btnUploadImg"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="30dp"
android:background="@drawable/button_shape"
android:padding="15dp"
android:text="Upload Photo"
android:textColor="@color/white"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/cardView" />
<Button
android:id="@+id/btnLogOut"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:background="@drawable/button_shape"
android:text="Log out"
android:textColor="@color/white"
app:layout_constraintEnd_toEndOf="@+id/btnUploadImg"
app:layout_constraintHorizontal_bias="0.48"
app:layout_constraintStart_toStartOf="@+id/btnUploadImg"
app:layout_constraintTop_toBottomOf="@+id/btnUploadImg" />
Log out functionality
Once the logout button is clicked we want to sign out and start the MainActivity.
btnLogOut.setOnClickListener(v -> {
FirebaseAuth.getInstance().signOut();
startActivity(new Intent(ProfileActivity.this, MainActivity.class).setFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP));
finish();
});
To clear all the activity so the user can’t go back use the above flags.
Intent.FLAG_ACTIVITY_CLEAR_TASK
- This flag is used to clear the entire task stack (all activities) when starting a new activity.
- If the activity you’re starting is not already a part of the current task, it will become the root of a new task.
- If the activity you’re starting is already in the task, it will be brought to the front, and all activities above it in the stack will be cleared.
Intent.FLAG_ACTIVITY_CLEAR_TOP
- This flag is used to clear all activities in the task that are on top of the activity you’re starting.
- If the activity is already running in the current task, it will be brought to the front, and all activities above it will be cleared.
- If the activity is not in the task, it will be started as the root of a new task.
Working with Firebase Storage-Uploading profile picture to Firebase
To upload user data such as email , profile picture, and username, etc. to the database need to enable Firebase Realtime Database.
Click on Create Database and start in test mode.
Intregate realtime database in Android studio by Firebase tool.
Select the required option and add the realtime database.
User.java
When the user signs up then we need to put the additional data in the database. (That additional data usually the user object)
so create the User class with the username , email and profilePicture string objects.profilePicture will be the url which will be uploaded later.
Create a contractor and an empty constructor because of firebase it needed in order to use objects directly in database operations. Generates getters and setters.
package rishiz.com.hifi;
public class User {
private String username;
private String email;
private String profilePicture;
public User() {
}
public User(String username, String email, String profilePicture) {
this.username = username;
this.email = email;
this.profilePicture = profilePicture;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getProfilePicture() {
return profilePicture;
}
public void setProfilePicture(String profilePicture) {
this.profilePicture = profilePicture;
}
}
Uploading User data while signing up
To upload the entered data while signing up add the following lines to the handleSignUp method in MainActivity after the task.isComplete().
FirebaseDatabase.getInstance().getReference("user/" + FirebaseAuth.getInstance().getCurrentUser().getUid()).setValue(new User(edtUsername.getText().toString(), edtEmail.getText().toString(), ""));
When we handle sign-up up if the task is successful we want to access FirebaseDatabase.getInstance().getReference(“user”) the user reference will create a user node in the realtime database and then it will set value which will be a new user object with the user name ,email and the profilePicture which is passed profilePicture is initially empty string which will initialize later.
As the reference, every user needs to be unique and that’s why we want it to have a central node that will be the user after that, we want to have some kind of branch where every user will separated with some kind of identifier and that identifier is actually one that get from the FirebaseAuth object with getCurrentUser and getUid.
Uploading Image
Picking the image from the gallery
Declare and initialize the image(profile picture) view in the profile activity.
On click of profile picture icon or image gallery should be opened for that need to create Intent with action PICK and set the type of intent image.
profilePic.setOnClickListener(v -> {
Intent photIntent = new Intent(Intent.ACTION_PICK);
photIntent.setType("image/*");
startActivityForResult(photIntent, 1);
});
The type will specifically tell the system to open the gallery.
for handling the result after selecting an image from the gallery override the method onActivityResult here check requestCode==1,resultCode == RESULT_OK, and check the date is not null because the data will be the image.
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1 && resultCode == RESULT_OK && data != null) {
imgPath = data.getData();
getImageInImageView();
}
}
To store the data create a Uri type variable imgPath.
private void getImageInImageView() {
Bitmap bitmap = null;
try {
bitmap = MediaStore.Images.Media.getBitmap(getContentResolver(), imgPath);
} catch (IOException e) {
e.printStackTrace();
}
profilePic.setImageBitmap(bitmap);
}
bitmap is the image that will get from imgPath to transfer the Uri to the bitmap we are using ,
MediaStore.Images.Media.getBitmap(getContentResolver(), imgPath)
and set the selected image to the profilePicture.
Try catch is used to handle the condition if the image is not found.
Uploading the picked image to Firebase storage
To upload photos we need Firebase storage, Firebase storage is something where we upload the files on Firebase.
and for that, we need to include dependencies first open the Firebase Assistance by going to Tools->Firebase and lets click on storage.
and add storage to the app.
Next, enable storage in the Firebase console.
Click on Get Started next and done.
Declare the upload image button initialize it and call the uploadImage() method on click.
private void uploadImg() {
final ProgressDialog progressDialog = new ProgressDialog(this);
progressDialog.setTitle("Uploading..");
progressDialog.show();
FirebaseStorage.getInstance().getReference("images/" + UUID.randomUUID().toString()).putFile(imgPath).addOnCompleteListener(new OnCompleteListener<UploadTask.TaskSnapshot>() {
@Override
public void onComplete(@NonNull Task<UploadTask.TaskSnapshot> task) {
if (task.isComplete()) {
task.getResult().getStorage().getDownloadUrl().addOnCompleteListener(new OnCompleteListener<Uri>() {
@Override
public void onComplete(@NonNull Task<Uri> task) {
updateProfilePicture(task.getResult().toString());
}
});
Toast.makeText(ProfileActivity.this, "Image Uploaded!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(ProfileActivity.this, task.getException().getLocalizedMessage(), Toast.LENGTH_SHORT).show();
}
progressDialog.dismiss();
}
}).addOnProgressListener(new OnProgressListener<UploadTask.TaskSnapshot>() {
@Override
public void onProgress(@NonNull UploadTask.TaskSnapshot taskSnapshot) {
double progress = 100.0 * taskSnapshot.getBytesTransferred() / taskSnapshot.getTotalByteCount();
progressDialog.setMessage("Uploaded " + (int) progress + "%");
}
});
}
when uploading the image we need to show the progress for the we use ProgressDialog.
final ProgressDialog progressDialog = new ProgressDialog(this);
progressDialog.setTitle("Uploading..");
progressDialog.show();
To get the reference of the storage to upload the image call
FirebaseStorage.getInstance().getReference(“images/” + UUID.randomUUID().toString())
reference will be images/ with some kind of random string generated as above which will be the name of the uploaded image and thus there is no choice for the image with the same name.
put the file and add the addCompleteListner on the task complete or not dismiss the progress dialog.
if the following error occurs then update the version of the Firebase storage dependency.
Once the image is uploaded the Firebase Storage looks like as below,
To track the progress of the image uploaded in the form of a percentage add addOnProgressListener on addOnCompleteListener.On progress add the below code to show the progress in the integer form.
double progress = 100.0 * taskSnapshot.getBytesTransferred() / taskSnapshot.getTotalByteCount();
progressDialog.setMessage("Uploaded " + (int) progress + "%");
To associate this image with the user that uploaded this picture we need URL of this image and put this URL into the empty string (profilePicture) in the database.
To get the URL.
task.getResult().getStorage().getDownloadUrl()
the URL is available only when the get storage and getDownload task is completed so you need to add one more addOnCompleteListener when the task is successful upload the picture.
public void onComplete(@NonNull Task<Uri> task) {
updateProfilePicture(task.getResult().toString());
}
To get the path for the profilePicture we have to put this uploaded image get the Database reference and add the path of the profilePicture.
private void updateProfilePicture(String url) {
FirebaseDatabase.getInstance().getReference("user/" + FirebaseAuth.getInstance().getCurrentUser().getUid() + "/profilePicture").setValue(url);
}
and set the downloaded URL.
Fetching user from Firebase
FriendsActivity layout
Open the layout of FreindsActivity and add recyclerview to put the user in it also add the progress bar
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/gradient"
tools:context=".FriendsActivity">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="90dp"
android:layout_height="90dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_marginTop="70dp"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
</androidx.constraintlayout.widget.ConstraintLayout>
Initially, we want to recyclerview visibility gone because we want to display the progress bar first when will get the data we will hide this progress bar and show the recyclerview.
UserAdapter
Create the adapter class with the name UsersAdapter.In this adapter create the user type arraylist, context, and the interface for click event listening.
package rishiz.com.hifi;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import java.util.ArrayList;
public class UserAdapter extends RecyclerView.Adapter<UserAdapter.UserHolder> {
private final ArrayList<User> users;
private final Context context;
private final OnUserClickListener onUserClickListener;
public UserAdapter(ArrayList<User> users, Context context, OnUserClickListener onUserClickListener) {
this.users = users;
this.context = context;
this.onUserClickListener = onUserClickListener;
}
@NonNull
@Override
public UserHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.user_holder, parent, false);
return new UserHolder(view);
}
@Override
public void onBindViewHolder(@NonNull UserHolder holder, int position) {
holder.txtUsername.setText(users.get(position).getUsername());
Glide.with(context).load(users.get(position).getProfilePicture()).error(R.drawable.ic_baseline_person_24).placeholder(R.drawable.ic_baseline_person_24).into(holder.imageView);
}
@Override
public int getItemCount() {
return users.size();
}
interface OnUserClickListener {
void onUserClicked(int position);
}
class UserHolder extends RecyclerView.ViewHolder {
TextView txtUsername;
ImageView imageView;
public UserHolder(@NonNull View itemView) {
super(itemView);
itemView.setOnClickListener(v -> {
onUserClickListener.onUserClicked(getAdapterPosition());
});
txtUsername = itemView.findViewById(R.id.txtUsername);
imageView = itemView.findViewById(R.id.img_pro);
}
}
}
interface has an onUserClicked method with parameter position.
In FriendsActivity we know which user is actually clicked for create the UserAdapter Constructor with parameter.
Create a view holder layout that is user_holder.xml, The root layout will be cardview with height wrap content and the required margin and corner radius. Include linear layout in it and this layout add another cardview for profile picture.
<?xml version="1.0" encoding="utf-8"?>
<androidx.cardview.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_marginHorizontal="10dp"
android:layout_marginTop="10dp"
app:cardBackgroundColor="@color/black_transparent"
app:cardCornerRadius="14dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:padding="7dp">
<androidx.cardview.widget.CardView
android:layout_width="70dp"
android:layout_height="70dp"
app:cardCornerRadius="50dp">
<ImageView
android:id="@+id/img_pro"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"/>
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/txtUsername"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:textColor="@color/white"
android:layout_gravity="center"
tools:text="Username"
android:textSize="20dp" />
</LinearLayout>
</androidx.cardview.widget.CardView>
On onCreateViewHolder inflate this layout and the view of this layout is initialized in the UserHolder class.
Glide Dependency
To inflate the image view with the profile picture we need glide library dependency, get this from below
Glide Dependency: bumptech/glide: An image loading and caching library for Android focused on smooth scrolling (github.com)
Glide.with(context).load(users.get(position).getProfilePicture()).error(R.drawable.ic_baseline_person_24).placeholder(R.drawable.ic_baseline_person_24).into(holder.imageView);
get the profile picture by the method getProfilePicture() .R.drawable.ic_baseline_person_24 image will be inflated when an error occurs. placeholder image is used when the image is not fetched if the URL is invalid or if the image does not exit. finally, inflate into holder.imageView.
Go to FriendsActivity and declare the variable of UserAdapter.OnUserClickListener, RecyclerView, ArrayList<User>,ProgressBar,UserAdapter and initialize.
Implement the onUserClicked method of OnUserClickListener and call the getUsers method to fetch the users.
private void getUsers() {
users.clear();
FirebaseDatabase.getInstance().getReference("user").addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
for (DataSnapshot dataSnapshot : snapshot.getChildren()) {
users.add(dataSnapshot.getValue(User.class));
}
userAdapter = new UserAdapter(users, FriendsActivity.this, onUserClickListener);
recyclerView.setAdapter(userAdapter);
progressBar.setVisibility(View.GONE);
recyclerView.setVisibility(View.VISIBLE);
for (User user : users) {
if (user.getEmail().equals(FirebaseAuth.getInstance().getCurrentUser().getEmail())) {
myImgUrl = user.getProfilePicture();
return;
}
}
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
}
get the FirebaseDatabase reference and add the addListenerForSingleValueEvent.
snapshot.getChildren will give the below value.
users.add(dataSnapshot.getValue(User.class)) this will add the value of user in the arraylist.
Call the UserAdapter constructor set the adapter and set the visibility of the recycler view VISIBLE.
Implementing swipe to refresh
Add Swiperefreshlayout dependency to app gradle.
Swiperefreshlayout dependency:Swiperefreshlayout | Jetpack | Android Developers
Add the swiperefreshlayout as the root layout for the activity_friends layout.That is constraint layout is surrounded to swiperefresh layout.
<?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swapLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@drawable/gradient"
tools:context=".FriendsActivity">
<androidx.constraintlayout.widget.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/progressBar"
style="?android:attr/progressBarStyle"
android:layout_width="90dp"
android:layout_height="90dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
android:layout_marginTop="70dp"
app:layout_constraintBottom_toTopOf="parent"
app:layout_constraintEnd_toStartOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/user_holder" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>
Declare and initialize the swipeRefreshLayout in FreindsActicity and perform the function on setOnRefreshListener.
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
getUsers();
swipeRefreshLayout.setRefreshing(false);
}
});
swipeRefreshLayout.setRefreshing(false) is for stopping the infinite spinning, After once the refreshing is done spining will stop.
getUsers() duplicating the data to avoid this by adding users.clear(); at the top in the getUser method.
Messaging Chat app final
MessageActivity Design
Create MessageActivity open layout and add linear layout with the following attribute
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="175dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
We need this layout instead of toolbar as below
for this, we need to remove toolbar.
Removing of toolbar
Just replace Theme.MaterialComponents.DayNight.DarkActionBar in the theme file with Theme.MaterialComponents.DayNight.NoActionBar.
In this linear layout add the cardview with height ,width 55dp gravity will be center and add image view in this card view add cardCornerRadius 44dp to make circle.
For ImageView scaletype is centerCrop and add src which will be the default image.
Next in the linear layout add the TextView to show the username of the user that we are chatting with.
The Final LinearLayout :
<LinearLayout
android:id="@+id/linearLayout"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="175dp"
android:orientation="horizontal"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent">
<androidx.cardview.widget.CardView
android:layout_width="55dp"
android:layout_height="55dp"
android:layout_gravity="center"
app:cardCornerRadius="44dp">
<ImageView
android:id="@+id/img_toolbar"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_baseline_person_24" />
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/txtChattingWith"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="15dp" />
</LinearLayout>
Add a progress bar in the root layout
<ProgressBar
android:id="@+id/progressMsg"
android:layout_width="100dp"
android:layout_height="0dp"
android:layout_marginBottom="309dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="0.498"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/linearLayout"
app:layout_constraintVertical_bias="1.0" />
add recycler view for the messages.
Add EditText for the text messages.
<EditText
android:id=”@+id/textMsg”
android:layout_width=”0dp”
android:layout_height=”wrap_content”
android:background=”@drawable/message_input_bgc”
android:maxHeight=”100dp”
android:minHeight=”45dp”
android:layout_marginHorizontal=”9dp”
android:paddingHorizontal=”20dp”
android:paddingVertical=”9dp”
app:layout_constraintBottom_toBottomOf=”parent”
app:layout_constraintEnd_toStartOf=”@+id/sendMsg”
app:layout_constraintStart_toStartOf=”parent” />
We use minHeight and maxHeight attributes means the editText field height can’t be smaller than 45dp and can’t be bigger than 100dp the editText field will expand till the 100dp height.
To give custom background create message_input_bgc.xml drawable resource file.
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/colorSecondary"/>
<corners android:radius="15dp"/>
</shape>
To send the messages we need the Image view which on click send the messages, for that create the vector asset of send icon.Constraint the send view as per requirement
<ImageView
android:id="@+id/sendMsg"
android:layout_width="40dp"
android:layout_height="40dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="4dp"
android:src="@drawable/ic_baseline_send_24"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" />
Now go to MessageActivity declare and initialize the views in the layout.
Message object
Create a Message object means Messages class having,
1.receiver, sender and content variable
2. Empty constructor and parameterized constructor
3.getter and setter
package rishiz.com.hifi;
public class Message {
private String sender;
private String receiver;
private String content;
public Message() {
}
public Message(String sender, String receiver, String content) {
this.sender = sender;
this.receiver = receiver;
this.content = content;
}
public String getSender() {
return sender;
}
public void setSender(String sender) {
this.sender = sender;
}
public String getReceiver() {
return receiver;
}
public void setReceiver(String receiver) {
this.receiver = receiver;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
Operation on onUserClickListener of the FreindsActivity
We need to Start the MessageActivity on the user click the user row of FreindsActivity but with sharing some details of the user.
onUserClickListener = new UserAdapter.OnUserClickListener() {
@Override
public void onUserClicked(int position) {
startActivity(new Intent(FriendsActivity.this, MessageActivity.class)
.putExtra("username_of_roommate", users.get(position).getUsername())
.putExtra("email_of_roommate", users.get(position).getEmail())
.putExtra("img_of_roommate", users.get(position).getProfilePicture())
.putExtra("my_img", myImgUrl)); }
}
.putExtra(“username_of_roommate”, users.get(position).getUsername()) will transfer the username to the MessageActivity which will be set to the textview and it will display the username at the top of MessageActivity.
To get current user image , add below code in the getUsers() method.
for (User user : users) {
if (user.getEmail().equals(FirebaseAuth.getInstance().getCurrentUser().getEmail())) {
myImgUrl = user.getProfilePicture();
return;
}
}
putExtra(“my_img”, myImgUrl)); is transferring the current user image.
Go to MessageActivity and get the passed value.
usernameOfRoommate = getIntent().getStringExtra("username_of_roommate");
emailOfRoomate = getIntent().getStringExtra("email_of_roommate");
Chat room setup
let’s imagine we have Radha and Sita and they are chatting with each other so basically we need one chat room id .
We need only one room for two different users it does not matter if Radha send a message to Sita first or if sita sends to Radha that room must be unique.So to generate this kind of ID the easiest thing is to make an id with the name
So Basically our chat room ID should composed by the usernames of the users in the chat room like RadhSita or SitaRadha.
Create a method setUpChatRoom in MessageActivity.
private void setUpChatRoom() {
FirebaseDatabase.getInstance().getReference("user/" + FirebaseAuth.getInstance().getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
String myUsername = snapshot.getValue(User.class).getUsername();
if (usernameOfRoommate.compareTo(myUsername) > 0) {
chatRoomId = myUsername + usernameOfRoommate;
} else if (usernameOfRoommate.compareTo(myUsername) == 0) {
chatRoomId = myUsername + usernameOfRoommate;
} else {
//chatRoomId = myUsername + usernameOfRoommate;
chatRoomId = usernameOfRoommate + myUsername;
}
attachMessageListener(chatRoomId);
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
}
FirebaseDatabase.getInstance().getReference(“user/” + FirebaseAuth.getInstance().getUid()) from this line we get the current user uid and thus we also get the username.
We need to put listener to the specific node and when the message is changed here and we get the specific data we need to notify activity and it will fetch new messages attachMessageListener method will do this.
private void attachMessageListener(String chatRoomId) {
FirebaseDatabase.getInstance().getReference("messages/" + chatRoomId).addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
messages.clear();
for (DataSnapshot dataSnapshot : snapshot.getChildren()) {
messages.add(dataSnapshot.getValue(Message.class));
}
messageAdapter.notifyDataSetChanged();
recyclerView.scrollToPosition(messages.size() - 1);
recyclerView.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
}
addValueEventListener is used here because it will trigger the onDataChange every time and the new messages will receive add this messages to the message arraylist.
recyclerView.scrollToPosition(messages.size() – 1) will scroll to the last message received.
MessageAdapter
In this, we have messages of type ArrayList,senderImg, and recieverImg of type string and Context variables
MesssageHolder
Add ConstraintLayout inside root ConstraintLayout inside this add cardview with height and width 35dp. Inside this cardview add image for profile user.
Add textview for the messages inside constraintlayout.
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<androidx.constraintlayout.widget.ConstraintLayout
android:id="@+id/ccLayout"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.cardview.widget.CardView
android:id="@+id/profile_cardView"
android:layout_width="35dp"
android:layout_height="35dp"
app:cardCornerRadius="35dp"
android:layout_marginHorizontal="10dp">
<ImageView
android:id="@+id/small_img_profile"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/ic_baseline_person_24"
/>
</androidx.cardview.widget.CardView>
<TextView
android:id="@+id/text_Msg_Content"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
tools:text="jalkfgaklgakfgnalkfngalkfngalkfngakd"
android:maxWidth="200dp"
android:padding="8dp"
android:layout_marginHorizontal="9dp"
android:background="@drawable/message_input_bgc"/>
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
In the image or the code you have seen, we do not constrain anything because in the runtime we will move the profile picture and the text from left to right whether it is sent by the current user or another user. This logic will done in onBindViewHolder.
In the MessageHolder class define and initialize the ConstraintLayout, TextView, and ImageView.
Operation on ConstrainLayout in onBindViewHolder
onBindViewHolder gets the messages and this will get from the ArrayList.
holder.txtMsg.setText(messages.get(position).getContent());
Check if the sender equals to current user
messages.get(position).getSender().equals(FirebaseAuth.getInstance().getCurrentUser().getEmail())
If the sender is current user then we have to constrain the layout right side.
Create a ConstraintSet object then clone the constraintLayout.
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(constraintLayout);
Clear the Constraint for the cardview and textview from the left side.
constraintSet.clear(R.id.profile_cardView, ConstraintSet.LEFT);
constraintSet.clear(R.id.text_Msg_Content, ConstraintSet.LEFT);
Connect the cardview right side to the constraintLayout with constraint right with margin zero this equals to app:layout_constraintRight_toRightOf=”parent”
constraintSet.connect(R.id.profile_cardView, ConstraintSet.RIGHT, R.id.ccLayout, ConstraintSet.RIGHT, 0);
Constraint the message content to the left of the cardview with a margin zero.
constraintSet.connect(R.id.text_Msg_Content, ConstraintSet.RIGHT, R.id.profile_cardView, ConstraintSet.LEFT, 0);
apply these constraint set at last.
if we are not a sender we are a receiver then in the else part do the opposite of if conditions.
inflate the sender image with a glide in if block.
Glide.with(context).load(senderImg).error(R.drawable.ic_baseline_person_24).placeholder(R.drawable.ic_baseline_person_24).into(holder.proImg);
and inflate the receiver image in the else block.
MessageAdapter Final code:
package rishiz.com.hifi;
import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.ConstraintSet;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.google.firebase.auth.FirebaseAuth;
import java.util.ArrayList;
public class MessageAdapter extends RecyclerView.Adapter<MessageAdapter.MessageHolder> {
private final ArrayList<Message> messages;
private final String senderImg;
private final String recieverImg;
private final Context context;
public MessageAdapter(ArrayList<Message> messages, String senderImg, String recieverImg, Context context) {
this.messages = messages;
this.senderImg = senderImg;
this.recieverImg = recieverImg;
this.context = context;
}
@NonNull
@Override
public MessageHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(context).inflate(R.layout.message_holder, parent, false);
return new MessageHolder(view);
}
@Override
public void onBindViewHolder(@NonNull MessageHolder holder, int position) {
holder.txtMsg.setText(messages.get(position).getContent());
ConstraintLayout constraintLayout = holder.ccll;
if (messages.get(position).getSender().equals(FirebaseAuth.getInstance().getCurrentUser().getEmail())) {
Glide.with(context).load(senderImg).error(R.drawable.ic_baseline_person_24).placeholder(R.drawable.ic_baseline_person_24).into(holder.proImg);
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(constraintLayout);
constraintSet.clear(R.id.profile_cardView, ConstraintSet.LEFT);
constraintSet.clear(R.id.text_Msg_Content, ConstraintSet.LEFT);
constraintSet.connect(R.id.profile_cardView, ConstraintSet.RIGHT, R.id.ccLayout, ConstraintSet.RIGHT, 0);
constraintSet.connect(R.id.text_Msg_Content, ConstraintSet.RIGHT, R.id.profile_cardView, ConstraintSet.LEFT, 0);
constraintSet.applyTo(constraintLayout);
} else {
Glide.with(context).load(recieverImg).error(R.drawable.ic_baseline_person_24).placeholder(R.drawable.ic_baseline_person_24).into(holder.proImg);
ConstraintSet constraintSet = new ConstraintSet();
constraintSet.clone(constraintLayout);
constraintSet.clear(R.id.profile_cardView, ConstraintSet.RIGHT);
constraintSet.clear(R.id.text_Msg_Content, ConstraintSet.RIGHT);
constraintSet.connect(R.id.profile_cardView, ConstraintSet.LEFT, R.id.ccLayout, ConstraintSet.LEFT, 0);
constraintSet.connect(R.id.text_Msg_Content, ConstraintSet.LEFT, R.id.profile_cardView, ConstraintSet.RIGHT, 0);
constraintSet.applyTo(constraintLayout);
}
}
@Override
public int getItemCount() {
return messages.size();
}
class MessageHolder extends RecyclerView.ViewHolder {
ConstraintLayout ccll;
TextView txtMsg;
ImageView proImg;
public MessageHolder(@NonNull View itemView) {
super(itemView);
ccll = itemView.findViewById(R.id.ccLayout);
txtMsg = itemView.findViewById(R.id.text_Msg_Content);
proImg = itemView.findViewById(R.id.small_img_profile);
}
}
}
Now let’s go to MessageActivity declare the MessageAdapter object and initialize it.
messageAdapter = new MessageAdapter(messages, getIntent().getStringExtra("my_img"), getIntent().getStringExtra("img_of_roomate"), MessageActivity.this);
To inflate the image at the toolbar.
Glide.with(MessageActivity.this).load(getIntent().getStringExtra(“img_of_roommate”)).placeholder(R.drawable.ic_baseline_person_24).error(R.drawable.ic_baseline_person_24).into(imgToolbar);
After getting the messages we need to notify the adapter that the dataset has changed.
To send the messages on click of send Image.
FirebaseDatabase.getInstance().getReference(“messages/” + chatRoomId).push().setValue(new Message(FirebaseAuth.getInstance().getCurrentUser().getEmail(), emailOfRoomate, txtMsg.getText().toString()));
push is used to generate random strings as shown below.
Then on that random string set value of email of sender and receiver and typed text.
After sending the message set the edit text field to null.
MessageActivity Final code:
package rishiz.com.hifi;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ProgressBar;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.database.DataSnapshot;
import com.google.firebase.database.DatabaseError;
import com.google.firebase.database.FirebaseDatabase;
import com.google.firebase.database.ValueEventListener;
import java.util.ArrayList;
public class MessageActivity extends AppCompatActivity {
private RecyclerView recyclerView;
private EditText txtMsg;
private TextView txtChattingWith;
private ProgressBar progressBar;
private ImageView sendMsg, imgToolbar;
private String usernameOfRoommate, emailOfRoomate, chatRoomId;
private MessageAdapter messageAdapter;
private ArrayList<Message> messages;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_message);
usernameOfRoommate = getIntent().getStringExtra("username_of_roommate");
emailOfRoomate = getIntent().getStringExtra("email_of_roommate");
recyclerView = findViewById(R.id.recyclerViewMsg);
txtMsg = findViewById(R.id.textMsg);
txtChattingWith = findViewById(R.id.txtChattingWith);
progressBar = findViewById(R.id.progressMsg);
sendMsg = findViewById(R.id.sendMsg);
imgToolbar = findViewById(R.id.img_toolbar);
sendMsg.setOnClickListener(v -> {
FirebaseDatabase.getInstance().getReference("messages/" + chatRoomId).push().setValue(new Message(FirebaseAuth.getInstance().getCurrentUser().getEmail(), emailOfRoomate, txtMsg.getText().toString()));
txtMsg.setText("");
});
txtChattingWith.setText(usernameOfRoommate);
messages = new ArrayList<>();
messageAdapter = new MessageAdapter(messages, getIntent().getStringExtra("my_img"), getIntent().getStringExtra("img_of_roomate"), MessageActivity.this);
recyclerView.setLayoutManager(new LinearLayoutManager(MessageActivity.this));
recyclerView.setAdapter(messageAdapter);
Glide.with(MessageActivity.this).load(getIntent().getStringExtra("img_of_roommate")).placeholder(R.drawable.ic_baseline_person_24).error(R.drawable.ic_baseline_person_24).into(imgToolbar);
setUpChatRoom();
}
private void setUpChatRoom() {
FirebaseDatabase.getInstance().getReference("user/" + FirebaseAuth.getInstance().getUid()).addListenerForSingleValueEvent(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
String myUsername = snapshot.getValue(User.class).getUsername();
if (usernameOfRoommate.compareTo(myUsername) > 0) {
chatRoomId = myUsername + usernameOfRoommate;
} else if (usernameOfRoommate.compareTo(myUsername) == 0) {
chatRoomId = myUsername + usernameOfRoommate;
} else {
//chatRoomId = myUsername + usernameOfRoommate;
chatRoomId = usernameOfRoommate + myUsername;
}
attachMessageListener(chatRoomId);
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
}
private void attachMessageListener(String chatRoomId) {
FirebaseDatabase.getInstance().getReference("messages/" + chatRoomId).addValueEventListener(new ValueEventListener() {
@Override
public void onDataChange(@NonNull DataSnapshot snapshot) {
messages.clear();
for (DataSnapshot dataSnapshot : snapshot.getChildren()) {
messages.add(dataSnapshot.getValue(Message.class));
}
messageAdapter.notifyDataSetChanged();
recyclerView.scrollToPosition(messages.size() - 1);
recyclerView.setVisibility(View.VISIBLE);
progressBar.setVisibility(View.GONE);
}
@Override
public void onCancelled(@NonNull DatabaseError error) {
}
});
}
}