The market for messaging apps is exploding. People are looking for alternatives to standard text messaging. Since the opportunity huge, why not create a messaging app? In this post, we’ll walk you through a bare bones WhatsApp clone (we’ll call it KiiChat) for Android that you can build with minimal effort by leveraging Kii Cloud. Good news is that with the rise of mobile backend services such as Kii.com, it has become very easy to create such applications very quickly
If you live on another planet, you probably don’t know that
WhatsApp (recently bought by Facebook) is the most popular cross-platform mobile messaging app which allows you to exchange messages (without having to pay for SMS), create groups, send unlimited images, video and audio media messages between members all over the world.
Development of server-side functionality is essential to creating instant messanging apps like Skype, WhatsApp or LINE and, until now, individual developers had to implement all of it (which was a big gotcha). Fortunately, with the availability of backend services like Kii, you can quickly develop a very functional app in a matter of hours. At the end of this post you will have the opportunity to download the complete sample project. The app allows you to do the following (via our
Android SDK):
- Support groups of users (and group scope object buckets)
- Change default data storage permissions (by setting a new ACL in an object bucket)
- Send messages to users subscribed to a topic (via the Push to user feature)
- Notify the app of new chat messages (via the Push to app feature)
- Query for objects on the cloud (messages, photos, etc)
- Upload photos (via upload of object bodies)
- Download photos (via download of object bodies)
- Login with Facebook account
Creating the App Backend
In order to use the Android SDK you must first register your application on Kii’s developer portal. Once registered, your application will have an associated App Key and App ID that you’ll use to initialize the SDK.
Register an Application
- Log in on the developer portal: https://developer.kii.com
- Press the “Create App” button.This bring up the “Create An App” menu.
- Enter the name of your app, select the desired server location and the mobile platforms you’d like to use (for this project you should select Android) and then click on the “Create” button.
- These are your application’s credentials which will be used by Kii Cloud to uniquely identify your application.
Copy these credentials and add them to the project’s constants file called
ApplicationConst.java
. This will be used in the next step: initializing the backend.
Initializing the Backend
It’s now time to link your actual app to the app backend that you created in the previous step. In a nutshell you’ll have to add our small SDK library to your Android project and initialize Kii with the App ID and App Key. Let’s take a closer look at this.
Configuring your Project
Follow the steps below to add the Kii Cloud SDK to your application’s project:
- Copy the latest Kii library (KiiCloudStorageSDK-xxx.jar) to your project’s libs directory {your android application project root}/libs
Make sure it’s included in your project’s properties as a referenced library.
- Add the following permissions to your application’s
AndroidManifest.xml
:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.GET_ACCOUNTS" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE" />
<uses-permission android:name="com.kii.sample.chat.permission.C2D_MESSAGE" />
<permission android:name="com.kii.sample.chat.permission.C2D_MESSAGE" android:protectionLevel="signature" />
C2DM permissions are necessary to perform push notifications for messaging in the app.
- Add the following code to initialize the SDK on the class
KiiChatApplication
.
import com.kii.cloud.storage.*;
// Configures the SDK to use the specified Application ID and Key.
// It must be called prior to any API calls.
// It is ok to call this method multiple times
Kii.initialize(ApplicationConst.APP_ID, ApplicationConst.APP_KEY, Kii.Site.JP);
KiiAnalytics.initialize(context, ApplicationConst.APP_ID, ApplicationConst.APP_KEY, KiiAnalytics.Site.JP);
Insert your application’s credentials (i.e. APP_ID and APP_KEY) in the placeholder parameters of this method. Also pass the Server Location (Site.US
,Site.JP
or Site.CN
) that you’ve specified on the developer portal (defines the location of the backend server, the closer it is to your users the faster).
The Object Model
Let take a look at the object model for this messenger app:
We’ll explain each class of the model in more detail as we introduce the app’s functionality (more below).
Onboarding Users
The MainActivity
String token = PreferencesManager.getStoredAccessToken();
if (!TextUtils.isEmpty(token)) {
KiiUser.loginWithToken(new KiiUserCallBack() {
@Override
public void onLoginCompleted(int token, KiiUser user, Exception e) {
if (e == null) {
ChatRoom.ensureSubscribedBucket(user);
Intent intent = new Intent(MainActivity.this, ChatMainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
} else {
PreferencesManager.setStoredAccessToken("");
Intent intent = new Intent(MainActivity.this, SigninActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}
}
},token);
} else {
Intent intent = new Intent(MainActivity.this, SigninActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
}
Note that we use a
KiiUserCallback
for the login method (Kii Cloud supports asyc non-blocking calls across the API so you can keep doing work while you wait for the backend to return a result). Note that in this code snippet if there’s no token (or token based authentication fails in any way) then the
SigninActivity
will be started where you can use a standard form based login/registration screen (“Sign-in with your e-mail”) or Facebook based authentication:
If you check “Remember Me” in the screen above then a Kii user token will be stored that will be later used for token based authentication (as it was explained above). When you click on “Create new account” you’ll see a typical registration form:
User Registration and Login
Kii supports
user registration out-of-the-box and even allows you to validate users via e-mail/SMS. But most importantly the code needed to register/login a user is very simple. Let’s take a look at the most important part of a
SignupTask
(defined in
SignupDialogFragment
):
@Override
protected Boolean doInBackground(Void... params) {
try {
KiiUser.Builder builder = KiiUser.builderWithEmail(email);
KiiUser kiiUser = builder.build();
kiiUser.setDisplayname(username);
kiiUser.register(password);
Logger.i("registered user uri=" + kiiUser.toUri().toString());
return super.doInBackground(params);
} catch (Exception e) {
Logger.e("failed to sign up", e);
return false;
}
}
With a
Kii user builder you can build a user using a username, e-mail and/or phone. After you set the basic info on the builder the
KiiUser.register() method just takes a password as parameter and you’re done! (We use the blocking register API call here since this is already part of an Android async task).
Once you enter an e-mail and password combination that belongs to an existing user you’ll get a response from the backend (success or failure via an exception). Again the code is very simple (see
SigninDialogFragment
) and requires a unique user identifier such as username, e-mail or phone (in this case e-mail) and a password to verify if they match:
KiiUser.logIn(new KiiUserCallBack() {
@Override
public void onLoginCompleted(int token, KiiUser user, Exception e) {
if (e != null) {
Logger.e("Unable to login.", e);
ToastUtils.showShort(getActivity(), "Unable to login");
SimpleProgressDialogFragment.hide(getFragmentManager());
return;
}
if (checkRemember) {
Logger.i(user.getAccessToken());
PreferencesManager.setStoredAccessToken(user.getAccessToken());
}
new PostSigninTask(user.getDisplayname(), user.getEmail()).execute();
}
}, email, password);
You probably noticed unlike the previous registration example we’re now passing an implicit
callback to make an asynchronous call (the user registration call was inside an Android async task in the previous example so it wasn’t necessary to use a callback though you should know it’s available). The advantage of using
Kii’s non-blocking APIs is that the communication with the backend runs on separate thread and the initiating method call returns right away (you process the backend result when the callback is called) which is useful to avoid blocking the UI.
Ok, by now we do have a valid user viar a user registration or login. That means that you can now call
KiiUser.getCurrentUser()
from anywhere in your code and you’ll have a Kii user to work with. But it becomes necessary to couple that user to our model class for users:
ChatUser
and initialize that user to be able to receive push notifications (used to send messages between users in this app). This is achieved in post signup/signin tasks performed by
ChatUserInitializeTask.initializeChatUser()
:
KiiUser kiiUser = KiiUser.getCurrentUser();
ChatUser user = ChatUser.findByUri(kiiUser.toUri());
if (user == null) {
user = new ChatUser(kiiUser.toUri().toString(), username, email);
user.getKiiObject().save();
}
Facebook Login
To wrap-up the user onboarding mechanisms let’s take a look at the Facebook based authentication. Kii allows you to use
Twitter and
Facebook based app authentication via Kii Social Connect. With it you can let your users to sign-up and sign-in to your app with their Facebook accounts by just adding a few parameters in the developer portal and a few lines of code in your app and you will be social-ready!
First of all you’ll have to create an app on Facebook and then configure your app on Kii’s developer console to
connect that Facebook app. The procedure is as follows:
Create a Facebook App
To get started, you first need to create a Facebook App and get your Facebook Application ID.
Configure a Kii Application for Facebook
Please execute the following step on the developer portal to configure your application by setting your Facebook Application ID.
- Click on the “Edit” button in your application console at the developer portal.
- Click on “Settings” to bring up the application’s configuration menu.
- Paste your Facebook App ID.
The code below shows a KiiChat’s login using Kii Social Connect with Facebook:
KiiFacebookConnect connect = (KiiFacebookConnect) Kii.socialConnect(SocialNetwork.FACEBOOK);
connect.initialize(ApplicationConst.FACEBOOK_APP_ID, null, null);
Bundle options = new Bundle();
String[] permission = new String[] { "email" };
options.putStringArray(KiiFacebookConnect.FACEBOOK_PERMISSIONS, permission);
connect.logIn(SigninActivity.this, options, new KiiSocialCallBack() {
public void onLoginCompleted(SocialNetwork network, KiiUser user, Exception exception) {
if (exception == null) {
if (checkRemember.isChecked()) {
Logger.i(user.getAccessToken());
PreferencesManager.setStoredAccessToken(user.getAccessToken());
}
new PostSigninTask(user.getDisplayname(), user.getEmail()).execute();
} else {
Logger.e("failed to sign up", exception);
ToastUtils.showShort(SigninActivity.this, "Unable to sign up");
}
}
});
The code is fairly easy to follow:
- Creates a social network connector instance with the
socialConnect
method passing the target social network service name (SocialNetwork.FACEBOOK
in this example).
- Initializes the social network connector with the
initialize
method passing the Facebook App ID.
- Starts the sign-in process with the
logIn
method. If the specified Facebook account is new, the SDK will first execute the sign-up process if necessary. This method is non-blocking, so it uses a callback to get the results.
In the above code, we are setting the permission for fetching email address from Facebook upon executing the logIn
method. When a user logins with a new Facebook account with this option, Kii Cloud extracts his/her email address from Facebook and checks if there exists a Kii account with the same email address. If such an account exists and the email address is already verified, Kii Cloud will not execute the sign-up process, but it will link the specified Facebook account with this account. Similarly to the previously described user registration and login process, after a successful Facebook login the system will return a valid Kii User when you call KiiUser.getCurrentUser()
Don’t forget that the Android activity that handles the Facebook login must implement the onActivityResult
method that must include code to wrap up the Facebook authentication:
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
Kii.socialConnect(SocialNetwork.FACEBOOK).respondAuthOnActivityResult(requestCode, resultCode, data);
}
Logging Out
The user can also log out from the app at any time. The logout code is in
FriendListFragment
and is extremely easy to follow:
PreferencesManager.setStoredAccessToken("");
KiiUser.logOut();
intent = new Intent(getActivity(), MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
startActivity(intent);
That concludes out user onboarding functionality. Let’s now continue with the functionality to add friends for chatting after you successfully login to the app.
Adding Friends
When you click on the “+” sign on the top bar of the app (while in the Friends tab) you’re presented with a search bar that allows you to look for users in the backend so you can add them to your friends (in the screenshot you can see that I already added a friend called “pub2″):
Kii Cloud provides a
powerful query system that allows you to quickly get the data you need. Once a search keyword is provide a
UserListLoader
takes care of fetching the users from the backend:
@Override
public List<ChatUser> loadInBackground() {
List<ChatUser> users = new ArrayList<ChatUser>();
try {
List<ChatUser> results = ChatUser.searchByKeyword(keyword);
for (ChatUser user : results) {
if (!TextUtils.equals(user.getUri(), KiiUser.getCurrentUser().toUri().toString())) {
users.add(user);
}
}
} catch (Exception e) {
Logger.e("Unable to list users", e);
}
return users;
}
The interesting method here (the one with Kii based code) is
ChatUser.searchByKeyword()
which provides the underlying code to query the backend for
ChatUser
s:
public static List<ChatUser> searchByKeyword(String keyword) throws Exception {
KiiQuery query = null;
if (TextUtils.equals("*", keyword)) {
query = new KiiQuery();
} else {
query = new KiiQuery(
KiiClause.or(
KiiClause.startsWith(FIELD_USERNAME, keyword),
KiiClause.startsWith(FIELD_EMAIL, keyword)
)
);
}
List<ChatUser> users = new ArrayList<ChatUser>();
List<KiiObject> objects = getBucket().query(query).getResult();
for (KiiObject object : objects) {
users.add(new ChatUser(object));
}
return users;
}
As you can see in the code if you use the keyword * as search term then a clause-less
KiiQuery
is created (equivalent to a “catch all” query) to get a list of every registered user.
If the keyword is different then two
KiiClause
s are added to the query to constrain the results to match the beginning of a username or of an e-mail. After the query is built then it is passed to an object bucket (
KiiBucket.query()
) and results are fetched. After you get the list of users you can tap on a user in the list in order to add it as a friend:
When you add a friend the
ChatUser
is converted to a
ChatFriend
and it’s then maintained in a separate list of friends with very similar characteristics as to what was described for global users (except that friends are kept in a
user scope bucketwhich is only visible to each user).
Sending Messages
Once you have added friends to your friend’s list you can invite them to chat by tapping on them:
@Override
protected KiiGroup doInBackground(Void... params) {
try {
String chatRoomName = ChatRoom.getChatRoomName(KiiUser.getCurrentUser(), this.chatFriend);
String uniqueKey = ChatRoom.getUniqueKey(KiiUser.getCurrentUser(), this.chatFriend);
for (int i = 0; i < getListView().getCount(); i++) {
KiiGroup kiiGroup = (KiiGroup)getListView().getItemAtPosition(i);
if (TextUtils.equals(uniqueKey, ChatRoom.getUniqueKey(kiiGroup))) {
return kiiGroup;
}
}
KiiGroup kiiGroup = Kii.group(chatRoomName);
KiiUser target = KiiUser.createByUri(Uri.parse(this.chatFriend.getUri()));
target.refresh();
kiiGroup.addUser(target);
kiiGroup.save();
KiiBucket chatBucket = ChatRoom.getBucket(kiiGroup);
KiiUser.getCurrentUser().pushSubscription().subscribeBucket(chatBucket);
KiiTopic topic = target.topicOfThisUser(ApplicationConst.TOPIC_INVITE_NOTIFICATION);
Data data = new Data();
data.put(ChatRoom.CHAT_GROUP_URI, kiiGroup.toUri().toString());
KiiPushMessage message = KiiPushMessage.buildWith(data).build();
topic.sendMessage(message);
Logger.i("sent notification to " + target.toUri().toString());
return kiiGroup;
} catch (Exception e) {
Logger.e("failed to start chat", e);
return null;
}
}
In the above code you can see that a
ChatRoom
is matched to a
KiiGroup
. Then the target user (the one that will be contacted) is added to the group. A
group scope bucket is used here to hold the messages sent by the users and the current user is subscribed to receive push notifications from it (all subscribed users to the group bucket will receive a notification when a new message is posted). Finally a message is built with the chat group URI to notify the target user of the invitation.
In the code above we don’t see any low level configuration of push notifications. However this is done as soon as a user registers or signs in. In the “User Registration and Login” section we described how a user was coupled to
ChatUser
during an initialization process performed in
ChatUserInitializeTask.initializeChatUser()
. That method also initializes the push notification system so users can send/receive messages. Let’s take a closer look at the code:
KiiUser kiiUser = KiiUser.getCurrentUser();
ChatUser user = ChatUser.findByUri(kiiUser.toUri());
if (user == null) {
user = new ChatUser(kiiUser.toUri().toString(), username, email);
user.getKiiObject().save();
}
KiiUser.pushInstallation().install(GCMUtils.register());
KiiTopic topic = KiiUser.topic(ApplicationConst.TOPIC_INVITE_NOTIFICATION);
try {
topic.save();
} catch (ConflictException e) {
}
KiiACL acl = topic.acl();
acl.putACLEntry(new KiiACLEntry(KiiAnyAuthenticatedUser.create(), TopicAction.SEND_MESSAGE_TO_TOPIC, true));
try {
acl.save();
} catch (ACLOperationException e) {
Throwable t = e.getCause();
if (!(t instanceof ConflictException)){
throw e;
}
}
KiiPushSubscription subscription = kiiUser.pushSubscription();
try {
subscription.subscribe(topic);
} catch (ConflictException e) {
}
kiiUser.set(INITIALIZED_USER_KEY, true);
kiiUser.update();
As you can see above push notifications are initialized for the current user, a
KiiTopic
is created to receive invite notifications and its permissions are changed to allow any registered user to send an invitation. Finally the current user is subscribed to the topic so it can receive invitations.
This set the stage for the interchange of user messages. The class
ChatActivity
encapsulates all the chat interaction itself. Let’s take a look at the code called when the “Send” message button is clicked:
this.btnSend.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
btnSend.setEnabled(false);
final ChatMessage message = new ChatMessage(kiiGroup);
message.setMessage(editMessage.getText().toString());
message.setSenderUri(KiiUser.getCurrentUser().toUri().toString());
new SendMessageTask(message).execute();
}
});
In the code above a ChatMessage
is initialized on a the current user group, the message text is grabbed from the UI text box and the sender is set to the current user. Let’s take a look at the SendMessageTask
main background method:
@Override
protected Boolean doInBackground(Void... params) {
try {
this.message.getKiiObject().save();
ChatStamp.sendUsageEvent(this.message);
return true;
} catch (Exception e) {
Logger.e("failed to send messsage", e);
return false;
}
Here the message is saved in the backend triggering a modification in the user group associated with it. All group members will get a push notification when this happens. Note that an
analytics event is also sent (for detail see
ChatStamp.sendUsageEvent()
). Kii Cloud can aggregate this type of events (and also any stored data) to later offer you the chance to create metrics and slice and dice the information (with advanced visualization) from a convenient web console.
To wrap-up this section let’s review some of the details of how to receive this message notifications. In order to detect incoming push notification an AndroidBroadcastReceiver
is set up:
private final BroadcastReceiver handleMessageReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
updateMessage(false);
}
};
private void updateMessage(boolean showProgress) {
new GetMessageTask(showProgress).execute();
}
And GetMessageTask
‘s main background process refreshes the list of messages (including the incoming one) using a ChatRoom
that is associated to the relevant group:
@Override
protected List<ChatMessage> doInBackground(Void... params) {
try {
ChatRoom chatRoom = new ChatRoom(kiiGroup);
List<ChatMessage> messages = null;
if (lastGotTime == null) {
messages = chatRoom.getMessageList();
} else {
messages = chatRoom.getMessageList(lastGotTime);
}
if (messages.size() > 0) {
lastGotTime = messages.get(messages.size() - 1).getKiiObject().getCreatedTime();
}
return messages;
} catch (Exception e) {
Logger.e("failed to get message", e);
return null;
}
}
Sending Photos (Stamps)
With Kii Cloud it’s very easy to attach a files to objects that you save in the backend (you just upload/download them as
object bodies). Here’s a screenshot of two friends sharing images:
Image files are wrapped by the ChatStamp
class which basically holds 3 members: a Kii object (representing the image abstraction and metadata in the backend), a Uri (a unique identifier for the object on the backend) and a File (the image binary itself which will be attached to the Kii object as an object body). Let’s see how the app saves and loads image stamps from the cloud:
public void save() throws Exception {
this.kiiObject.save();
if (this.imageFile != null) {
this.uri = this.kiiObject.toUri().toString();
KiiUploader uploader = this.kiiObject.uploader(KiiChatApplication.getContext(), this.imageFile);
uploader.transfer(null);
File cacheFile = StampCacheUtils.getCacheFile(this.kiiObject.toUri().toString());
this.imageFile.renameTo(cacheFile);
}
}
First of all the wrapped object is saved (this is exactly the same as saving an object on Kii Cloud that has no association to a file). Then, if the
ChatStamp
has an associated image, we retrieve a
file uploader from the Kii object and transfer the image file to the cloud as an object body (the null parameter on
transfer()
means we’re not passing a callback to get feedback from the transfer outcome). If the object already has an object body on the cloud then uploading a new file will replace it. Note that if the upload gets interrupted it will be suspended and a
SuspendedException
will be thrown. In that case, you can resume the upload (see
Resuming Uploads). Finally the associated image file is moved a local cache which will later help to accelerate image loading.
Loading a ChatStamp
image is also simple with almost half the code dedicated to fetching the image from the cache if present:
public Bitmap getImage() {
try {
byte[] image = null;
if (this.imageFile != null) {
image = readImageFromLocal(this.imageFile);
} else if (this.uri != null) {
File cacheFile = StampCacheUtils.getCacheFile(this.uri);
if (cacheFile.exists()) {
image = readImageFromLocal(cacheFile);
} else {
Logger.i("downloading stamp image from KiiCloud");
KiiDownloader downloader = this.kiiObject.downloader(KiiChatApplication.getContext(), cacheFile);
downloader.transfer(null);
image = readImageFromLocal(cacheFile);
}
}
if (image != null) {
return BitmapFactory.decodeByteArray(image, 0, image.length);
}
Logger.w("failed to download stamp image");
return null;
} catch (Exception e) {
Logger.e("failed to download stamp image", e);
return null;
}
}
The concept is very similar to the image saving code but now we use a
file downloader. and load the image directly into our local cache (if it’s not already present in it). Finally, from the local cache we return the image file as a bitmap for displaying.
With this low level file management management facilitated by Kii, the high level retrieval of the images comes down to pairing a
ChatStamp
image to an
ImageView
for display (see class
ChatStampImageFetcher
and
ChatActivity
for high level management of stamps):
if (chatMessage.isStamp()) {
ChatStamp stamp = new ChatStamp(chatMessage);
imageFetcher.fetchStamp(stamp, holder.stamp);
}
Wrap-Up and Project Download
As you can see Kii Cloud gives you all the functionality of a robust, scalable backend by just adding a library to your project and using a simple client side API designed to match your backend management needs but providing a higher level of abstraction.
We’re making available the
full Android project for your reference so you can take a look at the details, learn and maybe use similar classes in your next Kii Cloud powered app. The project uses our own app credentials but you can easily swap those out and use your own. In the these final sections we’ll show you how to properly configure the project so you can run it with no problems:
Dependencies
- android-support-v4.jar (rev. 19.1)
- android-support-v7-appcompat.jar (rev. 19.1)
- google-play-services.jar (for froyo rev. 16)
Project Setup
You need to import following project to your workspace if you use Eclipse.
{SDK-DIR}/extras/android/support/v7/appcompat
{SDK-DIR}/extras/google/google_play_services_froyo/libproject/google-play-services_lib
KiiChat uses
GCM (Google Cloud Messaging) in order to send push notification and GCM needs google account.
So you need to set up the google account on your emulator.
- Make sure you are using emulator targetted on Google API
- Add account on emulator as setting->account