{"id":31522410,"url":"https://github.com/pluggableai/fanmeter-readme","last_synced_at":"2026-04-08T20:03:06.809Z","repository":{"id":315222020,"uuid":"1058555580","full_name":"pluggableai/fanmeter-readme","owner":"pluggableai","description":"Readme files for the Fanmeter SDK.","archived":false,"fork":false,"pushed_at":"2025-09-17T11:07:18.000Z","size":74,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2025-10-03T15:07:59.022Z","etag":null,"topics":["android","capaci","cordova","documentation","flutter","ionic","ios","nativescript","react-native"],"latest_commit_sha":null,"homepage":"https://www.fanofthematch.ai/","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/pluggableai.png","metadata":{"files":{"readme":"README-android.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-09-17T08:43:52.000Z","updated_at":"2025-09-17T11:06:24.000Z","dependencies_parsed_at":"2025-09-17T11:47:16.262Z","dependency_job_id":"4959f22c-4a0d-4722-a426-e250b7d501bd","html_url":"https://github.com/pluggableai/fanmeter-readme","commit_stats":null,"previous_names":["pluggableai/fanmeter-readme"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/pluggableai/fanmeter-readme","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pluggableai%2Ffanmeter-readme","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pluggableai%2Ffanmeter-readme/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pluggableai%2Ffanmeter-readme/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pluggableai%2Ffanmeter-readme/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/pluggableai","download_url":"https://codeload.github.com/pluggableai/fanmeter-readme/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/pluggableai%2Ffanmeter-readme/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":31571601,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-04-08T14:31:17.711Z","status":"ssl_error","status_checked_at":"2026-04-08T14:31:17.202Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["android","capaci","cordova","documentation","flutter","ionic","ios","nativescript","react-native"],"created_at":"2025-10-03T15:03:38.146Z","updated_at":"2026-04-08T20:03:06.770Z","avatar_url":"https://github.com/pluggableai.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Pluggable's Fanmeter SDK for Android\n\nThe Fanmeter SDK is the plugin responsible for enabling Fanmeter in any mobile application. It is a simple plug-\u0026-play SDK that makes available a set of methods that can be used to allow users to participate in activations such as the **FAN OF THE MATCH**. The SDK is available in multiple formats, i.e., as a native SDK or as a framework plugin, such as for Flutter or React Native, among others.\n\n## Plug-\u0026-Play vs Build your own UI\n\nThere are two possible types of integration. One is plug-\u0026-play and uses a default native UI that is launched to your users for them to participate in Fanmeter events - it is also able to launch and deliver push notifications as soon as the event starts and finishes, if you configure it. If, on the other hand, you wish to create your own Fanmeter UI, you will follow a \"build your own UI\" approach.\n\n  1. **Plug-\u0026-Play UI**: you want everything automated (including a default Fanmeter view). You should also integrate with FCM to handle received notifications that start the event (in summary, you'll need to use the *execute*, *launchFanmeterNotification* and the *launchFanmeterView* methods);\n\n  2. **Build your own UI**: you want to handle the conditions yourself and develop your own UI (just call the *startService*, *stopService*, and *isServiceRunning* methods).\n\n## Pre-Conditions\n\n### Meta-Data\n\nOn Android, push notification permission is required to inform the user that a foreground service is running. Additionally, Location permission is necessary for fans to participate in geo-restricted events. Make sure to request these permissions.\n\nNote that, for Android only, Fanmeter is required to use a **Data Sync foreground service** to communicate non-intrusive sensor data with Pluggable's server. Since API 34 of Android, when publishing an app that uses such services, you are required to fill a **Policy Declaration** where you must justify the need for such services. In Fanmeter's case, when filling such policy, you will be asked the question \"*What tasks require your app to use the FOREGROUND_SERVICE_DATA_SYNC permission?*\". There, you should:\n\n1. Select the option **OTHER** in **Other tasks**;\n\n2. Provide the following video link: \n    ```\n    https://youtu.be/w4d7Pgksok0\n    ```\n\n3. Provide the following description: \n    ```\n    Fanmeter is an Android SDK, incorporated in this app, that uses a Data Sync foreground service to communicate non-intrusive sensor data with Pluggable servers, which are used to quantify the engagement of the user in real-time. The foreground service must start as soon as the user opts to participate in the event (so that it can collect and communicate data) and must keep running until the user himself decides to terminate his/her participation.\n    ```\n\n### Push Notifications\n\nIf you want a **fully plug-\u0026-play experience** and wish to receive push notifications (if not receiving yet) from Fanmeter in your app, you should integrate Firebase Cloud Messaging. A complete, step-by-step tutorial is available in [Firebase's wiki](https://firebase.google.com/docs/cloud-messaging).\n\n## Set up the Fanmeter SDK in your Android App\n\nFew steps are required to be ready to use this SDK.\n\n### Import the SDK\n\n1. In your project's Gradle file, declare the dependencies for the Fanmeter SDK:\n\n    i. Check the latest version of the SDK and, in the dependencies tag, import the SDK:\n    ```gradle\n    implementation 'io.github.pluggableai:fanmeter:4.3.0'\n    ```\n\n    ii. The SDK may require additional default libraries. Hence, in the same tag and build.gradle file, you may need to add:\n    ```gradle\n    implementation 'com.android.volley:volley:1.2.1'\n    implementation 'androidx.work:work-runtime-ktx:2.8.0'\n    implementation 'androidx.constraintlayout:constraintlayout:2.1.4'\n    implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'\n    implementation 'androidx.databinding:viewbinding:7.1.2'\n    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'\n    implementation 'com.google.android.gms:play-services-location:21.3.0'\n    ```\n\n2. Sync your app to ensure that all dependencies have the necessary versions.\n\n### Push Notifications\n\nIf your app is not yet receiving push notifications and you aim to, you must, in your app's manifest, inside the application tag, add a service that extends `FirebaseMessagingService`. For better engagement with the Plug-\u0026-Play UI, you should handle push notifications. This is required if you want to do any message handling beyond receiving notifications on apps in the background. To receive notifications in foregrounded apps, to receive data payload, to send upstream messages, and so on, you must extend this service.\n\n```xml\n\u003cservice\n   android:name=\".notifications.MyFirebaseMessagingService\"\n   android:foregroundServiceType=\"remoteMessaging\"\n   android:exported=\"false\"\u003e\n   \u003cintent-filter\u003e\n       \u003caction android:name=\"com.google.firebase.MESSAGING_EVENT\" /\u003e\n   \u003c/intent-filter\u003e\n\u003c/service\u003e\n```\n\nThen, optionally, add metadata elements to set a default notification icon and colour. Android uses these values whenever incoming messages do not explicitly set these features.\n\n```xml\n\u003cmeta-data\n   android:name=\"com.google.firebase.messaging.default_notification_icon\"\n   android:resource=\"@drawable/notification_icon\" /\u003e\n\u003cmeta-data\n   android:name=\"com.google.firebase.messaging.default_notification_color\"\n   android:resource=\"@color/notification_color\" /\u003e\n\u003cmeta-data\n   android:name=\"com.google.firebase.messaging.default_notification_channel_id\"\n   android:value=\"@string/default_notification_channel_id\" /\u003e\n```\n\nFor Android, to customize the used notification icon, just add the desired icon in the Android's drawable folder and name it ic_push_app_icon. Otherwise, a default icon, not related to your app, will be used.\n\n### Methods initialize and getEventData\n\nThe `EntryPoint.initialize()` method is mandatory for both plug-\u0026-play and build your own UI scenarios. It is responsible for initializing the SDK and must be invoked before using any other method (a possible approach is to invoke it in the app's main activity). It returns success or an error code.\n\n```kotlin\nprivate const val YOUR_COMPANY_NAME: String = \"companyName\"\nprivate const val YOUR_COMPANY_KEY: String = \"AAAA-BBBB-CCCC-DDDD\"\nprivate const val USER_ID: String = \"externalUserId\"\nprivate const val TOKEN_ID: String = \"externalTokenId\"\nprivate const val USER_EMAIL: String = \"externalUserEmail\"\nprivate const val FCM_TOKEN: String = \"fcm_token\"\nprivate const val TICKET_NUMBER: String = \"ticket_number\"\nprivate const val TICKET_STAND: String = \"ticket_stand\"\nprivate const val REGULATION_URL: String = \"https://link.to.the.regulation\"\nprivate const val FANMETER_LOG: Boolean = false\n\n// When the Fanmeter SDK is initialized, it initializes all company and user data.\nval res = EntryPoint.initialize(\n    this,\n    YOUR_COMPANY_NAME,\n    YOUR_COMPANY_KEY,\n    USER_ID,\n    TOKEN_ID,\n    USER_EMAIL,\n    FCM_TOKEN,\n    TICKET_NUMBER,\n    TICKET_STAND,\n    REGULATION_URL,\n    FANMETER_LOG\n)\n```\n\nWhere:\n  * *this*, is the application context;\n  * *YOUR_COMPANY_NAME*, the name of your company in Fanmeter;\n  * *YOUR_COMPANY_KEY*, your company's license key;\n  * *USER_ID*, the user identifier in your db (can be the username, the uuid, ...);\n  * *TOKEN_ID*, the individual smartphone identifier (allows for the same account in different devices);\n  * *USER_EMAIL*, the user's email. Nullable;\n  * *FCM_TOKEN*, the FCM token id of the user. Nullable;\n  * *TICKET_NUMBER*, the user's ticket number - used for additional analytics. Nullable;\n  * *TICKET_STAND*, the stand where the user is - used for additional analytics. Nullable;\n  * *REGULATION_URL*, the URL to the regulation for your events. Nullable;\n  * *FANMETER_LOG*, enables additional logging.\n\nThe `EntryPoint.getEventData()` method is used to obtain the full data of a particular Fanmeter event, in a dictionary, including the defined rewards and leaderboard classifications, if existing. If null, or not provided, this returns the closest event to date:\n\n```kotlin\nprivate const val EVENT_NAME: String = \"Round 1 2025-2026\"\nprivate const val EVENT_ID: String = \"-99\"\n\n// After initialized, you will be able to use the SDK methods.\nEntryPoint.getEventData(\n    applicationContext,\n    EVENT_NAME\n) { eventData -\u003e\n    val eventId = eventData.optLong(\"eventId\", -99)\n    Log.d(\"A_LOG_TAG\", \"EntryPoint.getEventData($EVENT_NAME): $eventData\")\n}\n\n// OR.\n\n// After initialized, you will be able to use the SDK methods.\nEntryPoint.getEventData(\n    applicationContext\n) { eventData -\u003e\n    val eventId = eventData.optLong(\"eventId\", -99)\n    Log.d(\"A_LOG_TAG\", \"EntryPoint.getEventData(): $eventData\")\n}\n```\n\n### Listeners for User Participation\n\nIn both scenarios, you can subscribe to a listener that will notify you whenever there is a change in the user's participation status. For example, if the user leaves the venue of a geo-restricted event, the listener will alert you to this change. This can be particularly useful for updating the status of banners or buttons within your app.\n\nTwo methods are available: `EntryPoint.subscribeUserParticipationListener()` and `EntryPoint.unsubscribeUserParticipationListener()`. The first method subscribes the listener to a specific event id, and you can subscribe to multiple events. The second method unsubscribes the listener (if no event id is provided, it will unsubscribe all active listeners).\n\nWhile the unsubscribeUserParticipationListener method always returns 1 (Success), as it forces the stop of any or all running listeners, the subscribeUserParticipationListener listener can return the following status codes:\n\n  * 0: User is not participating; \n  * 1: Validating the user participation (validation may take up to 30 seconds);\n  * 2: User is participating;\n  * 3: User has GPS disabled; \n  * 4: User is out of venue; \n  * 5: Unknown GPS coordinates.\n\nThese methods can be invoked as shown below:\n\n```kotlin\n// Subscribing a listener.\nEntryPoint.subscribeUserParticipationListener(\n    applicationContext,\n    eventId = EVENT_ID\n) { state -\u003e\n    Log.d(\"A_LOG_TAG\", \"Event $eventId | EntryPoint.subscribeUserParticipationListener: $state\")\n}\n\n// OR.\n\n// Unsubscribing a listener. If no event id is set, it cancels all listeners.\nEntryPoint.unsubscribeUserParticipationListener() {\n    Log.d(\"A_LOG_TAG\", \"EntryPoint.unsubscribeUserParticipationListener: $it\")\n}\n```\n\n### Plug-\u0026-Play UI\n\nAfter initializing the SDK, you are now ready to start calling Fanmeter. In particular, if you want to automate the entire process, this library exposes three methods, that must be called as demonstrated below. In particular:\n\n  * `execute`, launches the SDK Fanmeter default's view as soon as a Fanmeter notification is tapped by the user; \n  * `launchFanmeterNotification`, launches a local Fanmeter notification to the user, which is required by Android when the app is in the foreground; \n  * `launchFanmeterView`, launches the SDK Fanmeter default's view. It can be associated with a button or banner in your app, redirecting the user to the Fanmeter native view, allowing users without notification permissions to participate in the event. It will open the Fanmeter view with the event with the closest date to the current date.\n\nThe `execute` method is used for backgrounded processes and will open the default Fanmeter view to the user. It must be called in the app's Main Activity. An example is as follows:\n\n```kotlin\n// Loads the Fanmeter SDK.\nimport com.pluggableai.fanmeter.EntryPoint\n\nclass MainActivity: ComponentActivity() {\n\n    //...\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        //...\n        if(intent != null){\n            launchFanmeter(intent)\n        }\n    }\n\n    private fun launchFanmeter(intent: Intent) {\n        Log.d(TAG, \"Message data payload: ${intent.extras}\")\n        // Execute the Fanmeter SDK and launch the default Fanmeter view.\n        EntryPoint.execute(\n            this,\n            intent.extras,\n            null\n        ){\n            Log.d(TAG, \"EntryPoint.execute: $it\")\n        }\n    }\n}\n```\n\nWhere:\n  * *this*, is the application context;\n  * *extras*, the remote data received with the intent;\n  * *notificationClassResponse*, the name of the class that is being instantiated when the user clicks the notification - example: \"com.company.activities.SearchActivity\" (null opens the app's default view).\n\nThe `launchFanmeterNotification` method launches a local notification when the app is in a foreground state. Since you have already added the service that extends `FirebaseMessagingService`, you are now required to implement it so that we can display notifications when the app is in the foreground (background notifications are displayed automatically). The service should override the `onMessageReceived` callback. It should handle any message within 20 seconds of receipt (10 seconds on Android Marshmallow) - the Fanmeter SDK respects these limits and executes in milliseconds. You must guarantee that you don't add any extra processing that consumes the available computation time. The next lines show an example of a class that extends `FirebaseMessagingService` and that calls the SDK's `EntryPoint.launchFanmeterNotification`.\n\n```kotlin\nimport android.util.Log\nimport com.google.firebase.messaging.FirebaseMessagingService\nimport com.google.firebase.messaging.RemoteMessage\n// Loads the Fanmeter SDK.\nimport com.pluggableai.fanmeter.EntryPoint\n\nclass MyFirebaseMessagingService: FirebaseMessagingService() {\n    companion object {\n        private const val TAG: String = \"MyFirebaseMessagingService\"\n    }\n\n    override fun onMessageReceived(remoteMessage: RemoteMessage) {\n        Log.d(TAG, \"Notification received from: ${remoteMessage.from}\")\n        // Check if message contains a data payload.\n        if (remoteMessage.data.isNotEmpty()) {\n            launchFanmeter(remoteMessage)\n        }\n    }\n\n    private fun launchFanmeter(remoteMessage: RemoteMessage) {\n        Log.d(TAG, \"Message data payload: ${remoteMessage.data}\")\n        // Execute the Fanmeter SDK and show a local notification to the user.\n        EntryPoint.launchFanmeterNotification(\n            this,\n            remoteMessage.data,\n            null\n        ){\n            Log.d(TAG, \"onMessageReceived | launchFanmeterNotification: $it\")\n        }\n    }\n}\n```\n\nWhere:\n  * *this*, is the application context;\n  * *notificationData*, the remote data received with the notification;\n  * *notificationClassResponse*, the name of the class that is being instantiated when the user clicks the notification - example: \"com.company.activities.SearchActivity\" (null opens the app's default view).\n\nFinally, the `launchFanmeterView` method should be called on a button click, redirecting users to Fanmeter's default UI. This method accepts an eventId, that should be obtained using the `getEventData` method. If a null eventId is passed, this method will open the closest available event to the current date.\n\n```kotlin\n// Launch view button!\nButton(onClick = {\n    EntryPoint.launchFanmeterView(\n        applicationContext\n    ){\n        Log.d(TAG, \"EntryPoint.launchFanmeterView(): $it\")\n    }\n})\n```\n\nWhere:\n  * *applicationContext*, is the application context.\n\nThese functions return the following values:\n\n  * 1, success;\n  * -80, no GPS/PUSH permissions;\n  * -81, GPS disabled;\n  * -82, invalid event coordinates;\n  * -89, SDK not initialized;\n  * -91, invalid notification data;\n  * -92, invalid company license;\n  * -93, invalid event;\n  * -94, event not happening now;\n  * -95, invalid external user data;\n  * -96, failed to get event data;\n  * -97, failed to start the Fanmeter service;\n  * -98, another Fanmeter service is already running.\n\nYou are also **required to subscribe the user to a FCM topic** so that the user can receive Fanmeter notifications. This can be done, for example, on your Main Activity.\n\n```kotlin\n//...\n\nclass MainActivity: ComponentActivity() {\n    //...\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        //...\n        if(intent != null){\n            launchFanmeter(intent)\n        }\n        //...\n        Firebase.messaging.subscribeToTopic(\"football_senior\").addOnCompleteListener { task -\u003e\n            Log.d(\"messaging.subscribeToTopic\", task.isSuccessful)\n        }\n    }\n}\n```\n\n**NOTE**: You can, and should, customize the used notification icon. For that, just add the desired icon in the Android's drawable folder and name it `ic_push_app_icon`.\n\n### Build your own UI\n\nIf you want full control and implement your own UI, this library exposes three methods, that must be called as demonstrated below. In particular:\n\n  * `startService`, starts the Fanmeter service that enables Fanmeter for your client's device during a particular event;\n  * `stopService`, stops the Fanmeter service. The service will, still, terminate automatically as soon as the event ends. This returns 1, if success, otherwise an error code;\n  * `isServiceRunning`, used to check the current status of the Fanmeter service. Returns 1, if the service is running, otherwise 0.\n\nThe `startService` method is used to start the Fanmeter service. This method accepts an eventId, that should be obtained using the `getEventData` method and should be called as follows (associated, for example, to a particular button):\n\n```kotlin\n// Start the Fanmeter Service using a specific button!\nButton(onClick = {\n    EntryPoint.startService(\n        applicationContext,\n        EVENT_ID,\n        null\n    ){\n        Log.d(\"EntryPoint.startService\", \"Service started: $it\")\n    }\n})\n```\n\nWhere:\n  * *applicationContext*, is the application context;\n  * *EVENT_ID*, the id of the event the user will participate when the start service is called;\n  * *notificationClassResponse*, the name of the class that is being instantiated when the user clicks the notification - example: \"com.company.activities.SearchActivity\" (null opens the app's default view).\n\nThis method returns the following values:\n\n  * 1, success;\n  * -80, no GPS/PUSH permissions;\n  * -81, GPS disabled;\n  * -82, invalid event coordinates;\n  * -89, SDK not initialized;\n  * -92, invalid company license;\n  * -93, invalid event;\n  * -94, event not happening now;\n  * -95, invalid external user data;\n  * -96, failed to get event data;\n  * -97, failed to start the Fanmeter service;\n  * -98, another Fanmeter service is already running.\n\nThe `stopService` method is used to stop the Fanmeter service (can be toggled with the previous method). Even if the user does not explicitly stop the service, it will automatically stop as soon as the event finishes. This returns 1, if success, otherwise an error code.\n\n```kotlin\n// Stop the Fanmeter Service using a specific button!\nButton(onClick = {\n    EntryPoint.stopService(\n        applicationContext\n    ){\n        Log.d(\"EntryPoint.stopService\", \"Service stopped: $it\")\n    }\n})\n```\n\nWhere:\n  * *applicationContext*, is the application context.\n\nFinally, the `isServiceRunning` method is used to check the current status of the Fanmeter service. This returns 1, if the service is running, otherwise 0.\n\n```kotlin\n// Check the Fanmeter Service status.\nEntryPoint.isServiceRunning(\n    applicationContext\n){\n    Log.d(\"EntryPoint.isServiceRunning\", \"Service status: $it\")\n}\n```\n\nWhere:\n  * *applicationContext*, is the application context.\n\n### Additional info\n\nOther important methods are to **request user permission** to be able to **send notifications** and **request GPS permission**. Also, **get the user token and listen for changes** to get the user FCM_TOKEN and update it when it changes.\n\nIn addition, **prevent multiple calls to the launchFanmeterView** method so that it is only triggered when no previous call is in progress.\n\n**NOTE**: You can, and should, customize the used notification icon. For that, just add the desired icon in the Android's drawable folder and name it `ic_push_app_icon`.\n\nIn addition, please note that FCM via APNs does not work on iOS Simulators. To receive messages and notifications a real device is required. The same is recommended for Android.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpluggableai%2Ffanmeter-readme","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpluggableai%2Ffanmeter-readme","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpluggableai%2Ffanmeter-readme/lists"}