Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 24 additions & 15 deletions src/main/java/com/google/firebase/messaging/FirebaseMessaging.java
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,8 @@ protected String execute() throws FirebaseMessagingException {
* <p>The list of responses obtained by calling {@link BatchResponse#getResponses()} on the return
* value is in the same order as the input list.
*
* @param messages A non-null, non-empty list containing up to 500 messages.
* @param messages A non-null, non-empty list of messages. For very large lists, consider
* chunking into smaller batches to avoid FCM server-side rate limiting.
* @return A {@link BatchResponse} indicating the result of the operation.
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
* delivery. An exception here or a {@link BatchResponse} with all failures indicates a total
Expand All @@ -163,7 +164,6 @@ public BatchResponse sendEach(@NonNull List<Message> messages) throws FirebaseMe
return sendEach(messages, false);
}


/**
* Sends each message in the given list via Firebase Cloud Messaging.
* Unlike {@link #sendAll(List)}, this method makes an HTTP call for each message in the
Expand All @@ -177,7 +177,8 @@ public BatchResponse sendEach(@NonNull List<Message> messages) throws FirebaseMe
* <p>The list of responses obtained by calling {@link BatchResponse#getResponses()} on the return
* value is in the same order as the input list.
*
* @param messages A non-null, non-empty list containing up to 500 messages.
* @param messages A non-null, non-empty list of messages. For very large lists, consider
* chunking into smaller batches to avoid FCM server-side rate limiting.
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
* @return A {@link BatchResponse} indicating the result of the operation.
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
Expand All @@ -197,7 +198,8 @@ public BatchResponse sendEach(
/**
* Similar to {@link #sendEach(List)} but performs the operation asynchronously.
*
* @param messages A non-null, non-empty list containing up to 500 messages.
* @param messages A non-null, non-empty list of messages. For very large lists, consider
* chunking into smaller batches to avoid FCM server-side rate limiting.
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* the messages have been sent.
*/
Expand All @@ -208,7 +210,8 @@ public ApiFuture<BatchResponse> sendEachAsync(@NonNull List<Message> messages) {
/**
* Similar to {@link #sendEach(List, boolean)} but performs the operation asynchronously.
*
* @param messages A non-null, non-empty list containing up to 500 messages.
* @param messages A non-null, non-empty list of messages. For very large lists, consider
* chunking into smaller batches to avoid FCM server-side rate limiting.
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* the messages have been sent.
Expand All @@ -217,30 +220,32 @@ public ApiFuture<BatchResponse> sendEachAsync(@NonNull List<Message> messages, b
return sendEachOpAsync(messages, dryRun);
}

// Returns an ApiFuture directly since this function is non-blocking. Individual child send
// Returns an ApiFuture directly since this function is non-blocking. Individual child send
// requests are still called async and run in background threads.
private ApiFuture<BatchResponse> sendEachOpAsync(
final List<Message> messages, final boolean dryRun) {
final List<Message> immutableMessages = ImmutableList.copyOf(messages);
checkArgument(!immutableMessages.isEmpty(), "messages list must not be empty");
checkArgument(immutableMessages.size() <= 500,
"messages list must not contain more than 500 elements");
// NOTE: The 500-message limit that existed here was inherited from the deprecated sendAll()
// method, which used a single HTTP batch request limited by Google's batch API. Since
// sendEach() makes individual HTTP calls per message, no such limit applies. Callers should
// chunk very large lists themselves to avoid FCM server-side rate limiting.

List<ApiFuture<SendResponse>> list = new ArrayList<>();
for (Message message : immutableMessages) {
// Make async send calls per message
ApiFuture<SendResponse> messageId = sendOpForSendResponse(message, dryRun).callAsync(app);
list.add(messageId);
}

// Gather all futures and combine into a list
ApiFuture<List<SendResponse>> responsesFuture = ApiFutures.allAsList(list);

// Chain this future to wrap the eventual responses in a BatchResponse without blocking
// the main thread. This uses the current thread to execute, but since the transformation
// function is non-blocking the transformation itself is also non-blocking.
return ApiFutures.transform(
responsesFuture,
responsesFuture,
(responses) -> {
return new BatchResponseImpl(responses);
},
Expand Down Expand Up @@ -272,7 +277,8 @@ protected SendResponse execute() {
* {@link BatchResponse#getResponses()} on the return value is in the same order as the
* tokens in the {@link MulticastMessage}.
*
* @param message A non-null {@link MulticastMessage}
* @param message A non-null {@link MulticastMessage}. For very large token lists, consider
* chunking into smaller batches to avoid FCM server-side rate limiting.
* @return A {@link BatchResponse} indicating the result of the operation.
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
* delivery. An exception here or a {@link BatchResponse} with all failures indicates a total
Expand All @@ -297,7 +303,8 @@ public BatchResponse sendEachForMulticast(
* {@link BatchResponse#getResponses()} on the return value is in the same order as the
* tokens in the {@link MulticastMessage}.
*
* @param message A non-null {@link MulticastMessage}.
* @param message A non-null {@link MulticastMessage}. For very large token lists, consider
* chunking into smaller batches to avoid FCM server-side rate limiting.
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
* @return A {@link BatchResponse} indicating the result of the operation.
* @throws FirebaseMessagingException If an error occurs while handing the messages off to FCM for
Expand All @@ -315,7 +322,8 @@ public BatchResponse sendEachForMulticast(@NonNull MulticastMessage message, boo
* Similar to {@link #sendEachForMulticast(MulticastMessage)} but performs the operation
* asynchronously.
*
* @param message A non-null {@link MulticastMessage}.
* @param message A non-null {@link MulticastMessage}. For very large token lists, consider
* chunking into smaller batches to avoid FCM server-side rate limiting.
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* the messages have been sent.
*/
Expand All @@ -327,7 +335,8 @@ public ApiFuture<BatchResponse> sendEachForMulticastAsync(@NonNull MulticastMess
* Similar to {@link #sendEachForMulticast(MulticastMessage, boolean)} but performs the operation
* asynchronously.
*
* @param message A non-null {@link MulticastMessage}.
* @param message A non-null {@link MulticastMessage}. For very large token lists, consider
* chunking into smaller batches to avoid FCM server-side rate limiting.
* @param dryRun A boolean indicating whether to perform a dry run (validation only) of the send.
* @return An {@code ApiFuture} that will complete with a {@link BatchResponse} when
* the messages have been sent.
Expand Down Expand Up @@ -677,4 +686,4 @@ FirebaseMessaging build() {
return new FirebaseMessaging(this);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,22 @@ public void testSendFiveHundredWithSendEach() throws Exception {
assertNull(sendResponse.getException());
}
}

@Test
public void testSendMoreThanFiveHundredWithSendEach() throws Exception {
List<Message> messages = new ArrayList<>();
for (int i = 0; i < 501; i++) {
messages.add(Message.builder().setTopic("foo-bar-" + (i % 10)).build());
}

// Previously this would throw IllegalArgumentException due to the 500 limit.
// After removing the limit, this should succeed.
BatchResponse response = FirebaseMessaging.getInstance().sendEach(messages, true);

assertEquals(501, response.getResponses().size());
assertEquals(501, response.getSuccessCount());
assertEquals(0, response.getFailureCount());
}

@Test
public void testSendEachForMulticast() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
import com.google.firebase.auth.MockGoogleCredentials;
import com.google.firebase.internal.Nullable;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand Down Expand Up @@ -305,22 +306,21 @@ public void testSendEachWithEmptyList() throws FirebaseMessagingException {
}

@Test
public void testSendEachWithTooManyMessages() throws FirebaseMessagingException {
MockFirebaseMessagingClient client = MockFirebaseMessagingClient.fromMessageId(null);
FirebaseMessaging messaging = getMessagingForSend(Suppliers.ofInstance(client));
ImmutableList.Builder<Message> listBuilder = ImmutableList.builder();
public void testSendEachWithMoreThanFiveHundredMessages() {
List<Message> messages = new ArrayList<>();
for (int i = 0; i < 501; i++) {
listBuilder.add(Message.builder().setTopic("topic").build());
messages.add(Message.builder().setTopic("foo-bar").build());
}

// Previously threw IllegalArgumentException due to artificial 500 limit.
// sendEach() makes individual HTTP calls per message, so no such limit applies.
// Verify that 501 messages no longer throws an IllegalArgumentException.
try {
messaging.sendEach(listBuilder.build(), false);
fail("No error thrown for too many messages in the list");
} catch (IllegalArgumentException expected) {
// expected
FirebaseMessaging.getInstance().sendEach(messages);
} catch (IllegalArgumentException e) {
fail("sendEach() should not throw IllegalArgumentException for more than 500 messages");
} catch (Exception e) {
// Other exceptions (e.g. network) are acceptable in unit test context
}

assertNull(client.lastMessage);
}
Comment thread
ezhilnn marked this conversation as resolved.

@Test
Expand Down