Profiles, Pagination and Locators

By now, you’re pretty well versed with how the SDK works and this level of knowledge with get you through most use cases. However, if you need more control, this is how you can do so.

Managing user profiles

Mitter.io allows you to create your user profiles the way you want without assuming anything specific profile information fields. For example, if you’re building a sports chat app and you want to store a user’s rank, you have the control to do so.

How are user profiles handled?

As said already, Mitter.io works as a transparent medium between you and your users, which means that you’re totally free to customise your user’s experience the way you want.

You can have as many profile information fields you want and how you want them. There isn’t any restriction on that.

The standard process of storing a user profile attribute is two-fold:

  • First, you define a custom attribute by specifying its type and name

  • Then, you update that attribute for a user with actual data

There is, however, an option to skip the first part (defining your attribute) and move directly to store the value of the attribute. This is where Mitter.io standard attributes come to play.

You can learn more about User Profiles in the reference section.

Exploring the bundled profile attributes

Mitter.io ships with a decent number of pre-defined attributes for user profiles, so that you can get started right away without defining your attributes first.

Currently, Mitter.io ships with:

  • FirstName

  • LastName

  • AvatarUrl

  • Mobile

  • Dob

  • Bio

  • Gender

  • Email

  • Street

  • City

  • State

  • Zip

  • Country

If you need attribute outside the scope of this list, you need to define it first from your backend service. Currently, the SDK doesn’t provide a way to define your attributes. This can only be done from the backend controlling your Mitter.io application.

Note: We may at a later point change this behaviour.

Updating your user’s profile

Assuming that you’re using the bundled attributes to create your user’s profile, the SDK provides an easy and fluent API for the job.

Defining your user’s profile is as easy as this:

Kotlin
Java
val userProfile = UserProfile().Builder()
.withFirstName("Rahul")
.withLastName("Chowdhury")
.withGender("Male")
.withCountry("India")
.build()
UserProfile userProfile = new UserProfile().new Builder()
.withFirstName("Rahul")
.withLastName("Chowdhury")
.withGender("Male")
.withCountry("India")
.build();

This will provide you with a UserProfile object which you can pass to the updateCurrentUserProfile() method to create/update the currently logged-in user’s profile in a single shot.

This is how:

Kotlin
Java
users.updateCurrentUserProfile(
userProfile,
object : Mitter.OnValueUpdatedCallback {
override fun onSuccess() {
Log.d("Mitter", "User Profile updated!")
}
override fun onError(error: ApiError) {
Log.d("Mitter", "ApiError: $error")
}
}
)
users.updateCurrentUserProfile(
userProfile,
new Mitter.OnValueUpdatedCallback() {
@Override
public void onSuccess() {
Log.d("Mitter", "User Profile updated!");
}
@Override
public void onError(ApiError apiError) {
Log.d("Mitter", "ApiError: " + apiError);
}
}
);

Updating custom defined attributes

If you’ve already defined a custom profile attribute from your backend and want to update the value of the same for your currently logged-in user, there’s a slightly different approach for that.

In that case, you need to make use of the addCurrentUserProfileAttribute() method to set your user’s profile attribute.

There are 2 variations of this method:

  • You can provide just the attribute value and let the SDK define the content encoding and other properties for you

  • You can construct the Attribute object yourself and pass it to the method

Since constructing the Attribute object is pretty straightforward, we’ll stick to using the approach where we provide just the value of the attribute.

Let’s update a custom profile attribute:

Kotlin
Java
users.addCurrentUserProfileAttribute(
attributeType = "com.acme.user.attributes.Rank",
attributeValue = "Grand Master",
onValueUpdatedCallback = object : Mitter.OnValueUpdatedCallback {
override fun onSuccess() {
Log.d("Mitter", "User Profile updated!")
}
override fun onError(error: ApiError) {
Log.d("Mitter", "ApiError: $error")
}
}
)
users.addCurrentUserProfileAttribute(
"com.acme.user.attributes.Rank",
"Grand Master",
new Mitter.OnValueUpdatedCallback() {
@Override
public void onSuccess() {
Log.d("Mitter", "User Profile updated!");
}
@Override
public void onError(ApiError apiError) {
Log.d("Mitter", "ApiError: " + apiError);
}
}
);

Fetching a user’s profile

There are two methods to get a user’s profile:

  • getCurrentUserProfile() - Get the currently logged-in user’s profile

  • getUserProfile() - Get another user’s profile

Let’s see how you can get the currently logged-in user’s profile:

Kotlin
Java
users.getCurrentUserProfile(object : Mitter.OnValueAvailableCallback<EntityProfile> {
override fun onValueAvailable(value: EntityProfile) {
value.attributes.forEach {
Log.d("Mitter", "${it.key}: ${it.value}")
}
}
override fun onError(apiError: ApiError) {
Log.d("Mitter", "ApiError: $apiError")
}
})
users.getCurrentUserProfile(
new Mitter.OnValueAvailableCallback<EntityProfile>() {
@Override
public void onValueAvailable(EntityProfile entityProfile) {
for (Attribute attribute : entityProfile.getAttributes()) {
Log.d("Mitter", attribute.getKey() + ": " + attribute.getValue());
}
}
@Override
public void onError(ApiError apiError) {
Log.d("Mitter", "ApiError: " + apiError);
}
}
);

By calling this method, you get an EntityProfile object which contains a list of Attribute objects that has been set for the user. You can loop through the list to get the entire profile information.

For any other user, the method structure is similar, the difference being that you need to pass the user ID for the user you want to fetch the profile. This is how:

Kotlin
Java
users.getUserProfile(
userId = "ae200062-f54a-4b6e-a791-afb178d1389f",
onValueAvailableCallback = object : Mitter.OnValueAvailableCallback<EntityProfile> {
override fun onValueAvailable(value: EntityProfile) {
value.attributes.forEach {
Log.d("Mitter", "${it.key}: ${it.value}")
}
}
override fun onError(apiError: ApiError) {
Log.d("Mitter", "ApiError: $apiError")
}
}
)
users.getUserProfile(
"ae200062-f54a-4b6e-a791-afb178d1389f",
new Mitter.OnValueAvailableCallback<EntityProfile>() {
@Override
public void onValueAvailable(EntityProfile entityProfile) {
for (Attribute attribute : entityProfile.getAttributes()) {
Log.d("Mitter", attribute.getKey() + ": " + attribute.getValue());
}
}
@Override
public void onError(ApiError apiError) {
Log.d("Mitter", "ApiError: " + apiError);
}
}
);

Managing channel profiles

Channel profiles are very similar to user profiles. They work in the same manner as the former. The only difference here, SDK-wise is that you need to call addChannelProfileAttribute() to update your channel profile data.

The SDK, currently, doesn’t have a fluent API for updating channel profile. You need to update each parameter as and when they’re required.

Also, there’s isn’t a getChannelProfile() method to fetch a channel’s profile information. You can easily get a channel’s profile information by calling the getChannel() method and accessing the EntityProfile object.

This is how you can do the same:

Kotlin
Java
channels.getChannel(
channelId = "fa1fb538-05ae-4d21-980e-dff88ae379f4",
onValueAvailableCallback = object : Mitter.OnValueAvailableCallback<Channel> {
override fun onValueAvailable(value: Channel) {
value.entityProfile?.attributes?.forEach {
Log.d("Mitter", "${it.key}: ${it.value}")
}
}
override fun onError(error: ApiError) {
Log.d("Mitter", error.toString())
}
}
)
channels.getChannel(
"fa1fb538-05ae-4d21-980e-dff88ae379f4",
new Mitter.OnValueAvailableCallback<Channel>() {
@Override
public void onValueAvailable(Channel channel) {
for (Attribute attribute : channel.getEntityProfile().getAttributes()) {
Log.d("Mitter", attribute.getKey() + ": " + attribute.getValue());
}
}
@Override
public void onError(ApiError apiError) {
Log.d("Mitter", "ApiError: " + apiError);
}
}
);

Fetching messages in pages

To achieve pagination, first, you need to get a reference to the MessagePaginationManager.

The Messaging object has a method called getPaginatedMessagesInChannel() which hands over a manager to you. Refer to this code:

Kotlin
Java
val messagePaginationManager = messaging.getPaginatedMessagesInChannel(channelId)
final MessagePaginationManager messagePaginationManager = messaging.getPaginatedMessagesInChannel(
channelId,
new FetchMessageConfig()
);

Here, you provide a channel ID and get a manager to handle pagination for that particular channel. Once you’ve got that, you can easily paginate front and back in the list of messages present in the channel.

Here’s how:

Kotlin
Java
messagePaginationManager.fetchNextPage(object : PaginationManager.Callback<Message> {
override fun onPageAvailable(items: List<Message>) {
Log.d("Mitter", "Page Size: ${items.size}")
items.forEach {
Log.d("Mitter", it.textPayload)
}
messagePaginationManager.fetchPreviousPage(object : PaginationManager.Callback<Message> {
override fun onPageAvailable(items: List<Message>) {
Log.d("Mitter", "Previous: Page Size: ${items.size}")
items.forEach {
Log.d("Mitter", it.textPayload)
}
messagePaginationManager.fetchPreviousPage(object : PaginationManager.Callback<Message> {
override fun onPageAvailable(items: List<Message>) {
Log.d("Mitter", "Previous - 2: Page Size: ${items.size}")
items.forEach {
Log.d("Mitter", it.textPayload)
}
}
override fun onError(apiError: ApiError) {
Log.d("Mitter", "Message Pagination - ApiError: ${apiError.message}")
}
})
}
override fun onError(apiError: ApiError) {
Log.d("Mitter", "Message Pagination - ApiError: ${apiError.message}")
}
})
}
override fun onError(apiError: ApiError) {
Log.d("Mitter", "Message Pagination - ApiError: ${apiError.message}")
}
})
messagePaginationManager.fetchNextPage(new PaginationManager.Callback<Message>() {
@Override
public void onPageAvailable(List<? extends Message> list) {
Log.d("Mitter", "Page Size: " + list.size());
for (Message message : list) {
Log.d("Mitter", message.getTextPayload());
}
messagePaginationManager.fetchPreviousPage(new PaginationManager.Callback<Message>() {
@Override
public void onPageAvailable(List<? extends Message> list) {
Log.d("Mitter", "Previous: Page Size: " + list.size());
for (Message message : list) {
Log.d("Mitter", message.getTextPayload());
}
messagePaginationManager.fetchPreviousPage(new PaginationManager.Callback<Message>() {
@Override
public void onPageAvailable(List<? extends Message> list) {
Log.d("Mitter", "Previous - 2: Page Size: " + list.size());
for (Message message : list) {
Log.d("Mitter", message.getTextPayload());
}
}
@Override
public void onError(ApiError apiError) {
Log.d("Mitter", "Message Pagination - ApiError: " + apiError.getMessage());
}
});
}
@Override
public void onError(ApiError apiError) {
Log.d("Mitter", "Message Pagination - ApiError: " + apiError.getMessage());
}
});
}
@Override
public void onError(ApiError apiError) {
Log.d("Mitter", "Message Pagination - ApiError: " + apiError.getMessage());
}
});

In this example, you can also call fetchPreviousPage() instead of the initial fetchNextPage(), it’ll return the same list of messages because the pointer isn’t initialised, yet. As soon you make the first call, fetchPreviousPage() and fetchNextPage() behaves exactly as they should.

As you can see, you can go any level deep to fetch your messages. However, in ideal cases, you won’t be nesting this much. You would probably have a loop or a UI action that calls either fetchPreviousPage() or the fetchNextPage() resulting in a much flatter structure.

A typical use case for this type of paging is to have an infinite scrolling list. In the list, you can call fetchPreviousPage() as soon as the user tends to reach the top/bottom of the list based on your display logic.

Note: You don’t need to maintain paging state anywhere, the SDK does all that for you.

Locating users in your application

Mitter.io supports attaching additional pieces of information to a user known as User Locators. User Locators are simply look-up keys that allow you to search a particular user by that key. To learn more about locators, refer to this section.

Using user locators

Currently, Mitter.io supports only 2 types of user locators:

  • email - For attaching email addresses

  • tele - For attaching phone numbers

To use any of these locators while attaching them to a user or searching a user using a locator, you need to specify these keys. The SDK can smartly add in the keys for you while attaching a locator, judging from the locator value that you provide.

For example, if you provide a mobile number while attaching a locator, it’ll add the type automatically for you. The same goes for attaching email locators.

Attaching locators to a user

The SDK provides a method called addCurrentUserLocator() to attach a locator of type email or tele to the current user. Here’s how it works:

Kotlin
Java
users.addCurrentUserLocator("+911234567890")
users.addCurrentUserLocator("+911234567890", null);

This adds a locator of type tele to the current user, where the value of the locator is +911234567890. You can also add an email locator to the current user using the same method:

Kotlin
Java
users.addCurrentUserLocator("rahul@mitter.io")
users.addCurrentUserLocator("rahul@mitter.io", null);

Note: You don’t need to explicitly specify any type, the SDK infers the type from the value you enter.

Searching for users using a locator

Now that you’ve attached a locator, you can easily search for a user using the user locators. You can get a list of users that matches your query parameter by calling the getUserByLocators() method on the Users object and passing a list of serialised locators.

Here’s how you can do this:

Kotlin
Java
users.getUserByLocators(
locators = listOf(
"email:rahul@mitter.io"
),
onValueAvailableCallback = object : Mitter.OnValueAvailableCallback<List<User>> {
override fun onValueAvailable(value: List<User>) {
Log.d("Mitter", "Users: $value")
}
override fun onError(apiError: ApiError) {
Log.d("Mitter", apiError.toString())
}
}
)
users.getUserByLocators(
Arrays.asList(
"email:rahul@mitter.io"
),
new Mitter.OnValueAvailableCallback<List<? extends User>>() {
@Override
public void onValueAvailable(List<? extends User> users) {
Log.d("Mitter", "Users: " + users);
}
@Override
public void onError(ApiError apiError) {
Log.d("Mitter", apiError.toString());
}
}
);

If your query matches any existing user in the scope of your application, you get a list of such users in the callback.

Wrap up

That completes the advanced customisation and querying for users section. We can’t wait to see what you build with Mitter.io.