Firebase Chat implementation in Android

Firebase Chat implementation in Android
22 June 2016 Pawel Cyron
firebase

This tutorial will help you to create an Android chat based app on Firebase Realtime Database.

Making chat module for the Android app with Firebase is very convenient because Firebase API is taking care of itself (refreshing).

If we would to do it with other API, we should create a websocket or (with lower performance) we should make service which updates every some period of time. But when we use Firebase API, it gets notified whenever a table of database, which it checks is updated.

Its called Firebase Realtime Database (you can read more about it in this link: https://firebase.google.com/docs/database/).

Great thing is that Firebase is creating database instance on our device automatically, so it can work offline without any programmer impact. With Firebase we dont need most of API because client app is connected directly to database.

Firebase Realtime Database by Google:

€œThe Firebase Realtime Database lets you build rich, collaborative applications by allowing secure access to the database directly from client-side code. Data is persisted locally, and even while offline, realtime events continue to fire, giving the end user a responsive experience. When the device regains connection, the Realtime Database synchronizes the local data changes with the remote updates that occurred while the client was offline, merging any conflicts automatically.€

Lets begin and create chat that require google credentials to log in.

If you dont have configured Firebase, look into our first tutorial: Quick start with Firebase

At the begining add all necessary dependencies in build.gradle

dependencies {
...
compile 'com.google.firebase:firebase-core:9.0.2'
compile 'com.google.firebase:firebase-auth:9.0.2'
compile 'com.google.firebase:firebase-database:9.0.2'
compile 'com.google.firebase:firebase-storage:9.0.2'
compile 'com.google.android.gms:play-services-auth:9.0.2'
compile 'com.firebase:firebase-client-android:2.5.2+'
compile 'com.android.support:recyclerview-v7:+'
}
apply plugin: 'com.google.gms.google-services'

Now lets add method inside onActivityResult(), when we logged in with google credentials, to auth client.

private void handleSingInResult(GoogleSignInResult result) {
    if (result.isSuccess()) {
        account = result.getSignInAccount();
        Toast.makeText(this, getString(R.string.chatting_as) + account.getDisplayName(), Toast.LENGTH_SHORT).show();
        PrefsHelper.setUserName(account.getDisplayName());
        PrefsHelper.setUserEmail(account.getEmail());
        PrefsHelper.setUserIdToken(account.getIdToken());
        firebaseAuthWithGoogle();
    } else {
        // Let user know that signing process failed
        onBackPressed();
        Toast.makeText(this, R.string.failure_sign_in, Toast.LENGTH_SHORT).show();
    }
}
private void firebaseAuthWithGoogle() {
    AuthCredential credential = GoogleAuthProvider.getCredential(PrefsHelper.getUserIdToken(), null);
    FirebaseAuth.getInstance().signInWithCredential(credential)
            .addOnCompleteListener(this, new OnCompleteListener<AuthResult>() {
                @Override
                public void onComplete(@NonNull Task<AuthResult> task) {
                    Log.d("firebaseAuthWithGoogle", "signInWithCredential:onComplete:" + task.isSuccessful());

                    // If sign in fails, display a message to the user. If sign in succeeds
                    // the auth state listener will be notified and logic to handle the
                    // signed in user can be handled in the listener.
                    if (task.isSuccessful()) {
                        Log.i("firebaseAuthWithGoogle", "signInWithCredential");

                        ft = fragmentManager.beginTransaction().setCustomAnimations(R.anim.get_in_left_long,
                                R.anim.get_out_left_long, R.anim.get_in_right_long, R.anim.get_out_right_long);
                        ft.replace(R.id.container, ChatFragment.newInstance(), ChatFragment.TAG)
                                .commit();
                        mManagerAR.detachRadar();
                    } else {
                        PrefsHelper.setUserEmail("");
                        PrefsHelper.setUserIdToken("");
                        PrefsHelper.setUserName("");
                        Toast.makeText(MainActivity.this, R.string.auth_failure, Toast.LENGTH_SHORT).show();
                        onBackPressed();
                    }
                }
            });
}

That is a code of ChatFragment:

public class ChatFragment extends BaseFragment {

    private static final int RC_PHOTO_PICKER = 1;
    private Button sendBtn;
    private TextView chatterText;
    private Button logoutBtn;
    private EditText messageTxt;
    private RecyclerView messagesList;
    private ChatMessageAdapter adapter;
    private ImageButton imageBtn;
    private FirebaseApp app;
    private FirebaseDatabase database;
    private FirebaseStorage storage;
    private DatabaseReference databaseRef;
    private StorageReference storageRef;
    private String username;
    private Listener callback;

    public static final String TAG = "ChatFragment";

    public static ChatFragment newInstance() {
        Bundle args = new Bundle();
        ChatFragment fragment = new ChatFragment();
        fragment.setArguments(args);
        return fragment;
    }


    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View mainView = inflater.inflate(R.layout.fragment_chat, container, false);
        username = PrefsHelper.getUserName();
        bindView(mainView);
        initView();
        initFirebase();
        return mainView;
    }

    private void initView() {
        LinearLayoutManager layoutManager = new LinearLayoutManager(getActivity());
        messagesList.setHasFixedSize(false);
        messagesList.setLayoutManager(layoutManager);

        // Show an image picker when the user wants to upload an imasge
        imageBtn.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
                intent.setType("image/jpeg");
                intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
                startActivityForResult(Intent.createChooser(intent, getString(R.string.complete_action_with)), RC_PHOTO_PICKER);
            }
        });

        sendBtn.setOnClickListener(new View.OnClickListener() {
            public void onClick(View v) {
                ChatMessage chat = new ChatMessage(messageTxt.getText().toString(), username);
                // Push the chat message to the database
                databaseRef.push().setValue(chat);
                messageTxt.setText("");
            }
        });
        adapter = new ChatMessageAdapter(getActivity());
        messagesList.setAdapter(adapter);
        // When record added, list will scroll to bottom
        adapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
            public void onItemRangeInserted(int positionStart, int itemCount) {
                messagesList.smoothScrollToPosition(adapter.getItemCount());
            }
        });
        logoutBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                callback.onLogoutClicked();
            }
        });
        chatterText.setText(String.format(getString(R.string.chatting_as_s), PrefsHelper.getUserName()));
    }

    private void initFirebase() {
        // Get the Firebase app and all primitives we'll use
        app = FirebaseApp.getInstance();
        database = FirebaseDatabase.getInstance(app);
        storage = FirebaseStorage.getInstance(app);

        // Get a reference to our chat "room" in the database
        databaseRef = database.getReference("chat_" + getString(R.string.app_name));

        // Listen for when child nodes get added to the collection
        databaseRef.addChildEventListener(new ChildEventListener() {
            public void onChildAdded(DataSnapshot snapshot, String s) {
                // Get the chat message from the snapshot and add it to the UI
                ChatMessage chat = snapshot.getValue(ChatMessage.class);
                adapter.addMessage(chat);
            }

            public void onChildChanged(DataSnapshot dataSnapshot, String s) {
            }

            public void onChildRemoved(DataSnapshot dataSnapshot) {
            }

            public void onChildMoved(DataSnapshot dataSnapshot, String s) {
            }

            public void onCancelled(DatabaseError databaseError) {
            }
        });
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        try {
            callback = (Listener) context;
        } catch (ClassCastException e) {
            throw new ClassCastException(this.toString()
                    + " must implement ChatFragment.Listener");
        }
    }

    private void bindView(View container) {
        chatterText = (TextView) container.findViewById(R.id.chatting_as_text);
        logoutBtn = (Button) container.findViewById(R.id.logoutBtn);
        sendBtn = (Button) container.findViewById(R.id.sendBtn);
        messageTxt = (EditText) container.findViewById(R.id.messageTxt);
        messagesList = (RecyclerView) container.findViewById(R.id.messagesList);
        imageBtn = (ImageButton) container.findViewById(R.id.imageBtn);
    }

    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        //Recieved result from image picker
        if (requestCode == RC_PHOTO_PICKER && resultCode == getActivity().RESULT_OK) {
            Uri selectedImageUri = data.getData();
            // Get a reference to the location where we'll store our photos
            storageRef = storage.getReference("chat_photos_" + getString(R.string.app_name));
            // Get a reference to store file at chat_photos/<FILENAME>
            final StorageReference photoRef = storageRef.child(selectedImageUri.getLastPathSegment());
            // Upload file to Firebase Storage
            photoRef.putFile(selectedImageUri)
                    .addOnSuccessListener(getActivity(), new OnSuccessListener<UploadTask.TaskSnapshot>() {
                        public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
                            // When the image has successfully uploaded, we get its download URL
                            Uri downloadUrl = taskSnapshot.getDownloadUrl();
                            // Send message with Image URL
                            ChatMessage chat = new ChatMessage(downloadUrl.toString(), username);
                            databaseRef.push().setValue(chat);
                            messageTxt.setText("");
                        }
                    });
        }
    }

    public interface Listener {
        void onLogoutClicked();

    }
}

ChatFragment layout (fragment_chat.xml):

<?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:background="@android:color/white"
                android:layout_height="match_parent"
                android:paddingBottom="@dimen/activity_vertical_margin"
                android:paddingLeft="@dimen/activity_horizontal_margin"
                android:paddingRight="@dimen/activity_horizontal_margin"
                android:paddingTop="@dimen/activity_vertical_margin">

    <LinearLayout
        android:id="@+id/header"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_alignParentTop="true"
        android:orientation="horizontal">

        <TextView
            android:id="@+id/chatting_as_text"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:layout_weight="1"
            android:text="@string/chatting_as_s"/>

        <Button
            android:id="@+id/logoutBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:text="Logout"/>
    </LinearLayout>

    <android.support.v7.widget.RecyclerView
        android:id="@+id/messagesList"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:layout_above="@+id/footer"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:layout_below="@id/header"
        tools:listitem="@android:layout/two_line_list_item"/>

    <LinearLayout
        android:id="@+id/footer"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        android:orientation="horizontal">
<!-- Layout and code is prepare for uploading pictures into Firabase Storage and displaying them in chat-->
        <ImageButton
            android:id="@+id/imageBtn"
            android:layout_width="36dp"
            android:layout_height="36dp"
            android:background="@android:drawable/ic_menu_gallery"
            android:visibility="gone"/>

        <EditText
            android:id="@+id/messageTxt"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:layout_weight="1"
            android:inputType="textShortMessage|textAutoCorrect"
            android:hint="Write here a message"/>

        <Button
            android:id="@+id/sendBtn"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom"
            android:text="Send"/>
    </LinearLayout>


</RelativeLayout>

Final part is ChatMessageAdapter and Model:

public class ChatMessage {

    private String message;
    private String name;

    // Required default constructor for Firebase object mapping
    private ChatMessage() {
    }

    public ChatMessage(String message, String author) {
        this.message = message;
        this.name = author;
    }

    public String getMessage() {
        return message;
    }

    public String getName() {
        return name;
    }
}
public class ChatMessageAdapter extends RecyclerView.Adapter<ChatMessageAdapter.ChatMessageViewHolder> {

    private static final String TAG = "ChatMessageAdapter";
    private final Activity activity;
    List<ChatMessage> messages = new ArrayList<>();

    public ChatMessageAdapter(Activity activity) {
        this.activity = activity;
    }

    public void addMessage(ChatMessage chat) {
        messages.add(chat);
        notifyItemInserted(messages.size());
    }
    
    @Override
    public ChatMessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return new ChatMessageViewHolder(activity, activity.getLayoutInflater().inflate(android.R.layout.two_line_list_item, parent, false));
    }

    @Override
    public void onBindViewHolder(ChatMessageViewHolder holder, int position) {
        holder.bind(messages.get(position));
    }

    @Override
    public int getItemCount() {
        return messages.size();
    }

    public class ChatMessageViewHolder extends RecyclerView.ViewHolder {
        TextView name, message;
        ImageView image;

        public ChatMessageViewHolder(Activity activity, View itemView) {
            super(itemView);
            name = (TextView) itemView.findViewById(android.R.id.text1);
            message = (TextView) itemView.findViewById(android.R.id.text2);
            image = new ImageView(activity);
            ((ViewGroup) itemView).addView(image);

        }

        public void bind(ChatMessage chat) {
            name.setText(chat.getName());
            //Message is an image
            if (chat.getMessage().startsWith("https://firebasestorage.googleapis.com/") || chat.getMessage().startsWith("content://")) {
                message.setVisibility(View.INVISIBLE);
                image.setVisibility(View.VISIBLE);
                //Load image
              /*  Glide.with(activity)
                        .load(chat.getMessage())
                        .into(image);*/
            } else {
                message.setVisibility(View.VISIBLE);
                image.setVisibility(View.GONE);
                message.setText(chat.getMessage());
            }
        }
    }
}

At the end, we must implement ChatFragment.Listener into Activity to recive Logout callback.

 @Override
    public void onLogoutClicked() {
        Auth.GoogleSignInApi.signOut(googleApiClient).setResultCallback(new ResultCallback<Status>() {
            @Override
            public void onResult(@NonNull Status status) {
                PrefsHelper.setUserEmail("");
                PrefsHelper.setUserIdToken("");
                PrefsHelper.setUserName("");
                if (status.isSuccess()) {
                    Toast.makeText(MainActivity.this, R.string.logged_out, Toast.LENGTH_SHORT).show();
                }
                onBackPressed();
            }
        });
    }

If You want to have posibility to add pictures into chat, just set visibility property to visible in ChatFragment layout “imageBtn” and add displaying library in ChatMessageAdapter.

For practice example in Google Play, check our app Virtual London Tour

Also check standalone sample in our github

0 Comments

Leave a reply

Your email address will not be published. Required fields are marked *

*