Talk to us
Talk to us
menu

Building a Community Chat Feature: A Step-by-Step ZIM Integration Guide

Building a Community Chat Feature: A Step-by-Step ZIM Integration Guide

If your users need to gather around shared interests — a guild, a class, a fan group, a product community — a flat group chat runs out of room fast. Conversations collide, announcements get buried, and moderation becomes a free-for-all. ZEGOCLOUD’s ZIM answers this with a Discord-style model: a community that contains multiple channels, each with its own isolated message stream.

This guide is a hands-on integration walkthrough, not a concept pitch. By the end you will have a working in-app community: you will initialize the SDK and log in, create a community, add channels, invite and manage members, exchange channel messages, and apply mutes for moderation. The steps use the Android API names in the examples, but every step maps to the same guide set on iOS, macOS, Windows, and React Native.

Prerequisites

Before writing any community code, make sure two things are in place:

  • A ZIM-enabled project. Follow the standard Send and receive messages guide to integrate the SDK, initialize it, and complete token-based authentication login. Community calls reuse the same ZIM instance and the same token mechanism — no extra permission declarations are required.
  • ZIM SDK 3.0.0 or later. The community and channel APIs shipped in ZIM 3.0.0. Older SDK versions simply do not contain these methods.

One commercial detail to know up front: community is a premium capability. It runs on the ZIM premium edition and must be activated by ZEGOCLOUD technical support before the calls will succeed. Once enabled, a community supports up to 100,000 members by default (expandable to unlimited) and up to 100 channels by default (expandable to 1,000), while a single user can join up to 100 communities by default (expandable to 500).

The object model: community, channel, member, mute

The code makes far more sense once the four objects it manipulates are clear.

  • Community — the top-level container and the unit of user relationships. Users must create or join a community before any interaction. The product model is “Community–Channel–Thread” (Thread is planned for a future version); today everything lives in the first two tiers.
  • Channel — the actual chat surface inside a community, functionally similar to a traditional group and carrying its own isolated message stream. Two channel types currently exist: GENERAL, the default channel created automatically with the community and used for community-level tips and notifications, and PUBLIC, which owners and admins create afterward. Both behave identically; only the creation timing differs. Note for developers updating from earlier builds: the older private channel type has been removed — current SDK versions expose GENERAL and PUBLIC only, and joining a community auto-joins its default channel and all public channels.
  • Member — a user inside a community with one of three roles: owner (1), admin (2), or regular member (3). Roles gate the management calls below.
  • Mute — the moderation primitive, available in two scopes: muting whole channels (by member-role scope) and muting individual members within one channel or all channels.

One ID distinction prevents an entire class of bugs: a channel’s channelID identifies it for management operations (create, update, dismiss), while its conversationID — obtained from the channel object returned by queryCommunityChannelList — is what you use to send messages and manage conversations. Never substitute one for the other.

Step 1: Initialize ZIM and log in

Community features hang off the same singleton used for ordinary messaging. Create the ZIM instance with your appID, then log a user in with a token:

ZIMAppConfig appConfig = new ZIMAppConfig();
appConfig.appID = 123456789L;       // replace with your AppID
appConfig.appSign = "yourAppSign";
ZIM.create(appConfig);

ZIM zim = ZIM.getInstance();
ZIMUserInfo userInfo = new ZIMUserInfo();
userInfo.userID = "user_001";
userInfo.userName = "Ada";

ZIMLoginConfig loginConfig = new ZIMLoginConfig();
loginConfig.token = "your_token";
zim.login(userInfo, loginConfig, new ZIMLoggedInCallback() {
    @Override
    public void onLoggedIn(ZIMError errorInfo) {
        if (errorInfo.code == ZIMErrorCode.SUCCESS) {
            // ready to create or join a community
        }
    }
});

Register a ZIMEventHandler right after login as well — community list, channel list, member-state, and message callbacks all arrive there, and you will wire them up in the steps below.

Step 2: Create a community

Call createCommunity (on iOS/macOS the equivalent is createCommunityWithCommunityInfo) with a ZIMCommunityInfo and a ZIMCommunityCreateConfig. The creator automatically becomes the owner and is automatically joined — no separate join call is needed.

ZIMCommunityInfo communityInfo = new ZIMCommunityInfo();
communityInfo.setCommunityID("community_001"); // blank = server ID starting #B
communityInfo.setCommunityName("Study Group HQ");
communityInfo.setCommunityAvatarUrl("https://example.com/avatar.png");

ZIMCommunityCreateConfig config = new ZIMCommunityCreateConfig();
config.setCommunityNotice("Welcome! Pick a subject channel and introduce yourself.");

HashMap<String, String> attributes = new HashMap<>();
attributes.put("term", "fall");
config.setCommunityAttributes(attributes); // up to 10 pairs by default

zim.createCommunity(communityInfo, config, new ZIMCommunityCreatedCallback() {
    @Override
    public void onCommunityCreated(ZIMCommunityFullInfo fullInfo, ZIMError errorInfo) {
        if (errorInfo.code == ZIMErrorCode.SUCCESS) {
            String communityID = fullInfo.getBaseInfo().getCommunityID();
            // the GENERAL default channel already exists
        }
    }
});

A custom communityID accepts numbers, English letters, and a defined set of symbols but cannot start with #. Names cap at 300 characters and notices at 500 (both configurable). Existing users who know the ID enter via joinCommunity; on joining they are added to the default and all public channels automatically. Watch for error codes 6001003 (already exists) and 6001007 (permission error). Page the joined-community list with queryCommunityList using the nextFlag cursor (start at 0, stop when it returns 0), and refresh in onCommunityListChanged.

Step 3: Create channels within the community

Every community gets one GENERAL channel at creation. Subject channels come from createCommunityChannel, restricted to owner and admins; the creator is auto-joined.

ZIMCommunityChannelInfo channelInfo = new ZIMCommunityChannelInfo();
channelInfo.setChannelID("ch_math");
channelInfo.setChannelName("math");
channelInfo.setCommunityID(communityID);

ZIMCommunityChannelCreateConfig channelConfig = new ZIMCommunityChannelCreateConfig();
channelConfig.setChannelNotice("Homework questions and exam prep");
zim.createCommunityChannel(channelInfo, channelConfig,
    new ZIMCommunityChannelCreatedCallback() {
        @Override
        public void onCommunityChannelCreated(ZIMCommunityChannelFullInfo c,
                                              ZIMError errorInfo) {
            if (errorInfo.code == ZIMErrorCode.SUCCESS) {
                // c.getConversationID() -> needed for messaging
            }
        }
    });

Repeat for “physics” and “history”. Enumerate channels with queryCommunityChannelList and cache each ZIMCommunityChannel: baseInfo.channelID drives management calls, conversationID drives messaging. The default GENERAL channel cannot be dismissed; others are removed with dismissCommunityChannel. Stay in sync through onCommunityChannelListChanged and onCommunityChannelInfoUpdated.

Step 4: Join and manage members

Populate the community with inviteUsersIntoCommunity, which adds up to 100 already-registered users per call directly, with no consent step; per-user failures come back in errorUserList.

ArrayList<String> userIDs = new ArrayList<>();
userIDs.add("user_2");
userIDs.add("user_3");
zim.inviteUsersIntoCommunity(userIDs, communityID,
    new ZIMCommunityUsersInvitedCallback() {
        @Override
        public void onCommunityUsersInvited(String communityID,
                                            ArrayList<ZIMErrorUserInfo> errorUserList,
                                            ZIMError errorInfo) {
            // errorUserList names users the invite failed for
        }
    });

The rest of the member surface is role-gated, which is what makes moderation scale:

  • queryCommunityMemberList pages through all members (same nextFlag pattern); queryCommunityMembers fetches specific users including role and mute state.
  • updateCommunityMemberRole promotes a member to admin or demotes them — owner only.
  • kickCommunityMembers removes up to 100 members per call — owner and admins only.
  • transferCommunityOwner hands over ownership; the previous owner becomes a regular member. The owner cannot leave via leaveCommunity directly — they must transfer first or dismiss the community.

Live updates arrive through onCommunityMemberStateChanged (ENTERED/EXITED) and onCommunityMemberInfoUpdated (role, nickname, mute changes).

Step 5: Send and receive channel messages

Channel messaging reuses the standard sendMessage API. The community-specific part is the conversation type ZIMConversationType.COMMUNITY_CHANNEL, paired with the channel’s conversationID.

String conversationID = channel.getConversationID(); // NOT channelID

ZIMTextMessage textMessage = new ZIMTextMessage();
textMessage.setMessage("Does anyone have problem 4 done?");

ZIMMessageSendConfig msgConfig = new ZIMMessageSendConfig();
msgConfig.setPriority(ZIMMessagePriority.LOW);

zim.sendMessage(textMessage, conversationID,
    ZIMConversationType.COMMUNITY_CHANNEL, msgConfig,
    new ZIMMessageSentFullCallback() {
        @Override public void onMessageAttached(ZIMMessage message) { /* sending UI */ }
        @Override public void onMessageSent(ZIMMessage message, ZIMError error) {
            if (error.code == ZIMErrorCode.SUCCESS) { /* delivered */ }
        }
        @Override public void onMediaUploadingProgress(ZIMMediaMessage m, long c, long t) {}
        @Override public void onMultipleMediaUploadingProgress(ZIMMultipleMessage m,
            int mi, long ci, int ii, long ti, long tt) {}
    });

Keep two sends more than 100ms apart. Images, files, audio, video, custom, and combined messages work identically — same conversationID, same COMMUNITY_CHANNEL type, with upload progress in the callback. From SDK 3.0.0, all online messages (one-on-one, group, room, and community channel) arrive in the unified onMessageReceived callback; filter on receivedInfo.conversationType == COMMUNITY_CHANNEL and dispatch by the message’s conversationID. Offline backlog is delivered through the same callback after re-login where offline storage is enabled, and history is pulled with the standard get-message-history API using the channel conversationID.

Each channel is also a conversation: clearConversationUnreadMessageCount zeroes its badge on open, setConversationDraft stores a local draft, setConversationNotificationStatus silences one channel’s pushes, and onConversationChanged reflects unread and draft updates. Channels default to do-not-disturb while the community itself notifies; the community’s total unread sums only channels still in notification status.

Step 6: Apply mute for moderation

Moderation has two complementary levers. muteCommunityChannels freezes posting in one or more channels at once via ZIMCommunityChannelMuteConfig:

  • mode: NONE (0), NORMAL (1, regular members), ALL (2, everyone), or CUSTOM (3, supply a roles list).
  • duration: 1 to 2,592,001 seconds (roughly 30 days), or -1 for permanent.
ArrayList<String> channelIDs = new ArrayList<>();
channelIDs.add("ch_math");

ZIMCommunityChannelMuteConfig muteConfig = new ZIMCommunityChannelMuteConfig();
muteConfig.setDuration(3600);  // one hour
muteConfig.setMode(ZIMCommunityChannelMuteMode.ALL);

zim.muteCommunityChannels(true, channelIDs, communityID, muteConfig,
    new ZIMCommunityChannelsMutedCallback() {
        @Override
        public void onCommunityChannelsMuted(String communityID, boolean isMute,
                                             ArrayList<String> errorChannelIDs,
                                             ZIMError errorInfo) {
            // errorChannelIDs lists channels the call failed on
        }
    });

Channel-level mute is the current scope — community-wide global muting is not yet supported. For individuals, muteCommunityMembers (owner/admins only) targets specific users; setting channelID confines the mute to one channel, while leaving it blank applies across all channels, with the same duration range. Combine both with kickCommunityMembers and updateCommunityMemberRole, and your server can layer additional policy on top. Note that community management operations themselves are currently client-SDK only, and change notifications arrive through client callbacks.

End-to-end example: a study group app with per-subject channels

  1. On launch, ZIM.create with the app config and login with the student’s token (SDK 3.0.0+).
  2. A teacher calls createCommunity for “Study Group HQ”, becoming owner; the GENERAL channel exists immediately for announcements.
  3. The teacher calls createCommunityChannel three times: ch_math, ch_physics, ch_history, each with its own notice.
  4. The app invites classmates with inviteUsersIntoCommunity in batches of up to 100; joining auto-places them in the default and public channels. A teaching assistant is promoted via updateCommunityMemberRole(ADMIN, ...).
  5. Opening a subject reads the cached conversationID (from queryCommunityChannelList), clears the badge with clearConversationUnreadMessageCount, sends questions through sendMessage(..., COMMUNITY_CHANNEL), and renders answers from onMessageReceived filtered to that conversationID.
  6. During a timed quiz the teacher calls muteCommunityChannels(true, ["ch_math"], ..., mode ALL, duration 2700); a single disruptive student is instead handled with muteCommunityMembers scoped to that channel.

The same sequence ports to iOS/macOS (e.g. createCommunity:config:callback:, queryCommunityChannelListByCommunityID:), Windows C++, Web/UTS, and Flutter — the community/channel guides are published for Android, iOS, macOS, Windows, and React Native, with platform-specific signatures for every step.

Key takeaways

  • The integration path is short and fixed: initialize and log in → create/join community → create channels → manage members → send and receive with COMMUNITY_CHANNEL → moderate with mute.
  • Respect the two IDs: channelID for management, conversationID for messaging and conversations.
  • Moderation is role-based and two-scoped — channel-wide mute by role (NORMAL/ALL/CUSTOM) and per-member mute per channel or globally — plus kick and ownership transfer. Scale defaults (100,000 members, 100 channels) are expandable when the premium feature is activated.

FAQ

What SDK version do I need for communities?

Community and channel APIs arrived with ZIM 3.0.0, so use 3.0.0 or later.

How do I moderate messages in a channel?

Combine community member management with community mute to control posting: muteCommunityChannels controls who can post by role, muteCommunityMembers restrains specific members, and kickCommunityMembers removes users. Server-side moderation can layer on top.

Can a community have many channels?

Yes. A community is a container for multiple channels, each with its own messages and conversations — 100 by default, expandable to 1,000.

Is the integration the same across platforms?

The community/channel guides are published for Android, iOS, macOS, Windows, and React Native, with platform-specific API details in each guide.

Related posts

Read More

Let’s Build APP Together

Start building with real-time video, voice & chat SDK for apps today!

Talk to us

Take your apps to the next level with our voice, video and chat APIs

Free Trial
  • 10,000 minutes for free
  • 4,000+ corporate clients
  • 3 Billion daily call minutes

Stay updated with us by signing up for our newsletter!

Don't miss out on important news and updates from ZEGOCLOUD!

* You may unsubscribe at any time using the unsubscribe link in the digest email. See our privacy policy for more information.