Querying

Layer provides a flexible and expressive interface to query through conversations, messages, and announcements.

Note

Queries execute on the local database, and will only return results for conversations and messages where the authenticated user is a participant. Queries will not return empty conversations — a conversation must have at least one message in it in order to be written to the local database.

To demonstrate a simple example, the following queries Layer for the latest 20 messages in the given conversation:

// Return 20 most recent messages in myConversation
Query query = Query.builder(Message.class)
    .predicate(new Predicate(Message.Property.CONVERSATION, Predicate.Operator.EQUAL_TO, myConversation))
    .sortDescriptor(new SortDescriptor(Message.Property.SENT_AT, SortDescriptor.Order.DESCENDING))
    .limit(20)
    .build();

List<Message> recentMessages = layerClient.executeQuery(query, Query.ResultType.OBJECTS);

Constructing a query

The Query object is built with a Class object representing the class upon which the query will be performed. Querying is available on classes that conform to the Queryable protocol. Currently, Conversation, Message, and Announcement are the only classes which conform to the protocol.

Query query = Query.builder(Conversation.class).build();

Filtering results

Queries can be filtered to only include results that match certain conditions. These constraints are expressed in terms of predicates that contain a public property (such as createdAt or isUnread), a predicate operator (such as “is equal to” or “is less than or equal to”), and a value to compare against.

The following predicate will constrain the query result to message objects that belong to the provided conversation:

Query query = Query.builder(Message.class)
    .predicate(new Predicate(Message.Property.CONVERSATION, Predicate.Operator.EQUAL_TO, myConversation))
    .build();

Fields that support querying can be found in Message.Property and Conversation.Property.

Sorting results

You can sort results by setting a value for the sortDescriptor property in the query builder.

The following sort descriptor sorts results in ascending order based on the position property of Message objects:

Query query = Query.builder(Message.class)
    .sortDescriptor(new SortDescriptor(Message.Property.POSITION, SortDescriptor.Order.ASCENDING))
    .build();

Limits and offsets

If you’re displaying results in pages (for example, if you have 100 conversations and want to split them into 5 pages containing 20 conversations each), you can also apply limit and offset values on queries. The limit configures the maximum number of objects to be returned. The offset configures the number of rows to skip in the results.

Query query = Query.builder(Conversation.class)
    .limit(20)
    .offset(0)
    .build();

In the above example, every page would have a limit of 20. The first page would have an offset of 0; the second page would have an offset of 20, the third page would have an offset of 40, and so on.

Result types

Query results can be returned as object instances, object IDs, or as a count of objects matching the query.

// Fully realized objects
List<Message> results = (List<Message>)layerClient.executeQuery(query, Query.ResultType.OBJECTS);

// Object identifiers
List<URI> results = (List<URI>)layerClient.executeQuery(query, Query.ResultType.IDENTIFIERS);

// Count of objects
Long resultArray = layerClient.executeForCount(query);

The default type is Query.ResultType.OBJECTS.

Running a query

You can run a query by calling executeQuery on a LayerClient object. The method takes a Query instance, and, if successful, will return a List of objects which represent the results of the query.

// Return up to 10 conversations with unread messages, newest first
Query query = Query.builder(Conversation.class)
    .predicate(new Predicate(Conversation.Property.HAS_UNREAD_MESSAGES, Predicate.Operator.EQUAL_TO, true))
    .sortDescriptor(new SortDescriptor(Conversation.Property.LAST_MESSAGE_RECEIVED_AT, SortDescriptor.Order.DESCENDING))
    .limit(10)
    .build();

List<Conversation> results = layerClient.executeQuery(query, Query.ResultType.OBJECTS);

Query examples

The following examples demostrate common queries that your app might need:

Fetching all conversations

// Fetch all conversations, sorted by latest message received
Query query = Query.builder(Conversation.class)
    .sortDescriptor(new SortDescriptor(Conversation.Property.LAST_MESSAGE_RECEIVED_AT, SortDescriptor.Order.DESCENDING))
    .build();

List<Conversation> results = layerClient.executeQuery(query, Query.ResultType.OBJECTS);

Fetching a conversation with a particular ID

// Fetches conversation with a specific identifier
Query query = Query.builder(Conversation.class)
    .predicate(new Predicate(Conversation.Property.ID, Predicate.Operator.EQUAL_TO, identifier))
    .build();

List<Conversation> results = layerClient.executeQuery(query, Query.ResultType.OBJECTS);

Fetching all conversations with a certain set of participants

// Fetches all conversations between these users
Query query = Query.builder(Conversation.class)
    .predicate(new Predicate(Conversation.Property.PARTICIPANTS, Predicate.Operator.IN, participants))
    .build();

List<Conversation> results = layerClient.executeQuery(query, Query.ResultType.OBJECTS);

Fetching all messages

// Fetches all Message objects in random order
Query query = Query.builder(Message.class).build();

List<Message> results = layerClient.executeQuery(query, Query.ResultType.OBJECTS);

Counting unread messages

// Fetches the count of all unread messages for the authenticated user
Query query = Query.builder(Message.class)
    .predicate(new Predicate(Message.Property.IS_UNREAD, Predicate.Operator.EQUAL_TO, true))
    .build();

Long resultArray = layerClient.executeForCount(query);

Fetching all messages in a particular conversation

// Fetches all messages for a given conversation, most recent first
Query query = Query.builder(Message.class)
    .predicate(new Predicate(Message.Property.CONVERSATION, Predicate.Operator.EQUAL_TO, myConversation))
    .sortDescriptor(new SortDescriptor(Message.Property.POSITION, SortDescriptor.Order.DESCENDING))
    .build();

List<Message> results = layerClient.executeQuery(query, Query.ResultType.OBJECTS);

Fetching messages sent in the past week

long DAY_IN_MS = 1000 * 60 * 60 * 24;
Date lastWeek = new Date(System.currentTimeMillis() - (7 * DAY_IN_MS))

Query query = Query.builder(Message.class)
    .predicate(new Predicate(Message.Property.SENT_AT, Predicate.Operator.GREATER_THAN_OR_EQUAL_TO, lastWeek))
    .sortDescriptor(new SortDescriptor(Message.Property.SENT_AT, SortDescriptor.Order.DESCENDING))
    .build();

List<Message> results = layerClient.executeQuery(query, Query.ResultType.OBJECTS);

Fetching messages containing PNGs

// Fetch all messages containing PNGs
Query query = Query.builder(Message.class)
        .predicate(new Predicate(Message.Property.PART_MIME_TYPE, Predicate.Operator.IN, "image/png"))
        .build();
List<Message> messagesWithPngs = layerClient.executeQuery(query, Query.ResultType.OBJECTS);

Fetching message parts containing PNGs

// Fetching message parts containing PNGs
Query query = Query.builder(MessagePart.class)
        .predicate(new Predicate(MessagePart.Property.MIME_TYPE, Predicate.Operator.IN, "image/png"))
        .build();
List<MessagePart> messagePartsWithPngs = layerClient.executeQuery(query, Query.ResultType.OBJECTS);

Metadata queries

Conversation metadata is queryable, which allows you to find conversations with specific properties that matter to your app, such as background color, topic, or any other type of value. The following examples demonstrate how powerful querying on metadata can be; assume that we have created the following conversations at some point in our app:

/*
    {"first": {
        "second": {
            "third": "NestedResult"
        }
    }}
 */
Metadata nestedMetadata = ...;
/*
    {"backgroundColor": "blue"}
 */
Metadata blueMetadata = ...;
/*
    {"backgroundColor": "red",
     "title": "testUser"}
 */
Metadata redWithTitleMetadata = ...;
/*
    {"1": {
        "2": "Hello",
        "3": {
            "4": "Hola",
            "5": "Hey"
        }
    },
    "6": "Hi"}
 */
Metadata bigMapMetadata = ...;

Conversation convoWithNestedMap = layerClient.newConversation(participants);
convoWithNestedMap.putMetadata(nestedMetadata);

Conversation blueConvo = layerClient.newConversation(participants);
convoWithNestedMap.putMetadata(blueMetadata);

Conversation redConvoWithTitle = layerClient.newConversation(participants);
convoWithNestedMap.putMetadata(redWithTitleMetadata);

Conversation convoWithMap = layerClient.newConversation(participants);
convoWithNestedMap.putMetadata(bigMapMetadata);

Fetching conversations with specific metadata

This example shows a predicate that fetches conversations based on a value nested within the metadata:

Query<Conversation> query = Query.builder(Conversation.class)
        .predicate(new Predicate(Conversation.Property.METADATA, "first.second.third", Predicate.Operator.EQUAL_TO, "value"))
        .build();
List<Conversation> conversations = layerClient.executeQuery(query, Query.ResultType.OBJECTS); // this will return convoWithNestedMap

Fetching multiple conversations with specific metadata

Query<Conversation> query = Query.builder(Conversation.class)
        .predicate(new Predicate(Conversation.Property.METADATA, "backgroundColor", Predicate.Operator.IN, new String[]{"red", "blue"}))
        .build();
List<Conversation> conversations = layerClient.executeQuery(query, Query.ResultType.OBJECTS); // this will return blueConvo and redConvoWithTitle

Compound predicates

A compound predicate consists of a set regular predicates, along with a way to join them together (known as a “conjunction operator”).

On Android, compound predicates are built using the CompoundQuery class, which can be initialized with Predicate instances, along with a conjunction operator represented by a CompoundPredicate.Type.

The following example demonstrates a compound predicate that finds (the number of) messages in a certain conversation that were sent by a certain user:

Query query = Query.builder(Message.class)
    .predicate(new CompoundPredicate(CompoundPredicate.Type.AND,
        new Predicate(Message.Property.CONVERSATION, Predicate.Operator.EQUAL_TO, conversation),
        new Predicate(Message.Property.SENT_BY_USER_ID, Predicate.Operator.EQUAL_TO, userID)))
    .sortDescriptor(new SortDescriptor(Message.Property.SENT_AT, SortDescriptor.Order.DESCENDING))
    .build();

List<Message> results = layerClient.executeQuery(query, Query.ResultType.OBJECTS);

The following example demonstrates a compound predicate that finds the conversations with a metadata background color that is either “red” or “blue”, and whose metadata title has the value “testUser”:

Predicate predicate1 = new Predicate(Conversation.Property.METADATA, "backgroundColor", Predicate.Operator.IN, new String[]{"red", "blue"});
Predicate predicate2 = new Predicate(Conversation.Property.METADATA, "title", Predicate.Operator.EQUAL_TO, "testUser");
Query<Conversation> query = Query.builder(Conversation.class)
        .predicate(new CompoundPredicate(CompoundPredicate.Type.AND, predicate1, predicate2))
        .build();
List<Conversation> conversations = layerClient.executeQuery(query, Query.ResultType.OBJECTS); // this will give you redConvoWithTitle
Rich Content Typing indicators