Dale Hawkins ad237b2607
feat: Introduce FireMarkers Sample (#2325)
* feat: Adds a new sample demonstrating how to use Google Maps with Firebase Realtime Database on Android.

* feat(viewmodel): Add tests and error handling for controller logic

This commit introduces unit tests for the `MarkersViewModel` and enhances the app's robustness by adding error handling and improving documentation.

- **ViewModel Unit Tests:** Adds unit tests for `MarkersViewModel` using Mockito, Turbine, and Robolectric. The tests verify the controller/agent logic, ensuring that state is correctly managed based on the `controllerId` from Firebase. It also tests the new error reporting mechanism.
- **Error Handling:** Implements a `SharedFlow` in the `MarkersViewModel` to propagate database errors to the UI. The `MainActivity` now observes this flow and displays errors to the user in a `Snackbar`.
- **Architecture Documentation:** Replaces the static SVG architecture diagram with a more detailed Mermaid diagram in `ARCHITECTURE.md`. The new documentation explains the controller/agent synchronization pattern used for animations.
- **Dependency Updates:** Upgrades Gradle to version 9.1.0 and adds `mockito-kotlin` and `turbine` as test dependencies. The `libs.versions.toml` file is reorganized for better clarity.

* chore: Configure Gradle JVM args and expose ViewModel property

This commit includes two maintenance changes: enabling custom JVM arguments for the Gradle daemon and updating the visibility of a property in the `MarkersViewModel`.

* chore: Annotate the version catalog and build.gradle.kts file

- **Gradle Build Documentation:** Introduces extensive documentation and organization to the `libs.versions.toml` and `app/build.gradle.kts` files. Dependencies, plugins, and versions are now grouped logically with comments explaining their purpose, improving maintainability and clarity.
- **README Update:** The main `README.md` is updated to include a description of the new `FireMarkers` sample.
- **Manifest Cleanup:** Removes the redundant `android:label` from the `MainActivity` in the manifest.

* chore: adds copyright to the new source files

* chore: headers

---------

Co-authored-by: Enrique López Mañas <eenriquelopez@gmail.com>
2025-10-31 10:23:30 +03:00
..

Realtime, Synchronized Map Animations with Firebase and Google Maps

This sample demonstrates how to use the Firebase Realtime Database to drive perfectly synchronized, live animations on a Google Map across multiple Android devices. It showcases a controller/agent architecture where one device drives the animation state, and all other connected devices act as passive observers, ensuring all users see the same animation at the same time.

The app displays a set of markers that can animate between two complex shapes: a jack-o'-lantern and a Christmas tree. One device acts as the "controller," with UI controls to start, stop, and reset the animation. All other devices are "agents," with a button to take control.

Controller View Agent View
Screenshot of the controller UI with animation controls Screenshot of the agent UI with the 'Take Control' button

Key Concepts Demonstrated

  • Firebase Realtime Database: Connecting an Android app to a Firebase Realtime Database.
  • Controller/Agent Synchronization: A robust pattern where one device ("controller") writes animation state to Firebase, and other devices ("agents") listen for real-time updates to synchronize their UIs.
  • Shared Real-time State: Using a separate /animation node in Firebase to store and sync shared state like animation progress, running status, and the current controller's ID.
  • Dynamic, State-Driven UI: Using Jetpack Compose to build a UI that dynamically changes its controls based on whether the device is the current controller or an agent.
  • Procedural Animation: Implementing a client-side animation loop that smoothly interpolates marker properties (latitude, longitude, and color) between two predefined vector shapes.
  • Hilt for Dependency Injection: Using Hilt to provide a singleton FirebaseDatabase instance to the application's ViewModel.

For a detailed visual guide to the project's structure and data flow, see the ARCHITECTURE.md file.

Getting Started

This sample uses the Gradle build system. To build the app, use the gradlew build command or use "Import Project" in Android Studio.

Prerequisites

  • Android Studio (latest version recommended)
  • An Android device or emulator with API level 24 or higher
  • A Google account to create a Firebase project

Setup

To run this sample, you will need a Firebase project and a Google Maps API key.

1. Set up your Firebase Project:

The easiest way to connect your app to Firebase is by using the Firebase Assistant in Android Studio.

  • In Android Studio, go to Tools > Firebase.

  • In the Assistant panel, expand Realtime Database and follow the on-screen instructions to:

    1. Connect to Firebase: This will open a browser for you to log in and either create a new Firebase project or select an existing one.
    2. Add Realtime Database to your app: This will automatically add the necessary dependencies to your build.gradle.kts file and download the google-services.json configuration file into the app/ directory.
  • For more detailed instructions, see the official guide: Add Firebase to your Android project.

2. Configure Database Rules:

For this sample to work, your Realtime Database must be configured with public read/write rules. In the Firebase console, navigate to your Realtime Database and select the Rules tab. Replace the existing rules with the following:

{
  "rules": {
    ".read": "true",
    ".write": "true"
  }
}

Note: These rules are for demonstration purposes only and are insecure. They allow anyone to read or write to your database. For a production app, you must implement more restrictive rules to protect your data. See the official guide on Securing Realtime Database Rules for more information.

3. Add your Google Maps API Key:

The app requires a Google Maps API key to display the map.

  1. Follow the Maps SDK for Android documentation to get an API key.
  2. Create a file named secrets.properties in the project's root directory (at the same level as local.properties).
  3. Add your API key to the secrets.properties file, like this:
    MAPS_API_KEY="YOUR_API_KEY"
    
    (Replace YOUR_API_KEY with the key you obtained). The project is configured to read this key via the Secrets Gradle Plugin.

⚠️ IMPORTANT SECURITY NOTE: You must prevent your API key from being checked into source control. The included .gitignore file is configured to ignore the secrets.properties file. Do not remove this entry. Committing your API key to a public repository can lead to unauthorized use and result in unexpected charges to your account.

Once you have completed these steps, you can run the app on your device or emulator.