Android UI Kit Quickstart

UI Kit is a quick and hassle free way to set up chat using ChatCamp, It contains all the basic UI components which are required to setup the chat ui. You can customize the ui components according to your need.

How to setup

There are few steps by following which you can setup chat in no time. This guide assume that you have already registered yourself on ChatCamp dashboard and got the App ID.

1) Add UI Kit in gradle

First you need to add ChatCamp Ui Kit to your gradle. Add these lines in your app level gradle file.

repositories {
    maven {
        url "https://raw.githubusercontent.com/ChatCamp/ChatCamp-Android-SDK/master/"
    }
}

dependencies {
		compile 'com.github.ChatCamp:ChatCamp-Android-UI-Kit:0.0.50'
}

and add this in your project level gradle

allprojects {
    repositories {
        google()
        jcenter()
        maven { url 'https://jitpack.io' }
    }
}

2) Initialise ChatCamp

You need to initialise ChatCamp and connect to it, This has to be done at two places. First at the Home screen of the app, where the user ends after he has logged in. Second in the Application class which you have extended from Android Application class.

Do this in your Home screen

// Do this in your Home Screen
ChatCamp.init(this, Constant.APP_ID);
ChatCamp.connect(USER_ID, new ChatCamp.ConnectListener() {
            @Override
            public void onConnected(User user, ChatCampException e) {
              // Here you can update the display name of the user and send token 
              // for firebase service, more on this later
            }
}

Do this in your Application Class

public class BaseApplication extends Application implements Application.ActivityLifecycleCallbacks {
    private static BaseApplication instance;

    @Override
    public void onCreate() {
        super.onCreate();
        instance = this;
        registerActivityLifecycleCallbacks(this);
    }

    private void setupChatcamp() {
        if (!TextUtils.isEmpty(USER_ID)
                && ChatCamp.getConnectionState() != ChatCamp.ConnectionState.OPEN) {
            ChatCamp.init(this, Constant.APP_ID);
            ChatCamp.connect(USER_ID, new ChatCamp.ConnectListener() {
                @Override
                public void onConnected(User user, ChatCampException e) {
                   // do nothing
                }
            });
            return;
        }
    }

    public static BaseApplication getInstance() {
        return instance;
    }

    @Override
    public void onActivityCreated(Activity activity, Bundle savedInstanceState) {
        setupChatcamp();
    }

    @Override
    public void onActivityStarted(Activity activity) {
    }

    @Override
    public void onActivityResumed(Activity activity) {
    }

    @Override
    public void onActivityPaused(Activity activity) {
    }

    @Override
    public void onActivityStopped(Activity activity) {
    }

    @Override
    public void onActivitySaveInstanceState(Activity activity, Bundle outState) {
    }

    @Override
    public void onActivityDestroyed(Activity activity) {
    }
}

Once the initial code of setting up is done we can concentrate on integrating the UI components. There are 2 major screens, first is to show the list of conversations and second is the conversation screen itself.
##3) Show List of conversations
For showing the list of conversations we will use ui component - ChannelList . ChannelList can be added in xml file as any other view.

<com.chatcamp.uikit.channel.ChannelList
            android:id="@+id/channel_list"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

Then in the corresponding fragment or activity initialize the ChannelList in the onResume by setting params like type of channel (Open or Group)and type of group filter(All, Invited, Accepted).

@Override
    public void onResume() {
        super.onResume();
        groupFilter = GroupChannelListQuery.ParticipantState.ALL;
        channelList.setChannelType(BaseChannel.ChannelType.GROUP, groupFilter);
    }

If you want to listen to event when all the channels are loaded, like for showing a progress bar or showing a placeholder screen when there are no channels, you can set ChannelLoadedListener.

channelList.setOnChannelsLoadedListener(new ChannelList.OnChannelsLoadedListener() {
            @Override
            public void onChannelsLoaded() {
                progressBar.setVisibility(View.GONE);
                if(channelList.getAdapter().getItemCount() == 0) {
                    placeHolderText.setVisibility(View.VISIBLE);
                } else {
                    placeHolderText.setVisibility(View.GONE);
                }
            }
        });

You can set a click listener when a channel is clicked in the channel list. Most probably you will show the conversation screen when this is clicked but you can do other operations also.

channelList.setChannelClickListener(new ChannelAdapter.ChannelClickedListener() {
            @Override
            public void onClick(BaseChannel baseChannel) {
                Intent intent = new Intent(getActivity(), ConversationActivity.class);
                intent.putExtra("channelType", "group");
                intent.putExtra("participantState", groupFilter.name());
                intent.putExtra("channelId", baseChannel.getId());
                startActivity(intent);
            }
        });

Doing this you can see the list of your conversations, In the code we have considered GroupChannel but the same thing apply to OpenChannel also. For complete file please refer fragment_group_channel_list.xml and GroupChannelListFragment.java

4) Show conversation screen

Next is the Conversation Screen, Conversation Screen has 3 major components - HeaderView, MessagesList, MessageInput.
HeaderView is the Toolbar that we see in the Conversation screen.
MessageList is all the chats that we see
MessageInput is where we we send messages and send attachments.

The layout file looks like this when we put everything together.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout 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/parent_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context="io.chatcamp.app.ConversationActivity">

    <com.chatcamp.uikit.messages.HeaderView
        android:id="@+id/header_view"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <RelativeLayout
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:layout_weight="1">

        <com.chatcamp.uikit.messages.MessagesList
            android:id="@+id/messagesList"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            app:incomingBubblePaddingBottom="4dp"
            app:incomingBubblePaddingTop="4dp"
            app:incomingTextSize="14sp"
            app:outcomingBubblePaddingBottom="4dp"
            app:outcomingBubblePaddingTop="4dp"
            app:outcomingTextSize="14sp"
            app:textAutoLink="all" />

        <ProgressBar
            android:id="@+id/load_message_pb"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_centerInParent="true"/>

    </RelativeLayout>


    <me.zhanghai.android.materialprogressbar.MaterialProgressBar
        android:id="@+id/progress_bar"
        style="@style/Widget.MaterialProgressBar.ProgressBar.Horizontal"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:indeterminate="false"
        android:progress="0"
        android:visibility="gone"
        app:mpb_progressBackgroundTint="@color/green_light"
        app:mpb_progressStyle="horizontal"
        app:mpb_progressTint="@color/green" />

    <View
        android:id="@+id/divider"
        android:layout_width="match_parent"
        android:layout_height="1dp"
        android:layout_above="@+id/edit_conversation_input"
        android:layout_marginBottom="4dp"
        android:layout_marginLeft="16dp"
        android:layout_marginRight="16dp"
        android:background="@color/gray_light" />

    <com.chatcamp.uikit.messages.MessageInput
        android:id="@+id/edit_conversation_input"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingBottom="1dp"
        android:paddingLeft="8dp"
        android:paddingRight="8dp"
        android:paddingTop="1dp"
        app:attachmentButtonBackground="@drawable/bg_circular"
        app:attachmentButtonHeight="30dp"
        app:attachmentButtonIcon="@drawable/ic_attachment_app"
        app:attachmentButtonWidth="30dp"
        app:inputButtonBackground="@color/transparent"
        app:inputButtonIcon="@drawable/ic_send_app"
        app:voiceMessageButtonIcon="@drawable/ic_mic"
        app:voiceMessageButtonMuteIcon="@drawable/ic_mic_mute"
        app:inputHint="@string/hint_enter_a_message"
        app:inputTextSize="14sp"
        app:showAttachmentButton="true"
        app:showVoiceMessageButton="true"
        app:attachmentButtonDefaultBgColor="@color/red"/>
</LinearLayout>

The corresponding activity class looks like

package io.chatcamp.app;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ProgressBar;

import com.chatcamp.uikit.messages.HeaderView;
import com.chatcamp.uikit.messages.MessageInput;
import com.chatcamp.uikit.messages.MessagesList;
import com.chatcamp.uikit.messages.messagetypes.FileMessageFactory;
import com.chatcamp.uikit.messages.messagetypes.ImageMessageFactory;
import com.chatcamp.uikit.messages.messagetypes.MessageFactory;
import com.chatcamp.uikit.messages.messagetypes.TextMessageFactory;
import com.chatcamp.uikit.messages.messagetypes.VideoMessageFactory;
import com.chatcamp.uikit.messages.messagetypes.VoiceMessageFactory;
import com.chatcamp.uikit.messages.sender.AttachmentSender;
import com.chatcamp.uikit.messages.sender.CameraAttachmentSender;
import com.chatcamp.uikit.messages.sender.FileAttachmentSender;
import com.chatcamp.uikit.messages.sender.GalleryAttachmentSender;
import com.chatcamp.uikit.messages.sender.VoiceSender;
import com.chatcamp.uikit.messages.typing.DefaultTypingFactory;

import java.util.ArrayList;
import java.util.List;

import io.chatcamp.sdk.BaseChannel;
import io.chatcamp.sdk.ChatCamp;
import io.chatcamp.sdk.ChatCampException;
import io.chatcamp.sdk.GroupChannel;
import io.chatcamp.sdk.GroupChannelListQuery;
import io.chatcamp.sdk.OpenChannel;
import io.chatcamp.sdk.Participant;
import io.chatcamp.sdk.PreviousMessageListQuery;
import me.zhanghai.android.materialprogressbar.MaterialProgressBar;

public class ConversationActivity extends AppCompatActivity implements AttachmentSender.UploadListener {

    private MessagesList mMessagesList;
    private String channelType;
    private String channelId;
    private MessageInput input;
    private MaterialProgressBar progressBar;
    private PreviousMessageListQuery previousMessageListQuery;
    private BaseChannel channel;
    private HeaderView headerView;
    private ProgressBar loadMessagePb;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.e("Conversation Activity", "on create");
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_conversation);

        mMessagesList = findViewById(R.id.messagesList);
        input = findViewById(R.id.edit_conversation_input);
        progressBar = findViewById(R.id.progress_bar);
        headerView = findViewById(R.id.header_view);
        loadMessagePb = findViewById(R.id.load_message_pb);

        // use a linear layout manager

        setSupportActionBar(headerView.getToolbar());
        getSupportActionBar().setDisplayHomeAsUpEnabled(true);

        channelType = getIntent().getStringExtra("channelType");
        channelId = getIntent().getStringExtra("channelId");
        if (channelType.equals("open")) {
            OpenChannel.get(channelId, new OpenChannel.GetListener() {
                @Override
                public void onResult(OpenChannel openChannel, ChatCampException e) {
                    final OpenChannel o = openChannel;
                    setChannel(o);
                    openChannel.join(new OpenChannel.JoinListener() {
                        @Override
                        public void onResult(ChatCampException e) {
                            previousMessageListQuery = o.createPreviousMessageListQuery();
                            channel = o;
                        }
                    });

                }
            });

        } else {
            //TODO check the participant state - INVITED, ALL,  ACCEPTED
            final GroupChannelListQuery.ParticipantState groupFilter = GroupChannelListQuery.ParticipantState.ACCEPTED;//GroupChannelListQuery.ParticipantState.valueOf(getIntent().getStringExtra("participantState"));
            GroupChannel.get(channelId, new GroupChannel.GetListener() {
                @Override
                public void onResult(final GroupChannel groupChannel, ChatCampException e) {
                    setChannel(groupChannel);
                    if (groupFilter == GroupChannelListQuery.ParticipantState.INVITED) {
                        groupChannel.acceptInvitation(new GroupChannel.AcceptInvitationListener() {
                            @Override
                            public void onResult(GroupChannel groupChannel, ChatCampException e) {
                                channel = groupChannel;
                            }
                        });
                    } else {
                        channel = groupChannel;
                    }

                }
            });
        }
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent dataFile) {
        input.onActivityResult(requestCode, resultCode, dataFile);
        mMessagesList.onActivityResult(requestCode, resultCode, dataFile);
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions,
                                           int[] grantResults) {
        input.onRequestPermissionsResult(requestCode, permissions, grantResults);
        mMessagesList.onRequestPermissionsResult(requestCode, permissions, grantResults);
    }

    public void setChannel(BaseChannel channel) {
        mMessagesList.setOnMessagesLoadedListener(new MessagesList.OnMessagesLoadedListener() {
            @Override
            public void onMessagesLoaded() {
                loadMessagePb.setVisibility(View.GONE);
            }
        });
        headerView.setChannel(channel);
        invalidateOptionsMenu();
        input.setChannel(channel);
        input.setOnSendClickListener(new MessageInput.OnSendCLickedListener() {
            @Override
            public void onSendClicked(String text) {
                String a = text;
            }
        });
        MessageFactory[] messageFactories = new MessageFactory[5];
        messageFactories[0] = new TextMessageFactory();
        messageFactories[1] = new ImageMessageFactory(this);
        messageFactories[2] = new VideoMessageFactory(this);
        messageFactories[3] = new VoiceMessageFactory(this);
        messageFactories[4] = new FileMessageFactory(this);
        mMessagesList.addMessageFactories(messageFactories);
        mMessagesList.setChannel(channel);
        mMessagesList.setTypingFactory(new DefaultTypingFactory(this));
        FileAttachmentSender fileAttachmentSender = new FileAttachmentSender(this, channel, "File", R.drawable.ic_document);
        fileAttachmentSender.setUploadListener(this);
        GalleryAttachmentSender galleryAttachmentSender = new GalleryAttachmentSender(this, channel, "Gallery", R.drawable.ic_gallery);
        galleryAttachmentSender.setUploadListener(this);
        CameraAttachmentSender cameraAttachmentSender = new CameraAttachmentSender(this, channel, "Camera", R.drawable.ic_camera);
        cameraAttachmentSender.setUploadListener(this);
        List<AttachmentSender> attachmentSenders = new ArrayList<>();
        attachmentSenders.add(fileAttachmentSender);
        attachmentSenders.add(cameraAttachmentSender);
        attachmentSenders.add(galleryAttachmentSender);

        input.setAttachmentSenderList(attachmentSenders);
        VoiceSender voiceSender = new VoiceSender(this, channel);
        voiceSender.setUploadListener(this);
        input.setVoiceSender(voiceSender);
    }

    @Override
    public void onUploadProgress(int progress) {
        progressBar.setVisibility(View.VISIBLE);
        progressBar.setProgress(progress);
    }

    @Override
    public void onUploadSuccess() {
        progressBar.setVisibility(View.GONE);
    }

    @Override
    public void onUploadFailed(ChatCampException error) {
        progressBar.setVisibility(View.GONE);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            finish();
            return true;
        } else if (item.getItemId() == R.id.action_block) {
            headerView.onOptionsItemSelected(item);
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        MenuInflater inflater = new MenuInflater(this);
        inflater.inflate(R.menu.menu_conversation, menu);
        headerView.onCreateOptionMenu(menu.findItem(R.id.action_block));
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onPrepareOptionsMenu(Menu menu) {
        headerView.onPrepareOptionsMenu(menu.findItem(R.id.action_block));
        return super.onPrepareOptionsMenu(menu);
    }
}

5) Push notifications

Now the only thing remaining is notifications. For notification, we use a combination of firebase and local listeners. When the app is not running then we use Firebase but when the app is running we use local listeners to show the notifications.
For setting up firebase follow https://docs.chatcamp.io/docs/android-chat-push-notifications.
Once you are done with the setup of firebase you need to pass the firebase token to ChatCamp. This you can do when you connect to ChatCamp. Remember we have some initialization code for connecting to ChatCamp, We can pass the firebase token in the callback of that method.

ChatCamp.connect(userId, new ChatCamp.ConnectListener() {
            @Override
            public void onConnected(User user, ChatCampException e) {
                if (e != null) {
                    if (FirebaseInstanceId.getInstance().getToken() != null) {
                        ChatCamp.updateUserPushToken(FirebaseInstanceId.getInstance().getToken(), new ChatCamp.UserPushTokenUpdateListener() {
                            @Override
                            public void onUpdated(User user, ChatCampException e) {
                                Log.d("CHATCAMP_APP", "PUSH TOKEN REGISTERED");

                            }
                        });
                    }
                }
            }
        });

Since the firebase token keeps on updating we also need to update token once the firebase token is updated in FirebaseInstanceIdService

public class ChatCampAppFirebaseInstanceIDService extends FirebaseInstanceIdService {
    @Override
    public void onTokenRefresh() {
        // Get updated InstanceID token.
        String refreshedToken = FirebaseInstanceId.getInstance().getToken();
        if(FirebaseInstanceId.getInstance().getToken() != null && ChatCamp.getConnectionState() == ChatCamp.ConnectionState.OPEN) { ChatCamp.updateUserPushToken(FirebaseInstanceId.getInstance().getToken(), new ChatCamp.UserPushTokenUpdateListener() {
                @Override
                public void onUpdated(User user, ChatCampException e) {
                    Log.d("CHATCAMP_APP", "PUSH TOKEN REGISTERED");

                }
            });
        }
    }
}

Once the token is set we will start getting receiving notifications but we need to create notifications to show it in android notification tray. This we will do in FirebaseMessagingService

public class ChatCampAppFirebaseMessagingService extends FirebaseMessagingService {

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    public void onMessageReceived(RemoteMessage remoteMessage) {
        super.onMessageReceived(remoteMessage);
        String channelId = remoteMessage.getData().get("channelId");
        String channelType = remoteMessage.getData().get("channelType");
        String data = remoteMessage.getData().get("message");
        Message message = Message.createfromSerializedData(data);
        String serverType = remoteMessage.getData().get("server");
        sendNotification(this, channelId, channelType, message,serverType);
        Log.e("PUSH NOTIFICATION", "push notification");
    }

    public static void sendNotification(Context context, String channelId, String channelType, Message message, String serverType) {
        if(!serverType.equalsIgnoreCase("Chatcamp")) {
            // this notification is not sent from Chatcamp
            return;
        }
        NotificationManager notificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);

        final String CHANNEL_ID = "CHANNEL_ID";
        if (Build.VERSION.SDK_INT >= 26) {  // Build.VERSION_CODES.O
            NotificationChannel mChannel = new NotificationChannel(CHANNEL_ID, "CHANNEL_NAME", NotificationManager.IMPORTANCE_HIGH);
            notificationManager.createNotificationChannel(mChannel);
        }

        Intent intent = new Intent(context, ConversationActivity.class);
        String participantState = GroupChannelListQuery.ParticipantState.ALL.name();
        intent.putExtra("channelId", channelId);
        intent.putExtra("channelType", channelType);
        intent.putExtra("participantState", participantState);
        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        PendingIntent pendingIntent = PendingIntent.getActivity(context, 0 /* Request code */, intent, PendingIntent.FLAG_UPDATE_CURRENT);

        Uri defaultSoundUri = RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
        NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(context, CHANNEL_ID)
                .setSmallIcon(R.drawable.icon_default_contact)
                .setColor(Color.parseColor("#7469C4"))  // small icon background color
                .setLargeIcon(BitmapFactory.decodeResource(context.getResources(), R.drawable.icon_default_contact))
                .setContentTitle(message.getUser().getDisplayName())
                .setAutoCancel(true)
                .setSound(defaultSoundUri)
                .setPriority(Notification.PRIORITY_MAX)
                .setDefaults(Notification.DEFAULT_ALL)
                .setContentIntent(pendingIntent);

        if (message.getType().equals("attachment")) {
            if (message.getAttachment().getType().contains("image")) {
                notificationBuilder.setContentText("Image");
            } else if (message.getAttachment().getType().contains("video")) {
                notificationBuilder.setContentText("video");
            }  else if (message.getAttachment().getType().contains("application") || message.getAttachment().getType().contains("css") ||
                    message.getAttachment().getType().contains("csv") || message.getAttachment().getType().contains("text")) {
                notificationBuilder.setContentText("document");
            }
        } else if(message.getType().equals("text")) {
            notificationBuilder.setContentText(message.getText());
        } else {
            notificationBuilder.setContentText("new Message");
        }

        notificationManager.notify(0 /* ID of notification */, notificationBuilder.build());
    }
}

This code is enough to show the notification when the app is not running but when the app is running we need to use local listeners to show notification, This will be done in Application class. We already have a setupChatCamp() function in Application class, we need to modify it.

private void setupChatcamp() {
        ChatCamp.addChannelListener("NOTIFICATION", new ChatCamp.ChannelListener() {
            @Override
            public void onGroupChannelMessageReceived(GroupChannel groupChannel, Message message) {
                if (!BaseApplication.getInstance().getGroupId().equals(groupChannel.getId())
                        && !message.getUser().getId().equalsIgnoreCase(ChatCamp.getCurrentUser().getId())) {
                    sendNotification(BaseApplication.this, groupChannel.getId(),
                            BaseChannel.ChannelType.GROUP.name(), message, "chatcamp");
                }
            }
        });
        if (!TextUtils.isEmpty(LocalStorage.getInstance().getUserId())
                && !TextUtils.isEmpty(LocalStorage.getInstance().getUsername())
                && ChatCamp.getConnectionState() != ChatCamp.ConnectionState.OPEN) {
            ChatCamp.init(this, Constant.APP_ID);
            ChatCamp.connect(LocalStorage.getInstance().getUserId(), new ChatCamp.ConnectListener() {
                @Override
                public void onConnected(User user, ChatCampException e) {
                    // do nothing
                   
                }
            });
            return;
        }
    }

Now there is one last thing that we need to add, If the user is on the conversation screen and getting message for that chat we don't want to show notification so we need to add a variable which will store the channelId of the current open conversation screen. Application class is the best place to do that. We will add a variable groupId and make getter setter for it.

private String groupId = "";

public void setGroupId(String groupId) {
        this.groupId = groupId;
    }

    public String getGroupId() {
        return groupId;
    }

Then in the Conversation Screen we need to set the value of this variable, We will use activity lifecycle functions onResume and onPause for this. Add these two functions in ConversationActivity

@Override
    protected void onResume() {
        super.onResume();
        if (!TextUtils.isEmpty(channelId)) {
            BaseApplication.getInstance().setGroupId(channelId);
        }
    }

    @Override
    protected void onPause() {
        BaseApplication.getInstance().setGroupId("");
        super.onPause();
    }

That's it, Enjoy a seamless chat experience between different platforms.