New Anvil Firebase Integration

Hi everyone,

I’m excited to announce a new addition to the Anvil community: Anvil Firebase Messaging (Firebase HTTP v1). This library is tailor-made to integrate Firebase Cloud Messaging (FCM) seamlessly into your Anvil apps. It’s designed to make adding robust messaging and notification features more accessible and efficient.

Key Features:

  • Easy Setup: Simplified process for configuring Firebase in both the Anvil app and Firebase Console.

  • Client-Side Integration: Effortlessly set up Firebase Messaging on the client side with minimal code.

  • Server-Side Interaction: Extensive server-side capabilities to manage messages, topics, and much more.

  • Service Worker Support: Includes a pre-configured service worker script for handling notifications.

  • Comprehensive Documentation: Complete with a quick start guide, detailed class descriptions, and illustrative examples.

Quick Start Guide:

Our detailed Quick Start Guide leads you through:

  1. Setting up your Firebase project in the Firebase Console.

  2. Setting Up the Anvil App and Integrating Dependencies

  3. Client-side integration of Firebase Messaging within Anvil.

  4. Server-side interactions with Firebase Messaging in Anvil.

Future Expansions:

While currently focused on messaging, I’m looking forward to expanding this library to include more Firebase functionality, such as Firestore Database interactions, and more. Stay tuned for updates!

How Can This Help Your Anvil App?

Firebase Messaging enables you to:

  • Send instant notifications to your users.

  • Manage notifications effectively through topic-based messaging.

  • Boost user engagement with interactive notifications.

Getting Started:

To dive in, check out the library on GitHub or import it directly into your Anvil app using Third-Party dependancy ID RKFWZQMLZXJT6WEG. Follow our Quick Start Guide for step-by-step instructions on integrating Firebase Messaging into your Anvil apps.

Feedback and Contributions:

Your feedback and contributions are incredibly valuable! If you have any suggestions, questions, or wish to contribute to the library, feel free to respond to this post or get involved on GitHub.

Happy coding, and I’m eager to see how Firebase Messaging enhances your Anvil apps!

Best regards

11 Likes

This is super exciting, and fills a gap that users are constantly looking to fill. Thanks for putting the work into it!

3 Likes

@chad63e FYI we have a working Firebase integration for Anvil that supports Firestore, Analytics, etc. It’s a couple years old, but was working fine last I checked, and it is still used in production on at least one website. Interactive performance of the database is quite good in Anvil, and can be better than the Anvil back end in some cases. We also built some routines to make interacting with Firestore easier from Anvil, and the library has some additional capabilities, some of which may overlap with the excellent Anvil Extras. We never had time to document and release this to the Anvil community. If you’d be interested in looking at it, we can probably arrange its release. Let me know.

4 Likes

@chad63e Oh, forgot to mention, we have support for Firebase Authentication as well, with some pre-built Anvil components for registering, logging in, resetting your password, etc. Firebase Auth works hand-in-hand with Firestore. There are also some library routines for manipulating dates, logging, message handling, maintaining a ‘stack’ of open forms, and a few other things. The library has types, and some basic unit tests. If you’re interested, please let me know.

2 Likes

@chad63e There is a very simple demo app here: https://mfsl-owner-portal.anvil.app/

You can register for an account, log in, and add some properties to a list. This is using Firebase Auth.
The data is saved in Firestore.

John

Edit: We’ve come to an agreement to release the source for our library. What I’m thinking is that it may make sense to merge them under your direction. I can facilitate and provide technical assistance but I can’t do significant further development or documentation at this time. I’ve looked at your code; your approach to integrating with Firestore looks similar to mine. Very nice job on the documentation. Keep up the good work.

3 Likes

The prebuilt components for registration, login, reset, etc. look very handy. I’m looking forward to seeing this, thank you!

1 Like

@junderhill This sounds like it has most of the parts I haven’t had a chance to implement into my integration. Do you have a GitHub link or Anvil App clone link for people to check it out?

2 Likes

@chad63e @nickantonaccio I’ll get the thing posted, and you folks can take a look at it. We can talk after that if you want.

2 Likes

I’m currently at this step: “Add firebase-admin Version 6.4.0 or greater Still in the “Settings” tab, locate the “Server Uplink Environment” section. Add the following line to the “Uplink packages” field: firebase-admin==6.4.0. Ensure you have the necessary server-side environment set up to accommodate this version.”

However I don’t see this section in my Settings tab. Is it only visible at a certain plan or above? I am on Individual right now I believe.

Hey Mark, you should be able to navigate to the Python Version and switch it to 3.10, then you will have the option to add third-party libraries. It will look like this.

Oh, it’s just referring to the custom packages section? Yes, I do have that, thanks.

How do we actually use the client-side functions like subscribe_to_topic, unsubscribe_to_topic, and send_message_to_user ? They require parameters “topic” and “token”. I would imagine “token” refers to the registration token of the opted-in user, but where do we find out the token assigned to each user?

Hey Mark,

I put this together quickly, so I hope I didn’t miss anything. This is an example of how to use the client side.

import anvil.server
from anvil import *
from anvil.js.window import history, location

if history.state is not None or "url" not in history.state:
    state = {"url": location.hash, "pos": 0}
    history.replaceState(state, "", state["url"])

from Firebase.client import ActionMap, FirebaseClient, FirebaseConfig
from Firebase.messages import (
    Message,
    MulticastMessage,
    SimpleMessage,
    WebpushConfig,
    WebpushFCMOptions,
    WebpushNotification,
    WebpushNotificationAction,
)

from ._anvil_designer import MainTemplate

PUBLIC_VAPID_KEY = anvil.server.call("get_public_vapid_key")

APP_URL = anvil.server.get_app_origin("published")
BELL_ICON = "https://cdn.pixabay.com/photo/2015/12/16/17/41/bell-1096280_960_720.png"
STOCK_IMAGE = "https://mobileroadie.com/blog/wp-content/uploads/2019/10/what-is-push-notifications-cover.jpg"
GOOGLE_ICON = "https://c0.klipartz.com/pngpicture/669/506/gratis-png-google-chrome-logo-complemento-de-la-extension-del-navegador-del-navegador-web-google-chrome-chrome-google-logotipo-icono-social-thumbnail.png"
MY_TOPIC = "your_topic_name"


class Main(MainTemplate):
    def __init__(self, **properties):
        # Set Form properties and Data Bindings.
        self.init_components(**properties)

        # Any code you write here will run before the form opens.

    # --- PUBLIC METHODS ---

    def initilaize_app(self, **event_args):
        # You will need to initialize your Firebase app after the form is shown on the page.

        # initialize the Firebase config. You can find this in the Firebase console.
        firebase_config = FirebaseConfig.from_dict(
            {
                "apiKey": "__your_api_key__",
                "authDomain": "__your_auth_domain__",
                "projectId": "__your_project_id__",
                "storageBucket": "__your_storage_bucket__",
                "messagingSenderId": "__your_messaging_sender_id__",
                "appId": "__your_app_id__",
                "measurementId": "__your_measurement_id__",
            }
        )

        # instantiate the Firebase Client
        self.client = FirebaseClient(
            config=firebase_config,
            public_vapid_key=PUBLIC_VAPID_KEY,
            message_handler=self.handle_message,
            subscribe_handler=self.handle_subscribe,
            unsubscribe_handler=self.handle_unsubscribe,
            save_token_handler=self.handle_save_token,
            topics=MY_TOPIC,
            with_logging=False,
        )

        # initialize the Firebase app
        self.client.initialize_app()

        # request permission to send notifications
        granted = self.client.request_notification_permission()
        print(f"Notifications Granted: {granted}")

    # --- EVENT HANDLERS ---

    def get_client(self, **event_args):
        try:
            return self.client
        except Exception:
            return None

    def handle_message(self, message):
        """Handle a message from Firebase"""
        print(f"Message Recieved: {message}")

    def handle_subscribe(self, topic: str, token: str):
        """Handle a subscribe event from Firebase"""
        response = anvil.server.call(
            "subscribe_to_topic",
            topic=topic,
            token=token,
        )

        print(response)

    def handle_unsubscribe(self, topic: str, token: str):
        """Handle an unsubscribe event from Firebase"""
        print(f"Unsubscribed from: {topic}")

    def handle_save_token(self, token: str):
        """Handle a save token event from Firebase"""
        print(f"Saved token: {token}")

    # --- FORM EVENTS ---

    def form_show(self, **event_args):
        """This method is called when the form is shown on the page"""
        self.initilaize_app()

    # --- USE EXAMPLES ---

    def button_message_click(self, **event_args):
        """This method is called when the button is clicked"""

        # Create a WebpushNotification object with the desired properties
        notification = WebpushNotification(
            title="New Message",
            body="You have a new message from your example app.",
            icon=BELL_ICON,
            image=STOCK_IMAGE,
            data={"temp": 70.6},
            silent=False,
            vibrate=["0.0s", "0.2s", "0.1s", "0.2s"],
        )

        # Define the link for the WebpushFCMOptions
        link = f"{APP_URL}/#action"

        # Create a WebpushFCMOptions object with the defined link
        options = WebpushFCMOptions(link=link)

        # Create a WebpushConfig object with the notification and options
        webpush = WebpushConfig(
            notification=notification,
            fcm_options=options,
        )

        # Create a Message object with the webpush configuration and the client token
        message = Message(
            webpush=webpush,
            token=self.client.token,
        )

        # Call the server function to send the message and store the response
        task = anvil.server.call("send_message", message=message)
        self.add_task(task)

    def button_topic_message_click(self, **event_args):
        """This method is called when the button is clicked"""

        # Create a WebpushNotification object with the desired properties
        notification = WebpushNotification(
            title="New Topic Message",
            body="You have a new topic message from your example app.",
            icon=BELL_ICON,
            image=STOCK_IMAGE,
            data={"temp": 70.6},
            silent=False,
            vibrate=["0.0s", "0.2s", "0.1s", "0.2s"],
        )

        # Define the link for the WebpushFCMOptions
        link = f"{APP_URL}/#action"

        # Create a WebpushFCMOptions object with the defined link
        options = WebpushFCMOptions(link=link)

        # Create a WebpushConfig object with the notification and options
        webpush = WebpushConfig(
            notification=notification,
            fcm_options=options,
        )

        # Create a Message object with the webpush configuration and the topic
        message = Message(
            webpush=webpush,
            topic=MY_TOPIC,
        )

        # Call the server function to send the message and store the response
        task = anvil.server.call("send_message", message=message)
        self.add_task(task)

    def button_simple_message_click(self, **event_args):
        """This method is called when the button is clicked"""

        # Create a SimpleMessage object with the desired properties
        message = SimpleMessage(
            title="New Simple Message",
            body="You have a new simple message from your example app.",
            icon=BELL_ICON,
            image=STOCK_IMAGE,
            token=self.client.token,
            data={"temp": 70.6},
            silent=False,
            vibrate=["0.0s", "0.2s", "0.1s", "0.2s"],
            link=f"{APP_URL}/#action",
        )

        # Call the server function to send the message and store the response
        task = anvil.server.call("send_message", message=message)
        self.add_task(task)

    def button_multicast_message_click(self, **event_args):
        """This method is called when the button is clicked"""

        # Create a WebpushNotification object with the desired properties
        notification = WebpushNotification(
            title="New Multicast Message",
            body="You have a new multi-cast message from your example app.",
            icon=BELL_ICON,
            image=STOCK_IMAGE,
            data={"temp": 70.6},
            silent=False,
            vibrate=["0.0s", "0.2s", "0.1s", "0.2s"],
        )

        # Define the link for the WebpushFCMOptions
        link = f"{APP_URL}/#action"

        # Create a WebpushFCMOptions object with the defined link
        options = WebpushFCMOptions(link=link)

        # Create a WebpushConfig object with the notification and options
        webpush = WebpushConfig(
            notification=notification,
            fcm_options=options,
        )

        # Create a MulticastMessage object with the webpush configuration and the client tokens
        message = MulticastMessage(
            webpush=webpush,
            tokens=[self.client.token],
        )

        # Call the server function to send the multicast message and store the response
        response = anvil.server.call("send_multicast_message", message=message)

        # Print the response
        print(f"Response: {response}")

    def button_send_multi_messages_click(self, **event_args):
        """This method is called when the button is clicked"""

        # Create a WebpushNotification object with the desired properties
        notification = WebpushNotification(
            title="New Topic Message",
            body="You have a new topic message from your example app.",
            icon=BELL_ICON,
            image=STOCK_IMAGE,
            data={"temp": 70.6},
            silent=False,
            vibrate=["0.0s", "0.2s", "0.1s", "0.2s"],
        )

        # Define the link for the WebpushFCMOptions
        link = f"{APP_URL}/#action"

        # Create a WebpushFCMOptions object with the defined link
        options = WebpushFCMOptions(link=link)

        # Create a WebpushConfig object with the notification and options
        webpush = WebpushConfig(
            notification=notification,
            fcm_options=options,
        )

        # Create a Message object with the webpush configuration and the topic
        message1 = Message(
            webpush=webpush,
            topic=MY_TOPIC,
        )

        # Create a SimpleMessage object with the desired properties
        message2 = SimpleMessage(
            title="New Simple Message",
            body="You have a new simple message from your example app.",
            icon=BELL_ICON,
            image=STOCK_IMAGE,
            token=self.client.token,
            data={"temp": 70.6},
            silent=False,
            vibrate=["0.0s", "0.2s", "0.1s", "0.2s"],
            link=f"{APP_URL}/#action",
        )

        # Combine the messages into a list
        messages = [message1, message2]

        # Call the server function to send the multiple messages and store the response
        response = anvil.server.call("send_multiple_messages", messages)

        # Print the response
        print(f"Response: {response}")

    def button_action_message_1_click(self, **event_args):
        """This method is called when the button is clicked"""

        # Define the action title and endpoint
        action_title = "open_action"
        endpoint = "/#action"

        # Create the full URL for the action
        link_url = f"{APP_URL}{endpoint}"

        # Create an ActionMap object with the action name, full URL, and API endpoint flag
        action_map = ActionMap(
            action_name=action_title, full_url=link_url, is_api_endpoint=False
        )

        # Add the action map to the client
        self.client.add_action_map(action_map)

        # Create a WebpushNotificationAction object with the action title
        action = WebpushNotificationAction(
            action=action_title,
            title="Open Action",
        )

        # Create a WebpushNotification object with the desired properties and the action
        notification = WebpushNotification(
            title="New Action Message",
            body="You have a new ACTION message from your example app.",
            icon=BELL_ICON,
            data={"temp": 70.6},
            silent=False,
            vibrate=["0.0s", "0.2s", "0.1s", "0.2s"],
            actions=[action],
        )

        # Create a WebpushFCMOptions object with the link URL
        options = WebpushFCMOptions(link=link_url)

        # Create a WebpushConfig object with the notification and options
        webpush = WebpushConfig(notification=notification, fcm_options=options)

        # Create a Message object with the webpush configuration and the client token
        message = Message(
            webpush=webpush,
            token=self.client.token,
        )

        # Call the server function to send the message and store the response
        response = anvil.server.call("send_message", message=message)

        # Print the response
        print(f"Response: {response}")

    def button_action_message_2_click(self, **event_args):
        """This method is called when the button is clicked"""

        # Define the action title and endpoint
        action_title = "mark_read"
        endpoint = "/mark-read"  # Use just the relative path

        # Create an ActionMap object with the action name, endpoint, data, and API endpoint flag
        action_map = ActionMap(
            action_name=action_title,
            endpoint=endpoint,
            data={"message_id": "test_message_id"},
            is_api_endpoint=True,
        )

        # Add the action map to the client
        self.client.add_action_map(action_map)

        # Create a WebpushNotificationAction object with the action title
        action = WebpushNotificationAction(
            action=action_title,
            title="Mark As Read",
        )

        # Create a WebpushNotification object with the desired properties and the action
        notification = WebpushNotification(
            title="New Action Message",
            body="You have a new ACTION message from your example app.",
            icon=BELL_ICON,
            silent=False,
            vibrate=["0.0s", "0.2s", "0.1s", "0.2s"],
            actions=[action],
        )

        # Create a WebpushConfig object with the notification
        webpush = WebpushConfig(
            notification=notification,
        )

        # Create a Message object with the webpush configuration and the client token
        message = Message(
            webpush=webpush,
            token=self.client.token,
        )

        # Call the server function to send the message and store the response
        response = anvil.server.call("send_message", message=message)

        # Print the response
        print(f"Response: {response}")

    def button_action_message_3_click(self, **event_args):
        """This method is called when the button is clicked"""

        # Define the action title and endpoint
        action_title = "mark_unread"
        endpoint = "/mark-unread"

        # Create an ActionMap object with the action name, endpoint, parameters, and API endpoint flag
        action_map = ActionMap(
            action_name=action_title,
            endpoint=endpoint,
            params={"message_id": "test_message_id"},
            is_api_endpoint=True,
        )

        # Add the action map to the client
        self.client.add_action_map(action_map)

        # Create a WebpushNotificationAction object with the action title
        action = WebpushNotificationAction(
            action=action_title,
            title="Mark As Unread",
        )

        # Create a WebpushNotification object with the desired properties and the action
        notification = WebpushNotification(
            title="New Action Message",
            body="You have a new ACTION message from your example app.",
            icon=BELL_ICON,
            silent=False,
            vibrate=["0.0s", "0.2s", "0.1s", "0.2s"],
            actions=[action],
        )

        # Create a WebpushConfig object with the notification
        webpush = WebpushConfig(
            notification=notification,
        )

        # Create a Message object with the webpush configuration and the client token
        message = Message(
            webpush=webpush,
            token=self.client.token,
        )

        # Call the server function to send the message and store the response
        response = anvil.server.call("send_message", message=message)

        # Print the response
        print(f"Response: {response}")

    def add_task(self, task):
        task_id = task.get_id()
        if task_id not in self.tasks:
            self.tasks[task_id] = task
            self.timer_1.interval = 0.5

    def timer_1_tick(self, **event_args):
        """
        This method is called Every [interval] seconds. Does not trigger if [interval] is 0.
        It checks for completed tasks and removes them from the tasks dictionary.
        """
        with anvil.server.no_loading_indicator:
            completed_tasks = [
                task_id for task_id, task in self.tasks.items() if task.is_completed()
            ]
            for task_id in completed_tasks:
                response = self.tasks[task_id].get_return_value()
                print(f"Response: {response}")
                del self.tasks[task_id]

            if not self.tasks:
                self.timer_1.interval = 0

Let me know if I can help with anything else. Sorry if I’m slow in responding, I don’t always see these messages when I’m focused in on my fulltime.

Thank you, could you share your server functions as well please?

I’ve integrated this with my app, and I see the native popup for “Allow notifications?” when I install it on my home screen in Android and iPhone, however:

  • I get the push notifications in the device on Android, when the app is running
  • I don’t get the push notifications in the device on Android if I close the app
  • I don’t get any push notifications on iPhone whether or not the app is running

I can see that the response is being received in the app, and that the device tokens are being registered regardless of whether I’m running it in Android or iPhone, however when the app is closed, there’s no push delivered to the device, and on iPhone I don’t get any system push notifications at all.

Were you able to get them working on Android when the app is not running, or on iPhone at all?

I haven’t worked with this for a while, but if memory serves me, I think I had run into similar issues but didn’t have time to dig down into the cause (whether it be my code, or something obscure in the service). If you find something please let me know, and I’ll try to get this updated as quickly as I can. Also, I wasn’t able to test on any iPhones.

First of all, thanks for doing all the work in this integration!

I have a few questions. I used the old WebPushNotifications integration made by another user of the forums and am just now upgrading to this new version.

I had two main scenarios where I used PushNotifications:

  1. A notification just informing the user of something. It had one or multiple links as buttons that opened the app in a certain “page”;
  2. A notification that was asking the user for a confirmation. It had two links that when clicked DIDN’T open the app, just send a specific message to an API endpoint like this @anvil.server.http_endpoint("/session/confirm/:session/:user/:response")

If I undertood correctly, for the fist case I will use WebpushNotification normally, creating WebpushNotificationActions for each button that I need. How do I specify the action for the click in the body of the notification (not in any button)? Using the options = WebpushFCMOptions(link=link_url)?

For the second case, I would do basically the same… The problem is that for each WebpushNotificationAction I need an ActionMap that has a specific endpoint. In my case, the endpoint receives dinamically constructed PathParams. I could change the endpoint so that it receives the data needed in the body of a POST, but is there any way of passing the data added in the WebpushNotification to the data sent in the POST?

I saw that you used data={"message_id": "test_message_id"}. Are those just fixed strings? Is there any way pass the WebpushNotification.data to ActionMap.data or something like this?

Edit: I tried a few things and can confirm that PushNotifications are only appearing if the app is open or recently closed (both on browser or installed in android).