Getting the Most Out of Pigeon (Part 1)
Asynchronous Pushing
By default all pushes in Pigeon are synchronous. While this works great for testing, you should almost always be using async pushing in production. Pigeon is certainly fast, but dispatching 10,000+ push notifications at once can still take a few seconds.
It's easy to set up with the :on_response
key. Define a function that takes a single parameter. Your notification will be returned with updated information about errors or updates that you may want to handle.
APNS
def send_push do
n = Pigeon.APNS.Notification.new("message", "device token", "push topic")
Pigeon.APNS.push(n, on_response: &handle_push/1)
end
def handle_push(%APNS.Notification{response: :success}) do
# success! probably don't need to do anything
end
def handle_push(%APNS.Notification{response: :bad_device_token}) do
# bad token! remove it from the database!
end
def handle_push(%APNS.Notification{response: _other_error}) do
# some other error happened?
end
FCM
FCM is a little more complicated, but still pretty straightforward. Pushes can be sent in batches of 1,000, so the responses must be iterated individually.
def send_push do
data = %{message: "your message"}
n = Pigeon.FCM.Notification.new(["regid_1", "regid_2"], data)
Pigeon.FCM.push(n, on_response: &handle_push/1)
end
def handle_push(%FCM.Notification{status: :success} = n) do
# success! let's check each reg ID
for response <- n.response, do: handle_regid(response)
end
def handle_push(%FCM.Notification{status: _error} = n) do
# entire push batch failed!
end
def handle_regid({:success, regid}) do
# success!
end
def handle_regid({:update, {old_regid, new_regid}) do
# replace the regid in the database!
end
def handle_regid({:invalid_registration, regid}) do
# remove it!
end
def handle_regid({:not_registered, regid}) do
# remove it!
end
def handle_regid({_error, regid}) do
# some other error happened
end
Don't like that many function handlers? FCM.Notification
provides shortcut
helpers to get the regids you're looking for.
def send_push do
data = %{message: "your message"}
n = Pigeon.FCM.Notification.new(["regid_1", "regid_2"], data)
Pigeon.FCM.push(n, on_response: handle_push/1)
end
def handle_push(%FCM.Notification{status: :success} = notif) do
to_update = FCM.Notification.update?(notif)
to_remove = FCM.Notification.remove?(notif)
# do the reg ID update and deletes
end
def handle_push(%FCM.Notification{status: _other_error}) do
# some other error happened
end
ADM
ADM is an interesting hybrid of APNS and FCM behavior. Only one ADM push can be sent at a time, but the responses are identical to FCM.
def send_push do
data = %{ message: "your message" }
n = Pigeon.ADM.Notification.new("your registration id", data)
Pigeon.ADM.push(n, on_response: &handle_push/1)
end
def handle_push(%ADM.Notification{response: :success}) do
# success!
end
def handle_push(%ADM.Notification{response: :update} = n) do
new_regid = n.update_registration_id
# update it!
end
def handle_push(%ADM.Notification{response: :invalid_registration_id}) do
# remove it!
end
def handle_push(%ADM.Notification{response: :unregistered}) do
# remove it!
end
def handle_push(%ADM.Notification{response: _other_error}) do
# some other error happened?
end
Conclusion
Pigeon's async API is a bit different from traditional module callbacks, but the design was intentional. Data on the notification struct dictates what the callback function should do, instead of creating separate functions for each type of response. It's a much cleaner approach that takes full advantage of Elixir pattern matching, leading to far more readable code.
Interested in learning more about Pigeon? Check it out on
GitHub!