Giter Site home page Giter Site logo

docs's Introduction

Chat SDK Development Guide

Contents

  1. Interacting with the server - how to make requests and receive responses
  2. Handing entities - Creating, modifying and deleting entities
  3. Custom Authentication Authenticate with Firebase using an existing app server
  4. Code Examples - How to perform common tasks
  5. UI Customization - How to customize the UI and network interactions

Architecture and getting started

The easiest way to get started is by understanding the core principles that are used in Chat SDK. Once you understand these principles and design patterns, it will make customization much easier.

High level Architecture

The Chat SDK is broken down into the following major parts:

  1. Core: This includes interface definitions and common services.
  2. CoreData: This contains the ORM which stores all the user, thread and message data
  3. UI: This component contains all the app's user interface
  4. NetworkAdapter: This component handles communication with the network.

Now that you know the basic structure, we're going to go into some detail about some important classes and design patterns that can be used to manipulate the Chat SDK.

Interacting With The Server

In this section an explanation will be provide of how to interact with the messaging server. Performing tasks like creating threads, sending messages, working with users.

Core Concepts

The client server interaction generally looks like this:

The user performs some action -> A request is made to the server -> The server responds -> The UI is updated

For example, if a user writes a message and clicks "send", the message needs to be sent to the server. Once that's done, it needs to be displayed in the chat view.

To perform these actions, you need to understand the Chat SDK service architecture.

  1. NetworkManager: A singleton that makes the services available to the whole app
  2. NetworkAdapter: A wrapper class that contains references to all the possible services
  3. Handler: A service that contains a group of related functions
  4. Function: An individual action that can be performed.

This can be illustrated with some simple examples:

Creating a public thread

To create a public thread, the UI calls the following:

iOS

[[BNetworkManager sharedManager].a.publicThread createPublicThreadWithName: name]

Here we have: Network Manager -> Adapter -> Handler -> Function

Or a more concise form:

[BChatSDK.publicThread createPublicThreadWithName: name]

Android

NetworkManager.shared().a.publicThread().createPublicThreadWithName(threadName)

Here we have: Network Manager -> Adapter -> Handler -> Function

Or the concise form:

ChatSDK.publicThread().createPublicThreadWithName(threadName)

Note: The NM class is just a convenience class that contains static getter functions to make calls to the NetworkManager more concise. The NM class should always be used unless you want to set a new handler.

Takeaway

The most important point is that if you want to find out which services are available, you should start by looking at the handler classes. These are documented and their names give you a good idea as to what they do.

You can find a full list of handler classes in the BNetworkFacade protocol for iOS and the BaseNetworkAdapter class for Android.

Case Study
Imagine you wanted to find out how to send an image message. First you would look at the BNetworkFacade or the BaseNetworkAdapter and you would see the following property ImageMessageHandler. If you open that interface you would see the function sendMessageWithImage. Calling this would cause the image to be uploaded to the server and then the image message would be added to the thread.

Handling the response

After we've made a request to the server, we will need to wait some time for the server to respond. Generally speaking there are two ways to handle the response:

  1. Using the Promise or Observable returned by the function
  2. Listening for app level notifications

Promises and Observables

This document won't go into a full explanation of promises and observables because they are very common design patterns and there are plenty of excellent explanations available online. The basic idea is that the function will return an object which will allow you to register a callback to receive a notification when the function server response comes back.

Note
A common mistake when using Observables is to forget to call subscribe(). Unless you call subscribe(), the method won't actually be executed! For example, pushUser() will do nothing. You have to call pushUser().subscribe() and then the method will be executed.

In our example of creating a public thread:

iOS

[BChatSDK.publicThread createPublicThreadWithName:name].thenOnMain(^id(id<PThread> thread) {
    // Success
    return Nil;
}, ^id(NSError * error) {
	// Failure
    return error;
});

Android

ChatSDK.publicThread().createPublicThreadWithName(threadName)
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new BiConsumer<Thread, Throwable>() {
    @Override
    public void accept(Thread thread, Throwable throwable) throws Exception {
        if(throwable == null) {
          // Success
        }
        else {
          // Handle error
	    }
});

In each example, we can define a function that should be called if the request is successful and another that will be called if there is an error.

Note
You can use the observeOn function to tell the observable to execute the result on the main thread.

App level events

There are some events that don't happen as a result of a function that we have called. For example, if another user sends us a message. These events are handled slightly differently in iOS and Android:

iOS

In iOS these events are handled by notifications. You can find a full list in the BNetworkFacade.h file. For example, if we wanted to receive notifications whenever a new message is received:

[_notificationList add:[[NSNotificationCenter defaultCenter] addObserverForName:bNotificationMessageAdded object:Nil queue:Nil usingBlock:^(NSNotification * notification) {
    dispatch_async(dispatch_get_main_queue(), ^{
        id<PMessage> messageModel = notification.userInfo[bNotificationMessageAddedKeyMessage];
    });
}]];

A couple of key points:

  1. All notifications are dispatched on a background thread so if you want to update the UI, you will need to use GSD to run your code on the main thread.

  2. Sometimes the notification will contain a payload that's stored in the userInfo dictionary. In the example above, you can access the message. You can see what will be available by looking at the BNetworkFacade. Below the name of the notification, there will be one or more keys that can be used to get the user info objects.

Android

In Android events are sent through an event bus using a PublishSubject. You can access the event bus using the following:

ChatSDK.events().source()

or

ChatSDK.events().sourceOnMain()

When you subscribe to this source, you will receive a stream of NetworkEvent objects. You can also apply filters. For example:

ChatSDK.events().sourceOnMain()
    .filter(NetworkEvent.filterType(EventType.MessageAdded, EventType.ThreadReadReceiptUpdated))
    .filter(NetworkEvent.filterThreadEntityID(thread.getEntityID()))
    .subscribe(...);

Here we are only listening to the event types: MessageAdded and ThreadReadReceiptUpdated for a particular thread.

To get a stream of all incoming messages, the following could be used:

Disposable d = ChatSDK.events().sourceOnMain()
        .filter(NetworkEvent.filterType(EventType.MessageAdded))
        .subscribe(new Consumer<NetworkEvent>() {
            @Override
            public void accept(NetworkEvent networkEvent) throws Exception {
                Message message = networkEvent.message;
            }
        });

To stop listening we can use the disposable:

d.dispose()

The Chat SDK also includes a helper class called DisposableList. You can add multiple disposables to this list and then call list.dispose() to dispose of them all at one time.

Note: It's important to dispose of all of your observables when you destroy an activity. Otherwise, the observer will persist and may try to perform actions on an activity which no longer exists. This will cause the app to crash.

Handling Entities

Instant Messaging basics

In an instant messenger there are three core entities:

  1. User (BUser, co.chatsdk.core.dao.User)
  2. Thread (BThread, co.chatsdk.core.dao.Thread)
  3. Message (BMessage, co.chatsdk.core.dao.Message)

Note: In iOS, the entities are hidden behind protocol. For example, rather than dealing with a BUser object directly, we would always use the id<PUser> protocol. Because of platform differences, this isn't possible in Android so we use the database object directly.

User has a many-to-many relationship with thread and thread has a one-to-many relationship with message. We will go into more detail about how to create and request these entities later in this guide.

These entites exist both on the server and locally in the app's database. Both iOS and Android use an Object Relational Maping (ORM) to simplify data persistence. iOS uses CoreData and Android uses GreenDAO.

Common tasks are handled by the BStorageManager singleton iOS and co.chatsdk.core.session.StorageManager singleton in Android.

Creating a new Entity

iOS

id<PMessage> message = [BChatSDK.db createEntity:bMessageEntity];

Android

Message message = ChatSDK.db().createEntity(Message.class);

ChatSDK.db()

Saving an entity

iOS

message.type = @(bMessageTypeText);
[BChatSDK.db save];

Android

message.setMessageType(MessageType.Text);
message.update();

Fetching an entity using it's entity ID

iOS

id<PUser> user = [BChatSDK.db fetchEntityWithID:userEntityID withType:bUserEntity];

Android

User user = ChatSDK.db().fetchEntityWithEntityID(userEntityID, User.class);

There is also a useful fetchOrCreate method which will try to fetch an entity and if it doesn't exist, return a new entity.

Deleting entities

iOS

id<PUser> user = [BChatSDK.db fetchEntityWithID:userEntityID withType:bUserEntity];
[BChatSDK.db deleteEntity: user]

Android

User user = ChatSDK.db().fetchEntityWithEntityID(userEntityID, User.class);
DaoCore.deleteEntity(user);

Queries

More advanced queries are also possible.

iOS

Get the current user's contacts.

NSPredicate * predicate = [NSPredicate predicateWithFormat:@"type = %@ AND owner = %@", @(bUserConnectionTypeContact), currentUser];
NSArray * entities = [BChatSDK.db fetchEntitiesWithName:bUserConnectionEntity withPredicate:predicate];

Android

List<ContactLink> contactLinks = DaoCore.fetchEntitiesWithProperty(ContactLink.class,
        ContactLinkDao.Properties.LinkOwnerUserDaoId, currentUser.getId());

For more advanced queries it's recommended to look at the documentation for CoreData and GreenDAO.

Code Examples

In this section concrete examples will be provided of how to perform common tasks.

Authentication

Authentication is handled by the BAuthenticationHandler in iOS and the AuthenticationHandler in Android.

It is very important to authenticate your user before you load up any of the Chat SDK views. Failure to do this will cause the app to crash.

Authenticate a new user

To authenticate a user you need to pass an account details object to the authenticate method in the authentication handler.

iOS

BAccountDetails * accountDetails = [BAccountDetails username: @"[email protected]" password:@"some password"];
[BChatSDK.auth authenticate: accountDetails].thenOnMain(...);

Android

AccountDetails details = AccountDetails.username("[email protected]", "some password");
ChatSDK.auth().authenticate(details).subscribe(...);

Registering a new user

To register a new user, just use the Register type.

iOS

BAccountDetails * accountDetails = [BAccountDetails signUp: @"Joe" password:@"Joe123"];
[BChatSDK.auth authenticate: accountDetails].thenOnMain(...);

Android

AccountDetails details = AccountDetails.signUp("Joe", "Joe123");
ChatSDK.auth().authenticate(details).subscribe(...);

Authenticate using cached details

The Chat SDK will automatically cache the user's login details saving them from logging in each time the app opens.

iOS

[BChatSDK.auth authenticate].thenOnMain(^id(id success) {
    ...
    return Nil;
}, Nil);

Android

ChatSDK.auth().authenticate().subscribe(...);

Logging out

iOS

[BChatSDK.auth logout];

Android

ChatSDK.auth().logout().subscribe(...);

Custom Authentication

With Firebase, you can also authenticate using a custom token that's been generated on your server. It works like this:

  1. The user authenticates with your server
  2. The server generates a new authentication token based on the user's unique ID
  3. The token is passed back to the client and into the Chat SDK
Generating the token

To generate a token, you should follow the Firebase custom authentication guide.

Firebase also has an Admin SDK for Node.js, Java, Python and Go which makes the process more straightforward. you can install it using this guide.

In PHP, an implementation may look like this:

// Get your service account's email address and private key from the JSON key file
$service_account_email = "[email protected]";
$private_key = "-----BEGIN PRIVATE KEY-----...";

function create_custom_token($uid, $is_premium_account) {
  global $service_account_email, $private_key;

  $now_seconds = time();
  $payload = array(
    "iss" => $service_account_email,
    "sub" => $service_account_email,
    "aud" => "https://identitytoolkit.googleapis.com/google.identity.identitytoolkit.v1.IdentityToolkit",
    "iat" => $now_seconds,
    "exp" => $now_seconds+(60*60),  // Maximum expiration time is one hour
    "uid" => $uid,
    "claims" => array(
      "premium_account" => $is_premium_account
    )
  );
  return JWT::encode($payload, $private_key, "RS256");
}

The id should be the id your server uses to identify the user who is currently logged in. This token should be passed back to the app.

Authenticating on the client

iOS

BAccountDetails * details = [BAccountDetails token:@"Your token"];
[BChatSDK.auth authenticate: details].thenOnMain(...);

Android

AccountDetails details = AccountDetails.token("Your token");
ChatSDK.auth().authenticate(details).subscribe(...);

Users

User meta data

The user entity is designed to be customisable. For that reason most of the user's properties are stored as key-value pairs. Some of the more common properties also have getters and setters for convenience. Custom data can be set and retrieved by doing the following:

iOS

// Set the value
[user setMetaString:@"value" forKey:@"Key"];

// Get the value
[user metaStringForKey:@"key"];

Android

// Set the value
user.setMetaString("key", "value");

// Get the value
user.metaStringForKey("key");

When you push a user, these values will automatically be synchronized with the server and to all other devices have subscribed to that user.

Pushing a user's details to the server

To synchronize the current user with the server the following method can be used:

iOS

[BChatSDK.core pushUser].thenOnMain(...);

Android

ChatSDK.core().pushUser().subscribe(...);

Subscribing to a user

In most cases, the Chat SDK will update the local database automatically if a user's details change on the remote server. By default, the Chat SDK will monitor the following users:

  1. Contacts
  2. Users who are members of our threads

In case you want to handle this manually, you can use the following methods:

iOS

[BChatSDK.core observeUser: @"entityID"]

Android

// Subscribe to user
ChatSDK.core().userOn(user);

// Unsubscribe to the user
ChatSDK.core().userOff(user);

Getting a user given the user's entity ID

In some cases, you may have a user's entity ID and want to access the user object. To do this, you need to use the user wrapper object.

iOS

CCUserWrapper * wrapper = [CCUserWrapper userWithEntityID: userID];
[wrapper metaOn]
[wrapper onlineOn]
id<PUser> user = [wrapper model]

Android

UserWrapper wrapper = UserWrapper.initWithEntityId(userID);
wrapper.metaOn();
wrapper.onlineOn();
User user = wrapper.getModel();  

The user wrapper object is used to synchronize the local user object which is stored in the database with the remote user data stored in Firebase. When we call metaOn we are adding listeners so whenever the user's meta data changes on the server, the local database object will be automatically updated. onlineOn does a similar thing but with the user's presence (online/offline) state.

Contacts

Adding a contact

iOS

[BChatSDK.contact addContact:user withType:bUserConnectionTypeContact];

Android

ChatSDK.contact().addContact(user, ConnectionType.Contact).subscribe(...);

Getting a list of contacts

iOS

NSArray * users = [BChatSDK.contact contactsWithType:bUserConnectionTypeContact];

Android

List<User> users = ChatSDK.contact().contacts();

Adding a contact from a user ID

If you want to manage your contact list on your own server, you can use the following code to display these users on the contacts screen.

  1. You would need to download the list of contacts from your server. The list should be composed of the entity IDs of the users.

  2. Download the user object using the entity ID. See instructions here.

  3. Add the user to contacts:

iOS

[BChatSDK.contact addContact: user withType: bUserConnectionTypeContact];

Android

ChatSDK.contact().addContact(user, ConnectionType.Contact);
  1. We only need to do the above steps once. Once the user is added to contacts, whenever the app launches, all the necessary listeners will be added and the user will be displayed in the contacts view.

Threads

In the Chat SDK, a thread represents a conversation between a number of users. Threads can have different types: private, public, group etc...

In iOS, threads are handled by the Core Handler. In Android, they are handled by the Thread Handler.

Creating a private thread

To create a thread, you need a list of user entities that you want to add. The following code will create a new thread and then display it in the chat view.

iOS

[BChatSDK.core createThreadWithUsers:@[user1, user2,...] name: @"Optional Name" threadCreated:^(NSError * error, id<PThread> thread) {
    UIViewController * cvc = [BChatSDK.ui chatViewControllerWithThread:thread];
    [self.navigationController pushViewController:cvc animated:YES];
}];

Swift

_ = BChatSDK.core().createThread(withUsers: [user1!, user2!], threadCreated: {(error: Error?, thread:PThread?) in
    let cvc = BChatSDK.ui().chatViewController(with: thread)
    self.viewController.navigationController?.pushViewController(cvc!, animated: true)
})

Android

ChatSDK.thread().createThread("Optional Name", user1, user2, user3...)
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe((Consumer<java.lang.Thread>) thread -> {
    // Start the chat activity
    ChatSDK.ui().startChatActivityForID(getApplicationContext(), thread.getEntityID());
}, (Consumer<Throwable>) throwable -> {
    // Handle error
});

Adding or removing a user to a thread

iOS

// Adding users
[BChatSDK.core addUsers:@[user1, user2,...] toThread:thread].thenOnMain(...);

// Removing users
[BChatSDK.core removeUsers:@[user1, user2,...] fromThread:thread].thenOnMain(...);

Android

// Adding users
ChatSDK.thread().addUsersToThread(user1, user2,...).subscribe(...);

// Removing users
ChatSDK.thread().removeUsersFromThread(user1, user2,...).subscribe(...);

Creating a public thread

Public threads are visible to everyone who is logged into the app. They are more like public chat rooms.

iOS

[BChatSDK.publicThread createPublicThreadWithName:name].thenOnMain(^id(id<PThread> thread) {
    UIViewController * vc = [BChatSDK.ui chatViewControllerWithThread:thread];
    
    [self.navigationController pushViewController:vc animated:YES];
    return Nil;
}, ^id(NSError * error) {
    // Handle error
    return error;
});

Android

ChatSDK.publicThread().createPublicThreadWithName(threadName)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe((thread, throwable) -> {
                    if(throwable == null) {
                        ChatSDK.ui().startChatActivityForID(getContext(), thread.getEntityID());
                    }
                    else {
                        // Handle error
                    }

Getting a list of threads for a user

Sometimes it's useful to get a full list of threads for a particular type.

Public Threads

iOS

NSArray * threads = [BChatSDK.core threadsWithType:bThreadTypePublicGroup];

Android

List<Thread> * threads = ChatSDK.thread().getThreads(ThreadType.Public);
Private Threads

iOS

NSArray * threads = [BChatSDK.core threadsWithType:bThreadFilterPrivate];

Android

List<Thread> * threads = ChatSDK.thread().getThreads(ThreadType.Private);

Messaging

Send a text message to a user:

iOS

[BChatSDK.core sendMessageWithText:text withThreadEntityID:_thread.entityID].thenOnMain(...);

Android

ChatSDK.thread().sendMessageWithText("Message Text", thread).subscribe(...);

Customizing the User Interface

There are two main ways to customize the user interface.

  1. Modify the UI module directly
  2. Change the UI by subclassing and using the Interface Manager

Modifying the UI directly

Since the Chat SDK is open source, you could modify the user interface files directly. This has some benefits as well as some disadvantages. The main advantage is that this method is quick and doesn't require any configuration and allows you to see your changes immediately. The problem comes when it's time to upgrade. If you just replaced the UI module with the latest version from Github, all of your customizations would be lost and you would go back to the vanilla Chat SDK UI.

The way to get around this is to fork the project using Git. You would make all of your customizations on a separate branch. When it was time to update the UI module, you would need to merge the latest version with your branch and resolve any conflicts that may have arisen.

So here is an outline of the procedure.

  1. Create a fork of the Chat SDK project on Github
  2. Clone the fork to your computer using git clone [link to your fork]. You would replace the square brackets with the actual URL of your Github fork
  3. Open your Podfile and include the Chat SDK as development pods
pod "ChatSDK", :path => "[Absolute path to the ChatSDK.podspec file]"

You should find where you downloaded the Chat SDK files and locate the ChatSDK.podspec file. Right click this file and click Get Info. Then click drag to highlight the path after it says Where:. Press Command + C to copy this path to the clipboard. Then replace the square brackets with the path you copied. 4. Run pod install 5. Modify the Chat SDK directly - you can do this from within Xcode

Upgrading the Chat SDK

In the future you may want to upgrade the Chat SDK library. To do this, you need to complete the following steps:

  1. Find the location where you saved the Chat SDK library
  2. Open this location in the terminal app
  3. Add the original version of the Chat SDK as a remote
git remote add chatsdk https://github.com/chat-sdk/chat-sdk-ios.git
  1. Merge the latest version of the Chat SDK with your fork
git pull chatsdk master
  1. Resolve any conflicts
git mergetool

Using the interface manager

The second method is a little more complex to set up initially but it more robust over the longer term. This method involves subclassing the UI element that you need to modify. After you've made your changes, you need to find a way to tell the Chat SDK to use your subclass rather than the default class. That can be achieved using the InterfaceManager.

The InterfaceManager follows a similar pattern as the NetworkManager. It is a singleton class that has a replaceable adapter which provides methods that are used by the UI to request views. For example, when the app wants to show a user profile view, it uses the following method:

iOS

UIViewController * profileView = [BChatSDK.ui profileViewControllerWithUser:user];

Android

ChatSDK.ui().startProfileActivity(getContext(), clickedUser.getEntityID());

Notice that the calling class just requests the profile view controller or activity. It has no idea what class will actually be returned. That is decided by the interface manager.

The first step to add your own custom UI is to subclass the interface adapter. So we create a new class called MyAppInterfaceAdapter which inherits from the DefaultInterfaceAdapter in iOS and the BaseInterfaceAdapter in Android.

iOS

@interface MyAppInterfaceAdapter : DefaultInterfaceAdapter { ...

Android

public class MyAppInterfaceAdaper extends BaseInterfaceAdapter { ...

Now we need to tell the Chat SDK to use our custom interface adapter. In the main app start method where you initialize the Chat SDK add the following:

iOS

BChatSDK.shared.interfaceManager = [[MyAppDefaultInterfaceAdapter alloc] init];

Android

ChatSDK.shared().setInterfaceAdapter(new MyAppInterfaceAdapter(context));

Next, we need to subclass the view we want to change. For example, If we wanted to modify the profile view, the first step would be to create a subclass. We could call this MyAppProfileViewController for iOS or MyAppProfileActivity for Android.

Finally, we need to override the method that provides this view in our interface adapter. To do that, add the following to your MyAppInterfaceAdapter.

iOS

-(UIViewController *) profileViewControllerWithUser: (id<PUser>) user {
    
    UIStoryboard * storyboard = [UIStoryboard storyboardWithName:@"MyAppProfile"
                                                          bundle:[NSBundle chatUIBundle]];
    
    BMyAppProfileTableViewController * controller = [storyboard instantiateInitialViewController];

    controller.user = user;
    return controller;
}

Android

public Class getProfileActivity() {
    return MyAppProfileActivity.class;
}

So now, lets see what happens. When the app requests the profile view from the interface manager, the interface manager will ask its adapter to provide the view. Since we replaced the standard adapter with a custom version, the getProfile method that you just created will be called. It will return your customized profile view which will be used by the Chat SDK.

This method may seem a little more complex to setup initially, but it's more convenient in the long term. It means that you don't need to make any modifications to the Chat SDK library and updates can be installed without worrying about losing your changes.

Customizing message cells

Since table views work very differently in Android and iOS, it's helpful to look at each separately.

iOS

The chat view uses a UITableView and the cell type that is used when rendering a specific message type is setup in the registerMessageCells method.

(void) registerMessageCells {
    
    // Default message types
    
    [self.tableView registerClass:[BTextMessageCell class] forCellReuseIdentifier:@(bMessageTypeText).stringValue];
    [self.tableView registerClass:[BImageMessageCell class] forCellReuseIdentifier:@(bMessageTypeImage).stringValue];
    [self.tableView registerClass:[BLocationCell class] forCellReuseIdentifier:@(bMessageTypeLocation).stringValue];
    [self.tableView registerClass:[BSystemMessageCell class] forCellReuseIdentifier:@(bMessageTypeSystem).stringValue];
    
    // Some optional message types
    if ([delegate respondsToSelector:@selector(customCellTypes)]) {
        for (NSArray * cell in delegate.customCellTypes) {
            [self.tableView registerClass:cell.firstObject forCellReuseIdentifier:[cell.lastObject stringValue]];
        }
    }
}

First we setup the standard message types associating the cell class with a string identifier (in this case the integer value message type).

Then we loop over the custom cell types. This means that if you want to register your own cell type, you would need to override the customCellTypes method:

-(NSMutableArray *) customCellTypes {
    NSMutableArray * types = [NSMutableArray arrayWithArray:[super customCellTypes]];
    
    [types addObject:@[[CustomMessageCell class], @(bMessageTypeText)]]
    
    return types;
}

Android

Currently, there isn't a way to define a completely custom message cell for Android. However, you can define a custom message handler which can modify the standard message cell view. To do this first you need to make a class that implements the CustomMessageHandler interface.

public interface CustomMessageHandler {
    void updateMessageCellView (Message message, Object viewHolder, Context context);
}

Then you need to register your new class with the interface manager:

ChatSDK.ui().addCustomMessageHandler(new YourCustomMessageHandler());

Then you need to implement the updateMessageCellView method. This method will be called for every cell and cells can be reused so if we're not careful we can run into problems. Imagine we want to add an icon to text message cells.

If we used something like layout.addView to add the icon, this would add an icon to every text view. But the second time the view was displayed, it would add a second icon! And when the cell was reused for an image message, that message would also have the icon.

To avoid this, we need to do the following:

  1. Check the cell type - is it text or image?
  2. If it's a text view, check to see if we've already added an icon
  3. If we haven't, add the icon
  4. If it's not a text type and we have added an icon (it's been resued) remove the icon
    @Override
    public void updateMessageCellView(final Message message, Object viewHolder, final Context context) {
    
        if(viewHolder instanceof MessagesListAdapter.MessageViewHolder) {
        
            // Get the view holder and layout
            MessagesListAdapter.MessageViewHolder messageViewHolder = (MessagesListAdapter.MessageViewHolder) viewHolder;
            ViewGroup layout = messageViewHolder.extraLayout;
            
            CustomMessageView customView = null;

            // Loop over the child views
            for(int i = 0; i < layout.getChildCount(); i++) {
                View view = layout.getChildAt(i);
                
                // If the child view is an instance of our custom view break and save 
                // the view
                if(view instanceof CustomMessageView) {
                    customView = (CustomMessageView) view;
                    break;
                }
            }

            // If this isn't the correct message type remove the view
            if(message.getMessageType() != MessageType. ... ) {
                if(customView != null) {
                    layout.removeView(customView.getView());
                }
                return;
            }

			  // See note below
            if(customView == null) {
                customView = new CustomMessageView(context);
                layout.addView(customView);
            }
            if(customView.getView().getParent() == null) {
                layout.addView(customView.getView());
            }

            // The view has now been added so you can configure it as needed. 

        }
    }

Note If you display a custom view in the cell, it's recommended to use create your custom class that extends LinearLayout. Then make a property called View view. Then add a method called getView() which can be called by the below code. This is useful because it makes it easier to retrieve your custom view. You can loop over the message cell layout and check if any sub view is an instanceOf your custom view. Then you can get the actual custom view using getView().

public class CustomMessageView extends LinearLayout {

    private View view;

    public VideoMessageView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initView();
    }

    public VideoMessageView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public VideoMessageView(Context context) {
        super(context);
        initView();
    }

    private void initView() {

        LayoutInflater inflater = LayoutInflater.from(getContext());
        view = inflater.inflate(R.layout.your_layout, null);
    }

    public View getView () {
        return view;
    }
}

Modifying the database from your server

In some cases it may be necessary to access the Firebase database directly from your server. To do this, you can use the Firebase Admin SDK.

docs's People

Contributors

bensmiley avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

docs's Issues

Can't store contacts

Hi, I love your SDK but I've problems storing the contacts, I'm in android.

This is my code.

 public void insertContact(String userID, SilentResponseListener success, SilentResponseListener  failure)
{
    UserWrapper wrapper = UserWrapper.initWithEntityId(userID);
    wrapper.metaOn();
    wrapper.onlineOn();
    User user = wrapper.getModel();
    String currentUserID = NMWrapper.auth(mContext).getCurrentUserEntityID(); // this is for initializing the chatSDK if is not yet initialized
    if (currentUserID == null)
    {
        AuthService.getInstance(mContext).logOut();
    }
    Disposable disposable = NM.contact().addContact(user, ConnectionType.Contact).subscribe(new Action() {
        @Override
        public void run() {
            Log.d("success","supa success");
            success.run();
        }
    }, new Consumer<Throwable>() {
        @Override
        public void accept(Throwable throwable) throws Exception {
            throwable.printStackTrace();
            Log.d("This is failure",throwable.getMessage());
            failure.run();
        }
    });
}

And I've been debugging and the user is retrieved successfully and my user is logged in, but every time I ask for the contacts i.e. NM.contact().contacts() the list is empty!

And don't know what I'm doing wrong.

EDIT
Also, the success function is always called

EDIT2
Stop your horses!!! I see what is the problem, the name of the contact can't be null and I was leaving the name null because the name is the same as the ID.

I cant add chatsdk to my existing projects

i have problem with implement chatsdk in my existing project. i try this documentation -> https://chatsdk.co/docs/android-quickstart/ but in step add to gradle, i got error like this.

AGPBI: {"kind":"error","text":"error: resource android:attr/ttcIndex not found.","sources":[{"file":"/Users/muhwid/.gradle/caches/transforms-1/files-1.1/appcompat-v7-27.1.0.aar/35c2e1d7328226ef85a2fdd8d20e7d76/res/values/values.xml","position":{"startLine":250,"startColumn":4,"startOffset":27058,"endColumn":68,"endOffset":27122}}],"original":"","tool":"AAPT"}
:app:processDebugResources
:app:processDebugResources FAILED
FAILURE: Build failed with an exception.
What went wrong:
Execution failed for task ':app:processDebugResources'.
Failed to process resources, see aapt output above for details.

please help me with this issue

Documentation is incomplete or there is a bug in authentication for token only users

(updated to latest sdk)
The steps in the documentation are:

AccountDetails details = new AccountDetails.token("Token");
NM.auth().authenticate(details).subscribe();

When the second line executes, there is an immediate exception that complains about the token format which is in the documentation but there is no documentation about the token format.

E/AndroidRuntime: FATAL EXCEPTION: main
                  Process: org.karmist.austin, PID: 7083
                  io.reactivex.exceptions.OnErrorNotImplementedException: The custom token format is incorrect. Please check the documentation.
                      at io.reactivex.internal.observers.EmptyCompletableObserver.onError(EmptyCompletableObserver.java:51)
                      at io.reactivex.internal.operators.completable.CompletableSubscribeOn$SubscribeOnObserver.onError(CompletableSubscribeOn.java:74)
                      at io.reactivex.internal.operators.completable.CompletablePeek$CompletableObserverImplementation.onError(CompletablePeek.java:96)
                      at io.reactivex.internal.operators.single.SingleFlatMapCompletable$FlatMapCompletableObserver.onError(SingleFlatMapCompletable.java:97)
                      at io.reactivex.internal.operators.single.SingleCreate$Emitter.tryOnError(SingleCreate.java:95)
                      at io.reactivex.internal.operators.single.SingleCreate$Emitter.onError(SingleCreate.java:81)
                      at co.chatsdk.firebase.FirebaseAuthenticationHandler$6$1.onComplete(FirebaseAuthenticationHandler.java:102)
                      at com.google.android.gms.tasks.zzf.run(Unknown Source)
                      at android.os.Handler.handleCallback(Handler.java:751)
                      at android.os.Handler.dispatchMessage(Handler.java:95)
                      at android.os.Looper.loop(Looper.java:154)
                      at android.app.ActivityThread.main(ActivityThread.java:6121)
                      at java.lang.reflect.Method.invoke(Native Method)
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
                   Caused by: com.google.firebase.auth.FirebaseAuthInvalidCredentialsException: The custom token format is incorrect. Please check the documentation.
                      at com.google.android.gms.internal.zzdkj.zzak(Unknown Source)
                      at com.google.android.gms.internal.zzdjl.zza(Unknown Source)
                      at com.google.android.gms.internal.zzdku.zzal(Unknown Source)
                      at com.google.android.gms.internal.zzdkw.onFailure(Unknown Source)
                      at com.google.android.gms.internal.zzdkl.onTransact(Unknown Source)
                      at android.os.Binder.execTransact(Binder.java:565)



When one follows the directions for the username/password scenario there is an immediate exception
               AccountDetails details = AccountDetails.username("Joe", "Joe123");
                NM.auth().authenticate(details).subscribe();
E/AndroidRuntime: FATAL EXCEPTION: main
                  Process: org.karmist.austin, PID: 2130
                  io.reactivex.exceptions.OnErrorNotImplementedException: The email address is badly formatted.
                      at io.reactivex.internal.observers.EmptyCompletableObserver.onError(EmptyCompletableObserver.java:51)
                      at io.reactivex.internal.operators.completable.CompletableSubscribeOn$SubscribeOnObserver.onError(CompletableSubscribeOn.java:74)
                      at io.reactivex.internal.operators.completable.CompletablePeek$CompletableObserverImplementation.onError(CompletablePeek.java:96)
                      at io.reactivex.internal.operators.single.SingleFlatMapCompletable$FlatMapCompletableObserver.onError(SingleFlatMapCompletable.java:97)
                      at io.reactivex.internal.operators.single.SingleCreate$Emitter.tryOnError(SingleCreate.java:95)
                      at io.reactivex.internal.operators.single.SingleCreate$Emitter.onError(SingleCreate.java:81)
                      at co.chatsdk.firebase.FirebaseAuthenticationHandler$6$1.onComplete(FirebaseAuthenticationHandler.java:102)
                      at com.google.android.gms.tasks.zzf.run(Unknown Source)
                      at android.os.Handler.handleCallback(Handler.java:751)
                      at android.os.Handler.dispatchMessage(Handler.java:95)
                      at android.os.Looper.loop(Looper.java:154)
                      at android.app.ActivityThread.main(ActivityThread.java:6121)
                      at java.lang.reflect.Method.invoke(Native Method)
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
                   Caused by: com.google.firebase.auth.FirebaseAuthInvalidCredentialsException: The email address is badly formatted.
                      at com.google.android.gms.internal.zzdkj.zzak(Unknown Source)
                      at com.google.android.gms.internal.zzdjl.zza(Unknown Source)
                      at com.google.android.gms.internal.zzdku.zzal(Unknown Source)
                      at com.google.android.gms.internal.zzdkw.onFailure(Unknown Source)
                      at com.google.android.gms.internal.zzdkl.onTransact(Unknown Source)
                      at android.os.Binder.execTransact(Binder.java:565)

Is there a step missing or could I possibly have misconfigured something? Neither user in the above cases shows up in firebase.

when I try signup

  AccountDetails details = AccountDetails.signUp("Joe", "Joe123");
                NM.auth().authenticate(details).subscribe();

there is an email exception

E/AndroidRuntime: FATAL EXCEPTION: main
                  Process: org.karmist.austin, PID: 5129
                  io.reactivex.exceptions.OnErrorNotImplementedException: The email address is badly formatted.
                      at io.reactivex.internal.observers.EmptyCompletableObserver.onError(EmptyCompletableObserver.java:51)
                      at io.reactivex.internal.operators.completable.CompletableSubscribeOn$SubscribeOnObserver.onError(CompletableSubscribeOn.java:74)
                      at io.reactivex.internal.operators.completable.CompletablePeek$CompletableObserverImplementation.onError(CompletablePeek.java:96)
                      at io.reactivex.internal.operators.single.SingleFlatMapCompletable$FlatMapCompletableObserver.onError(SingleFlatMapCompletable.java:97)
                      at io.reactivex.internal.operators.single.SingleCreate$Emitter.tryOnError(SingleCreate.java:95)
                      at io.reactivex.internal.operators.single.SingleCreate$Emitter.onError(SingleCreate.java:81)
                      at co.chatsdk.firebase.FirebaseAuthenticationHandler$6$1.onComplete(FirebaseAuthenticationHandler.java:102)
                      at com.google.android.gms.tasks.zzf.run(Unknown Source)
                      at android.os.Handler.handleCallback(Handler.java:751)
                      at android.os.Handler.dispatchMessage(Handler.java:95)
                      at android.os.Looper.loop(Looper.java:154)
                      at android.app.ActivityThread.main(ActivityThread.java:6121)
                      at java.lang.reflect.Method.invoke(Native Method)
                      at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
                      at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
                   Caused by: com.google.firebase.auth.FirebaseAuthInvalidCredentialsException: The email address is badly formatted.
                      at com.google.android.gms.internal.zzdkj.zzak(Unknown Source)
                      at com.google.android.gms.internal.zzdjl.zza(Unknown Source)
                      at com.google.android.gms.internal.zzdku.zzal(Unknown Source)
                      at com.google.android.gms.internal.zzdkw.onFailure(Unknown Source)
                      at com.google.android.gms.internal.zzdkl.onTransact(Unknown Source)
                      at android.os.Binder.execTransact(Binder.java:565)

Problem with sending message with bMessageTypeCustom

Normally I can send text message to tread by
[NM.core sendMessageWithText:@"Some text" withThreadEntityID:thread.entityID];
but in my progect I want to make custom cell for layout massage basic on info in text field of message
in this case I should set message to bMessageTypeCustom
how in Docs example:

id<PMessage> message = [[BStorageManager sharedManager].a createEntity:bMessageEntity];
            message.type = @(bMessageTypeCustom);
            [message setText:[NSString stringWithString:@"MyIdentifierText-CodeForCustomCellInitiation"]];

then I got method for send message

[NM.core sendMessage:message];

but I can't set message tread
in documentation ic next:

// Use thread setMessage instead
//-(void) setThread: (id<PThread>) thread;
-(id<PThread>) thread;

method sendThread is commented and it noted to use setMessage method that is absent in ChatSDK

How to send message with bMessageTypeCustom. ???

how do you implement Chat SDK into a live video streaming

i am implementing the chatsdk into a custom application where i need the chat functionality on a page that is playing the live stream.

one half of the page will have the chat functionality and the other half will stream the video. there are no options for implementing such functionality in your current SDK. how do i implement it.

I want to update the new messages count whenever a new message in received in ThreadType.PublicGroup

I have added this code in onCreate() but the listener is not fired. Can you please help?
I am using ChatSDK Android 4.8.18.
This listener is only called once when I enter the activity.

Disposable d = ChatSDK.events().sourceOnMain()
               .filter(NetworkEvent.filterType(EventType.MessageAdded))
               .filter(NetworkEvent.filterThreadEntityID(thread.getName()))
               .subscribe(new Consumer<NetworkEvent>() {
                   @Override
                   public void accept(NetworkEvent networkEvent) throws Exception {
                       txtBadgeCount.setText(String.valueOf(thread.getUnreadMessagesCount()));
                   }
               });

ChatviewController is crashing

Hi,
My project is already setup with firebase and I want to add Chat SDK.
I am using custom login so I setup the ChatSDK in app delegate according to documents. My code is as under.

  let config = BConfiguration.init();
        config.rootPath = "test"
        // Configure other options here...
         config.allowUsersToCreatePublicChats = true
        BChatSDK.initialize(config, app: application, options: launchOptions)

After that I am login to the firebase and ChatSDK as under

 BIntegrationHelper.authenticate(withToken: Auth.auth().currentUser?.refreshToken)
 BIntegrationHelper.updateUser(withName: userName, image: nil, url: nil)

I observed that there is no database entry in firebase after login so I implemented following method.

_ =  BChatSDK.core()?.pushUser()?.thenOnMain({ thread in
                    // Success
                    return nil
                }, { error in
                    // Failure
                    print(error.debugDescription)
                    return error
                })

I am getting error in above case and no database entries on Firebase.

I am opening chatviewcontroller on button click as Under

let wrapper2 : CCUserWrapper = CCUserWrapper.user(withEntityID: selectedID)
       wrapper2.metaOn()
        //   wrapper2.onlineOn()
         let user2 : PUser = wrapper2.model()
        if (selectedName.count > 0 )
        {
            user2.setName(selectedName)
        }
        else
        {
            user2.setName("No name set")
            }
         wrapper2.push() //database entry in users table but not on indices
       // BChatSDK.contact().addContact(user2, with: bUserConnectionTypeContact)
        let wrapper1 : CCUserWrapper = CCUserWrapper.user(withEntityID: myID
        )
        wrapper1.metaOn()
        wrapper1.onlineOn()
       let user1 : PUser = wrapper1.model()
        if (myName.count > 0 )
        {
            user1.setName(myName)
        }
        else
        {
            user1.setName("No name set")
              }
         wrapper1.push() // This push creates database users entry but not in indices.
        let users : Array = [user1, user2]
        _ = BChatSDK.core().createThread(withUsers: users , threadCreated: {(error: Error?, thread:PThread?) in
          if ((error) != nil)
            {
                print(error.debugDescription)
            }
            else
            {
            let cvc = BChatSDK.ui().chatViewController(with: thread)
            self.navigationController?.pushViewController(cvc!, animated: true)
            }
        }) }

My app crashed in above case.

Stack trace is as under

018-11-30 03:41:31.296060+0500 ModoBuddy[73840:7171000] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
*** First throw call stack:
(
	0   CoreFoundation                      0x00000001094ea1bb __exceptionPreprocess + 331
	1   libobjc.A.dylib                     0x0000000108a84735 objc_exception_throw + 48
	2   CoreFoundation                      0x00000001094364ec _CFThrowFormattedException + 194
	3   CoreFoundation                      0x0000000109412775 -[__NSArrayM insertObject:atIndex:] + 1269
	4   ChatSDK                             0x00000001059d5893 -[BAbstractCoreHandler fetchThreadWithUsers:] + 211
	5   ModoBuddy                           0x0000000103b06b3c -[BFirebaseCoreHandler createThreadWithUsers:name:threadCreated:] + 140
	6   ModoBuddy                           0x0000000103b071ec -[BFirebaseCoreHandler createThreadWithUsers:threadCreated:] + 124
	7   ModoBuddy                           0x0000000103a72342 $S9ModoBuddy28FriendsProfileViewControllerC10actMessageyyypF + 2466
	8   ModoBuddy                           0x0000000103a729cc $S9ModoBuddy28FriendsProfileViewControllerC10actMessageyyypFTo + 76
	9   UIKitCore                           0x000000010d87aecb -[UIApplication sendAction:to:from:forEvent:] + 83
	10  UIKitCore                           0x000000010d2b60bd -[UIControl sendAction:to:forEvent:] + 67
	11  UIKitCore                           0x000000010d2b63da -[UIControl _sendActionsForEvents:withEvent:] + 450
	12  UIKitCore                           0x000000010d2b531e -[UIControl touchesEnded:withEvent:] + 583
	13  UIKitCore                           0x000000010d8b60a4 -[UIWindow _sendTouchesForEvent:] + 2729
	14  UIKitCore                           0x000000010d8b77a0 -[UIWindow sendEvent:] + 4080
	15  UIKitCore                           0x000000010d895394 -[UIApplication sendEvent:] + 352
	16  UIKit                               0x000000012a1b6183 -[UIApplicationAccessibility sendEvent:] + 85
	17  UIKitCore                           0x000000010d96a5a9 __dispatchPreprocessedEventFromEventQueue + 3054
	18  UIKitCore                           0x000000010d96d1cb __handleEventQueueInternal + 5948
	19  CoreFoundation                      0x000000010944f721 __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17
	20  CoreFoundation                      0x000000010944ef93 __CFRunLoopDoSources0 + 243
	21  CoreFoundation                      0x000000010944963f __CFRunLoopRun + 1263
	22  CoreFoundation                      0x0000000109448e11 CFRunLoopRunSpecific + 625
	23  GraphicsServices                    0x0000000110e2b1dd GSEventRunModal + 62
	24  UIKitCore                           0x000000010d87981d UIApplicationMain + 140
	25  ModoBuddy                           0x0000000103ada7c4 main + 68
	26  libdyld.dylib                       0x000000010abed575 start + 1
	27  ???                                 0x0000000000000001 0x0 + 1
)
libc++abi.dylib: terminating with uncaught exception of type NSException

Kindly help me out
thanks

android - The AccountDetails functions are statics

Should fix this way:
-AccountDetails details = new AccountDetails.token("Your token");
+AccountDetails details = AccountDetails.token("Your token");

To keep doc more clear i think it will be better to update those lines too:
-AccountDetails details = username("[email protected]", "some password");
+AccountDetails details = AccountDetails.username("[email protected]", "some password");

-AccountDetails details = signUp("Joe", "Joe123");
+AccountDetails details = AccountDetails.signUp("Joe", "Joe123");

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.