Qiscus SDK


Source link: https://github.com/qiscus/qiscus-sdk-android

Qiscus SDK Android



Quick Start

Create a new app

Register on https://www.qiscus.com/dashboard using your email and password and then create new application

You should create one application per service, regardless of the platform. For example, an app released in both Android and iOS would require only one application to be created in the Dashboard.

All users within the same Qiscus application are able to communicate with each other, across all platforms. This means users using iOS, Android, web clients, etc. can all chat with one another. However, users in different Qiscus applications cannot talk to each other.

Done! Now you can use the APP_ID into your apps and get chat functionality by implementing Qiscus into your app.

integrating SDK with an existing app

Add to your project build.gradle

allprojects {

  repositories {

.....

maven {
 url  "https://dl.bintray.com/qiscustech/maven" 
}

  
}
 
}

Then add to your app module build.gradle

dependencies {

  compile 'com.qiscus.sdk:chat:2.14.0' 
}

Authentication

Init with APP ID

Init Qiscus at your application class with your application ID

public class SampleApps extends Application {

  @Override
  public void onCreate() {

super.onCreate();

Qiscus.init(this, "APP_ID");

  
}
 
}

Setup user using userId and userKey

Before user can start chatting each other, they must login to qiscus engine.

Qiscus.setUser("[email protected]", "userKey")

 .withUsername("Tony Stark")

 .withAvatarUrl("http://avatar.url.com/handsome.jpg")

 .save(new Qiscus.SetUserListener() {

  @Override

  public void onSuccess(QiscusAccount qiscusAccount) {

startActivity(new Intent(this, ConsultationListActivity.class));

  
}

  @Override

  public void onError(Throwable throwable) {

if (throwable instanceof HttpException) {
 //Error response from server

 HttpException e = (HttpException) throwable;

 try {

  String errorMessage = e.response().errorBody().string();

  Log.e(TAG, errorMessage);

  showError(errorMessage);

 
}
 catch (IOException e1) {

  e1.printStackTrace();

 
}

}
 else if (throwable instanceof IOException) {
 //Error from network

 showError("Can not connect to qiscus server!");

}
 else {
 //Unknown error

 showError("Unexpected error!");

}

  
}

 
}
);

Setup user using JWT Token

Another alternative is using jwt token. Using this authorization schema, you can only save your user unique identity such as email in your back-end server. You will no need to save two password, one for Qiscus SDK and one for your authorization logic. All you need is generating identity token using JWT in order to login or register an user.

First, you need to get nonce using QiscusApi.requestNonce() method. You do not need to send any parameter. Nonce will be expired 10 minutes after request. Afterwards, in your application back-end server you must generate the JWT token using this:

JOSE header

{

"alg": "HS256",  // must be HMAC algorithm
"typ": "JWT", // must be JWT
"ver": "v2" // must be v2 
}
 

JWT claim set

{

"iss": "QISCUS SDK APP ID", // your qiscus app id, can obtained from dashboard
"iat": 1502985644, // current timestamp in unix
"exp": 1502985704, // An arbitrary time in the future when this token should expire. In epoch/unix time. We encourage you to limit 2 minutes
"nbf": 1502985644, // current timestamp in unix
"nce": "nonce", // nonce string from nonce API
"prn": "[email protected]", // your user identity such as email
"name": "shark laser", // optional, string for user name
"avatar_url": "" // optional, string url of user avatar 
}
 

Above JOSE header and claim set must be signed using QISCUS SDK SECRET key that you can get from dashboard with algorithm HMAC (HS256, HS384 or HS512). Then you can verify your identity token using Qiscus.setUser(String token, Qiscus.SetUserListener listener) method or Qiscus.setUserAsObservable(String token) method if you want to using RxJava. Here sample code how to set user using jwt token.

QiscusApi.getInstance().requestNonce() //Request nonce from qiscus api

.flatMap(nonce -> YourAppApi.getInstance().getJwtToken(nonce)) //Get jwt token from your backend api

.flatMap(Qiscus::setUserAsObservable) //Set qiscus user with the jwt token

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe(qiscusAccount -> {

startActivity(new Intent(this, ConsultationListActivity.class));

}
, throwable -> {

if (throwable instanceof HttpException) {
 //Error response from server

 HttpException e = (HttpException) throwable;

 try {

  String errorMessage = e.response().errorBody().string();

  Log.e(TAG, errorMessage);

  showError(errorMessage);

 
}
 catch (IOException e1) {

  e1.printStackTrace();

 
}

}
 else if (throwable instanceof IOException) {
 //Error from network

 showError("Can not connect to qiscus server!");

}
 else {
 //Unknown error

 showError("Unexpected error!");

}

}
);

Updating a User Profile and Avatar

Updating user profile calls Qiscus.updateUser(name, avatar, listener) :

Qiscus.updateUser("Tony Stark", "http://avatar.url.com/handsome.jpg", new Qiscus.SetUserListener() {

 @Override

 public void onSuccess(QiscusAccount qiscusAccount) {

  startActivity(new Intent(this, ConsultationListActivity.class));

 
}

  @Override

 public void onError(Throwable throwable) {

  if (throwable instanceof HttpException) {
 //Error response from server

HttpException e = (HttpException) throwable;

try {

 String errorMessage = e.response().errorBody().string();

 Log.e(TAG, errorMessage);

 showError(errorMessage);

}
 catch (IOException e1) {

 e1.printStackTrace();

}

  
}
 else if (throwable instanceof IOException) {
 //Error from network

showError("Can not connect to qiscus server!");

  
}
 else {
 //Unknown error

showError("Unexpected error!");

  
}

 
}

}
);

Disconnect or Logout

Whenever you no longer want the user to receive update

Qiscus.clearUser();

Chat Rooms

Creating 1-to-1 chat

Start chat with target is very easy, all you need is just call

Qiscus.buildChatWith("[email protected]")

 .build(this, new Qiscus.ChatActivityBuilderListener() {

  @Override

  public void onSuccess(Intent intent) {

startActivity(intent);

  
}

  @Override

  public void onError(Throwable throwable) {

if (throwable instanceof HttpException) {
 //Error response from server

 HttpException e = (HttpException) throwable;

 try {

  String errorMessage = e.response().errorBody().string();

  Log.e(TAG, errorMessage);

  showError(errorMessage);

 
}
 catch (IOException e1) {

  e1.printStackTrace();

 
}

}
 else if (throwable instanceof IOException) {
 //Error from network

 showError("Can not connect to qiscus server!");

}
 else {
 //Unknown error

 showError("Unexpected error!");

}

  
}

 
}
);

Creating a Group Room

Qiscus also support group chat. To create new group chat, all you need is just call

Qiscus.buildGroupChatRoom("GroupName", Arrays.asList("[email protected]", "[email protected]", "[email protected]"))

 .withAvatar("http://avatar.url.com/group.jpg")

 .build(new Qiscus.ChatBuilderListener() {

  @Override

  public void onSuccess(QiscusChatRoom qiscusChatRoom) {

startActivity(QiscusGroupChatActivity.generateIntent(MainActivity.this, qiscusChatRoom));

  
}

 @Override

  public void onError(Throwable throwable) {

if (throwable instanceof HttpException) {
 //Error response from server

 HttpException e = (HttpException) throwable;

 try {

  String errorMessage = e.response().errorBody().string();

  Log.e(TAG, errorMessage);

  showError(errorMessage);

 
}
 catch (IOException e1) {

  e1.printStackTrace();

 
}

}
 else if (throwable instanceof IOException) {
 //Error from network

 showError("Can not connect to qiscus server!");

}
 else {
 //Unknown error

 showError("Unexpected error!");

}

  
}

  
}
);

for accessing room that created by this call, you need to call it with its roomId. This methode is always creating new chat room.

Get a room by room id

When you already know your chat room id, you can easily go to that room. Just call

QiscusApi.getChatRoom(int roomId);

For example :

QiscusApi.getInstance()

.getChatRoom(123)

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.map(qiscusChatRoom -> QiscusGroupChatActivity.generateIntent(this, qiscusChatRoom))

.subscribe(this::startActivity, throwable -> {

if (throwable instanceof HttpException) {
 //Error response from server

 HttpException e = (HttpException) throwable;

 try {

  String errorMessage = e.response().errorBody().string();

  Log.e(TAG, errorMessage);

  showError(errorMessage);

 
}
 catch (IOException e1) {

  e1.printStackTrace();

 
}

}
 else if (throwable instanceof IOException) {
 //Error from network

 showError("Can not connect to qiscus server!");

}
 else {
 //Unknown error

 showError("Unexpected error!");

}

}
);

Create or join room by defined id

You probably want to set defined id for the room you are creating so that the id can be reference for users to get into.

Usual usage for this is when user create common room or channel which expecting other users can join to the same channel by knowing the channel name or id, you can use the channel name or id as qiscus room defined id.

Additional note: If room with predefined unique id is not exist then it will create a new one with requester as the only one participant. Otherwise, if room with predefined unique id is already exist, it will return that room and add requester as a participant.

When first call (room is not exist), if requester did not send avatar_url and/or room name it will use default value. But, after the second call (room is exist) and user (requester) send avatar_url and/or room name, it will be updated to that value. Object changed will be true in first call and when avatar_url or room name is updated.

Qiscus.buildGroupChatRoomWith("UniqueId")

.withName("RoomName")

.withAvatar("http://avatar.url.com/group.jpg")

.build(new Qiscus.ChatBuilderListener() {

 @Override

 public void onSuccess(QiscusChatRoom qiscusChatRoom) {

  startActivity(QiscusGroupChatActivity.generateIntent(MainActivity.this, qiscusChatRoom));

 
}

  @Override

 public void onError(Throwable throwable) {

  if (throwable instanceof HttpException) {
 //Error response from server

HttpException e = (HttpException) throwable;

try {

 String errorMessage = e.response().errorBody().string();

 Log.e(TAG, errorMessage);

 showError(errorMessage);

}
 catch (IOException e1) {

 e1.printStackTrace();

}

  
}
 else if (throwable instanceof IOException) {
 //Error from network

showError("Can not connect to qiscus server!");

  
}
 else {
 //Unknown error

showError("Unexpected error!");

  
}

 
}

}
);

Inviting users to an existing Room

Currently we recommend to invite user into existing room through our REST API for simplicity and security reason

Leaving a Group Room

Currently we recommend to kick user out of specific room through our REST API for simplicity and security reason

Get room list

To get all room list you can call QiscusApi.getInstance().getChatRooms(int page, int limit, boolean showMembers), page start from 1, limit indicate the max rooms per page, showMembers is flag for load room members also or not. Here sample code:

QiscusApi.getInstance().getChatRooms(1, 20, true)

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe(qiscusChatRooms -> {

 //qiscusChatRooms is list of rooms result.

}
, throwable -> {

 //Something went wrong

}
);

Event Handler

Implement QiscusChatPresenter.View to your Activity Or Fragment

public class MainActivity extends AppCompatActivity implements QiscusChatPresenter.View {

  private QiscusChatPresenter qiscusChatPresenter;

@Override
  protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

qiscusChatPresenter = new QiscusChatPresenter(this, qiscusChatRoom);

  
}

@Override
  public void initRoomData(QiscusChatRoom qiscusChatRoom, List<QiscusComment> comments) {

// Do your implementation
  
}

@Override
  public void showComments(List<QiscusComment> qiscusComments) {

// Do your implementation
  
}

@Override
  public void onLoadMore(List<QiscusComment> qiscusComments) {

// Do your implementation
  
}

@Override
  public void onSendingComment(QiscusComment qiscusComment) {

// Do your implementation
  
}

@Override
  public void onSuccessSendComment(QiscusComment qiscusComment) {

// Do your implementation
  
}

@Override
  public void onFailedSendComment(QiscusComment qiscusComment) {

// Do your implementation
  
}

@Override
  public void onNewComment(QiscusComment qiscusComment) {

// Do your implementation
  
}

@Override
  public void onCommentDeleted(QiscusComment qiscusComment) {

// Do your implementation
  
}

@Override
  public void refreshComment(QiscusComment qiscusComment) {

// Do your implementation
  
}

@Override
  public void updateLastDeliveredComment(int lastDeliveredCommentId) {

// Do your implementation
  
}

@Override
  public void updateLastReadComment(int lastReadCommentId) {

// Do your implementation
  
}

@Override
  public void onFileDownloaded(File file, String mimeType) {

// Do your implementation
  
}

@Override
  public void onUserTyping(String user, boolean typing) {

// Do your implementation
  
}
 
}

EventBus, so you can listen event from anywhere, It does not matter whether it's an activity or not. For example from your application class

public class SampleApps extends Application {

@Override
  public void onCreate() {

super.onCreate();

Qiscus.init(this, "APP_ID");

 EventBus.getDefault().register(this);

  
}

/** 
  * Subscribe anywhere to listen new message if you just got new message from someone 
  */
  @Subscribe
  public void onGetNewQiscusComment(QiscusCommentReceivedEvent event) {

QiscusComment qiscusComment = event.getQiscusComment();

// Do your implementation
  
}

 /** 
  * Call QiscusPusherApi.getInstance().listenRoom(qiscusChatRoom);
 to get room event from anywhere at your application 
  */
  @Subscribe
  public void onGetNewQiscusRoomEvent(QiscusChatRoomEvent event) {

switch (event.getEvent()) {

 case TYPING:

  // Someone is typing on this room event.getRoomId();


  break;

 case DELIVERED:

  // Someone just received your message event.getCommentId()

  break;

 case READ:

  // Someone just read your message event.getCommentId()

  break;

}

  
}

/** 
  * Call QiscusPusherApi.getInstance().listenUserStatus("[email protected]");
 to listen status of [email protected] 
  */
  @Subscribe
  public void onUserStatusUpdated(QiscusUserStatusEvent event) {

// A user just changed his/her status from (online or offline)

// event.getUser() changed to event.isOnline() at event.getLastActive()
  
}
 
}

UI Customization

Theme Customization

Boring with default template? You can customized it, try it!, we have more items than those below code, its just example.

Qiscus.getChatConfig()

 .setStatusBarColor(R.color.blue)

 .setAppBarColor(R.color.red)

 .setTitleColor(R.color.white)

 .setLeftBubbleColor(R.color.green)

 .setRightBubbleColor(R.color.yellow)

 .setRightBubbleTextColor(R.color.white)

 .setRightBubbleTimeColor(R.color.grey)

 .setTimeFormat(date -> new SimpleDateFormat("HH:mm").format(date));

UI Source code

If you want full customisations, you can modify everything on the view by forking our repository or just right away modifying our ** CustomChatActivity.java **based on your needs.

Push Notifications

First install FCM to your apps, you can follow this steps. You can skip this step, if your apps already use FCM. Then put your api key to qiscus dashboard.

Now lets integrate with Qiscus client sdk, first enable FCM at Qiscus chat config.

Qiscus.getChatConfig().setEnableFcmPushNotification(true);

After that, lets change your firebase service to extend Qiscus firebase service instead of firebase service class.

public class MyFirebaseIdService extends QiscusFirebaseIdService {

  @Override
  public void onTokenRefresh() {

super.onTokenRefresh();
 // Must call super

 //Below is your own apps specific code

// e.g register the token to your backend

String refreshedToken = FirebaseInstanceId.getInstance().getToken();

sendTokenToMyBackend(refreshedToken);

  
}
 
}
public class MyFirebaseMessagingService extends QiscusFirebaseService {

  @Override
  public void onMessageReceived(RemoteMessage remoteMessage) {

if (handleMessageReceived(remoteMessage)) {
 // For qiscus

 return;

}

 //Your FCM PN here
  
}
 
}

If extension is not possible or desirable, use the following code the ensure Qiscus handle the FCM.

public class MyFirebaseIdService extends FirebaseInstanceIdService {

  @Override
  public void onTokenRefresh() {

super.onTokenRefresh();

String refreshedToken = FirebaseInstanceId.getInstance().getToken();

 //Register token to qiscus

Qiscus.setFcmToken(refreshedToken);

 //Below is your own apps specific code

// e.g register the token to your backend

sendTokenToMyBackend(refreshedToken);

  
}
 
}
public class MyFirebaseMessagingService extends FirebaseMessagingService {

  @Override
  public void onMessageReceived(RemoteMessage remoteMessage) {

if (QiscusFirebaseService.handleMessageReceived(remoteMessage)) {
 // For qiscus

 return;

}

 //Your FCM PN here
  
}
 
}

Offline Messages

Post Messages

During post message, if you don't have any internet connection, message will be store locally and will be automatically being send once your internet connection is back. For you want to enqueue a message manually you can call this api:

QiscusApi.getInstance().postComment(qiscusComment)

.doOnSubscribe(() -> Qiscus.getDataStore().addOrUpdate(qiscusComment))

.doOnError(throwable -> {

 qiscusComment.setState(QiscusComment.STATE_PENDING);

 Qiscus.getDataStore().addOrUpdate(qiscusComment);

}
)

.subscribeOn(Schedulers.io())

.observeOn(AndroidSchedulers.mainThread())

.subscribe(commentSend -> {

 //Success

}
, throwable -> {

 //we will automatically retry again later

}
);

Get Messages

Messages are stored locally so you can still access the messages when you don't have internet connection. However any new messages will not being received after you have your internet connection back. To access data locally, you can use QiscusDataStore, here sample code to get local room and message

QiscusChatRoom room = Qiscus.getDataStore().getChatRoom(roomId);
 List<QiscusComment> comments = Qiscus.getDataStore().getComments(room.getLastTopicId(), count);

Miscellaneous

Android Support Libraries

Qiscus SDK is using appcompat libraries to support some features. If your apps using appcompat too, we highly recommended to using the latest stable appcompat version, or using the same version with Qiscus SDK. You can check the appcompat version of Qiscus SDK here. You can also force Qiscus SDK to use your apps appcompat version. Use "exclude group" at your build.gradle, for example:

//Qiscus sdk without android support libraries compile('com.qiscus.sdk:chat:2.14.0') {

  transitive = true
  exclude group: 'com.android.support' 
}
  //Qiscus sdk needs all of this android support libraries //Just add the same version with your apps dependencies compile 'com.android.support:support-v4:yourVersion' compile 'com.android.support:appcompat-v7:yourVersion' compile 'com.android.support:recyclerview-v7:yourVersion' compile 'com.android.support:cardview-v7:yourVersion' compile 'com.android.support:design:yourVersion' compile 'com.android.support:customtabs:yourVersion' compile 'com.android.support:support-v13:yourVersion'

If you have problem can not download android support libraries, please add Google's Maven repository to your project build.gradle

allprojects {

  repositories {

.....

maven {
 url  "https://maven.google.com" 
}

  
}
 
}

RxJava support

// Setup qiscus account with rxjava example Qiscus.setUser("[email protected]", "password")

 .withUsername("Tony Stark")

 .withAvatarUrl("http://avatar.url.com/handsome.jpg")

 .save()

 .subscribeOn(Schedulers.io())

 .observeOn(AndroidSchedulers.mainThread())

 .subscribe(qiscusAccount -> {

  startActivity(new Intent(this, ConsultationListActivity.class));

 
}
, throwable -> {

if (throwable instanceof HttpException) {
 //Error response from server

 HttpException e = (HttpException) throwable;

 try {

  String errorMessage = e.response().errorBody().string();

  Log.e(TAG, errorMessage);

  showError(errorMessage);

 
}
 catch (IOException e1) {

  e1.printStackTrace();

 
}

}
 else if (throwable instanceof IOException) {
 //Error from network

 showError("Can not connect to qiscus server!");

}
 else {
 //Unknown error

 showError("Unexpected error!");

}

 
}
);

  // Start a chat activity with rxjava example

 Qiscus.buildChatWith("[email protected]")

 .build(this)

 .subscribeOn(Schedulers.io())

 .observeOn(AndroidSchedulers.mainThread())

 .subscribe(intent -> {

  startActivity(intent);

 
}
, throwable -> {

if (throwable instanceof HttpException) {
 //Error response from server

 HttpException e = (HttpException) throwable;

 try {

  String errorMessage = e.response().errorBody().string();

  Log.e(TAG, errorMessage);

  showError(errorMessage);

 
}
 catch (IOException e1) {

  e1.printStackTrace();

 
}

}
 else if (throwable instanceof IOException) {
 //Error from network

 showError("Can not connect to qiscus server!");

}
 else {
 //Unknown error

 showError("Unexpected error!");

}

 
}
);

Doesn't like RxJava

For you who doesn't comport with RxJava method, we provide utility class to execute RxJava method. Here sample code how to get specific qiscus chat room by id.

QiscusRxExecutor.execute(QiscusApi.getInstance().getChatRoom(123), new QiscusRxExecutor.Listener<QiscusChatRoom>() {

@Override

public void onSuccess(QiscusChatRoom result) {

 //Success getting the room

}

 @Override

public void onError(Throwable throwable) {

 //Something went wrong

}

  
}
);

Proguard

If you are using Proguard in your application, make sure you add Proguard rules of Qiscus from Qiscus Proguard Rules to your Proguard rules.

Sample Application

You can get the sample apps here

Resources

This is a library which allows you to create a record button view in android.

Custom slider for Android build from scratch.

A helper class for Toolbar configuration.

Convalida is a simple, lightweight and powerful field validation library for Android.

This is a vertical seekbar like in the IOS 11 control center with an image at the bottom.

This is an Android library for cropping images. Move images smoothly, and crop images precisely.

Enjoy cropping!

Topics


2D Engines   3D Engines   9-Patch   Action Bars   Activities   ADB   Advertisements   Analytics   Animations   ANR   AOP   API   APK   APT   Architecture   Audio   Autocomplete   Background Processing   Backward Compatibility   Badges   Bar Codes   Benchmarking   Bitmaps   Bluetooth   Blur Effects   Bread Crumbs   BRMS   Browser Extensions   Build Systems   Bundles   Buttons   Caching   Camera   Canvas   Cards   Carousels   Changelog   Checkboxes   Cloud Storages   Color Analysis   Color Pickers   Colors   Comet/Push   Compass Sensors   Conferences   Content Providers   Continuous Integration   Crash Reports   Credit Cards   Credits   CSV   Curl/Flip   Data Binding   Data Generators   Data Structures   Database   Database Browsers   Date &   Debugging   Decompilers   Deep Links   Dependency Injections   Design   Design Patterns   Dex   Dialogs   Distributed Computing   Distribution Platforms   Download Managers   Drawables   Emoji   Emulators   EPUB   Equalizers &   Event Buses   Exception Handling   Face Recognition   Feedback &   File System   File/Directory   Fingerprint   Floating Action   Fonts   Forms   Fragments   FRP   FSM   Functional Programming   Gamepads   Games   Geocaching   Gestures   GIF   Glow Pad   Gradle Plugins   Graphics   Grid Views   Highlighting   HTML   HTTP Mocking   Icons   IDE   IDE Plugins   Image Croppers   Image Loaders   Image Pickers   Image Processing   Image Views   Instrumentation   Intents   Job Schedulers   JSON   Keyboard   Kotlin   Layouts   Library Demos   List View   List Views   Localization   Location   Lock Patterns   Logcat   Logging   Mails   Maps   Markdown   Mathematics   Maven Plugins   MBaaS   Media   Menus   Messaging   MIME   Mobile Web   Native Image   Navigation   NDK   Networking   NFC   NoSQL   Number Pickers   OAuth   Object Mocking   OCR Engines   OpenGL   ORM   Other Pickers   Parallax List   Parcelables   Particle Systems   Password Inputs   PDF   Permissions   Physics Engines   Platforms   Plugin Frameworks   Preferences   Progress Indicators   ProGuard   Properties   Protocol Buffer   Pull To   Purchases   Push/Pull   QR Codes   Quick Return   Radio Buttons   Range Bars   Ratings   Recycler Views   Resources   REST   Ripple Effects   RSS   Screenshots   Scripting   Scroll Views   SDK   Search Inputs   Security   Sensors   Services   Showcase Views   Signatures   Sliding Panels   Snackbars   SOAP   Social Networks   Spannable   Spinners   Splash Screens   SSH   Static Analysis   Status Bars   Styling   SVG   System   Tags   Task Managers   TDD &   Template Engines   Testing   Testing Tools   Text Formatting   Text Views   Text Watchers   Text-to   Toasts   Toolkits For   Tools   Tooltips   Trainings   TV   Twitter   Updaters   USB   User Stories   Utils   Validation   Video   View Adapters   View Pagers   Views   Watch Face   Wearable Data   Wearables   Weather   Web Tools   Web Views   WebRTC   WebSockets   Wheel Widgets   Wi-Fi   Widgets   Windows   Wizards   XML   XMPP   YAML   ZIP Codes