diff --git a/.github/workflows/firebase_messaging.yaml b/.github/workflows/firebase_messaging.yaml index 53b5a0c36f83..90a07aa220ab 100644 --- a/.github/workflows/firebase_messaging.yaml +++ b/.github/workflows/firebase_messaging.yaml @@ -38,18 +38,24 @@ jobs: profile: Nexus 5X script: ./.github/workflows/scripts/drive-example.sh android - ios: + apple: runs-on: macos-latest - timeout-minutes: 30 + timeout-minutes: 40 steps: - uses: actions/checkout@v1 with: fetch-depth: 0 - name: "Install Flutter" - run: ./.github/workflows/scripts/install-flutter.sh stable + run: ./.github/workflows/scripts/install-flutter.sh dev - name: "Install Tools" - run: ./.github/workflows/scripts/install-tools.sh - - name: "Build Example" + run: | + ./.github/workflows/scripts/install-tools.sh + flutter config --enable-macos-desktop + - name: "Build iOS Example" run: ./.github/workflows/scripts/build-example.sh ios - - name: "Drive Example" + - name: "Drive iOS Example" run: ./.github/workflows/scripts/drive-example.sh ios + - name: "Build MacOS Example" + run: ./.github/workflows/scripts/build-example.sh macos + - name: "Drive MacOS Example" + run: ./.github/workflows/scripts/drive-example.sh macos diff --git a/.github/workflows/scripts/build-example.sh b/.github/workflows/scripts/build-example.sh index b65c08f5e564..b048dd2c9983 100755 --- a/.github/workflows/scripts/build-example.sh +++ b/.github/workflows/scripts/build-example.sh @@ -10,7 +10,7 @@ melos bootstrap if [ "$ACTION" == "android" ] then melos exec -c 1 --scope="$FLUTTERFIRE_PLUGIN_SCOPE_EXAMPLE" -- \ - flutter build apk --debug --target="$TARGET_FILE" + flutter build apk --debug --target="$TARGET_FILE" --dart-define=CI=true MELOS_EXIT_CODE=$? pkill dart || true pkill java || true @@ -21,7 +21,7 @@ fi if [ "$ACTION" == "ios" ] then melos exec -c 1 --scope="$FLUTTERFIRE_PLUGIN_SCOPE_EXAMPLE" -- \ - flutter build ios --no-codesign --simulator --debug --target="$TARGET_FILE" + flutter build ios --no-codesign --simulator --debug --target="$TARGET_FILE" --dart-define=CI=true exit fi @@ -31,6 +31,6 @@ then echo "TODO: Skipping macOS testing due to Flutter dev branch issue." exit melos exec -c 1 --scope="$FLUTTERFIRE_PLUGIN_SCOPE_EXAMPLE" -- \ - flutter build macos --debug --target="$TARGET_FILE" + flutter build macos --debug --target="$TARGET_FILE" --dart-define=CI=true exit fi diff --git a/.github/workflows/scripts/drive-example.sh b/.github/workflows/scripts/drive-example.sh index e5566852e48c..8f6a47c39edd 100755 --- a/.github/workflows/scripts/drive-example.sh +++ b/.github/workflows/scripts/drive-example.sh @@ -7,7 +7,7 @@ then # Sleep to allow emulator to settle. sleep 15 melos exec -c 1 --fail-fast --scope="$FLUTTERFIRE_PLUGIN_SCOPE_EXAMPLE" --dir-exists=test_driver -- \ - flutter drive --no-pub --target=./test_driver/MELOS_PARENT_PACKAGE_NAME_e2e.dart + flutter drive --no-pub --target=./test_driver/MELOS_PARENT_PACKAGE_NAME_e2e.dart --dart-define=CI=true exit fi @@ -22,7 +22,7 @@ then # Uncomment following line to have simulator logs printed out for debugging purposes. # xcrun simctl spawn booted log stream --predicate 'eventMessage contains "flutter"' & melos exec -c 1 --fail-fast --scope="$FLUTTERFIRE_PLUGIN_SCOPE_EXAMPLE" --dir-exists=test_driver -- \ - flutter drive -d \"$SIMULATOR\" --no-pub --target=./test_driver/MELOS_PARENT_PACKAGE_NAME_e2e.dart + flutter drive -d \"$SIMULATOR\" --no-pub --target=./test_driver/MELOS_PARENT_PACKAGE_NAME_e2e.dart --dart-define=CI=true MELOS_EXIT_CODE=$? xcrun simctl shutdown "$SIMULATOR" exit $MELOS_EXIT_CODE @@ -34,7 +34,7 @@ then echo "TODO: Skipping macOS testing due to Flutter dev branch issue." exit melos exec -c 1 --fail-fast --scope="$FLUTTERFIRE_PLUGIN_SCOPE_EXAMPLE" --dir-exists=test_driver -- \ - flutter drive -d macos --no-pub --target=./test_driver/MELOS_PARENT_PACKAGE_NAME_e2e.dart + flutter drive -d macos --no-pub --target=./test_driver/MELOS_PARENT_PACKAGE_NAME_e2e.dart --dart-define=CI=true exit fi @@ -43,6 +43,6 @@ then melos bootstrap chromedriver --port=4444 & melos exec -c 1 --scope="$FLUTTERFIRE_PLUGIN_SCOPE_EXAMPLE" --dir-exists=web -- \ - flutter drive --release --no-pub --verbose-system-logs --browser-name=chrome --target=./test_driver/MELOS_PARENT_PACKAGE_NAME_e2e.dart + flutter drive --release --no-pub --verbose-system-logs --browser-name=chrome --target=./test_driver/MELOS_PARENT_PACKAGE_NAME_e2e.dart --dart-define=CI=true exit fi diff --git a/.gitignore b/.gitignore index a77df6c58d3c..deefb0cd7f0b 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ .atom/ .idea/ .vscode/* +.melos_tool/* !.vscode/tasks.json !.vscode/settings.json diff --git a/README.md b/README.md index 4cdd9b35c465..21bec8b8d195 100644 --- a/README.md +++ b/README.md @@ -17,9 +17,13 @@ --- FlutterFire is a set of [Flutter plugins](https://flutter.io/platform-plugins/) -that enable Flutter apps to use [Firebase](https://firebase.google.com/) services. You can follow an example that shows how to use these plugins in the [Firebase for Flutter](https://codelabs.developers.google.com/codelabs/flutter-firebase/index.html#0) codelab. +that enable Flutter apps to use [Firebase](https://firebase.google.com/) services. You can follow an example that shows +how to use these plugins in +the [Firebase for Flutter](https://codelabs.developers.google.com/codelabs/flutter-firebase/index.html#0) codelab. -[Flutter](https://flutter.dev) is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, web, and desktop from a single codebase. Flutter is used by developers and organizations around the world, and is free and open source. +[Flutter](https://flutter.dev) is Google’s UI toolkit for building beautiful, natively compiled applications for mobile, +web, and desktop from a single codebase. Flutter is used by developers and organizations around the world, and is free +and open source. > *Note*: FlutterFire is still under development (see [roadmap](https://github.com/FirebaseExtended/flutterfire/issues/2582)), and some APIs and platforms might not be available yet. [Feedback](https://github.com/FirebaseExtended/flutterfire/issues) and [Pull Requests](https://github.com/FirebaseExtended/flutterfire/pulls) are most welcome! @@ -28,23 +32,23 @@ that enable Flutter apps to use [Firebase](https://firebase.google.com/) service **Table of contents:** - - [AdMob (`firebase_admob`)](#firebase_admob) - - [Analytics (`firebase_analytics`)](#firebase_analytics) - - [Authentication (`firebase_auth`)](#firebase_auth) - - [Cloud Firestore (`cloud_firestore`)](#cloud_firestore) - - [Cloud Functions (`cloud_functions`)](#cloud_functions) - - [Cloud Messaging (`firebase_messaging`)](#firebase_messaging) - - [Cloud Storage (`firebase_storage`)](#firebase_storage) - - [Core (`firebase_core`)](#firebase_core) - - [Crashlytics (`firebase_crashlytics`)](#firebase_crashlytics) - - [Realtime Database (`firebase_database`)](#firebase_database) - - - [Dynamic Links (`firebase_dynamic_links`)](#firebase_dynamic_links) - - [In-App Messaging (`firebase_in_app_messaging`)](#firebase_in_app_messaging) - - [ML Custom (`firebase_ml_custom`)](#firebase_ml_custom) - - [ML Vision (`firebase_ml_vision`)](#firebase_ml_vision) - - [Performance Monitoring (`firebase_performance`)](#firebase_performance) - - [Remote Config (`firebase_remote_config`)](#firebase_remote_config) +- [AdMob (`firebase_admob`)](#firebase_admob) +- [Analytics (`firebase_analytics`)](#firebase_analytics) +- [Authentication (`firebase_auth`)](#firebase_auth) +- [Cloud Firestore (`cloud_firestore`)](#cloud_firestore) +- [Cloud Functions (`cloud_functions`)](#cloud_functions) +- [Cloud Messaging (`firebase_messaging`)](#firebase_messaging) +- [Cloud Storage (`firebase_storage`)](#firebase_storage) +- [Core (`firebase_core`)](#firebase_core) +- [Crashlytics (`firebase_crashlytics`)](#firebase_crashlytics) +- [Realtime Database (`firebase_database`)](#firebase_database) + +- [Dynamic Links (`firebase_dynamic_links`)](#firebase_dynamic_links) +- [In-App Messaging (`firebase_in_app_messaging`)](#firebase_in_app_messaging) +- [ML Custom (`firebase_ml_custom`)](#firebase_ml_custom) +- [ML Vision (`firebase_ml_vision`)](#firebase_ml_vision) +- [Performance Monitoring (`firebase_performance`)](#firebase_performance) +- [Remote Config (`firebase_remote_config`)](#firebase_remote_config) --- @@ -52,7 +56,8 @@ that enable Flutter apps to use [Firebase](https://firebase.google.com/) service > [![firebase_admob][admob_badge_pub]][admob_pub] [![pub points][admob_badge_pub_points]][admob_pub_points] -Google AdMob is a mobile advertising platform that you can use to generate revenue from your app. Using AdMob with Firebase provides you with additional app usage data and analytics capabilities. [[Learn More][admob_product]] +Google AdMob is a mobile advertising platform that you can use to generate revenue from your app. Using AdMob with +Firebase provides you with additional app usage data and analytics capabilities. [[Learn More][admob_product]] [[View Source][admob_code]] @@ -68,7 +73,9 @@ Google AdMob is a mobile advertising platform that you can use to generate reven > ![firebase_analytics][analytics_badge_ci] [![firebase_analytics][analytics_badge_pub]][analytics_pub] [![pub points][analytics_badge_pub_points]][analytics_pub_points] -Google Analytics for Firebase provides automatic captures of certain key application events and user properties, and you can define your own custom events to measure the things that uniquely matter to your application. [[Learn More][analytics_product]] +Google Analytics for Firebase provides automatic captures of certain key application events and user properties, and you +can define your own custom events to measure the things that uniquely matter to your +application. [[Learn More][analytics_product]] [[View Source][analytics_code]] @@ -84,7 +91,9 @@ Google Analytics for Firebase provides automatic captures of certain key applica > ![firebase_auth][auth_badge_ci] [![firebase_auth][auth_badge_pub]][auth_pub] [![pub points][auth_badge_pub_points]][auth_pub_points] -Firebase Authentication provides easy-to-use APIs to authenticate users to your app. It supports authentication using passwords, phone numbers, popular federated identity providers like Google, Facebook and Twitter, and more. [[Learn More][auth_product]] +Firebase Authentication provides easy-to-use APIs to authenticate users to your app. It supports authentication using +passwords, phone numbers, popular federated identity providers like Google, Facebook and Twitter, and +more. [[Learn More][auth_product]] [[View Source][auth_code]] @@ -100,7 +109,8 @@ Firebase Authentication provides easy-to-use APIs to authenticate users to your > ![cloud_firestore][firestore_badge_ci] [![cloud_firestore][firestore_badge_pub]][firestore_pub] [![pub points][firestore_badge_pub_points]][firestore_pub_points] -Cloud Firestore is a NoSQL document database that lets you easily store, sync, and query data for your mobile and web apps - at global scale. [[Learn More][firestore_product]] +Cloud Firestore is a NoSQL document database that lets you easily store, sync, and query data for your mobile and web +apps - at global scale. [[Learn More][firestore_product]] [[View Documentation][firestore_docs]] [[View Source][firestore_code]] @@ -116,7 +126,9 @@ Cloud Firestore is a NoSQL document database that lets you easily store, sync, a > ![cloud_functions][functions_badge_ci] [![cloud_functions][functions_badge_pub]][functions_pub] [![pub points][functions_badge_pub_points]][functions_pub_points] -The Cloud Functions for Firebase plugin let you call functions directly from within your app. To call a function from your app in this way, write and deploy an HTTPS Callable function in Cloud Functions, and then add client logic to call the function from your app. [[Learn More][functions_product]] +The Cloud Functions for Firebase plugin let you call functions directly from within your app. To call a function from +your app in this way, write and deploy an HTTPS Callable function in Cloud Functions, and then add client logic to call +the function from your app. [[Learn More][functions_product]] [[View Source][functions_code]] @@ -132,7 +144,9 @@ The Cloud Functions for Firebase plugin let you call functions directly from wit > [![firebase_messaging][messaging_badge_pub]][messaging_pub] [![pub points][messaging_badge_pub_points]][messaging_pub_points] -Firebase Cloud Messaging (FCM) provides a reliable and battery-efficient connection between your server and devices that allows you to deliver and receive messages and notifications on iOS & Android, at no cost. [[Learn More][messaging_product]] +Firebase Cloud Messaging (FCM) provides a reliable and battery-efficient connection between your server and devices that +allows you to deliver and receive messages and notifications on iOS & Android, at no +cost. [[Learn More][messaging_product]] [[View Source][messaging_code]] @@ -140,7 +154,7 @@ Firebase Cloud Messaging (FCM) provides a reliable and battery-efficient connect | Android | iOS | MacOS | Web | |:-------:|:---:|:-----:|:---:| -| ✔️ | ✔️ | | | +| ✔️ | ✔️ | ✔️ | | ---- @@ -148,7 +162,8 @@ Firebase Cloud Messaging (FCM) provides a reliable and battery-efficient connect > ![firebase_storage][storage_badge_ci] [![firebase_storage][storage_badge_pub]][storage_pub] [![pub points][storage_badge_pub_points]][storage_pub_points] -Cloud Storage is designed to help you quickly and easily store and serve user-generated content, such as photos and videos. [[Learn More][storage_product]] +Cloud Storage is designed to help you quickly and easily store and serve user-generated content, such as photos and +videos. [[Learn More][storage_product]] [[View Source][storage_code]] @@ -164,7 +179,8 @@ Cloud Storage is designed to help you quickly and easily store and serve user-ge > ![firebase_core][core_badge_ci] ![firebase_core][core_badge_pub] [![pub points][core_badge_pub_points]][core_pub_points] -Firebase Core provides APIs to manage your Firebase application instances and credentials. This plugin is required by all FlutterFire plugins. +Firebase Core provides APIs to manage your Firebase application instances and credentials. This plugin is required by +all FlutterFire plugins. [[View Documentation][core_docs]] [[View Source][core_code]] @@ -180,7 +196,9 @@ Firebase Core provides APIs to manage your Firebase application instances and cr > [![firebase_crashlytics][crashlytics_badge_pub]][crashlytics_pub] [![pub points][crashlytics_badge_pub_points]][crashlytics_pub_points] -Firebase Crashlytics helps you track, prioritize, and fix stability issues that erode app quality, in realtime. Spend less time triaging and troubleshooting crashes and more time building app features that delight users. [[Learn More][crashlytics_product]] +Firebase Crashlytics helps you track, prioritize, and fix stability issues that erode app quality, in realtime. Spend +less time triaging and troubleshooting crashes and more time building app features that delight +users. [[Learn More][crashlytics_product]] [[View Source][crashlytics_code]] @@ -196,7 +214,8 @@ Firebase Crashlytics helps you track, prioritize, and fix stability issues that > ![firebase_database][database_badge_ci] [![firebase_database][database_badge_pub]][database_pub] [![pub points][database_badge_pub_points]][database_pub_points] -The Firebase Realtime Database is a cloud-hosted NoSQL database that lets you store and sync data between your users in realtime. [[Learn More][database_product]] +The Firebase Realtime Database is a cloud-hosted NoSQL database that lets you store and sync data between your users in +realtime. [[Learn More][database_product]] [[View Source][database_code]] @@ -212,7 +231,8 @@ The Firebase Realtime Database is a cloud-hosted NoSQL database that lets you st > [![firebase_dynamic_links][dynamic_links_badge_pub]][dynamic_links_pub] [![pub points][dynamic_links_badge_pub_points]][dynamic_links_pub_points] -Dynamic Links are smart URLs that allow you to send existing and potential users to any location within your iOS or Android app. [[Learn More][dynamic_links_product]] +Dynamic Links are smart URLs that allow you to send existing and potential users to any location within your iOS or +Android app. [[Learn More][dynamic_links_product]] [[View Source][dynamic_links_code]] @@ -228,7 +248,9 @@ Dynamic Links are smart URLs that allow you to send existing and potential users > [![firebase_in_app_messaging][in_app_messaging_badge_pub]][in_app_messaging_pub] [![pub points][in_app_messaging_badge_pub_points]][in_app_messaging_pub_points] -Firebase In-App Messaging helps you engage users who are actively using your app by sending them targeted and contextual messages that nudge them to complete key in-app actions - like beating a game level, buying an item, or subscribing to content. [[Learn More][in_app_messaging_product]] +Firebase In-App Messaging helps you engage users who are actively using your app by sending them targeted and contextual +messages that nudge them to complete key in-app actions - like beating a game level, buying an item, or subscribing to +content. [[Learn More][in_app_messaging_product]] [[View Source][in_app_messaging_code]] @@ -239,6 +261,7 @@ Firebase In-App Messaging helps you engage users who are actively using your app | ✔️ | ✔️ | | | ---- + ### `firebase_ml_custom` > [![firebase_ml_custom][ml_custom_badge_pub]][ml_custom_pub] [![pub points][ml_custom_badge_pub_points]][ml_custom_pub_points] @@ -275,7 +298,8 @@ Use Firebase ML turn-key solutions with the Cloud Vision APIs. [[Learn More][ml_ > ![firebase_performance][performance_badge_ci] [![firebase_performance][performance_badge_pub]][performance_pub] [![pub points][performance_badge_pub_points]][performance_pub_points] -Get insights into how your app performs from your users’ point of view, with automatic and customized performance tracing. [[Learn More][performance_product]] +Get insights into how your app performs from your users’ point of view, with automatic and customized performance +tracing. [[Learn More][performance_product]] [[View Source][performance_code]] @@ -291,7 +315,8 @@ Get insights into how your app performs from your users’ point of view, with a > ![firebase_remote_config][remote_config_badge_ci] [![firebase_remote_config][remote_config_badge_pub]][remote_config_pub] [![pub points][remote_config_badge_pub_points]][remote_config_pub_points] -With Firebase Remote Config, you can change the behavior and appearance of your app on the fly from the Firebase console, and then track performance in Google Analytics for Firebase. [[Learn More][remote_config_product]] +With Firebase Remote Config, you can change the behavior and appearance of your app on the fly from the Firebase +console, and then track performance in Google Analytics for Firebase. [[Learn More][remote_config_product]] [[View Source][remote_config_code]] @@ -305,141 +330,234 @@ With Firebase Remote Config, you can change the behavior and appearance of your ## Issues -Please file FlutterFire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/FirebaseExtended/flutterfire/issues/new). +Please file FlutterFire specific issues, bugs, or feature requests in +our [issue tracker](https://github.com/FirebaseExtended/flutterfire/issues/new). -Plugin issues that are not specific to FlutterFire can be filed in the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new). +Plugin issues that are not specific to FlutterFire can be filed in +the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new). ## Contributing -If you wish to contribute a change to any of the existing plugins in this repo, -please review our [contribution guide](https://github.com/FirebaseExtended/flutterfire/blob/master/CONTRIBUTING.md) +If you wish to contribute a change to any of the existing plugins in this repo, please review +our [contribution guide](https://github.com/FirebaseExtended/flutterfire/blob/master/CONTRIBUTING.md) and open a [pull request](https://github.com/FirebaseExtended/flutterfire/pulls). ## Status ![Status: Experimental](https://img.shields.io/badge/Status-Experimental-blue) -This repository is maintained by Googlers but is not a supported Firebase product. Issues here are answered by maintainers and other community members on GitHub on a best-effort basis. +This repository is maintained by Googlers but is not a supported Firebase product. Issues here are answered by +maintainers and other community members on GitHub on a best-effort basis. [admob_pub]: https://pub.dev/packages/firebase_admob + [admob_product]: https://firebase.google.com/docs/admob/ + [admob_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_admob + [admob_pub_points]: https://pub.dev/packages/firebase_admob/score + [admob_badge_pub_points]: https://badges.bar/firebase_admob/pub%20points + [admob_badge_pub]: https://img.shields.io/pub/v/firebase_admob.svg [analytics_pub]: https://pub.dev/packages/firebase_analytics + [analytics_product]: https://firebase.google.com/products/analytics/ + [analytics_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_analytics + [analytics_pub_points]: https://pub.dev/packages/firebase_analytics/score + [analytics_badge_pub_points]: https://badges.bar/firebase_analytics/pub%20points + [analytics_badge_pub]: https://img.shields.io/pub/v/firebase_analytics.svg + [analytics_badge_ci]: https://github.com/FirebaseExtended/flutterfire/workflows/firebase_analytics/badge.svg [auth_pub]: https://pub.dev/packages/firebase_auth + [auth_product]: https://firebase.google.com/products/auth/ + [auth_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_auth + [auth_pub_points]: https://pub.dev/packages/firebase_auth/score + [auth_badge_pub_points]: https://badges.bar/firebase_auth/pub%20points + [auth_badge_pub]: https://img.shields.io/pub/v/firebase_auth.svg + [auth_badge_ci]: https://github.com/FirebaseExtended/flutterfire/workflows/firebase_auth/badge.svg [core_pub]: https://pub.dev/packages/firebase_core + [core_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_core + [core_docs]: https://firebase.flutter.dev/docs/core/usage + [core_pub_points]: https://pub.dev/packages/firebase_core/score + [core_badge_pub_points]: https://badges.bar/firebase_core/pub%20points + [core_badge_pub]: https://img.shields.io/pub/v/firebase_core.svg + [core_badge_ci]: https://github.com/FirebaseExtended/flutterfire/workflows/firebase_core/badge.svg [crashlytics_pub]: https://pub.dev/packages/firebase_crashlytics + [crashlytics_product]: https://firebase.google.com/products/crashlytics/ + [crashlytics_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_crashlytics + [crashlytics_pub_points]: https://pub.dev/packages/firebase_crashlytics/score + [crashlytics_badge_pub_points]: https://badges.bar/firebase_crashlytics/pub%20points + [crashlytics_badge_pub]: https://img.shields.io/pub/v/firebase_crashlytics.svg [database_pub]: https://pub.dev/packages/firebase_database + [database_product]: https://firebase.google.com/products/database/ + [database_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_database + [database_pub_points]: https://pub.dev/packages/firebase_database/score + [database_badge_pub_points]: https://badges.bar/firebase_database/pub%20points + [database_badge_pub]: https://img.shields.io/pub/v/firebase_database.svg + [database_badge_ci]: https://github.com/FirebaseExtended/flutterfire/workflows/firebase_database/badge.svg [dynamic_links_pub]: https://pub.dev/packages/firebase_dynamic_links + [dynamic_links_product]: https://firebase.google.com/products/dynamic-links/ + [dynamic_links_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_dynamic_links + [dynamic_links_pub_points]: https://pub.dev/packages/firebase_dynamic_links/score + [dynamic_links_badge_pub_points]: https://badges.bar/firebase_dynamic_links/pub%20points + [dynamic_links_badge_pub]: https://img.shields.io/pub/v/firebase_dynamic_links.svg [firestore_pub]: https://pub.dev/packages/cloud_firestore + [firestore_docs]: https://firebase.flutter.dev/docs/firestore/usage + [firestore_product]: https://firebase.google.com/products/firestore/ + [firestore_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/cloud_firestore + [firestore_pub_points]: https://pub.dev/packages/cloud_firestore/score + [firestore_badge_pub_points]: https://badges.bar/cloud_firestore/pub%20points + [firestore_badge_pub]: https://img.shields.io/pub/v/cloud_firestore.svg + [firestore_badge_ci]: https://github.com/FirebaseExtended/flutterfire/workflows/firebase_firestore/badge.svg [functions_pub]: https://pub.dev/packages/cloud_functions + [functions_product]: https://firebase.google.com/products/functions/ + [functions_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/cloud_functions + [functions_pub_points]: https://pub.dev/packages/cloud_functions/score + [functions_badge_pub_points]: https://badges.bar/cloud_functions/pub%20points + [functions_badge_pub]: https://img.shields.io/pub/v/cloud_functions.svg + [functions_badge_ci]: https://github.com/FirebaseExtended/flutterfire/workflows/firebase_functions/badge.svg [in_app_messaging_pub]: https://pub.dev/packages/firebase_in_app_messaging + [in_app_messaging_product]: https://firebase.google.com/products/in-app-messaging/ + [in_app_messaging_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_in_app_messaging + [in_app_messaging_pub_points]: https://pub.dev/packages/firebase_in_app_messaging/score + [in_app_messaging_badge_pub_points]: https://badges.bar/firebase_in_app_messaging/pub%20points + [in_app_messaging_badge_pub]: https://img.shields.io/pub/v/firebase_in_app_messaging.svg [messaging_pub]: https://pub.dev/packages/firebase_messaging + [messaging_product]: https://firebase.google.com/products/cloud-messaging/ + [messaging_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_messaging + [messaging_pub_points]: https://pub.dev/packages/firebase_messaging/score + [messaging_badge_pub_points]: https://badges.bar/firebase_messaging/pub%20points + [messaging_badge_pub]: https://img.shields.io/pub/v/firebase_messaging.svg [ml_custom_pub]: https://pub.dev/packages/firebase_ml_custom + [ml_custom_product]: https://firebase.google.com/products/ml/ + [ml_custom_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_ml_custom + [ml_custom_pub_points]: https://pub.dev/packages/firebase_ml_custom/score + [ml_custom_badge_pub_points]: https://badges.bar/firebase_ml_custom/pub%20points + [ml_custom_badge_pub]: https://img.shields.io/pub/v/firebase_ml_custom.svg [ml_vision_pub]: https://pub.dev/packages/firebase_ml_vision + [ml_vision_product]: https://firebase.google.com/products/ml/ + [ml_vision_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_ml_vision + [ml_vision_pub_points]: https://pub.dev/packages/firebase_ml_vision/score + [ml_vision_badge_pub_points]: https://badges.bar/firebase_ml_vision/pub%20points + [ml_vision_badge_pub]: https://img.shields.io/pub/v/firebase_ml_vision.svg [performance_pub]: https://pub.dev/packages/firebase_performance + [performance_product]: https://firebase.google.com/products/performance/ + [performance_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_performance + [performance_pub_points]: https://pub.dev/packages/firebase_performance/score + [performance_badge_pub_points]: https://badges.bar/firebase_performance/pub%20points + [performance_badge_pub]: https://img.shields.io/pub/v/firebase_performance.svg + [performance_badge_ci]: https://github.com/FirebaseExtended/flutterfire/workflows/firebase_performance/badge.svg [remote_config_pub]: https://pub.dev/packages/firebase_remote_config + [remote_config_product]: https://firebase.google.com/products/remote-config/ + [remote_config_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_remote_config + [remote_config_pub_points]: https://pub.dev/packages/firebase_remote_config/score + [remote_config_badge_pub_points]: https://badges.bar/firebase_remote_config/pub%20points + [remote_config_badge_pub]: https://img.shields.io/pub/v/firebase_remote_config.svg + [remote_config_badge_ci]: https://github.com/FirebaseExtended/flutterfire/workflows/firebase_remote_config/badge.svg [storage_pub]: https://pub.dev/packages/firebase_storage + [storage_product]: https://firebase.google.com/products/storage/ + [storage_code]: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_storage + [storage_pub_points]: https://pub.dev/packages/firebase_storage/score + [storage_badge_pub_points]: https://badges.bar/firebase_storage/pub%20points + [storage_badge_pub]: https://img.shields.io/pub/v/firebase_storage.svg + [storage_badge_ci]: https://github.com/FirebaseExtended/flutterfire/workflows/firebase_storage/badge.svg diff --git a/docs/_assets/ios-notification-images-step-1.gif b/docs/_assets/ios-notification-images-step-1.gif new file mode 100644 index 000000000000..3bb6f06f7735 Binary files /dev/null and b/docs/_assets/ios-notification-images-step-1.gif differ diff --git a/docs/_assets/ios-notification-images-step-2.gif b/docs/_assets/ios-notification-images-step-2.gif new file mode 100644 index 000000000000..97d9e8548591 Binary files /dev/null and b/docs/_assets/ios-notification-images-step-2.gif differ diff --git a/docs/_assets/ios-notification-images-step-3.gif b/docs/_assets/ios-notification-images-step-3.gif new file mode 100644 index 000000000000..ab7ebdcce17e Binary files /dev/null and b/docs/_assets/ios-notification-images-step-3.gif differ diff --git a/docs/_assets/ios-provisional-notification.jpg b/docs/_assets/ios-provisional-notification.jpg new file mode 100644 index 000000000000..5146c56acae9 Binary files /dev/null and b/docs/_assets/ios-provisional-notification.jpg differ diff --git a/docs/messaging/ios-integration.mdx b/docs/messaging/apple-integration.mdx similarity index 65% rename from docs/messaging/ios-integration.mdx rename to docs/messaging/apple-integration.mdx index 6995ce50839c..26dd09fc8648 100644 --- a/docs/messaging/ios-integration.mdx +++ b/docs/messaging/apple-integration.mdx @@ -1,14 +1,18 @@ --- -title: iOS Messaging Integration -sidebar_label: iOS Integration -description: iOS requires additional configuration steps to be completed before you can receive messages. +title: FCM via APNs Integration +sidebar_label: Apple Integration +description: iOS & macOS require additional configuration steps to be completed before you can receive messages. --- -Integrating the Cloud Messaging plugin on iOS devices requires additional setup before your devices receive messages. +:::caution +This guide applies to both iOS & macOS Flutter apps, repeat each step for the platforms you require. E.g. if you support iOS & macOS then you will need to integrate twice, once per platform Xcode project. +::: + +Integrating the Cloud Messaging plugin on iOS & macOS devices requires additional setup before your devices receive messages. There are also a number of prerequisites which are required to be able to enable messaging: - You must have an active [Apple Developer Account](https://developer.apple.com/membercenter/index.action). -- You must have a physical iOS device to receive messages. +- For iOS; you must have a physical iOS device to receive messages. - Firebase Cloud Messaging integrates with the [Apple Push Notification service (APNs)](https://developer.apple.com/notifications/), however APNs only works with real devices. @@ -17,7 +21,7 @@ There are also a number of prerequisites which are required to be able to enable Before your application can start to receive messages, you must explicitly enable "Push Notifications" and "Background Modes" within Xcode. -Open your project workspace file via Xcode (`/ios/Runner.xcworkspace`). Once open, follow the steps below: +Open your project workspace file via Xcode (`/{ios|macos}/Runner.xcworkspace`). Once open, follow the steps below: 1. Select your project. 2. Select the project target. @@ -38,7 +42,7 @@ Next the "Push Notifications" capability needs to be added to the project. This Once selected, the capability will be shown below the other enabled capabilities. If no option appears when searching, the capability may already be enabled. -### Enable Background Modes +### Enable Background Modes (iOS only) Next the "Background Modes" capability needs to be enabled, along with both the "Background fetch" and "Remote notifications" sub-modes. This can be added via the "Capability" option on the "Signing & Capabilities" tab: @@ -55,7 +59,7 @@ Now ensure that both the "Background fetch" and the "Remote notifications" sub-m ![Xcode - Enabling Background Modes](https://images.prismic.io/invertase/3a618574-dd9f-4478-9f39-9834d142b2e5_xcode-background-modes-check.gif?auto=compress,format) -## Linking APNs with FCM (iOS) +## Linking APNs with FCM > Note: APNs is required for both `foreground` and `background` messaging to function correctly on iOS. @@ -99,7 +103,7 @@ application that you're developing. On the Apple Developer portal: 1. Click the "Identifiers" side menu item. -2. Click the plus button to register a App Identifier. +2. Click the plus button to register a App Identifier. 3. Select the "App IDs" option and click "Continue". 4. Select the "App" type option and click "Continue". @@ -127,7 +131,7 @@ A provisioning profile enables signed communicate between Apple and your applica real devices, a signed certificate ensures that the app being installed on a device is genuine and has the correct permissions enabled. -On the "Profiles" menu item, register a new Profile. Select the "iOS App Development" checkbox and click "Continue". +On the "Profiles" menu item, register a new Profile. Select the "iOS App Development" (or macOS) checkbox and click "Continue". If you followed [Step 2](#2-registering-an-app-identifier) correctly, your App Identifier will be available in the drop down provided: @@ -146,15 +150,74 @@ real device (using Xcode). Back within Xcode, select your project target and sel If Xcode (via Preferences) is linked to your Apple Account, Xcode can automatically sync the profile created above. Otherwise, you must manually add the profile from the Apple Developer console: -1. Select the project. -2. Select the project target. -3. Assign the provisioning profile. +1. Select the project (usually called 'Runner') in the left hand side project navigator. +2. Select the project target under the 'Targets' heading, usually called 'Runner'. +3. Select the Signing & Capabilities tab. +3. Assign the provisioning profile in the 'Signing' section. ![Xcode - Assign provisioning profile](ios-xcode-assign-provisioning-profile.png) +## (Advanced, Optional) Allowing Notification Images + +> If you want to know more about the specifics of this setup read the [official Firebase docs](https://firebase.google.com/docs/cloud-messaging/ios/send-image). + +On Apple devices, in order for incoming FCM [Notifications](notifications.mdx) to display images from the FCM payload, you must add an additional notification +service extension. This is not a required step. + +### Step 1 - Add a notification service extension + +- From Xcode top menu go to: **File > New > Target...** +- A modal will present a list of possible targets, scroll down or use the filter to select "Notification Service Extension". Press Next. +- Add a product name (use `ImageNotification` to follow along) and click **Finish**. +- Enable the scheme by clicking **Activate**. + +![Xcode - Add a notification service extension](ios-notification-images-step-1.gif) + +### Step 2 - Add target to the Podfile + +Ensure that your new extension has access to Firebase/Messaging pod by adding it in the Podfile: + +- From the Navigator open the Podfile: Pods > Podfile +- Scroll down to the bottom of the file and add: +```ruby +target 'ImageNotification' do + pod 'Firebase/Messaging' +end +``` + +- Install or update your pods using `pod install` from the `ios` and/or `macos` directory. + +![Xcode - Add target to the Podfile](ios-notification-images-step-2.gif) + +### Step 3 - Use the extension helper + +At this point everything should still be running normally. This is the final step which is invoking the extension helper. + +- From the navigator select your `ImageNotification` extension +- Open the `NotificationService.m` file +- At the top of the file import `FirebaseMessaging.h` right after the `NotificationService.h` as shown below: +```diff +#import "NotificationService.h" ++ #import "FirebaseMessaging.h" +``` +- Replace everything from line 25 to 28 with the extension helper: +```diff +- // Modify the notification content here... +- self.bestAttemptContent.title = [NSString stringWithFormat:@"%@ [modified]", self.bestAttemptContent.title]; + +- self.contentHandler(self.bestAttemptContent); ++ [[FIRMessaging extensionHelper] populateNotificationContent:self.bestAttemptContent withContentHandler:contentHandler]; +``` + +![Xcode - Use the extension helper](ios-notification-images-step-3.gif) + +### Step 4 + +Your device can now display images in a notification by specifying the `imageUrl` option in your FCM payload. Keep in mind that a 300KB max image size is enforced by the device. + ## Next steps -Once the above has been completed, you're ready to get started receiving messages on your iOS device for both +Once the above has been completed, you're ready to get started receiving messages on your iOS and/or macOS device for both testing and production. -> Usage documentation will be available once the Firebase Firebase Cloud Messaging plugin update lands as part of the [FlutterFire roadmap](https://github.com/FirebaseExtended/flutterfire/issues/2582). \ No newline at end of file +View the [Usage documentation](usage.mdx) to get started. diff --git a/docs/messaging/notifications.mdx b/docs/messaging/notifications.mdx new file mode 100644 index 000000000000..0519f4feb0cf --- /dev/null +++ b/docs/messaging/notifications.mdx @@ -0,0 +1,250 @@ +--- +title: Notifications +--- + +# Notifications + +Notifications are an important tool used on the majority of applications, aimed at improve user experience & used to engage users +with your application. The Cloud Messaging module provides basic support for displaying and handling notifications. + +## Displaying notifications + +As mentioned in the [Usage](usage.mdx) documentation, message payloads can include a `notification` property which the Firebase SDKs +intercept and attempt to display a visible notification to users. + +The default behaviour on both Android & iOS is to display a notification only when the app is in the background or terminated. If required, +you can override this behaviour by following the [Foreground Notifications](#foreground-notifications) documentation. + +FCM based notifications provide the basics for many use cases, such as displaying text and images. They do not support advanced notifications +such as actions, styling, foreground service notifications etc. To learn more, view the [Local Notifications](#local-notifications) +documentation. + +The documentation below outlines a few different ways you can start to send notification based messages to your devices. + +### Via Firebase Console + +The [Firebase Console](https://console.firebase.google.com/project/_/notification) provides a simple UI to allow devices to display a notification. Using the console, you can: + +- Send a basic notification with custom text and images. +- Target applications which have been added to your project. +- Schedule notifications to display at a later date. +- Send recurring notifications. +- Assign conversion events for your analytical tracking. +- A/B test user interaction (called "experiments"). +- Test notifications on your development devices. + +The Firebase Console automatically sends a message to your devices containing a notification property which is handled by the Firebase Cloud Messaging package. +See [Handling Interaction](#handling-interaction) to learn about how to support user interaction. + +### Via Admin SDKs + +Using one of the [various Firebase Admin SDKs](https://firebase.google.com/docs/reference/admin), you can send customised data payloads to your devices from your own servers. + +For example, when using the [`firebase-admin`] package in a Node.js environment to send messages from a server, a `notification` property can be added to the message payload: + +```js +const admin = require("firebase-admin"); + +await admin.messaging().sendMulticast({ + tokens: ["token_1", "token_2"], + notification: { + title: "Weather Warning!", + body: "A new weather warning has been issued for your location.", + imageUrl: "https://my-cdn.com/extreme-weather.png", + }, +}); +``` + +To learn more about how to integrate Cloud Messaging with your own setup, read the [Server Integration](server-integration.mdx) documentation. + +### Via REST + +If you are unable to use a [Firebase Admin SDK](https://firebase.google.com/docs/reference/admin), Firebase also provides support for sending messages to devices via HTTP POST requests: + +```http +POST https://fcm.googleapis.com/v1/projects/myproject-b5ae1/messages:send HTTP/1.1 + +Content-Type: application/json +Authorization: Bearer ya29.ElqKBGN2Ri_Uz...HnS_uNreA + +{ + "message":{ + "token":"token_1", + "data":{}, + "notification":{ + "title":"FCM Message" + "body":"This is an FCM notification message!", + } + } +} +``` + +To learn more about the REST API, view the [Firebase documentation](https://firebase.google.com/docs/cloud-messaging/send-message#rest), and select the "REST" tab under the code examples. + +## Handling Interaction + +Since notifications are a visible cue, it is common for users to interact with it (by pressing them). The default behaviour on both Android & iOS is to open the +application. If the application is terminated it will be started, if it is in the background it will be brought to the foreground. + +Depending on the content of a notification, you may wish to handle the users interaction when the application opens. For example, if a new chat message is sent via +a notification and the user presses it, you may want to open the specific conversation when the application opens. + +The `firebase-messaging` package provides two ways to handle this interaction: + +1. `getInitialMessage()`: If the application is opened from a terminated state a `Future` containing a `RemoteMessage` will be returned. Once consumed, the `RemoteMessage` will be removed. +2. `onMessageOpenedApp`: A `Stream` which posts a `RemoteMessage` when the application is opened from a background state. + +It is recommended that both scenarios are handled to ensure a smooth UX for your users. The code example below outlines how this can be achieved: + +```dart +class Application extends StatefulWidget { + @override + State createState() => _Application(); +} + +class _Application extends State { + @override + void initState() async { + super.initState(); + + // Get any messages which caused the application to open from + // a terminated state. + RemoteMessage initialMessage = + await FirebaseMessaging.instance.getInitialMessage(); + + // If the message also contains a data property with a "type" of "chat", + // navigate to a chat screen + if (initialMessage?.data['type'] == 'chat') { + Navigator.pushNamed(context, '/chat', + arguments: ChatArguments(initialMessage)); + } + + // Also handle any interaction when the app is in the background via a + // Stream listener + FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { + if (message.data['type'] == 'chat') { + Navigator.pushNamed(context, '/chat', + arguments: ChatArguments(initialMessage)); + } + }); + } + + @override + Widget build(BuildContext context) { + return Text("..."); + } +} +``` + +How you handle interaction depends on your application setup, however the example above shows a basic example of using a StatefulWidget. + +## Advanced Usage + +The below documentation outlines some advanced usage of notifications. + +### Foreground Notifications + +Foreground notifications (also known as "heads up") are those which display for a brief period of time above existing applications, and should +be used for important events. + +Android & iOS have different behaviours when handling notifications whilst applications are in the foreground so keep this in mind whilst developing. + +#### iOS Configuration + +Enabling foreground notifications is generally a straightforward process. Call the `setForegroundNotificationPresentationOptions` +method with named arguments: + +```dart +await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions( + alert: true, // Required to display a heads up notification + badge: true, + sound: true, +); +``` + +Set all values back to `false` to revert to the default functionality. + +#### Android configuration + +Android handles incoming notifications differently based on a few different factors: + +1. If the application is in the background or terminated, the assigned "Notification Channel" is used to determine how the notification is displayed. +2. If the application is currently in the foreground, a visible notification is not presented. + +##### Notification Channels + +On Android, notification messages are sent to [Notification Channels](https://developer.android.com/guide/topics/ui/notifiers/notifications.html#ManageChannels) +which are used to control how a notification is delivered. The default FCM channel used is hidden from users, +however provides a "default" importance level. Heads up notifications require a "max" importance level. + +This means that we need to first create a new channel with an maximum importance level & then assign incoming FCM notifications to this channel. Although this is outside +of the scope of FlutterFire, we can take advantage of the [flutter_local_notifications](https://pub.dev/packages/flutter_local_notifications) package to help us: + +1. Add the `flutter_local_notifications` package to your local project. +2. Create a new `AndroidNotificationChannel` instance: + +```dart +const AndroidNotificationChannel channel = AndroidNotificationChannel( + 'high_importance_channel', // id + 'High Importance Notifications', // title + 'This channel is used for important notifications.', // description + importance: Importance.max, +); +``` + +3. Create the channel on the device (if a channel with an id already exists, it will be updated): + +```dart +final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + +await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation() + ?.createNotificationChannel(channel); +``` + +Once created, we can now update FCM to use our own channel rather than the default FCM one. To do this, open the +`android/app/src/main/AndroidManifest.xml` file for your FlutterProject project. Add the following `meta-data` +schema within the `application` component: + +```xml + +``` + +View the [official documentation](https://firebase.google.com/docs/cloud-messaging/android/client#manifest) to learn more. + +##### Application in foreground + +If your own application is in the foreground, the Firebase Android SDK will block displaying any FCM notification no matter what +Notification Channel has been set. We can however still handle an incoming notification message via the `onMessage` stream and create +a custom local notification using [`flutter_local_notifications`](https://pub.dev/packages/flutter_local_notifications): + +```dart +FirebaseMessaging.onMessage.listen((RemoteMessage message) { + RemoteNotification notification = message.notification; + AndroidNotification android = message.notification?.android; + + // If `onMessage` is triggered with a notification, construct our own + // local notification to show to users using the created channel. + if (notification != null && android != null) { + flutterLocalNotificationsPlugin.show( + notification.hashCode, + notification.title, + notification.body, + NotificationDetails( + android: AndroidNotificationDetails( + channel.id, + channel.name, + channel.description, + icon: android?.smallIcon, + // other properties... + ), + )); + } +}); +``` + +To learn more about local notifications, view the [`flutter_local_notifications`](https://pub.dev/packages/flutter_local_notifications) +documentation. diff --git a/docs/messaging/overview.mdx b/docs/messaging/overview.mdx index 150d59706714..e3ddb2dd1cb3 100644 --- a/docs/messaging/overview.mdx +++ b/docs/messaging/overview.mdx @@ -3,7 +3,7 @@ title: Firebase Cloud Messaging sidebar_label: Overview --- -## What does it do? +## What does it do? Firebase Cloud Messaging (FCM) is a cross-platform messaging solution that lets you reliably send messages at no cost. @@ -11,7 +11,7 @@ Using FCM, you can notify a client app that new email or other data is available messages to drive user re-engagement and retention. For use cases such as instant messaging, a message can transfer a payload of up to 4KB to a client app. - + ## Installation @@ -31,15 +31,59 @@ dependencies: $ flutter pub get ``` -### 3. iOS Integration +### 3. Android Integration -iOS requires additional configuration before you can start receiving messages through Firebase. Read the integration documentation on how to [setup iOS with Firebase Cloud Messaging](messaging/ios-integration.mdx). +> If you are using Flutter Android Embedding V2 (Flutter Version >= 1.12) then no additional integration steps are required for Android. -### 4. (Web Only) Add the SDK +#### Flutter Android Embedding V1 + +For the Flutter Android Embedding V1, the background service must be provided a callback to register plugins with the +background isolate. This is done by giving the `FlutterFirebaseMessagingBackgroundService` a callback to call your +application's `onCreate` method. + +In particular, its `Application` class: + +```java {2,9} +// ... +import io.flutter.plugins.firebase.messaging.FlutterFirebaseMessagingBackgroundService; + +public class Application extends FlutterApplication implements PluginRegistrantCallback { + // ... + @Override + public void onCreate() { + super.onCreate(); + FlutterFirebaseMessagingBackgroundService.setPluginRegistrant(this); + } + + @Override + public void registerWith(PluginRegistry registry) { + GeneratedPluginRegistrant.registerWith(registry); + } + // ... +} +``` + +Which is usually reflected in the application's `AndroidManifest.xml`. E.g.: + +```xml {2} + **Note:** Not calling `FlutterFirebaseMessagingBackgroundService.setPluginRegistrant` will result in an exception being +thrown when an alarm eventually fires. + +### 4. Apple Integration + +iOS & macOS require additional configuration before you can start receiving messages through Firebase. +Read the integration documentation on how to [setup iOS or macOS with Firebase Cloud Messaging](messaging/apple-integration.mdx). + +### 5. (Web Only) Add the SDK > Web is currently not supported. See the [FlutterFire roadmap](https://github.com/FirebaseExtended/flutterfire/issues/2582). -### 5. Rebuild your app +### 6. Rebuild your app Once complete, rebuild your Flutter application: @@ -51,6 +95,4 @@ $ flutter run Once installed, you're ready to start using Firebase Cloud Messaging in your Flutter Project. -> Additional documentation will be available once the Firebase Cloud Messaging plugin update lands as part of the [FlutterFire roadmap](https://github.com/FirebaseExtended/flutterfire/issues/2582). - - +View the [Usage documentation](usage.mdx) to get started. diff --git a/docs/messaging/permissions.mdx b/docs/messaging/permissions.mdx new file mode 100644 index 000000000000..e8662e02f8c6 --- /dev/null +++ b/docs/messaging/permissions.mdx @@ -0,0 +1,101 @@ +--- +title: Permissions +--- + +# Understanding Permissions + +Before diving into requesting notification permissions from your users, it is important to understand how Apple and +web platforms handle permissions. + +Notifications cannot be shown to users if the user has not "granted" your application permission. +The overall notification permission of a single application can be either be "not determined", "granted" or "declined". +Upon installing a new application, the default status is "not determined". + +In order to receive a "granted" status, you must request permission from your user (see below). The user can either +accept or decline your request to grant permissions. If granted, notifications will be displayed based on the permission +settings which were requested. + +If the user declines the request, you cannot re-request permission, trying to request permission again will immediately +return a "denied" status without any user interaction - instead the user must manually enable notification +permissions from the device Settings UI (or via a custom UI). + +## Requesting permissions + +As explained in the [Usage documentation](usage.mdx), permission must be requested from your users in order to display +remote notifications from FCM, via the [`requestPermission`](!cloud_messaging.FirebaseMessaging.requestPermission) API: + +```dart +FirebaseMessaging messaging = FirebaseMessaging.instance; + +NotificationSettings settings = await messaging.requestPermission( + alert: true, + announcement: false, + badge: true, + carPlay: false, + criticalAlert: false, + provisional: false, + sound: true, +); + +if (settings.authorizationStatus == AuthorizationStatus.authorized) { + print('User granted permission'); +} else if (settings.authorizationStatus == AuthorizationStatus.provisional) { + print('User granted provisional permission'); +} else { + print('User declined or has not accepted permission'); +} +``` + +On Apple based platforms, once a permission request has been handled by the user (authorized or denied), it is not possible +to re-request permission. The user must instead update permission via the device Settings UI: + +- If the user denied permission altogether, they must enable app permission fully. +- If the user accepted requested permission (without sound), they must specifically enable the sound option themselves. + +## Permission settings + +Although overall notification permission can be granted, the permissions can be further broken down into 'settings'. + +Settings are used by the device to control notifications behavior, for example alerting the user with sound. When requesting +permission, you can provide arguments if you wish to override the defaults. This is demonstrated in the above example. + +The full list of permission settings can be seen in the table below along with their default values: + +| Permission | Default | Description | +| -------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------- | +| `alert` | `true` | Sets whether notifications can be displayed to the user on the device. | +| `announcement` | `false` | If enabled, Siri will read the notification content out when devices are connected to AirPods. | +| `badge` | `true` | Sets whether a notification dot will appear next to the app icon on the device when there are unread notifications. | +| `carPlay` | `true` | Sets whether notifications will appear when the device is connected to [CarPlay](https://www.apple.com/ios/carplay/). | +| `provisional` | `false` | Sets whether provisional permissions are granted. See [Provisional permission](#provisional-permission) for more information. | +| `sound` | `true` | Sets whether a sound will be played when a notification is displayed on the device. | + +The settings provided will be stored by the device and will be visible in the iOS Settings UI for your application. + +You can also read the current permissions without attempting to request them via the [`getNotificationSettings`](!cloud_messaging.FirebaseMessaging.getNotificationSettings) +method: + +```dart +NotificationSettings settings = await messaging.getNotificationSettings(); +``` + +## Provisional authorization + +Devices on iOS 12+ can use provisional authorization. This type of permission system allows for notification permission +to be instantly granted without displaying a dialog to your user. The permission allows notifications to be displayed quietly +(only visible within the device notification center). + +To enable provision permission, set the `provisional` argument to `true` when requesting permission: + +```dart +NotificationSettings settings = await messaging.requestPermission( + provisional: true, +); +``` + +When a notification is displayed on the device, the user will be presented with several actions prompting to keep receiving +notifications quietly, enable full notification permission or turn them off: + +![iOS Provisional Notification Example](ios-provisional-notification.jpg) + + diff --git a/docs/messaging/server-integration.mdx b/docs/messaging/server-integration.mdx new file mode 100644 index 000000000000..6fc3b2c7e99f --- /dev/null +++ b/docs/messaging/server-integration.mdx @@ -0,0 +1,202 @@ +--- +title: Server Integration +--- + +# Server Integration + +The Cloud Messaging module provides the tools required to enable you to send custom messages directly from your own servers. +For example, you could send a FCM message to a specific device when a new chat message is saved to your database and display +a notification, or update local device storage so the message is instantly available. + +Firebase provides a number of SDKs in different languages such as [Node.JS](https://www.npmjs.com/package/firebase-admin), +[Java](https://firebase.google.com/docs/reference/admin/java/reference/com/google/firebase/messaging/package-summary), +[Python](https://firebase.google.com/docs/reference/admin/python/firebase_admin.messaging), +[C#](https://firebase.google.com/docs/reference/admin/dotnet/namespace/firebase-admin/messaging) and +[Go](https://godoc.org/firebase.google.com/go/messaging). It also supports sending messages over +[HTTP](https://firebase.google.com/docs/reference/fcm/rest/v1/projects.messages). These methods allow you to send messages +directly to your user's devices via the FCM servers. + +## Device tokens + +To send a message to a device, you must access its unique token. +A token is automatically generated by the device and can be accessed using the Cloud Messaging module. +The token should be saved inside of your systems data-store and should be easily accessible when required. + +The examples below use a Cloud Firestore database to store and manage the tokens, and Firebase Authentication to manage the users identity. +You can however use any datastore or authentication method of your choice. + +> If using iOS, ensure you have completed the [setup](ios-integration.mdx) & [requested user permission](usage.mdx) before trying to receive messages! + +### Saving tokens + +Once your application has started, you can call the `getToken` method on the Cloud Messaging module to get the unique +device token (if using a different push notification provider, such as Amazon SNS, you will need to call `getAPNSToken` on iOS): + +```dart +Future saveTokenToDatabase(String token) async { + // Assume user is logged in for this example + String userId = FirebaseAuth.instance.currentUser.uid; + + await FirebaseFirestore + .collection('users') + .doc(userId) + .update({ + 'tokens': FieldValue.arrayUnion(token), + }); +} + +class Application extends StatefulWidget { + @override + State createState() => _Application(); +} + +class _Application extends State { + String _token; + + @override + void initState() async { + super.initState(); + // Get the token each time the application loads + String token = await FirebaseMessaging.instance.getToken(); + + // Save the initial token to the database + await saveTokenToDatabase(token); + + // Any time the token refreshes, store this in the database too. + FirebaseMessaging.instance.onTokenRefresh.listen(saveTokenToDatabase); + } + + @override + Widget build(BuildContext context) { + return Text("..."); + } +} +``` + +The above code snippet has a single purpose; storing the device FCM token on a remote database. When your application first initializes, the +users FCM token is fetched and stored in a database (Cloud Firestore in this example). If the token is refreshed at anypoint whilst your application +is open, the new token is also stored on the database. + +It is important to remember a user can have many tokens (from multiple devices, or token refreshes), therefore we use `FieldValue.arrayUnion` to store +new tokens. When a message is sent via an admin SDK, invalid/expired tokens will throw an error allowing you to then remove them from the database. + +### Using tokens + +With the tokens stored in a secure datastore, we now have the ability to send messages via FCM to those devices. + +The following example uses the Node.JS `firebase-admin` package to send messages to our devices, however any Firebase Admin SDK can be used. + +Imagine our application being similar to Instagram. Users are able to upload pictures, and other users can "like" those pictures. +Each time a post is liked, we want to send a message to the user that uploaded the picture. + +The code below simulates a function which is called with all of the information required when a picture is liked: + +```js +// Node.js e.g via a Firebase Cloud Function +var admin = require("firebase-admin"); + +// ownerId - who owns the picture someone liked +// userId - id of the user who liked the picture +// picture - metadata about the picture + +async function onUserPictureLiked(ownerId, userId, picture) { + // Get the owners details + const owner = admin.firestore().collection("users").doc(ownerId).get(); + + // Get the users details + const user = admin.firestore().collection("users").doc(userId).get(); + + await admin.messaging().sendToDevice( + owner.tokens, // ['token_1', 'token_2', ...] + { + data: { + owner: JSON.stringify(owner), + user: JSON.stringify(user), + picture: JSON.stringify(picture), + }, + }, + { + // Required for background/quit data-only messages on iOS + contentAvailable: true, + // Required for background/quit data-only messages on Android + priority: "high", + } + ); +} +``` + +Data-only messages are sent as low priority on both Android and iOS and will not trigger the background handler by default. To enable this +functionality, you must set the "priority" to `high` on Android and enable the `content-available` flag for iOS in the message payload. + +The data property can send an object of key-value pairs totaling 4KB as string values (hence the `JSON.stringify` calls). + +Within the application, you can then handle these messages how you see fit: + +```dart +FirebaseMessaging.onMessage.listen((RemoteMessage message) { + Map data = message.data; + + Owner owner = Owner.fromMap(jsonDecode(data['owner'])); + User user = User.fromMap(jsonDecode(data['user'])); + Picture picture = Picture.fromMap(jsonDecode(data['picture'])); + + print('The user ${user.name} liked your picture "${picture.title}"!'); +}); +``` + +Your application code can then handle messages as you see fit; updating local cache, displaying a notification or updating UI. The possibilities are endless! + +## Send messages to topics + +When devices [subscribe to topics](usage.mdx#subscribing-to-topics), you can send messages without specifying/storing any device tokens. + +Using the `firebase-admin` Admin SDK as an example, we can send a message to devices subscribed to a topic: + +```js +// Node.js e.g via a Firebase Cloud Function +const admin = require("firebase-admin"); + +const message = { + data: { + type: "warning", + content: "A new weather warning has been created!", + }, + topic: "weather", +}; + +admin + .messaging() + .send(message) + .then((response) => { + console.log("Successfully sent message:", response); + }) + .catch((error) => { + console.log("Error sending message:", error); + }); +``` + +### Conditional topics + +To send a message to a combination of topics, specify a condition, which is a boolean expression that specifies the target topics. +For example, the following condition will send messages to devices that are subscribed to `weather` and either `news` or `traffic`: + +```js +const admin = require("firebase-admin"); + +const message = { + data: { + content: "New updates are available!", + }, + condition: "'weather' in topics && ('news' in topics || 'traffic' in topics)", +}; + +admin + .messaging() + .send(message) + .then((response) => { + console.log("Successfully sent message:", response); + }) + .catch((error) => { + console.log("Error sending message:", error); + }); +``` diff --git a/docs/messaging/usage.mdx b/docs/messaging/usage.mdx index e0ca33105f55..4e5cd802f2ed 100644 --- a/docs/messaging/usage.mdx +++ b/docs/messaging/usage.mdx @@ -3,4 +3,293 @@ title: Cloud Messaging sidebar_label: Usage --- -Cloud Messaging usage +# Cloud Messaging + +To start using the Cloud Messaging package within your project, import it at the top of your project files: + +```dart +import 'package:cloud_messaging/cloud_messaging.dart'; +``` + +Before using Firebase Cloud Messaging, you must first have ensured you have [initialized FlutterFire](../overview.mdx#initializing-flutterfire). + +To create a new Messaging instance, call the [`instance`](!cloud_messaging.FirebaseMessaging.instance) getter on +[`FirebaseMessaging`](!cloud_messaging.FirebaseMessaging): + +```dart +FirebaseMessaging messaging = FirebaseMessaging.instance; +``` + +Messaging currently only supports usage with the default Firebase App instance. + +## Receiving messages + +The Cloud Messaging package connects applications to the [Firebase Cloud Messaging (FCM)](https://firebase.google.com/docs/cloud-messaging) +service. You can send message payloads directly to devices at no cost. Each message payload can be up to 4KB in size, containing pre-defined +or custom data to suit your applications requirements. + +Common use-cases for using messages could be: + +- Displaying a notification (see [Notifications](notifications.mdx)). +- Syncing message data silently on the device (e.g. via [shared_preferences](https://flutter.dev/docs/cookbook/persistence/key-value)). +- Updating the application's UI. + +> To learn about how to send messages to devices from your own server setup, view the [Server Integration](server-integration.mdx) documentation. + +Depending on a devices state, incoming messages are handled differently. To understand these scenarios & how to integrate FCM into your own application, it is first +important to establish the various states a device can be in: + +| State | Description | +| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| **Foreground** | When the application is open, in view & in use. | +| **Background** | When the application is open, however in the background (minimised). This typically occurs when the user has pressed the "home" button on the device, has switched to another app via the app switcher or has the application open on a different tab (web). | +| **Terminated** | When the device is locked or the application is not running. The user can terminate an app by "swiping it away" via the app switcher UI on the device or closing a tab (web). | + +There are a few preconditions which must be met before the application can receive message payloads via FCM: + +- The application must have opened at least once (to allow for registration with FCM). +- On iOS, if the user swipes away the application from app Switcher, it must be manually reopened again for background messages to start working again. +- On Android, if the user force quits the app from device settings, it must be manually reopened again for messages to start working. +- On iOS & macOS, you must have [correctly setup your project](apple-integration.mdx) to integrate with FCM and APNs. + +### Requesting permission (iOS) + +On iOS & Web (coming soon), before FCM payloads can be received on your device, you must first ask the users permission. Android applications are not required +to request permission. + +The `firebase_messaging` package provides a simple API for requesting permission via the [`requestPermission`](!cloud_messaging.FirebaseMessaging.requestPermission) method. +This API accepts a number of named arguments which define the type of permissions you'd like to request, such as whether +messaging containing notification payloads can trigger a sound or read out messages via Siri. By default, +the method requests sensible default permissions. The reference API provides full documentation on what each permission is for. + +To get started, call the method from your application (on iOS a native modal will be displayed, on web +the browsers native API flow will be triggered): + +```dart +FirebaseMessaging messaging = FirebaseMessaging.instance; + +NotificationSettings settings = await messaging.requestPermission( + alert: true, + announcement: false, + badge: true, + carPlay: false, + criticalAlert: false, + provisional: false, + sound: true, +); + +print('User granted permission: ${settings.authorizationStatus}'); +``` + +The [`NotificationSettings`](!cloud_messaging_platform_interface.NotificationSettings) class returned from +the request details information regarding the users decision. + +The `authorizationStatus` property can return a value which can be used to determine the users overall decision: + +- `authorized`: The user granted permission. +- `denied`: The user denied permission. +- `notDetermined`: The user has not yet chosen whether to grant permission. +- `provisional`: The user granted provisional permission (see [Provisional Permission](permissions#provisional-permission)). + +> On Android `authorizationStatus` is always `authorized`. + +The other properties on `NotificationSettings` return whether a specific permission is enabled, disabled or not supported on the current +device. + +For further information, view the [Permissions](permissions.mdx) documentation. + +## Handling messages + +Once permission has been granted & the different types of device state have been understood, your application can now start to handle the incoming +FCM payloads. + +A message payload can be viewed as one of three types: + +1. Notification only message: The payload contains a `notification` property, which will be used to [present a visible notification to the user](notifications.mdx). +2. Data only message: Also known as a "silent message", this payload contains custom key/value pairs within the `data` property which can be used how you see fit. These messages are considered "low priority" (more on this later). +3. Notification & Data messages: Payloads with both `notification` and `data` properties. + +Based on your applications current state, incoming payloads require different implementations to handle them: + +| | Foreground | Background | Terminated | +| ----------------------- | ----------- | --------------------------------------- | --------------------------------------- | +| **Notification** | `onMessage` | `onBackgroundMessage` | `onBackgroundMessage` | +| **Data** | `onMessage` | `onBackgroundMessage` (**_see below_**) | `onBackgroundMessage` (**_see below_**) | +| **Notification & Data** | `onMessage` | `onBackgroundMessage` | `onBackgroundMessage` | + +Data only messages are considered low priority by devices when your application is in the background or terminated, and will be +ignored. You can however explicitly increase the priority by sending additional properties on the FCM payload: + +- On Android, set the `priority` field to `high`. +- On Apple (iOS & MacOS), set the `content-available` field to `true`. + +Since the sending of FCM payloads is custom to your own setup, it is best to read the official [FCM API reference](https://firebase.google.com/docs/cloud-messaging/concept-options) for your chosen +Firebase Admin SDK. + +### Foreground messages + +To listen to messages whilst your application is in the foreground, listen to the [`onMessage`](!cloud_messaging.FirebaseMessaging.onMessage) stream. + +```dart +FirebaseMessaging.onMessage.listen((RemoteMessage message) { + print('Got a message whilst in the foreground!'); + print('Message data: ${message.data}'); + + if (message.notification != null) { + print('Message also contained a notification: ${message.notification}'); + } +}); +``` + +The stream contains a [`RemoteMessage`](!cloud_messaging_platform_interface.RemoteMessage), detailing +various information about the payload, such as where it was from, the unique ID, sent time, whether it contained +a notification & more. Since the message was retrieved whilst your application is in the foreground, you can directly access your Flutter +applications state & context. + +#### Foreground & Notification messages + +Notification messages which arrive whilst the application is in the foreground will not display a visible notification by default, on both +Android & iOS. It is however possible to override this behaviour: + +- On Android, you must create a "High Priority" notification channel. +- On iOS, you can update the presentation options for the application. + +More details on this are discussed in the [Notification: Foreground notifications](notifications.mdx#foreground-notifications) +documentation. + +### Background messages + +Handling messages whilst your application is in the background is a little different. Messages can +be handled via the [`onBackgroundMessage`](!cloud_messaging.FirebaseMessaging.onBackgroundMessage) handler. When received, an +isolate is spawned (Android only, iOS/macOS does not require a separate isolate) allowing you to handle messages even when your application is not running. + +There are a few things to keep in mind about your background message handler: + +1. It must not be an anonymous function. +2. It must be a top-level function (e.g. not a class method which requires initialization). + +```dart +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + // If you're going to use other Firebase services in the background, such as Firestore, + // make sure you call `initializeApp` before using other Firebase services. + await Firebase.initializeApp(); + + print("Handling a background message: ${message.messageId}"); +} + +void main() { + FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler); + runApp(MyApp()); +} +``` + +Since the handler runs in it's own isolate outside of your applications context, it is not possible to update +application state or execute any UI impacting logic. You can however perform logic such as HTTP requests, IO operations +(updating local storage), communicate with other plugins etc. + +It also also recommended to complete your logic as soon as possible. Running long intensive tasks impacts device performance +and may cause the OS to terminate the process. If tasks run for longer than 30 seconds, the device may automatically kill the process. + +#### Notifications + +If your message is a notification one (includes a `notification` property), the Firebase SDKs will intercept this and display a visible +notification to your users (assuming you have requested permission & the user has notifications enabled). Once displayed, the +background handler will be executed (if provided). + +To learn about how to handle user interaction with a notification, view the [Notifications](notifications.mdx) documentation. + +#### Debugging & Hot Reload + +FlutterFirebase Messaging does now support debugging and hot reloading for background isolates, but only if your main isolate is also being debugged, +e.g. run your application in debug and then background it by switching apps so it's no longer in the foreground. + +For viewing additional logs on iOS, the "console.app" application on your Mac also displays +system logs for your iOS device, including those from Flutter. + +#### Low priority messages + +As mentioned above, data only messages are classed as "low priority". Devices can throttle and ignore these messages if your +application is in the background, terminated or a variety of other conditions such as low battery or currently high CPU usage. + +You should not rely data only messages to be delivered, they should only be used to +support your applications non-critical functionality, e.g. pre-fetching data so the next time the user opens your app the +data is ready to be displayed and if the message never gets delivered then your app still functions and fetches data on open. + +To help improve delivery, you can bump the priority of messages. Note; this does still not guarantee delivery. + +For example, if using the `firebase-admin` NodeJS SDK package to send notifications via your server, add additional properties to the +message payload: + +```js +const admin = require("firebase-admin"); + +admin.initializeApp({ + credential: admin.credential.cert(require("./service-account-file.json")), + databaseURL: "https://....firebaseio.com", +}); + +admin.messaging().send({ + token: "device token", + data: { + hello: "world", + }, + // Set Android priority to "high" + android: { + priority: "high", + }, + // Add APNS (Apple) config + apns: { + payload: { + aps: { + contentAvailable: true, + }, + }, + headers: { + 'apns-push-type': 'background', + 'apns-priority': '5', // Must be `5` when `contentAvailable` is set to true. + 'apns-topic': 'io.flutter.plugins.firebase.messaging', // bundle identifier + }, + }, +}); +``` + +This configuration provides the best chance that your data-only message will be delivered to the device. You can read full +descriptions on the use of the properties on the [official Firebase documentation](https://firebase.google.com/docs/reference/admin/node/TokenMessage). + +Apple has very strict undisclosed polices on data only messages, and are very frequently ignored. For example sending too many in a certain time period +will cause the device to block messages, or if CPU consumption is high they will also be blocked. + +For Android you can view Logcat logs which will give a descriptive message on why a notification was not delivered. On Apple platforms the +"console.app" application will display "CANCELED" logs for those it chose to ignore, however doesn't provide a description as to why. + +## Topics + +Topics are a mechanism which allow a device to subscribe and unsubscribe from named PubSub channels, all managed via FCM. +Rather than sending a message to a specific device by FCM token, you can instead send a message to a topic and any devices +subscribed to that topic will receive the message. + +Topics allow you to simplify FCM server integration as you do not need to keep a store of device tokens. There are however some things to keep in mind about topics: + +- Messages sent to topics should not contain sensitive or private information. Do not create a topic for a specific user to subscribe to. +- Topic messaging supports unlimited subscriptions for each topic. +- One app instance can be subscribed to no more than 2000 topics. +- The frequency of new subscriptions is rate-limited per project. If you send too many subscription requests in a short period of time, FCM servers will respond with a 429 RESOURCE_EXHAUSTED ("quota exceeded") response. Retry with exponential backoff. +- A server integration can send a single message to multiple topics at once. This however is limited to 5 topics. + +To learn more about how to send messages to devices subscribed to topics, view the [Send messages to topics](server-integration#send-messages-to-topics) documentation. + +### Subscribing to topics + +To subscribe a device, call the [`subscribeToTopic`](!cloud_messaging.FirebaseMessaging.subscribeToTopic) method with the topic name: + +```dart +await FirebaseMessaging.instance.subscribeToTopic('weather'); +``` + +### Unsubscribing from topics + +To unsubscribe from a topic, call the [`unsubscribeFromTopic`](!cloud_messaging.FirebaseMessaging.unsubscribeFromTopic) method with the topic name: + +```dart +await FirebaseMessaging.instance.unsubscribeFromTopic('weather'); +``` diff --git a/docs/migration-guide.mdx b/docs/migration-guide.mdx index 848830405cf7..3365a4fd3d21 100644 --- a/docs/migration-guide.mdx +++ b/docs/migration-guide.mdx @@ -20,19 +20,20 @@ The table below shows the recommended package versions for each plugin this migr | `firebase_core` | `^{{ plugins.firebase_core }}` | | `firebase_crashlytics` | `^{{ plugins.firebase_crashlytics }}` | | `firebase_storage` | `^{{ plugins.firebase_storage }}` | +| `firebase_messaging` | `^{{ plugins.firebase_messaging }}` | ## Breaking Changes & Deprecations Although breaking changes have been kept to a minimum, the substantial changes and improvements which have been made -required us to break a few things (for the greater good). Where possible, we've deprecated some of the "old ways" of -integrating FlutterFire and you should aim to replace these deprecations as soon as possible. +required us to break a few things (for the greater good). Where possible, we've deprecated some "old ways" of +integrating FlutterFire, and you should aim to replace these deprecations as soon as possible. ## Migration Steps ### 1. Adding `firebase_core` -The FlutterFire plugins now depend on the `firebase_core` plugin. The plugin is responsible for connecting your -Flutter application to your Firebase project(s), and without it all other plugins will not work. +The FlutterFire plugins now depend on the `firebase_core` plugin. The plugin is responsible for connecting your Flutter +application to your Firebase project(s), and without it all other plugins will not work. Add the plugin to your `pubspec.yaml` file within your project: @@ -49,7 +50,7 @@ Update the Firebase plugins you use in your project to versions that are compati Edit the plugin versions in your `pubspec.yaml` file within your project to match the versions below: -```yaml {6,7,8,9,10} title="pubspec.yaml" +```yaml {6,7,8,9,10,11} title="pubspec.yaml" dependencies: flutter: sdk: flutter @@ -60,13 +61,13 @@ dependencies: cloud_firestore: "^{{ plugins.cloud_firestore }}" cloud_functions: "^{{ plugins.cloud_functions }}" firebase_storage: "^{{ plugins.firebase_storage }}" + firebase_messaging: "^{{ plugins.firebase_messaging }}" # Updated to work with new core only plugins (no new changes): firebase_admob: "^{{ plugins.firebase_admob }}" firebase_analytics: "^{{ plugins.firebase_analytics }}" firebase_database: "^{{ plugins.firebase_database }}" firebase_dynamic_links: "^{{ plugins.firebase_dynamic_links }}" firebase_in_app_messaging: "^{{ plugins.firebase_in_app_messaging }}" - firebase_messaging: "^{{ plugins.firebase_messaging }}" firebase_performance: "^{{ plugins.firebase_performance }}" firebase_remote_config: "^{{ plugins.firebase_remote_config }}" ``` @@ -81,42 +82,90 @@ We also recommend running `flutter clean` before continuing. ### 3. Platform Setup -Even though you may have already setup your platform to connect with Firebase, we recommend following the new documentation -steps for each platform to ensure everything is up-to-date. +Even though you may have already set up your platform to connect with Firebase, we recommend following the new +documentation steps for each platform to ensure everything is up-to-date. Previously, it was possible to use FlutterFire without a "default" Firebase application. This has now changed, and all -platforms now require an underlying default Firebase app has been registered. The platform specific guides below -show you how to set up a default Firebase application: +platforms now require an underlying default Firebase app has been registered. The platform specific guides below show +you how to set up a default Firebase application: #### A. [Android Installation](installation/android.mdx) - Ensure that the `com.google.gms:google-services` plugin is up-to-date. - - Update the version by changing the `classpath 'com.google.gms:google-services:4.3.3'` line in your `android/build.gradle` file to use version `4.3.3` or higher. + - Update the version by changing the `classpath 'com.google.gms:google-services:4.3.3'` line in + your `android/build.gradle` file to use version `4.3.3` or higher. - Ensure your apps Gradle wrapper distribution version is v5 or higher, FlutterFire uses v5.6.2 for its examples. - - Update the version by changing the `distributionUrl` value in your projects `android/gradle/wrapper/gradle-wrapper.properties` file. - - e.g. setting to v5.6.2: `distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip` -- Ensure that you're not manually importing any Firebase Android SDK dependencies in your `android/app/build.gradle` files `dependencies { ... }` section. - - e.g. remove any lines similar to: `implementation 'com.google.firebase:firebase-{PRODUCT}:{VERSION}` - - The reworked plugins now automatically manage the versions of the Firebase SDKs used internally so these are no longer required. - - See the [overriding native sdk versions documentation](overview#overriding-native-sdk-versions) if this is something you require. -- **Crashlytics only**: Remove the `maven { url 'https://maven.fabric.io/public' }` line from your projects `android/build.gradle` file. -- **Crashlytics only**: Remove the `classpath 'io.fabric.tools:gradle:1.31.2'` line from your projects `android/build.gradle` file. + - Update the version by changing the `distributionUrl` value in your + projects `android/gradle/wrapper/gradle-wrapper.properties` file. + - e.g. setting to v5.6.2: `distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip` +- Ensure that you're not manually importing any Firebase Android SDK dependencies in your `android/app/build.gradle` + files `dependencies { ... }` section. + - e.g. remove any lines similar to: `implementation 'com.google.firebase:firebase-{PRODUCT}:{VERSION}` + - The reworked plugins now automatically manage the versions of the Firebase SDKs used internally, so these are no + longer required. + - See the [overriding native sdk versions documentation](./overview.mdx#overriding-native-sdk-versions) if this + is something you require. + +- **Crashlytics only**: Remove the `maven { url 'https://maven.fabric.io/public' }` line from your + projects `android/build.gradle` file. +- **Crashlytics only**: Remove the `classpath 'io.fabric.tools:gradle:1.31.2'` line from your + projects `android/build.gradle` file. - **Crashlytics only**: Remove the `apply plugin: 'io.fabric'` line from your projects `android/app/build.gradle` file. -- **Crashlytics only**: Remove any dependencies referencing `*com.crashlytics.sdk.android:crashlytics*` line from your projects `android/app/build.gradle` file (inside the `dependencies { .. }` block). -- **Crashlytics only**: Follow the new Android integration steps shown in the [FlutterFire Crashlytics installation documentation](https://firebase.flutter.dev/docs/crashlytics/overview#a-android). +- **Crashlytics only**: Remove any dependencies referencing `*com.crashlytics.sdk.android:crashlytics*` line from your + projects `android/app/build.gradle` file (inside the `dependencies { .. }` block). +- **Crashlytics only**: Follow the new Android integration steps shown in + the [FlutterFire Crashlytics installation documentation](https://firebase.flutter.dev/docs/crashlytics/overview#a-android) + . + +- **Messaging only**: Remove any dependencies referencing `*com.google.firebase:firebase-messaging*` line from your + projects `android/app/build.gradle` file (inside the `dependencies { .. }` block). +- **Messaging only**: If you added the previous versions `intent-filter` to + your `android/app/src/main/AndroidManifest.xml` file, remove it: + +```diff +- +- +- +- +``` + +- **Messaging only**: Messaging now supports v2 embedding, it's recommended that + you [upgrade your Flutter app to use v2 embedding](https://github.com/flutter/flutter/wiki/Upgrading-pre-1.12-Android-projects) + if you haven't done so already. +- If your can't upgrade to v2 embedding yet, there are some additional manual steps you'll need to do before you can use + messaging in your app. Please see the Android section of + the [FlutterFire Messaging documentation](https://firebase.flutter.dev/docs/messaging/overview) for more information. +- If you were using v1 embedding on the previous version of messaging then note that the java class name has changed + to `FlutterFirebaseMessagingBackgroundService` from `FlutterFirebaseMessagingService` and you may need to update this + in your `MainActivity.java` file. #### B. [iOS Installation](installation/ios.mdx) These steps can also apply to macOS. -- If you manually import Firebase and configure it via `[FIRApp configure];` or `FirebaseApp.configure()` within your projects `ios/{projectName}/AppDelegate{.m/.swift}` file, - you can now safely remove it - FlutterFire automatically handles this for you. +- If you manually import Firebase and configure it via `[FIRApp configure];` or `FirebaseApp.configure()` within your + projects `ios/{projectName}/AppDelegate{.m/.swift}` file, you can now safely remove it - FlutterFire automatically + handles this for you. + - **Crashlytics only**: Open your `ios/Runner.xcworkspace` workspace file with Xcode. - 1. From Xcode select `Runner` from the left hand side project navigation. - 2. Select the `Build Phases` tab in the middle pane. - 3. Find a script in the list of Build Phases that contains `${PODS_ROOT}/Fabric/run` as the shell script. Once located, press the `x` on the right of the row to remove that script. If you can't find one then you can ignore this step. -- **Crashlytics only**: Follow the new iOS integration steps shown in the [FlutterFire Crashlytics installation documentation](https://firebase.flutter.dev/docs/crashlytics/overview#b-ios). -- If you face CocoaPods issues when building, follow the steps below in the [CocoaPods could not find compatible versions for pod](#cocoapods-could-not-find-compatible-versions-for-pod) section. + 1. From Xcode select `Runner` from the left-hand side project navigation. + 2. Select the `Build Phases` tab in the middle pane. + 3. Find a script in the list of Build Phases that contains `${PODS_ROOT}/Fabric/run` as the shell script. Once + located, press the `x` on the right of the row to remove that script. If you can't find one then you can ignore + this step. +- **Crashlytics only**: Follow the new iOS integration steps shown in + the [FlutterFire Crashlytics installation documentation](https://firebase.flutter.dev/docs/crashlytics/overview#b-ios) + . +- If you face CocoaPods issues when building, follow the steps below in + the [CocoaPods could not find compatible versions for pod](#cocoapods-could-not-find-compatible-versions-for-pod) + section. + +- **Messaging only**: If you previously added the `FirebaseAppDelegateProxyEnabled` key with a `false` value to + your `Info.plist` to disable swizzling; you can now remove it, the plugin now supports multiple delegates and will + internally forward to any other plugins that may have previously registered a delegate. +- Additionally, swizzling is now no longer used on iOS and the plugin calls `addApplicationDelegate` on the + FlutterPlugin Registrar instead. It is required for macOS however as the macOS registrar does not + support `addApplicationDelegate`. #### C. [Web Installation](installation/web.mdx) @@ -126,15 +175,16 @@ To ensure each plugin has a consistent API and to enable the possibility of some initialize FlutterFire before performing any other Firebase related functionality. The `firebase_core` package exposes a [`Firebase`](!firebase_core.Firebase) class, provides an -[`initializeApp`](!firebase_core.Firebase.initializeApp) method. This method should be called as soon as possible in your -application. It is responsible for ensuring the default app is ready, and bootstraps all additional FlutterFire plugins -which are installed. +[`initializeApp`](!firebase_core.Firebase.initializeApp) method. This method should be called as soon as possible in +your application. It is responsible for ensuring the default app is ready, and bootstraps all additional FlutterFire +plugins which are installed. To learn more, view the [Initializing FlutterFire](overview.mdx#initializing-flutterfire) documentation. ## Plugin Usage -Each plugin now provides a consistent API for integrating with Firebase. The example below shows how the `cloud_firestore` +Each plugin now provides a consistent API for integrating with Firebase. The example below shows how +the `cloud_firestore` package previously created a new instance vs the new way: ```dart @@ -155,7 +205,8 @@ Firestore firestore = Firestore(app: secondaryApp); FirebaseFirestore firestore = FirebaseFirestore.instanceFor(app: secondaryApp); ``` -To learn more about secondary Firebase app instances, view the [Initializing secondary apps](core/usage) documentation. +To learn more about secondary Firebase app instances, view the [Initializing secondary apps](./core/usage.mdx) +documentation. ## Plugin Changes @@ -163,297 +214,431 @@ To learn more about secondary Firebase app instances, view the [Initializing sec - **DEPRECATED**: `FirebaseApp.configure` method is now deprecated in favor of the `Firebase.initializeApp` method. - **DEPRECATED**: `FirebaseApp.allApps` method is now deprecated in favor of the `Firebase.apps` property. - - Previously, `allApps` was asynchronous & is now synchronous. + - Previously, `allApps` was asynchronous & is now synchronous. - **DEPRECATED**: `FirebaseApp.appNamed` method is now deprecated in favor of the `Firebase.app` method. - **BREAKING**: `FirebaseApp.options` getter is now synchronous. - `FirebaseOptions` has been reworked to better match web property names: - - **DEPRECATED**: `googleAppID` is now deprecated in favor of `appId`. - - **DEPRECATED**: `projectID` is now deprecated in favor of `projectId`. - - **DEPRECATED**: `bundleID` is now deprecated in favor of `bundleId`. - - **DEPRECATED**: `clientID` is now deprecated in favor of `androidClientId`. - - **DEPRECATED**: `trackingID` is now deprecated in favor of `trackingId`. - - **DEPRECATED**: `gcmSenderID` is now deprecated in favor of `messagingSenderId`. - - Added support for `authDomain`. - - Added support for `trackingId`. - - Required properties are now `apiKey`, `appId`, `messagingSenderId` & `projectId`. + - **DEPRECATED**: `googleAppID` is now deprecated in favor of `appId`. + - **DEPRECATED**: `projectID` is now deprecated in favor of `projectId`. + - **DEPRECATED**: `bundleID` is now deprecated in favor of `bundleId`. + - **DEPRECATED**: `clientID` is now deprecated in favor of `androidClientId`. + - **DEPRECATED**: `trackingID` is now deprecated in favor of `trackingId`. + - **DEPRECATED**: `gcmSenderID` is now deprecated in favor of `messagingSenderId`. + - Added support for `authDomain`. + - Added support for `trackingId`. + - Required properties are now `apiKey`, `appId`, `messagingSenderId` & `projectId`. - Added support for deleting Firebase app instances via the `delete` method on `FirebaseApp`. -- iOS: The default Firebase app is now automatically configured without needing to manually add Objective-C code to your iOS application. +- iOS: The default Firebase app is now automatically configured without needing to manually add Objective-C code to your + iOS application. - Added support for returning consistent error messages from `firebase-dart` plugin. - - Any FlutterFire related errors now throw a `FirebaseException`. + - Any FlutterFire related errors now throw a `FirebaseException`. - Added a `FirebaseException` class to handle all FlutterFire related errors. - - Matching the web sdk, the exception returns a formatted "[plugin/code] message" message when thrown. -- Added support for `setAutomaticDataCollectionEnabled` & `isAutomaticDataCollectionEnabled` on a `FirebaseApp` instance. + - Matching the web sdk, the exception returns a formatted "[plugin/code] message" message when thrown. +- Added support for `setAutomaticDataCollectionEnabled` & `isAutomaticDataCollectionEnabled` on a `FirebaseApp` + instance. - Added support for `setAutomaticResourceManagementEnabled` on a `FirebaseApp` instance. - Android: Gradle build tools updated to 3.5.0 from 3.3.0. -- Android: Removed Gradle ‘hacks’ and upgrade Flutter SDK requirement from `>=1.12.13+hotfix.4` to `>=1.12.13+hotfix.5` - based on PR https://github.com/flutter/plugins/pull/2651 +- Android: Removed Gradle ‘hacks’ and upgrade Flutter SDK requirement from `>=1.12.13+hotfix.4` to `>=1.12.13+hotfix.5` + - based on PR https://github.com/flutter/plugins/pull/2651 - Android: Switched to using Firebase BoM to manage SDK versions +### Authentication + +Overall, Firebase Auth has been heavily reworked to bring it inline with the federated plugin setup along with adding +new features, documentation and many more unit and end-to-end tests. The API has mainly been kept the same, however +there are some breaking changes. + +- **General**: + + - **BREAKING**: The `FirebaseUser` class has been renamed to `User`. + - **BREAKING**: The `AuthResult` class has been renamed to `UserCredential`. + - **NEW**: The `ActionCodeSettings` class is now consumable on all supporting methods. + - **NEW**: Added support for the `dynamicLinkDomain` property. + - **NEW**: Added a new `FirebaseAuthException` class (extends `FirebaseException`). + - All errors are now returned as a `FirebaseAuthException`, allowing you to access the code & message associated + with the error. + - In addition, it is now possible to access the `email` and `credential` properties on exceptions if they exist. + +- **`FirebaseAuth`** + + - **BREAKING**: Accessing the current user via `currentUser()` is now synchronous via the `currentUser` getter. + - **BREAKING**: `isSignInWithEmailLink()` is now synchronous. + - **DEPRECATED**: `FirebaseAuth.fromApp()` is now deprecated in favor of `FirebaseAuth.instanceFor()`. + - **DEPRECATED**: `onAuthStateChanged` has been deprecated in favor of `authStateChanges()`. + - **NEW**: Added support for `idTokenChanges()` stream listener. + - **NEW**: Added support for `userChanges()` stream listener. + - The purpose of this API is to allow users to subscribe to all user events without having to manually hydrate + app state in cases where a manual reload was required (e.g. `updateProfile()`). + - **NEW**: Added support for `applyActionCode()`. + - **NEW**: Added support for `checkActionCode()`. + - **NEW**: Added support for `verifyPasswordResetCode()`. + - **NEW**: Added support for accessing the current language code via the `languageCode` getter. + - **NEW**: `setLanguageCode()` now supports providing a `null` value. + - On web platforms, if `null` is provided the Firebase projects default language will be set. + - On native platforms, if `null` is provided the device language will be used. + - **NEW**: `verifyPhoneNumber()` exposes a `autoRetrievedSmsCodeForTesting` property. + - This allows developers to test automatic SMS code resolution on Android devices during development. + - **NEW** (iOS): `appVerificationDisabledForTesting` setting can now be set for iOS. + - This allows developers to skip ReCaptcha verification when testing phone authentication. + - **NEW** (iOS): `userAccessGroup` setting can now be set for iOS & macOS. + - This allows developers to share authentication states across multiple apps or extensions on iOS & macOS. For + more information see + the [Firebase iOS SDK documentation](https://firebase.google.com/docs/auth/ios/single-sign-on). + +- **`User`** + + - **BREAKING**: Removed the `UpdateUserInfo` class when using `updateProfile` in favor of named arguments. + - **NEW**: Added support for `getIdTokenResult()`. + - **NEW**: Added support for `verifyBeforeUpdateEmail()`. + - **FIX**: Fixed several iOS crashes when the Firebase SDK returned `nil` property values. + - **FIX**: Fixed an issue on Web & iOS where a user's email address would still show after unlinking the + email/password provider. + +- **`UserCredential`** + + - **NEW**: Added support for accessing the user's `AuthCredential` via the `credential` property. + +- **`AuthProvider`** & **`AuthCredential`** + - **DEPRECATED**: All sub-class (e.g. `GoogleAuthProvider`) `getCredential()` methods have been deprecated in favor + of `credential()`. + - **DEPRECATED**: `EmailAuthProvider.getCredentialWithLink()` has been deprecated in favor + of `EmailAuthProvider.credentialWithLink()`. + - **NEW**: Supporting providers can now assign scope and custom request parameters. + - The scope and parameters will be used on web platforms when triggering a redirect or popup + via `signInWithPopup()` or `signInWithRedirect()`. + ### Crashlytics - **BREAKING**: Removal of Fabric SDKs and migration to the new Firebase Crashlytics SDK. - **BREAKING**: The following methods have been removed as they are no longer available on the Firebase Crashlytics SDK: - - `setUserEmail` - - `setUserName` - - `getVersion` - - `isDebuggable` -- **BREAKING**: `log` now returns a Future. Calling `log` now sends logs immediately to the underlying Crashlytics SDK instead of pooling them in Dart. + - `setUserEmail` + - `setUserName` + - `getVersion` + - `isDebuggable` +- **BREAKING**: `log` now returns a Future. Calling `log` now sends logs immediately to the underlying Crashlytics SDK + instead of pooling them in Dart. - **BREAKING**: the methods `setInt`, `setDouble`, `setString` and `setBool` have been replaced by `setCustomKey`. - - `setCustomKey` returns a Future. Calling `setCustomKey` now sends custom keys immediately to the underlying Crashlytics SDK instead of pooling them in Dart. -- **DEPRECATED**: `enableInDevMode` has been deprecated, use `isCrashlyticsCollectionEnabled` and `setCrashlyticsCollectionEnabled` instead. + - `setCustomKey` returns a Future. Calling `setCustomKey` now sends custom keys immediately to the underlying + Crashlytics SDK instead of pooling them in Dart. +- **DEPRECATED**: `enableInDevMode` has been deprecated, use `isCrashlyticsCollectionEnabled` + and `setCrashlyticsCollectionEnabled` instead. - **DEPRECATED**: `Crashlytics` has been deprecated, use `FirebaseCrashlytics` instead. -- **NEW**: Custom keys that are automatically added by FlutterFire when calling `reportError` are now prefixed with `flutter_error_`. -- **NEW**: Calling `.crash()` on Android & iOS/macOS now reports a custom named exception to the Firebase Console. This allows you to easily locate test crashes. - - Name: `FirebaseCrashlyticsTestCrash`. - - Message: `This is a test crash caused by calling .crash() in Dart.`. -- **NEW**: `recordError` now uses a named native exception when reporting to the Firebase Console. This allows you to easily locate errors originating from Flutter. - - Name: `FlutterError`. +- **NEW**: Custom keys that are automatically added by FlutterFire when calling `reportError` are now prefixed + with `flutter_error_`. +- **NEW**: Calling `.crash()` on Android & iOS/macOS now reports a custom named exception to the Firebase Console. This + allows you to easily locate test crashes. + - Name: `FirebaseCrashlyticsTestCrash`. + - Message: `This is a test crash caused by calling .crash() in Dart.`. +- **NEW**: `recordError` now uses a named native exception when reporting to the Firebase Console. This allows you to + easily locate errors originating from Flutter. + - Name: `FlutterError`. - **NEW**: Added support for `checkForUnsentReports`. - - Checks a device for any fatal or non-fatal crash reports that haven't yet been sent to Crashlytics. - - See reference API docs for more information. + - Checks a device for any fatal or non-fatal crash reports that haven't yet been sent to Crashlytics. + - See reference API docs for more information. - **NEW**: Added support for `deleteUnsentReports`. - - If automatic data collection is disabled, this method queues up all the reports on a device for deletion. - - See reference API docs for more information. + - If automatic data collection is disabled, this method queues up all the reports on a device for deletion. + - See reference API docs for more information. - **NEW**: Added support for `didCrashOnPreviousExecution`. - - Checks whether the app crashed on its previous run. - - See reference API docs for more information. + - Checks whether the app crashed on its previous run. + - See reference API docs for more information. - **NEW**: Added support for `sendUnsentReports`. - - If automatic data collection is disabled, this method queues up all the reports on a device to send to Crashlytics. - - See reference API docs for more information. + - If automatic data collection is disabled, this method queues up all the reports on a device to send to + Crashlytics. + - See reference API docs for more information. - **NEW**: Added support for `setCrashlyticsCollectionEnabled`. - - Enables/disables automatic data collection by Crashlytics. - - See reference API docs for more information. + - Enables/disables automatic data collection by Crashlytics. + - See reference API docs for more information. - **NEW**: Added support for `isCrashlyticsCollectionEnabled`. - - Whether the current Crashlytics instance is collecting reports. If false, then no crash reporting data is sent to Firebase. - - See reference API docs for more information. + - If the current Crashlytics instance is collecting reports - if false, then no crash reporting data is sent to + Firebase. + - See reference API docs for more information. - **FIX**: Fixed a bug that prevented keys from being set on iOS devices. ### Firestore -Along with the below changes, the plugin has undergone a quality of life update to better support exceptions thrown. -Any Firestore specific errors now return a `FirebaseException` (see Core changes), allowing you to directly access the code +Along with the below changes, the plugin has undergone a quality of life update to better support exceptions thrown. Any +Firestore specific errors now return a `FirebaseException` (see Core changes), allowing you to directly access the code (e.g. permission-denied) and message. - **`FirebaseFirestore`**: - - **BREAKING**: Calling `settings()` now accepts a `Settings` instance. - - **DEPRECATED**: Calling `document()` is deprecated in favor of `doc()`. - - **DEPRECATED**: Calling `Firestore(app: app)` is now deprecated. Use `FirebaseFirestore.instance` or `FirebaseFirestore.instanceFor` instead. - - **BREAKING**: `enablePersistence` has now been removed, see `settings()` instead. - - **NEW**: Add `clearPersistence()` support. - - **NEW**: Add `disableNetwork()` support. - - **NEW**: Add `enableNetwork()` support. - - **NEW**: Add `snapshotInSync()` support. - - **NEW**: Add `terminate()` support. - - **NEW**: Add `waitForPendingWrites()` support. + - **BREAKING**: Calling `settings()` now accepts a `Settings` instance. + - **DEPRECATED**: Calling `document()` is deprecated in favor of `doc()`. + - **DEPRECATED**: Calling `Firestore(app: app)` is now deprecated. Use `FirebaseFirestore.instance` + or `FirebaseFirestore.instanceFor` instead. + - **BREAKING**: `enablePersistence` has now been removed, see `settings()` instead. + - **NEW**: Add `clearPersistence()` support. + - **NEW**: Add `disableNetwork()` support. + - **NEW**: Add `enableNetwork()` support. + - **NEW**: Add `snapshotInSync()` support. + - **NEW**: Add `terminate()` support. + - **NEW**: Add `waitForPendingWrites()` support. - **`CollectionReference`**: - - **BREAKING**: Getting a collection parent document via `parent()` has been changed to a getter `parent`. - - **DEPRECATED**: Calling `document()` is deprecated in favor of `doc()`. + - **BREAKING**: Getting a collection parent document via `parent()` has been changed to a getter `parent`. + - **DEPRECATED**: Calling `document()` is deprecated in favor of `doc()`. - **`Query`**: - - **BREAKING**: The internal query logic has been overhauled to better assert invalid queries locally. - - **BREAKING**: `getDocuments`/`get` has been updated to accept an instance of `GetOptions` (see below). - - **DEPRECATED**: Calling `getDocuments()` is deprecated in favor of `get()`. - - **NEW**: Query methods are now chainable. - - **NEW**: It is now possible to call same-point cursor based queries without throwing (e.g. calling `endAt()` and then `endBefore()` will replace the "end" cursor query with the `endBefore`). - - **NEW**: Added support for `limitToLast`. + - **BREAKING**: The internal query logic has been overhauled to better assert invalid queries locally. + - **BREAKING**: `getDocuments`/`get` has been updated to accept an instance of `GetOptions` (see below). + - **DEPRECATED**: Calling `getDocuments()` is deprecated in favor of `get()`. + - **NEW**: Query methods are now chainable. + - **NEW**: It is now possible to call same-point cursor based queries without throwing (e.g. calling `endAt()` and + then `endBefore()` will replace the "end" cursor query with the `endBefore`). + - **NEW**: Added support for `limitToLast`. - **`QuerySnapshot`**: - - **DEPRECATED**: `documents` has been deprecated in favor of `docs`. - - **DEPRECATED**: `documentChanges` has been deprecated in favor of `docChanges`. - - **NEW**: `docs` now returns a `List` vs `List`. This doesn't break existing functionality. + - **DEPRECATED**: `documents` has been deprecated in favor of `docs`. + - **DEPRECATED**: `documentChanges` has been deprecated in favor of `docChanges`. + - **NEW**: `docs` now returns a `List` vs `List`. This doesn't break + existing functionality. - **`DocumentReference`**: - - **BREAKING**: `setData`/`set` has been updated to accept an instance of `SetOptions` (see below, supports `mergeFields`). - - **BREAKING**: `get()` has been updated to accept an instance of `GetOptions` (see below). - - **BREAKING**: Getting a document parent collection via `parent()` has been changed to a getter `parent`. - - **DEPRECATED**: `documentID` has been deprecated in favor of `id`. - - **DEPRECATED**: `setData()` has been deprecated in favor of `set()`. - - **DEPRECATED**: `updateData()` has been deprecated in favor of `update()`. + - **BREAKING**: `setData`/`set` has been updated to accept an instance of `SetOptions` (see below, + supports `mergeFields`). + - **BREAKING**: `get()` has been updated to accept an instance of `GetOptions` (see below). + - **BREAKING**: Getting a document parent collection via `parent()` has been changed to a getter `parent`. + - **DEPRECATED**: `documentID` has been deprecated in favor of `id`. + - **DEPRECATED**: `setData()` has been deprecated in favor of `set()`. + - **DEPRECATED**: `updateData()` has been deprecated in favor of `update()`. - **`DocumentSnapshot`**: - - **BREAKING**: Getting a snapshots data via the `data` getter is now done via the `data()` method. - - **DEPRECATED**: `documentID` has been deprecated in favor of `id`. - - **NEW**: Added support for fetching nested snapshot data via the `get()` method. If no data exists at the given path, a `StateError` will be thrown. + - **BREAKING**: Getting a snapshots' data via the `data` getter is now done via the `data()` method. + - **DEPRECATED**: `documentID` has been deprecated in favor of `id`. + - **NEW**: Added support for fetching nested snapshot data via the `get()` method. If no data exists at the given + path, a `StateError` will be thrown. - **`DocumentChange`**: - - **DEPRECATED**: Calling `document()` is deprecated in favor of `doc()`. + - **DEPRECATED**: Calling `document()` is deprecated in favor of `doc()`. - **`WriteBatch`**: - - **BREAKING**: `setData`/`set` now supports `SetOptions` to merge data/fields (previously this accepted a `Map`). - - **DEPRECATED**: `setData()` has been deprecated in favor of `set()`. - - **DEPRECATED**: `updateData()` has been deprecated in favor of `update()`. + - **BREAKING**: `setData`/`set` now supports `SetOptions` to merge data/fields (previously this accepted a `Map`). + - **DEPRECATED**: `setData()` has been deprecated in favor of `set()`. + - **DEPRECATED**: `updateData()` has been deprecated in favor of `update()`. - **`Transaction`**: - - **BREAKING**: Transactions have been overhauled to address a number of issues: - - Values returned from the transaction will now be returned from the Future. Previously, only primitive values (e.g. `String`) were supported. It is now possible to return values such as a `DataSnapshot`. - - When manually throwing an exception, the context was lost and a generic `PlatformException` was thrown. You can now throw & catch on any exceptions. - - The modify methods on a transaction (`set`, `delete`, `update`) were previously Futures. This has been updated to better reflect how transactions should behave - they are now synchronous and are executed atomically once the transaction handler block has finished executing. + - **BREAKING**: Transactions have been overhauled to address a number of issues: + - Values returned from the transaction will now be returned from the Future. Previously, only primitive values ( + e.g. `String`) were supported. It is now possible to return values such as a `DataSnapshot`. + - When manually throwing an exception, the context was lost and a generic `PlatformException` was thrown. You + can now throw & catch on any exceptions. + - The modify methods on a transaction (`set`, `delete`, `update`) were previously Futures. This has been updated + to better reflect how transactions should behave - they are now synchronous and are executed atomically once + the transaction handler block has finished executing. - **`FieldPath`**: - - **NEW**: The constructor has now been made public to accept a `List` of `String` values. Previously field paths were accessible only via a dot-notated string path. This meant attempting to access a field in a document with a `.` in the name (e.g. `foo.bar@gmail.com`) was impossible. + - **NEW**: The constructor has now been made public to accept a `List` of `String` values. Previously field paths + were accessible only via a dot-notated string path. This meant attempting to access a field in a document with + a `.` in the name (e.g. `foo.bar@gmail.com`) was impossible. - **`GetOptions`**: New class created to support how data is fetched from Firestore (server, cache, serverAndCache). - **`SetOptions`**: New class created to both `merge` and `mergeFields` when setting data on documents. - **`GeoPoint`**: - - **BREAKING**: Added latitude and longitude validation when constructing a new `GeoPoint` instance. + - **BREAKING**: Added latitude and longitude validation when constructing a new `GeoPoint` instance. ### Functions - - **`FirebaseFunctions`**: - - **DEPRECATED**: Class `CloudFunctions` is now deprecated. Use `FirebaseFunctions` instead. - - **DEPRECATED**: Calling `CloudFunctions.instance` or `CloudFunctions(app: app, region: region)` is now deprecated. Use `FirebaseFunctions.instance` or `FirebaseFunctions.instanceFor(app: app, region: region)` instead. - - **DEPRECATED**: Calling `getHttpsCallable(functionName: functionName)` is deprecated in favor of `httpsCallable(functionName)` - - **DEPRECATED**: `CloudFunctionsException` is deprecated in favor of `FirebaseFunctionsException`. - - **NEW**: `FirebaseFunctionsException` now exposes a `details` property to retrieve any additional data provided with the exception returned by a HTTPS callable function. - - **NEW**: Internally, instances of `FirebaseFunctions` are now cached and lazily loaded. - - **NEW**: `httpsCallable` accepts an instance of `HttpsCallableOptions` (see below). - - - - **`HttpsCallable`**: - - **DEPRECATED**: Setting `timeout` is deprecated in favor of using `HttpsCallableOptions` (see below). - - - - **`HttpsCallableResult`**: - - **BREAKING**: `data` is now read-only, only its getter is exposed. - - **FIX**: `HttpsCallableResult`'s `data` property can now return a Map, List or a primitive value. Previously the Web implementation incorrectly assumed that a Map was always returned by the HTTPS callable function. - - - - **`HttpsCallableOptions`**: - - **NEW**: This new class has been created to support setting options for `httpsCallable` instances. - -### Authentication - -Overall, Firebase Auth has been heavily reworked to bring it inline with the federated plugin setup along with adding new features, documentation and many more unit and end-to-end tests. -The API has mainly been kept the same, however there are some breaking changes. - -- **General**: - - - **BREAKING**: The `FirebaseUser` class has been renamed to `User`. - - **BREAKING**: The `AuthResult` class has been renamed to `UserCredential`. - - **NEW**: The `ActionCodeSettings` class is now consumable on all supporting methods. - - **NEW**: Added support for the `dynamicLinkDomain` property. - - **NEW**: Added a new `FirebaseAuthException` class (extends `FirebaseException`). - - All errors are now returned as a `FirebaseAuthException`, allowing you to access the code & message associated with the error. - - In addition, it is now possible to access the `email` and `credential` properties on exceptions if they exist. - -- **`FirebaseAuth`** - - - **BREAKING**: Accessing the current user via `currentUser()` is now synchronous via the `currentUser` getter. - - **BREAKING**: `isSignInWithEmailLink()` is now synchronous. - - **DEPRECATED**: `FirebaseAuth.fromApp()` is now deprecated in favor of `FirebaseAuth.instanceFor()`. - - **DEPRECATED**: `onAuthStateChanged` has been deprecated in favor of `authStateChanges()`. - - **NEW**: Added support for `idTokenChanges()` stream listener. - - **NEW**: Added support for `userChanges()` stream listener. - - The purpose of this API is to allow users to subscribe to all user events without having to manually hydrate app state in cases where a manual reload was required (e.g. `updateProfile()`). - - **NEW**: Added support for `applyActionCode()`. - - **NEW**: Added support for `checkActionCode()`. - - **NEW**: Added support for `verifyPasswordResetCode()`. - - **NEW**: Added support for accessing the current language code via the `languageCode` getter. - - **NEW**: `setLanguageCode()` now supports providing a `null` value. - - On web platforms, if `null` is provided the Firebase projects default language will be set. - - On native platforms, if `null` is provided the device language will be used. - - **NEW**: `verifyPhoneNumber()` exposes a `autoRetrievedSmsCodeForTesting` property. - - This allows developers to test automatic SMS code resolution on Android devices during development. - - **NEW** (iOS): `appVerificationDisabledForTesting` setting can now be set for iOS. - - This allows developers to skip ReCaptcha verification when testing phone authentication. - - **NEW** (iOS): `userAccessGroup` setting can now be set for iOS & MacOS. - - This allows developers to share authentication states across multiple apps or extensions on iOS & MacOS. For more information see the [Firebase iOS SDK documentation](https://firebase.google.com/docs/auth/ios/single-sign-on). - -- **`User`** - - - **BREAKING**: Removed the `UpdateUserInfo` class when using `updateProfile` in favor of named arguments. - - **NEW**: Added support for `getIdTokenResult()`. - - **NEW**: Added support for `verifyBeforeUpdateEmail()`. - - **FIX**: Fixed several iOS crashes when the Firebase SDK returned `nil` property values. - - **FIX**: Fixed an issue on Web & iOS where a user's email address would still show after unlinking the email/password provider. - -- **`UserCredential`** - - - **NEW**: Added support for accessing the user's `AuthCredential` via the `credential` property. - -- **`AuthProvider`** & **`AuthCredential`** - - **DEPRECATED**: All sub-class (e.g. `GoogleAuthProvider`) `getCredential()` methods have been deprecated in favor of `credential()`. - - **DEPRECATED**: `EmailAuthProvider.getCredentialWithLink()` has been deprecated in favor of `EmailAuthProvider.credentialWithLink()`. - - **NEW**: Supporting providers can now assign scope and custom request parameters. - - The scope and parameters will be used on web platforms when triggering a redirect or popup via `signInWithPopup()` or `signInWithRedirect()`. +- **`FirebaseFunctions`**: + + - **DEPRECATED**: Class `CloudFunctions` is now deprecated. Use `FirebaseFunctions` instead. + - **DEPRECATED**: Calling `CloudFunctions.instance` or `CloudFunctions(app: app, region: region)` is now deprecated. + Use `FirebaseFunctions.instance` or `FirebaseFunctions.instanceFor(app: app, region: region)` instead. + - **DEPRECATED**: Calling `getHttpsCallable(functionName: functionName)` is deprecated in favor + of `httpsCallable(functionName)` + - **DEPRECATED**: `CloudFunctionsException` is deprecated in favor of `FirebaseFunctionsException`. + - **NEW**: `FirebaseFunctionsException` now exposes a `details` property to retrieve any additional data provided + with the exception returned by an HTTPS callable function. + - **NEW**: Internally, instances of `FirebaseFunctions` are now cached and lazily loaded. + - **NEW**: `httpsCallable` accepts an instance of `HttpsCallableOptions` (see below). + +- **`HttpsCallable`**: + + - **DEPRECATED**: Setting `timeout` is deprecated in favor of using `HttpsCallableOptions` (see below). + +- **`HttpsCallableResult`**: + + - **BREAKING**: `data` is now read-only, only its getter is exposed. + - **FIX**: `HttpsCallableResult`'s `data` property can now return a Map, List or a primitive value. Previously the + Web implementation incorrectly assumed that a Map was always returned by the HTTPS callable function. + +- **`HttpsCallableOptions`**: + - **NEW**: This new class has been created to support setting options for `httpsCallable` instances. + +### Messaging + +This plugin is now federated to allow integration with other platforms, along with upgrading underlying SDK versions. + +We've also added lots of features which can be seen in the changelog below, however notably the biggest changes are: + +- Removed all manual native code changes that were originally required for integration - this plugin works + out of the box once configured with Firebase & APNs. +- Support for macOS. +- iOS background handler support. +- Android background handler debugging and logging support. +- Android V2 embedding support. +- Reworked API for message handling (Streams + explicit handlers). +- Fully typed Message & Notification classes (vs raw Maps). +- New Apple notification permissions & support. +- Detailed documentation. + +- **`FirebaseMessaging`**: + + - **BREAKING**: `configure()` has been removed in favor of calling specific static methods which return Streams. + - **Why?**: The previous implementation of `configure()` caused unintended side effects if called multiple + times (either to register a different handler, or remove handlers). This change allows developers to be more + explicit about registering handlers and removing them without effecting others via Streams. + - **DEPRECATED**: Calling `FirebaseMessaging()` has been deprecated in favor of `FirebaseMessaging.instance` + & `FirebaseMessaging.instanceFor()`. + - **DEPRECATED**: `requestNotificationPermissions()` has been deprecated in favor of `requestPermission()`. + - **DEPRECATED**: `deleteInstanceID()` has been deprecated in favor of `deleteToken()`. + - **DEPRECATED**: `autoInitEnabled()` has been deprecated in favor of `isAutoInitEnabled`. + - **NEW**: Added support for `isAutoInitEnabled` as a synchronous getter. + - **NEW**: Added support for `getInitialMessage()`. This API has been added to detect whether a messaging containing + a notification has caused the application to be opened via users interaction. + - **NEW**: Added support for `deleteToken()`. + - **NEW**: Added support for `getToken()`. + - **NEW**: [Apple] Added support for `getAPNSToken()`. + - **NEW**: [Apple] Added support for `getNotificationSettings()`. See `NotificationSettings` below. + - **NEW**: [Apple] Added support for `requestPermission()`. See `NotificationSettings` below. New permissions such + as `carPlay`, `crtiticalAlert`, `announcement` are now supported. + - **NEW**: [Android] Added support for `sendMessage()`. The `sendMessage()` API enables support for sending FCM + payloads back to a custom server from the device. + - **NEW**: [Android] When receiving background messages on the separate background Dart executor whilst in debug, + you should now see flutter logs and be able to debug/add breakpoints your Dart background message handler. + - **NEW**: [Apple] Added support for `setForegroundNotificationPresentationOptions()`. By default, iOS devices will + not show notifications in the foreground. Use this API to override the defaults. See documentation for Android + foreground notifications. + - **NEW** - [Android] Firebase Cloud Messages that contain a notification are now always sent to Dart regardless of + whether the app was in the foreground or background. Previously, if a message came through that contained a + notification whilst your app was in the foreground then FCM would not notify the plugin messaging service of the + message (and subsequently your handlers in Dart) until the user interacted with it. + +- **Event handling**: + + - Event handling has been reworked to provide a more intuitive API for developers. Foreground based events can now + be accessed via Streams: + - **NEW**: `FirebaseMessaging.onMessage` Returns a Stream that is called when an incoming FCM payload is + received whilst the Flutter instance is in the foreground, containing a [RemoteMessage]. + - **NEW**: `FirebaseMessaging.onMessageOpenedApp` Returns a [Stream] that is called when a user presses a + notification displayed via FCM. This replaces the previous `onLaunch` and `onResume` handlers. + - **NEW**: `FirebaseMessaging.onBackgroundMessage()` Sets a background message handler to trigger when the app + is in the background or terminated. + +- `IosNotificationSettings`: + + - **DEPRECATED**: Usage of the `IosNotificationSettings` class is now deprecated (currently used with the now + deprecated `requestNotificationPermissions()` method). + - Instead of this class, use named arguments when calling `requestPermission()` and read the permissions back + via the returned `NotificationSettings` instance. + +- `NotificationSettings`: + + - **NEW**: A `NotificationSettings` class is returned from calls to `requestPermission()` + and `getNotificationSettings()`. It contains information such as the authorization status, along with the platform + specific settings. + +- `RemoteMessage`: + + - **NEW**: Incoming FCM payloads are now represented as a `RemoteMessage` rather than a raw `Map`. + +- `RemoteNotification`: + - **NEW**:When a message includes a notification payload, the `RemoteMessage` includes a `RemoteNotification` rather + than a raw `Map`. + +- **Other**: + + - Additional types are available throughout messaging to aid with the latest changes: + - `BackgroundMessageHandler`, `AppleNotificationSetting`, `AppleShowPreviewSetting`, `AuthorizationStatus` + , `AndroidNotificationPriority`, `AndroidNotificationVisibility` ### Storage -Overall, Firebase Storage has been heavily reworked to bring it inline with the federated plugin setup along with adding new features, -documentation and many more unit and end-to-end tests (tested on Android, iOS & MacOS). - -- **`FirebaseStorage`** - - - **DEPRECATED**: Constructing an instance is now deprecated, use `FirebaseStorage.instanceFor` or `FirebaseStorage.instance` instead. - - **DEPRECATED**: `getReferenceFromUrl()` is deprecated in favor of calling `ref()` with a path. - - **DEPRECATED**: `getMaxOperationRetryTimeMillis()` is deprecated in favor of the getter `maxOperationRetryTime`. - - **DEPRECATED**: `getMaxUploadRetryTimeMillis()` is deprecated in favor of the getter `maxUploadRetryTime`. - - **DEPRECATED**: `getMaxDownloadRetryTimeMillis()` is deprecated in favor of the getter `maxDownloadRetryTime`. - - **DEPRECATED**: `setMaxOperationRetryTimeMillis()` is deprecated in favor of `setMaxUploadRetryTime()`. - - **DEPRECATED**: `setMaxUploadRetryTimeMillis()` is deprecated in favor of `setMaxUploadRetryTime()`. - - **DEPRECATED**: `setMaxDownloadRetryTimeMillis()` is deprecated in favor of `setMaxDownloadRetryTime()`. - - **NEW**: To match the Web SDK, calling `ref()` creates a new `Reference` at the bucket root, whereas an optional path (`ref('/foo/bar.png')`) can be used to create a `Reference` pointing at a specific location. - - **NEW**: Added support for `refFromURL`, which accepts a Google Storage (`gs://`) or HTTP URL and returns a `Reference` synchronously. - -- **`Reference`** - - - **BREAKING**: `StorageReference` has been renamed to `Reference`. - - **DEPRECATED**: `getParent()` is deprecated in favor of `.parent`. - - **DEPRECATED**: `getRoot()` is deprecated in favor of `.root`. - - **DEPRECATED**: `getStorage()` is deprecated in favor of `.storage`. - - **DEPRECATED**: `getBucket()` is deprecated in favor of `.bucket`. - - **DEPRECATED**: `getPath()` is deprecated in favor of `.fullPath`. - - **DEPRECATED**: `getName()` is deprecated in favor of `.name`. - - **NEW**: Added support for `list(options)`. - - Includes `ListOptions` API (see below). - - **NEW**: Added support for `listAll()`. - - **NEW**: `putString()` has been added to accept a string value, of type Base64, Base64Url, a [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) or raw strings. - - Data URLs automatically set the `Content-Type` metadata if not already set. - - **NEW**: `getData()` does not require a `maxSize`, it can now be called with a default of 10mb. - -- **NEW `ListOptions`** - - - The `list()` method accepts a `ListOptions` instance with the following arguments: - - `maxResults`: limits the number of results returned from a call. Defaults to 1000. - - `pageToken`: a page token returned from a `ListResult` - used if there are more items to query. - -- **NEW `ListResult`** - - - A `ListResult` class has been added, which is returned from a call to `list()` or `listAll()`. It exposes the following properties: - - `items` (`List`): Returns the list of reference objects at the current reference location. - - `prefixes` (`List`): Returns the list of reference sub-folders at the current reference location. - - `nextPageToken` (`String`): Returns a string (or null) if a next page during a `list()` call exists. - -- **Tasks** - - Tasks have been overhauled to be closer to the expected Firebase Web SDK Storage API, allowing users to access and control on-going tasks easier. There are a number of breaking changes & features with this overhaul: - - **BREAKING**: `StorageUploadTask` has been renamed to `UploadTask` (extends `Task`). - - **BREAKING**: `StorageDownloadTask` has been renamed to `DownloadTask` (extends `Task`). - - **BREAKING**: `StorageTaskEvent` has been removed (see below). - - **BREAKING**: `StorageTaskSnapshot` has been renamed to `TaskSnapshot`. - - **BREAKING**: `pause()`, `cancel()` and `resume()` are now Futures which return a boolean value to represent whether the status update was successful. - - Previously, these were `void` methods but still carried out an asynchronous tasks, potentially leading to uncaught exceptions. - - **BREAKING**: `isCanceled`, `isComplete`, `isInProgress`, `isPaused` and `isSuccessful` have now been removed. Instead, you should subscribe to the stream (for paused/progress/complete/error events) or the task `Future` for task completion/errors. - - Additionally the latest `TaskSnapshot` now provides the latest `TaskState` via `task.snapshot.state`. - - **BREAKING**: The `events` stream (now `snapshotEvents`) previously returned a `StorageTaskEvent`, containing a `StorageTaskEventType` and `StorageTaskSnapshot` Instead, the stream now returns a `TaskSnapshot` which includes the `state`. - - **BREAKING**: A task failure and cancellation now throw a `FirebaseException` instead of a new event. - - **DEPRECATED**: `events` stream is deprecated in favor of `snapshotEvents`. - - **DEPRECATED**: `lastSnapshot` is deprecated in favor of `snapshot`. +Overall, Firebase Storage has been heavily reworked to bring it inline with the federated plugin setup along with adding +new features, documentation and many more unit and end-to-end tests (tested on Android, iOS & macOS). + +- **`FirebaseStorage`**: + + - **DEPRECATED**: Constructing an instance is now deprecated, use `FirebaseStorage.instanceFor` + or `FirebaseStorage.instance` instead. + - **DEPRECATED**: `getReferenceFromUrl()` is deprecated in favor of calling `ref()` with a path. + - **DEPRECATED**: `getMaxOperationRetryTimeMillis()` is deprecated in favor of the getter `maxOperationRetryTime`. + - **DEPRECATED**: `getMaxUploadRetryTimeMillis()` is deprecated in favor of the getter `maxUploadRetryTime`. + - **DEPRECATED**: `getMaxDownloadRetryTimeMillis()` is deprecated in favor of the getter `maxDownloadRetryTime`. + - **DEPRECATED**: `setMaxOperationRetryTimeMillis()` is deprecated in favor of `setMaxUploadRetryTime()`. + - **DEPRECATED**: `setMaxUploadRetryTimeMillis()` is deprecated in favor of `setMaxUploadRetryTime()`. + - **DEPRECATED**: `setMaxDownloadRetryTimeMillis()` is deprecated in favor of `setMaxDownloadRetryTime()`. + - **NEW**: To match the Web SDK, calling `ref()` creates a new `Reference` at the bucket root, whereas an optional + path (`ref('/foo/bar.png')`) can be used to create a `Reference` pointing at a specific location. + - **NEW**: Added support for `refFromURL`, which accepts a Google Storage (`gs://`) or HTTP URL and returns + a `Reference` synchronously. + +- **`Reference`**: + + - **BREAKING**: `StorageReference` has been renamed to `Reference`. + - **DEPRECATED**: `getParent()` is deprecated in favor of `.parent`. + - **DEPRECATED**: `getRoot()` is deprecated in favor of `.root`. + - **DEPRECATED**: `getStorage()` is deprecated in favor of `.storage`. + - **DEPRECATED**: `getBucket()` is deprecated in favor of `.bucket`. + - **DEPRECATED**: `getPath()` is deprecated in favor of `.fullPath`. + - **DEPRECATED**: `getName()` is deprecated in favor of `.name`. + - **NEW**: Added support for `list(options)`. + - Includes `ListOptions` API (see below). + - **NEW**: Added support for `listAll()`. + - **NEW**: `putString()` has been added to accept a string value, of type Base64, Base64Url, + a [Data URL](https://developer.mozilla.org/en-US/docs/Web/HTTP/Basics_of_HTTP/Data_URIs) or raw strings. + - Data URLs automatically set the `Content-Type` metadata if not already set. + - **NEW**: `getData()` does not require a `maxSize`, it can now be called with a default of 10mb. + +- **NEW `ListOptions`**: + + - The `list()` method accepts a `ListOptions` instance with the following arguments: + - `maxResults` - limits the number of results returned from a call. Defaults to 1000. + - `pageToken` - a page token returned from a `ListResult` - used if there are more items to query. + +- **NEW `ListResult`**: + + - A `ListResult` class has been added, which is returned from a call to `list()` or `listAll()`. It exposes the + following properties: + - `items` (`List`): Returns the list of reference objects at the current reference location. + - `prefixes` (`List`): Returns the list of reference sub-folders at the current reference location. + - `nextPageToken` (`String`): Returns a string (or null) if a next page during a `list()` call exists. + +- **Tasks**: + - Tasks have been overhauled to be closer to the expected Firebase Web SDK Storage API, allowing users to access and + control ongoing tasks easier. There are a number of breaking changes & features with this overhaul: + - **BREAKING**: `StorageUploadTask` has been renamed to `UploadTask` (extends `Task`). + - **BREAKING**: `StorageDownloadTask` has been renamed to `DownloadTask` (extends `Task`). + - **BREAKING**: `StorageTaskEvent` has been removed (see below). + - **BREAKING**: `StorageTaskSnapshot` has been renamed to `TaskSnapshot`. + - **BREAKING**: `pause()`, `cancel()` and `resume()` are now Futures which return a boolean value to represent + whether the status update was successful. + - Previously, these were `void` methods but still carried out an asynchronous tasks, potentially leading to + uncaught exceptions. + - **BREAKING**: `isCanceled`, `isComplete`, `isInProgress`, `isPaused` and `isSuccessful` have now been removed. + Instead, you should subscribe to the stream (for paused/progress/complete/error events) or the task `Future` + for task completion/errors. + - Additionally, the latest `TaskSnapshot` now provides the latest `TaskState` via `task.snapshot.state`. + - **BREAKING**: The `events` stream (now `snapshotEvents`) previously returned a `StorageTaskEvent`, containing + a `StorageTaskEventType` and `StorageTaskSnapshot` Instead, the stream now returns a `TaskSnapshot` which + includes the `state`. + - **BREAKING**: A task failure and cancellation now throw a `FirebaseException` instead of a new event. + - **DEPRECATED**: `events` stream is deprecated in favor of `snapshotEvents`. + - **DEPRECATED**: `lastSnapshot` is deprecated in favor of `snapshot`. #### Example @@ -480,16 +665,20 @@ task }); ``` -Subscribing to Stream updates and/or the tasks delegating Future is optional - if you require progress updates on your task use the Stream, otherwise -the Future will resolve once its complete. Using both together is also supported. +Subscribing to Stream updates and/or the tasks delegating Future is optional - if you require progress updates on your +task use the Stream, otherwise the Future will resolve once it's complete. Using both together is also supported. ## Common Upgrade Issues ### CocoaPods could not find compatible versions for pod -This issue can happen when you upgrade your FlutterFire packages and attempt to build for iOS or MacOS, this is usually down to one of the following: +This issue can happen when you upgrade your FlutterFire packages and attempt to build for iOS or macOS, this is usually +down to one of the following: -- Your `Podfile.lock` version inside your iOS or MacOS directory is out of date and locked to older versions of the Firebase iOS SDKs whereas the newly upgraded FlutterFire packages might be using newer versions of these SDKs. - - **Solution:** Delete the `Podfile.lock` file and try your build again. This file will get regenerated after the next `pod install`. -- Your pod specs repo is out of date, meaning CocoaPods locally isn't aware of any potential newer versions of the Firebase iOS SDKs that have been recently published. - - **Solution:** Run `pod repo update` in your terminal and try your build again. +- Your `Podfile.lock` version inside your iOS or macOS directory is out of date and locked to older versions of the + Firebase iOS SDKs whereas the newly upgraded FlutterFire packages might be using newer versions of these SDKs. + - **Solution:** Delete the `Podfile.lock` file and try your build again. This file will get regenerated after the + next `pod install`. +- Your pod specs repo is out of date, meaning CocoaPods locally isn't aware of any potential newer versions of the + Firebase iOS SDKs that have been recently published. + - **Solution:** Run `pod repo update` in your terminal and try your build again. diff --git a/docs/sidebars.js b/docs/sidebars.js index 0e862e3f253d..229c0f638626 100644 --- a/docs/sidebars.js +++ b/docs/sidebars.js @@ -31,18 +31,47 @@ module.exports = { "firestore/usage", toReferenceAPI("cloud_firestore"), ], - "Cloud Functions": ["functions/overview", "functions/usage", toReferenceAPI("cloud_functions")], - "Cloud Messaging": ["messaging/overview", "messaging/ios-integration", toReferenceAPI("firebase_messaging")], - "Cloud Storage": ["storage/overview", "storage/usage", toReferenceAPI("firebase_storage")], + "Cloud Functions": [ + "functions/overview", + "functions/usage", + toReferenceAPI("cloud_functions"), + ], + "Cloud Messaging": [ + "messaging/overview", + "messaging/apple-integration", + "messaging/permissions", + "messaging/notifications", + "messaging/server-integration", + toReferenceAPI("firebase_messaging"), + ], + "Cloud Storage": [ + "storage/overview", + "storage/usage", + toReferenceAPI("firebase_storage"), + ], Core: ["core/usage", toReferenceAPI("firebase_core")], - Crashlytics: ["crashlytics/overview", "crashlytics/usage", "crashlytics/reports", toReferenceAPI("firebase_crashlytics")], - "Realtime Database": ["database/overview", toReferenceAPI("firebase_database")], + Crashlytics: [ + "crashlytics/overview", + "crashlytics/usage", + "crashlytics/reports", + toReferenceAPI("firebase_crashlytics"), + ], + "Realtime Database": [ + "database/overview", + toReferenceAPI("firebase_database"), + ], // "Dynamic Links": ["dynamic-links/usage", toReferenceAPI("firebase_dynamic_links")], // "Instance ID": ["iid/usage", toReferenceAPI("firebase_in_app_messaging")], // "In-App Messaging": ["in-app-messaging/usage", toReferenceAPI("firebase_in_app_messaging")], // "ML Kit Natural Language": ["ml-language/usage"], // "ML Kit Vision": ["ml-vision/usage", toReferenceAPI("firebase_ml_vision")], - "Remote Config": ["remote-config/overview", toReferenceAPI("firebase_remote_config")], - "Performance Monitoring": ["performance/overview", toReferenceAPI("firebase_performance")], + "Remote Config": [ + "remote-config/overview", + toReferenceAPI("firebase_remote_config"), + ], + "Performance Monitoring": [ + "performance/overview", + toReferenceAPI("firebase_performance"), + ], }, }; diff --git a/melos.yaml b/melos.yaml index 4e61b57286af..d9343049adc4 100644 --- a/melos.yaml +++ b/melos.yaml @@ -51,9 +51,8 @@ scripts: # Run all test driver e2e tests in the example apps, use this for mobile. # Will target Android/iOS depending on what emulator/simulator you have running. - # TODO(Salakar): ignoring cloud functions as has no tests test:e2e: > - melos exec -c 1 --fail-fast --scope="*example*" --ignore=cloud_functions_example --dir-exists=test_driver -- \ + melos exec -c 1 --fail-fast --scope="*example*" --dir-exists=test_driver -- \ flutter drive --no-pub --target=./test_driver/MELOS_PARENT_PACKAGE_NAME_e2e.dart # Run all web test driver e2e tests in the example apps. @@ -66,9 +65,8 @@ scripts: # Run all MacOS test driver e2e tests in the example apps. # - Requires `flutter config --enable-macos-desktop` enabled. # - Requires `flutter channel master && flutter upgrade`. - # TODO(Salakar): ignoring cloud functions as has no tests test:e2e:macos: > - melos exec -c 1 --fail-fast --dir-exists=macos --scope="*example*" --ignore=cloud_functions_example --dir-exists=test_driver -- \ + melos exec -c 1 --fail-fast --dir-exists=macos --scope="*example*" --dir-exists=test_driver -- \ flutter drive -d macos --no-pub --target=./test_driver/MELOS_PARENT_PACKAGE_NAME_e2e.dart # Check pubspecs are valid and publishable in each package. @@ -76,7 +74,7 @@ scripts: melos exec -c 5 --fail-fast --no-private --ignore="*example*" -- \ pub publish --dry-run - # MacOS only. Ignores stub iOS podspecs in web federated plugins. + # MacOS hosts only. Ignores stub iOS podspecs in web federated plugins. lint:podspecs: > melos exec -c 1 --ignore="*web*" --fail-fast --file-exists=ios/\$MELOS_PACKAGE_NAME.podspec -- \ pod lib lint ./ios/\$MELOS_PACKAGE_NAME.podspec \ diff --git a/packages/firebase_messaging/README.md b/packages/firebase_messaging/README.md deleted file mode 100644 index 14313e6b489e..000000000000 --- a/packages/firebase_messaging/README.md +++ /dev/null @@ -1,304 +0,0 @@ -# Firebase Cloud Messaging for Flutter - -[![pub package](https://img.shields.io/pub/v/firebase_messaging.svg)](https://pub.dev/packages/firebase_messaging) - -A Flutter plugin to use the [Firebase Cloud Messaging (FCM) API](https://firebase.google.com/docs/cloud-messaging/). - -With this plugin, your Flutter app can receive and process push notifications as well as data messages on Android and iOS. Read Firebase's [About FCM Messages](https://firebase.google.com/docs/cloud-messaging/concept-options) to learn more about the differences between notification messages and data messages. - -For Flutter plugins for other Firebase products, see [README.md](https://github.com/FirebaseExtended/flutterfire/blob/master/README.md). - -## Usage -To use this plugin, add `firebase_messaging` as a [dependency in your pubspec.yaml file](https://flutter.io/platform-plugins/). - -## Getting Started - -Check out the `example` directory for a sample app using Firebase Cloud Messaging. - -### Android Integration - -To integrate your plugin into the Android part of your app, follow these steps: - -1. Using the [Firebase Console](https://console.firebase.google.com/) add an Android app to your project: Follow the assistant, download the generated `google-services.json` file and place it inside `android/app`. - -2. Add the classpath to the `[project]/android/build.gradle` file. -``` -dependencies { - // Example existing classpath - classpath 'com.android.tools.build:gradle:3.5.3' - // Add the google services classpath - classpath 'com.google.gms:google-services:4.3.2' -} -``` -3. Add the apply plugin to the `[project]/android/app/build.gradle` file. -``` -// ADD THIS AT THE BOTTOM -apply plugin: 'com.google.gms.google-services' -``` - -Note: If this section is not completed you will get an error like this: -``` -java.lang.IllegalStateException: -Default FirebaseApp is not initialized in this process [package name]. -Make sure to call FirebaseApp.initializeApp(Context) first. -``` - -Note: When you are debugging on Android, use a device or AVD with Google Play services. Otherwise you will not be able to authenticate. - -4. (optional, but recommended) If want to be notified in your app (via `onResume` and `onLaunch`, see below) when the user clicks on a notification in the system tray include the following `intent-filter` within the `` tag of your `android/app/src/main/AndroidManifest.xml`: - ```xml - - - - - ``` -#### Optionally handle background messages - ->Background message handling is intended to be performed quickly. Do not perform -long running tasks as they may not be allowed to finish by the Android system. -See [Background Execution Limits](https://developer.android.com/about/versions/oreo/background) -for more. - -By default background messaging is not enabled. To handle messages in the background: - -1. Add the `com.google.firebase:firebase-messaging` dependency in your app-level `build.gradle` file that is typically located at `/android/app/build.gradle`. - - ```gradle - dependencies { - // ... - - implementation 'com.google.firebase:firebase-messaging:' - } - ``` - - Note: you can find out what the latest version of the plugin is [here ("Cloud Messaging")](https://firebase.google.com/support/release-notes/android#latest_sdk_versions). - -1. Add an `Application.java` class to your app in the same directory as your `MainActivity.java`. This is typically found in `/android/app/src/main/java//`. - - ```java - package io.flutter.plugins.firebasemessagingexample; - - import io.flutter.app.FlutterApplication; - import io.flutter.plugin.common.PluginRegistry; - import io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback; - import io.flutter.plugins.GeneratedPluginRegistrant; - import io.flutter.plugins.firebasemessaging.FlutterFirebaseMessagingService; - - public class Application extends FlutterApplication implements PluginRegistrantCallback { - @Override - public void onCreate() { - super.onCreate(); - FlutterFirebaseMessagingService.setPluginRegistrant(this); - } - - @Override - public void registerWith(PluginRegistry registry) { - GeneratedPluginRegistrant.registerWith(registry); - } - } - ``` - -1. In `Application.java`, make sure to change `package io.flutter.plugins.firebasemessagingexample;` to your package's identifier. Your package's identifier should be something like `com.domain.myapplication`. - - ```java - package com.domain.myapplication; - ``` - -1. Set name property of application in `AndroidManifest.xml`. This is typically found in `/android/app/src/main/`. - - ```xml - - ``` - -1. Define a **TOP-LEVEL** or **STATIC** function to handle background messages - - ```dart - Future myBackgroundMessageHandler(Map message) async { - if (message.containsKey('data')) { - // Handle data message - final dynamic data = message['data']; - } - - if (message.containsKey('notification')) { - // Handle notification message - final dynamic notification = message['notification']; - } - - // Or do other work. - } - ``` - - Note: the protocol of `data` and `notification` are in line with the - fields defined by a [RemoteMessage](https://firebase.google.com/docs/reference/android/com/google/firebase/messaging/RemoteMessage). - -1. Set `onBackgroundMessage` handler when calling `configure` - - ```dart - _firebaseMessaging.configure( - onMessage: (Map message) async { - print("onMessage: $message"); - _showItemDialog(message); - }, - onBackgroundMessage: myBackgroundMessageHandler, - onLaunch: (Map message) async { - print("onLaunch: $message"); - _navigateToItemDetail(message); - }, - onResume: (Map message) async { - print("onResume: $message"); - _navigateToItemDetail(message); - }, - ); - ``` - - Note: `configure` should be called early in the lifecycle of your application - so that it can be ready to receive messages as early as possible. See the - [example app](https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_messaging/example) for a demonstration. - -### iOS Integration - -To integrate your plugin into the iOS part of your app, follow these steps: - -1. Generate the certificates required by Apple for receiving push notifications following [this guide](https://firebase.google.com/docs/cloud-messaging/ios/certs) in the Firebase docs. You can skip the section titled "Create the Provisioning Profile". - -1. Using the [Firebase Console](https://console.firebase.google.com/) add an iOS app to your project: Follow the assistant, download the generated `GoogleService-Info.plist` file, open `ios/Runner.xcworkspace` with Xcode, and within Xcode place the file inside `ios/Runner`. **Don't** follow the steps named "Add Firebase SDK" and "Add initialization code" in the Firebase assistant. - -1. In Xcode, select `Runner` in the Project Navigator. In the Capabilities Tab turn on `Push Notifications` and `Background Modes`, and enable `Background fetch` and `Remote notifications` under `Background Modes`. - -1. Follow the steps in the "[Upload your APNs certificate](https://firebase.google.com/docs/cloud-messaging/ios/client#upload_your_apns_certificate)" section of the Firebase docs. - -1. If you need to disable the method swizzling done by the FCM iOS SDK (e.g. so that you can use this plugin with other notification plugins) then add the following to your application's `Info.plist` file. - -```xml -FirebaseAppDelegateProxyEnabled - -``` - -After that, add the following lines to the `(BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions` -method in the `AppDelegate.m`/`AppDelegate.swift` of your iOS project. - -Objective-C: -```objectivec -if (@available(iOS 10.0, *)) { - [UNUserNotificationCenter currentNotificationCenter].delegate = (id) self; -} -``` - -Swift: -```swift -if #available(iOS 10.0, *) { - UNUserNotificationCenter.current().delegate = self as? UNUserNotificationCenterDelegate -} -``` - -### Dart/Flutter Integration - -From your Dart code, you need to import the plugin and instantiate it: - -```dart -import 'package:firebase_messaging/firebase_messaging.dart'; - -final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); -``` - -Next, you should probably request permissions for receiving Push Notifications. For this, call `_firebaseMessaging.requestNotificationPermissions()`. This will bring up a permissions dialog for the user to confirm on iOS. It's a no-op on Android. Last, but not least, register `onMessage`, `onResume`, and `onLaunch` callbacks via `_firebaseMessaging.configure()` to listen for incoming messages (see table below for more information). - -## Receiving Messages - -Messages are sent to your Flutter app via the `onMessage`, `onLaunch`, and `onResume` callbacks that you configured with the plugin during setup. Here is how different message types are delivered on the supported platforms: - -| | App in Foreground | App in Background | App Terminated | -| --------------------------: | ----------------- | ----------------- | -------------- | -| **Notification on Android** | `onMessage` | Notification is delivered to system tray. When the user clicks on it to open app `onResume` fires if `click_action: FLUTTER_NOTIFICATION_CLICK` is set (see below). | Notification is delivered to system tray. When the user clicks on it to open app `onLaunch` fires if `click_action: FLUTTER_NOTIFICATION_CLICK` is set (see below). | -| **Notification on iOS** | `onMessage` | Notification is delivered to system tray. When the user clicks on it to open app `onResume` fires. | Notification is delivered to system tray. When the user clicks on it to open app `onLaunch` fires. | -| **Data Message on Android** | `onMessage` | `onMessage` while app stays in the background. | *not supported by plugin, message is lost* | -| **Data Message on iOS** | `onMessage` | Message is stored by FCM and delivered to app via `onMessage` when the app is brought back to foreground. | Message is stored by FCM and delivered to app via `onMessage` when the app is brought back to foreground. | - -Additional reading: Firebase's [About FCM Messages](https://firebase.google.com/docs/cloud-messaging/concept-options). - -## Notification messages with additional data -It is possible to include additional data in notification messages by adding them to the `"data"`-field of the message. - -On Android, the message contains an additional field `data` containing the data. On iOS, the data is directly appended to the message and the additional `data`-field is omitted. - -To receive the data on both platforms: - -````dart -Future _handleNotification (Map message, bool dialog) async { - var data = message['data'] ?? message; - String expectedAttribute = data['expectedAttribute']; - /// [...] -} -```` - -## Sending Messages -Refer to the [Firebase documentation](https://firebase.google.com/docs/cloud-messaging/) about FCM for all the details about sending messages to your app. When sending a notification message to an Android device, you need to make sure to set the `click_action` property of the message to `FLUTTER_NOTIFICATION_CLICK`. Otherwise the plugin will be unable to deliver the notification to your app when the users clicks on it in the system tray. - -For testing purposes, the simplest way to send a notification is via the [Firebase Console](https://firebase.google.com/docs/cloud-messaging/send-with-console). Make sure to include `click_action: FLUTTER_NOTIFICATION_CLICK` as a "Custom data" key-value-pair (under "Advanced options") when targeting an Android device. The Firebase Console does not support sending data messages. - -Alternatively, a notification or data message can be sent from a terminal: - -```shell -DATA='{"notification": {"body": "this is a body","title": "this is a title"}, "priority": "high", "data": {"click_action": "FLUTTER_NOTIFICATION_CLICK", "id": "1", "status": "done"}, "to": ""}' -curl https://fcm.googleapis.com/fcm/send -H "Content-Type:application/json" -X POST -d "$DATA" -H "Authorization: key=" -``` - -Remove the `notification` property in `DATA` to send a data message. - -You could also test this from within Flutter using the [http](https://pub.dev/packages/http) package: - -```dart -// Replace with server token from firebase console settings. -final String serverToken = ''; -final FirebaseMessaging firebaseMessaging = FirebaseMessaging(); - -Future> sendAndRetrieveMessage() async { - await firebaseMessaging.requestNotificationPermissions( - const IosNotificationSettings(sound: true, badge: true, alert: true, provisional: false), - ); - - await http.post( - 'https://fcm.googleapis.com/fcm/send', - headers: { - 'Content-Type': 'application/json', - 'Authorization': 'key=$serverToken', - }, - body: jsonEncode( - { - 'notification': { - 'body': 'this is a body', - 'title': 'this is a title' - }, - 'priority': 'high', - 'data': { - 'click_action': 'FLUTTER_NOTIFICATION_CLICK', - 'id': '1', - 'status': 'done' - }, - 'to': await firebaseMessaging.getToken(), - }, - ), - ); - - final Completer> completer = - Completer>(); - - firebaseMessaging.configure( - onMessage: (Map message) async { - completer.complete(message); - }, - ); - - return completer.future; -} -``` - -## Issues and feedback - -Please file FlutterFire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/FirebaseExtended/flutterfire/issues/new). - -Plugin issues that are not specific to Flutterfire can be filed in the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new). - -To contribute a change to this plugin, -please review our [contribution guide](https://github.com/FirebaseExtended/flutterfire/blob/master/CONTRIBUTING.md) -and open a [pull request](https://github.com/FirebaseExtended/flutterfire/pulls). diff --git a/packages/firebase_messaging/analysis_options.yaml b/packages/firebase_messaging/analysis_options.yaml deleted file mode 100644 index d4ccef63f1d1..000000000000 --- a/packages/firebase_messaging/analysis_options.yaml +++ /dev/null @@ -1,11 +0,0 @@ -# This is a temporary file to allow us to land a new set of linter rules in a -# series of manageable patches instead of one gigantic PR. It disables some of -# the new lints that are already failing on this plugin, for this plugin. It -# should be deleted and the failing lints addressed as soon as possible. - -include: ../../analysis_options.yaml - -analyzer: - errors: - public_member_api_docs: ignore - unawaited_futures: ignore diff --git a/packages/firebase_messaging/android/build.gradle b/packages/firebase_messaging/android/build.gradle deleted file mode 100644 index 8718f844eb6a..000000000000 --- a/packages/firebase_messaging/android/build.gradle +++ /dev/null @@ -1,56 +0,0 @@ -group 'io.flutter.plugins.firebasemessaging' -version '1.0-SNAPSHOT' - -buildscript { - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' - } -} - -rootProject.allprojects { - repositories { - google() - jcenter() - } -} - -apply plugin: 'com.android.library' - -def firebaseCoreProject = findProject(':firebase_core') -if (firebaseCoreProject == null) { - throw new GradleException('Could not find the firebase_core FlutterFire plugin, have you added it as a dependency in your pubspec?') -} else if (!firebaseCoreProject.properties['FirebaseSDKVersion']) { - throw new GradleException('A newer version of the firebase_core FlutterFire plugin is required, please update your firebase_core pubspec dependency.') -} - -def getRootProjectExtOrCoreProperty(name, firebaseCoreProject) { - if (!rootProject.ext.has('FlutterFire')) return firebaseCoreProject.properties[name] - if (!rootProject.ext.get('FlutterFire')[name]) return firebaseCoreProject.properties[name] - return rootProject.ext.get('FlutterFire').get(name) -} - -android { - compileSdkVersion 29 - - defaultConfig { - minSdkVersion 16 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - lintOptions { - disable 'InvalidPackage' - } - dependencies { - api firebaseCoreProject - implementation platform("com.google.firebase:firebase-bom:${getRootProjectExtOrCoreProperty("FirebaseSDKVersion", firebaseCoreProject)}") - implementation 'com.google.firebase:firebase-messaging' - implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' - implementation 'androidx.annotation:annotation:1.1.0' - } -} - -apply from: file("./user-agent.gradle") diff --git a/packages/firebase_messaging/android/src/main/AndroidManifest.xml b/packages/firebase_messaging/android/src/main/AndroidManifest.xml deleted file mode 100644 index 34e4e6df5d39..000000000000 --- a/packages/firebase_messaging/android/src/main/AndroidManifest.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - diff --git a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FirebaseMessagingPlugin.java b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FirebaseMessagingPlugin.java deleted file mode 100644 index d475b9e0a3b8..000000000000 --- a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FirebaseMessagingPlugin.java +++ /dev/null @@ -1,329 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.firebasemessaging; - -import android.app.Activity; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; -import android.os.Bundle; -import android.util.Log; -import androidx.annotation.NonNull; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import com.google.android.gms.tasks.OnCompleteListener; -import com.google.android.gms.tasks.Task; -import com.google.firebase.iid.FirebaseInstanceId; -import com.google.firebase.iid.InstanceIdResult; -import com.google.firebase.messaging.FirebaseMessaging; -import com.google.firebase.messaging.RemoteMessage; -import io.flutter.embedding.engine.plugins.FlutterPlugin; -import io.flutter.embedding.engine.plugins.activity.ActivityAware; -import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; -import io.flutter.plugin.common.BinaryMessenger; -import io.flutter.plugin.common.MethodCall; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.MethodChannel.MethodCallHandler; -import io.flutter.plugin.common.MethodChannel.Result; -import io.flutter.plugin.common.PluginRegistry.NewIntentListener; -import io.flutter.plugin.common.PluginRegistry.Registrar; -import java.io.IOException; -import java.util.HashMap; -import java.util.Map; - -/** FirebaseMessagingPlugin */ -public class FirebaseMessagingPlugin extends BroadcastReceiver - implements MethodCallHandler, NewIntentListener, FlutterPlugin, ActivityAware { - - private static final String CLICK_ACTION_VALUE = "FLUTTER_NOTIFICATION_CLICK"; - private static final String TAG = "FirebaseMessagingPlugin"; - - private MethodChannel channel; - private Context applicationContext; - private Activity mainActivity; - - public static void registerWith(Registrar registrar) { - FirebaseMessagingPlugin instance = new FirebaseMessagingPlugin(); - instance.setActivity(registrar.activity()); - registrar.addNewIntentListener(instance); - instance.onAttachedToEngine(registrar.context(), registrar.messenger()); - } - - private void onAttachedToEngine(Context context, BinaryMessenger binaryMessenger) { - this.applicationContext = context; - channel = new MethodChannel(binaryMessenger, "plugins.flutter.io/firebase_messaging"); - final MethodChannel backgroundCallbackChannel = - new MethodChannel(binaryMessenger, "plugins.flutter.io/firebase_messaging_background"); - - channel.setMethodCallHandler(this); - backgroundCallbackChannel.setMethodCallHandler(this); - FlutterFirebaseMessagingService.setBackgroundChannel(backgroundCallbackChannel); - - // Register broadcast receiver - IntentFilter intentFilter = new IntentFilter(); - intentFilter.addAction(FlutterFirebaseMessagingService.ACTION_TOKEN); - intentFilter.addAction(FlutterFirebaseMessagingService.ACTION_REMOTE_MESSAGE); - LocalBroadcastManager manager = LocalBroadcastManager.getInstance(applicationContext); - manager.registerReceiver(this, intentFilter); - } - - private void setActivity(Activity flutterActivity) { - this.mainActivity = flutterActivity; - } - - @Override - public void onAttachedToEngine(FlutterPluginBinding binding) { - onAttachedToEngine(binding.getApplicationContext(), binding.getBinaryMessenger()); - } - - @Override - public void onDetachedFromEngine(FlutterPluginBinding binding) { - LocalBroadcastManager.getInstance(binding.getApplicationContext()).unregisterReceiver(this); - } - - @Override - public void onAttachedToActivity(ActivityPluginBinding binding) { - binding.addOnNewIntentListener(this); - this.mainActivity = binding.getActivity(); - } - - @Override - public void onDetachedFromActivityForConfigChanges() { - this.mainActivity = null; - } - - @Override - public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { - binding.addOnNewIntentListener(this); - this.mainActivity = binding.getActivity(); - } - - @Override - public void onDetachedFromActivity() { - this.mainActivity = null; - } - - // BroadcastReceiver implementation. - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - - if (action == null) { - return; - } - - if (action.equals(FlutterFirebaseMessagingService.ACTION_TOKEN)) { - String token = intent.getStringExtra(FlutterFirebaseMessagingService.EXTRA_TOKEN); - channel.invokeMethod("onToken", token); - } else if (action.equals(FlutterFirebaseMessagingService.ACTION_REMOTE_MESSAGE)) { - RemoteMessage message = - intent.getParcelableExtra(FlutterFirebaseMessagingService.EXTRA_REMOTE_MESSAGE); - Map content = parseRemoteMessage(message); - channel.invokeMethod("onMessage", content); - } - } - - @NonNull - private Map parseRemoteMessage(RemoteMessage message) { - Map content = new HashMap<>(); - content.put("data", message.getData()); - - RemoteMessage.Notification notification = message.getNotification(); - - Map notificationMap = new HashMap<>(); - - String title = notification != null ? notification.getTitle() : null; - notificationMap.put("title", title); - - String body = notification != null ? notification.getBody() : null; - notificationMap.put("body", body); - - content.put("notification", notificationMap); - return content; - } - - @Override - public void onMethodCall(final MethodCall call, final Result result) { - /* Even when the app is not active the `FirebaseMessagingService` extended by - * `FlutterFirebaseMessagingService` allows incoming FCM messages to be handled. - * - * `FcmDartService#start` and `FcmDartService#initialized` are the two methods used - * to optionally setup handling messages received while the app is not active. - * - * `FcmDartService#start` sets up the plumbing that allows messages received while - * the app is not active to be handled by a background isolate. - * - * `FcmDartService#initialized` is called by the Dart side when the plumbing for - * background message handling is complete. - */ - if ("FcmDartService#start".equals(call.method)) { - long setupCallbackHandle = 0; - long backgroundMessageHandle = 0; - try { - @SuppressWarnings("unchecked") - Map callbacks = ((Map) call.arguments); - setupCallbackHandle = callbacks.get("setupHandle"); - backgroundMessageHandle = callbacks.get("backgroundHandle"); - } catch (Exception e) { - Log.e(TAG, "There was an exception when getting callback handle from Dart side"); - e.printStackTrace(); - } - FlutterFirebaseMessagingService.setBackgroundSetupHandle(mainActivity, setupCallbackHandle); - FlutterFirebaseMessagingService.startBackgroundIsolate(mainActivity, setupCallbackHandle); - FlutterFirebaseMessagingService.setBackgroundMessageHandle( - mainActivity, backgroundMessageHandle); - result.success(true); - } else if ("FcmDartService#initialized".equals(call.method)) { - FlutterFirebaseMessagingService.onInitialized(); - result.success(true); - } else if ("configure".equals(call.method)) { - FirebaseInstanceId.getInstance() - .getInstanceId() - .addOnCompleteListener( - new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (!task.isSuccessful()) { - Log.w(TAG, "getToken, error fetching instanceID: ", task.getException()); - return; - } - channel.invokeMethod("onToken", task.getResult().getToken()); - } - }); - if (mainActivity != null) { - sendMessageFromIntent("onLaunch", mainActivity.getIntent()); - } - result.success(null); - } else if ("subscribeToTopic".equals(call.method)) { - String topic = call.arguments(); - FirebaseMessaging.getInstance() - .subscribeToTopic(topic) - .addOnCompleteListener( - new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (!task.isSuccessful()) { - Exception e = task.getException(); - Log.w(TAG, "subscribeToTopic error", e); - result.error("subscribeToTopic", e.getMessage(), null); - return; - } - result.success(null); - } - }); - } else if ("unsubscribeFromTopic".equals(call.method)) { - String topic = call.arguments(); - FirebaseMessaging.getInstance() - .unsubscribeFromTopic(topic) - .addOnCompleteListener( - new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (!task.isSuccessful()) { - Exception e = task.getException(); - Log.w(TAG, "unsubscribeFromTopic error", e); - result.error("unsubscribeFromTopic", e.getMessage(), null); - return; - } - result.success(null); - } - }); - } else if ("getToken".equals(call.method)) { - FirebaseInstanceId.getInstance() - .getInstanceId() - .addOnCompleteListener( - new OnCompleteListener() { - @Override - public void onComplete(@NonNull Task task) { - if (!task.isSuccessful()) { - Log.w(TAG, "getToken, error fetching instanceID: ", task.getException()); - result.success(null); - return; - } - - result.success(task.getResult().getToken()); - } - }); - } else if ("deleteInstanceID".equals(call.method)) { - new Thread( - new Runnable() { - @Override - public void run() { - try { - FirebaseInstanceId.getInstance().deleteInstanceId(); - if (mainActivity != null) { - mainActivity.runOnUiThread( - new Runnable() { - @Override - public void run() { - result.success(true); - } - }); - } - } catch (IOException ex) { - Log.e(TAG, "deleteInstanceID, error:", ex); - if (mainActivity != null) { - mainActivity.runOnUiThread( - new Runnable() { - @Override - public void run() { - result.success(false); - } - }); - } - } - } - }) - .start(); - } else if ("autoInitEnabled".equals(call.method)) { - result.success(FirebaseMessaging.getInstance().isAutoInitEnabled()); - } else if ("setAutoInitEnabled".equals(call.method)) { - Boolean isEnabled = (Boolean) call.arguments(); - FirebaseMessaging.getInstance().setAutoInitEnabled(isEnabled); - result.success(null); - } else { - result.notImplemented(); - } - } - - @Override - public boolean onNewIntent(Intent intent) { - boolean res = sendMessageFromIntent("onResume", intent); - if (res && mainActivity != null) { - mainActivity.setIntent(intent); - } - return res; - } - - /** @return true if intent contained a message to send. */ - private boolean sendMessageFromIntent(String method, Intent intent) { - if (CLICK_ACTION_VALUE.equals(intent.getAction()) - || CLICK_ACTION_VALUE.equals(intent.getStringExtra("click_action"))) { - Map message = new HashMap<>(); - Bundle extras = intent.getExtras(); - - if (extras == null) { - return false; - } - - Map notificationMap = new HashMap<>(); - Map dataMap = new HashMap<>(); - - for (String key : extras.keySet()) { - Object extra = extras.get(key); - if (extra != null) { - dataMap.put(key, extra); - } - } - - message.put("notification", notificationMap); - message.put("data", dataMap); - - channel.invokeMethod(method, message); - return true; - } - return false; - } -} diff --git a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java deleted file mode 100644 index bf9207649bbc..000000000000 --- a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseMessagingService.java +++ /dev/null @@ -1,332 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.firebasemessaging; - -import android.app.ActivityManager; -import android.app.KeyguardManager; -import android.content.Context; -import android.content.Intent; -import android.content.SharedPreferences; -import android.os.Handler; -import android.os.Process; -import android.util.Log; -import androidx.localbroadcastmanager.content.LocalBroadcastManager; -import com.google.firebase.messaging.FirebaseMessagingService; -import com.google.firebase.messaging.RemoteMessage; -import io.flutter.plugin.common.MethodChannel; -import io.flutter.plugin.common.PluginRegistry; -import io.flutter.view.FlutterCallbackInformation; -import io.flutter.view.FlutterMain; -import io.flutter.view.FlutterNativeView; -import io.flutter.view.FlutterRunArguments; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; - -public class FlutterFirebaseMessagingService extends FirebaseMessagingService { - - public static final String ACTION_REMOTE_MESSAGE = - "io.flutter.plugins.firebasemessaging.NOTIFICATION"; - public static final String EXTRA_REMOTE_MESSAGE = "notification"; - - public static final String ACTION_TOKEN = "io.flutter.plugins.firebasemessaging.TOKEN"; - public static final String EXTRA_TOKEN = "token"; - - private static final String SHARED_PREFERENCES_KEY = "io.flutter.android_fcm_plugin"; - private static final String BACKGROUND_SETUP_CALLBACK_HANDLE_KEY = "background_setup_callback"; - private static final String BACKGROUND_MESSAGE_CALLBACK_HANDLE_KEY = - "background_message_callback"; - - // TODO(kroikie): make isIsolateRunning per-instance, not static. - private static AtomicBoolean isIsolateRunning = new AtomicBoolean(false); - - /** Background Dart execution context. */ - private static FlutterNativeView backgroundFlutterView; - - private static MethodChannel backgroundChannel; - - private static Long backgroundMessageHandle; - - private static List backgroundMessageQueue = - Collections.synchronizedList(new LinkedList()); - - private static PluginRegistry.PluginRegistrantCallback pluginRegistrantCallback; - - private static final String TAG = "FlutterFcmService"; - - private static Context backgroundContext; - - @Override - public void onCreate() { - super.onCreate(); - - backgroundContext = getApplicationContext(); - FlutterMain.ensureInitializationComplete(backgroundContext, null); - - // If background isolate is not running start it. - if (!isIsolateRunning.get()) { - SharedPreferences p = backgroundContext.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); - long callbackHandle = p.getLong(BACKGROUND_SETUP_CALLBACK_HANDLE_KEY, 0); - startBackgroundIsolate(backgroundContext, callbackHandle); - } - } - - /** - * Called when message is received. - * - * @param remoteMessage Object representing the message received from Firebase Cloud Messaging. - */ - @Override - public void onMessageReceived(final RemoteMessage remoteMessage) { - // If application is running in the foreground use local broadcast to handle message. - // Otherwise use the background isolate to handle message. - if (isApplicationForeground(this)) { - Intent intent = new Intent(ACTION_REMOTE_MESSAGE); - intent.putExtra(EXTRA_REMOTE_MESSAGE, remoteMessage); - LocalBroadcastManager.getInstance(this).sendBroadcast(intent); - } else { - // If background isolate is not running yet, put message in queue and it will be handled - // when the isolate starts. - if (!isIsolateRunning.get()) { - backgroundMessageQueue.add(remoteMessage); - } else { - final CountDownLatch latch = new CountDownLatch(1); - new Handler(getMainLooper()) - .post( - new Runnable() { - @Override - public void run() { - executeDartCallbackInBackgroundIsolate( - FlutterFirebaseMessagingService.this, remoteMessage, latch); - } - }); - try { - latch.await(); - } catch (InterruptedException ex) { - Log.i(TAG, "Exception waiting to execute Dart callback", ex); - } - } - } - } - - /** - * Called when a new token for the default Firebase project is generated. - * - * @param token The token used for sending messages to this application instance. This token is - * the same as the one retrieved by getInstanceId(). - */ - @Override - public void onNewToken(String token) { - Intent intent = new Intent(ACTION_TOKEN); - intent.putExtra(EXTRA_TOKEN, token); - LocalBroadcastManager.getInstance(this).sendBroadcast(intent); - } - - /** - * Setup the background isolate that would allow background messages to be handled on the Dart - * side. Called either by the plugin when the app is starting up or when the app receives a - * message while it is inactive. - * - * @param context Registrar or FirebaseMessagingService context. - * @param callbackHandle Handle used to retrieve the Dart function that sets up background - * handling on the dart side. - */ - public static void startBackgroundIsolate(Context context, long callbackHandle) { - FlutterMain.ensureInitializationComplete(context, null); - String appBundlePath = FlutterMain.findAppBundlePath(); - FlutterCallbackInformation flutterCallback = - FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); - if (flutterCallback == null) { - Log.e(TAG, "Fatal: failed to find callback"); - return; - } - - // Note that we're passing `true` as the second argument to our - // FlutterNativeView constructor. This specifies the FlutterNativeView - // as a background view and does not create a drawing surface. - backgroundFlutterView = new FlutterNativeView(context, true); - if (appBundlePath != null) { - if (pluginRegistrantCallback == null) { - throw new RuntimeException("PluginRegistrantCallback is not set."); - } - FlutterRunArguments args = new FlutterRunArguments(); - args.bundlePath = appBundlePath; - args.entrypoint = flutterCallback.callbackName; - args.libraryPath = flutterCallback.callbackLibraryPath; - backgroundFlutterView.runFromBundle(args); - pluginRegistrantCallback.registerWith(backgroundFlutterView.getPluginRegistry()); - } - } - - /** - * Acknowledge that background message handling on the Dart side is ready. This is called by the - * Dart side once all background initialization is complete via `FcmDartService#initialized`. - */ - public static void onInitialized() { - isIsolateRunning.set(true); - synchronized (backgroundMessageQueue) { - // Handle all the messages received before the Dart isolate was - // initialized, then clear the queue. - Iterator i = backgroundMessageQueue.iterator(); - while (i.hasNext()) { - executeDartCallbackInBackgroundIsolate(backgroundContext, i.next(), null); - } - backgroundMessageQueue.clear(); - } - } - - /** - * Set the method channel that is used for handling background messages. This method is only - * called when the plugin registers. - * - * @param channel Background method channel. - */ - public static void setBackgroundChannel(MethodChannel channel) { - backgroundChannel = channel; - } - - /** - * Set the background message handle for future use. When background messages need to be handled - * on the Dart side the handler must be retrieved in the background isolate to allow processing of - * the incoming message. This method is called by the Dart side via `FcmDartService#start`. - * - * @param context Registrar context. - * @param handle Handle representing the Dart side method that will handle background messages. - */ - public static void setBackgroundMessageHandle(Context context, Long handle) { - backgroundMessageHandle = handle; - - // Store background message handle in shared preferences so it can be retrieved - // by other application instances. - SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); - prefs.edit().putLong(BACKGROUND_MESSAGE_CALLBACK_HANDLE_KEY, handle).apply(); - } - - /** - * Set the background message setup handle for future use. The Dart side of this plugin has a - * method that sets up the background method channel. When ready to setup the background channel - * the Dart side needs to be able to retrieve the setup method. This method is called by the Dart - * side via `FcmDartService#start`. - * - * @param context Registrar context. - * @param setupBackgroundHandle Handle representing the dart side method that will setup the - * background method channel. - */ - public static void setBackgroundSetupHandle(Context context, long setupBackgroundHandle) { - // Store background setup handle in shared preferences so it can be retrieved - // by other application instances. - SharedPreferences prefs = context.getSharedPreferences(SHARED_PREFERENCES_KEY, 0); - prefs.edit().putLong(BACKGROUND_SETUP_CALLBACK_HANDLE_KEY, setupBackgroundHandle).apply(); - } - - /** - * Retrieve the background message handle. When a background message is received and must be - * processed on the dart side the handle representing the Dart side handle is retrieved so the - * appropriate method can be called to process the message on the Dart side. This method is called - * by FlutterFirebaseMessagingServcie either when a new background message is received or if - * background messages were queued up while background message handling was being setup. - * - * @param context Application context. - * @return Dart side background message handle. - */ - public static Long getBackgroundMessageHandle(Context context) { - return context - .getSharedPreferences(SHARED_PREFERENCES_KEY, 0) - .getLong(BACKGROUND_MESSAGE_CALLBACK_HANDLE_KEY, 0); - } - - /** - * Process the incoming message in the background isolate. This method is called only after - * background method channel is setup, it is called by FlutterFirebaseMessagingServcie either when - * a new background message is received or after background method channel setup for queued - * messages received during setup. - * - * @param context Application or FirebaseMessagingService context. - * @param remoteMessage Message received from Firebase Cloud Messaging. - * @param latch If set will count down when the Dart side message processing is complete. Allowing - * any waiting threads to continue. - */ - private static void executeDartCallbackInBackgroundIsolate( - Context context, RemoteMessage remoteMessage, final CountDownLatch latch) { - if (backgroundChannel == null) { - throw new RuntimeException( - "setBackgroundChannel was not called before messages came in, exiting."); - } - - // If another thread is waiting, then wake that thread when the callback returns a result. - MethodChannel.Result result = null; - if (latch != null) { - result = new LatchResult(latch).getResult(); - } - - Map args = new HashMap<>(); - Map messageData = new HashMap<>(); - if (backgroundMessageHandle == null) { - backgroundMessageHandle = getBackgroundMessageHandle(context); - } - args.put("handle", backgroundMessageHandle); - - if (remoteMessage.getData() != null) { - messageData.put("data", remoteMessage.getData()); - } - if (remoteMessage.getNotification() != null) { - messageData.put("notification", remoteMessage.getNotification()); - } - - args.put("message", messageData); - - backgroundChannel.invokeMethod("handleBackgroundMessage", args, result); - } - - /** - * Set the registrant callback. This is called by the app's Application class if background - * message handling is enabled. - * - * @param callback Application class which implements PluginRegistrantCallback. - */ - public static void setPluginRegistrant(PluginRegistry.PluginRegistrantCallback callback) { - pluginRegistrantCallback = callback; - } - - /** - * Identify if the application is currently in a state where user interaction is possible. This - * method is only called by FlutterFirebaseMessagingService when a message is received to - * determine how the incoming message should be handled. - * - * @param context FlutterFirebaseMessagingService context. - * @return True if the application is currently in a state where user interaction is possible, - * false otherwise. - */ - // TODO(kroikie): Find a better way to determine application state. - private static boolean isApplicationForeground(Context context) { - KeyguardManager keyguardManager = - (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); - - if (keyguardManager.isKeyguardLocked()) { - return false; - } - int myPid = Process.myPid(); - - ActivityManager activityManager = - (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); - - List list; - - if ((list = activityManager.getRunningAppProcesses()) != null) { - for (ActivityManager.RunningAppProcessInfo aList : list) { - ActivityManager.RunningAppProcessInfo info; - if ((info = aList).pid == myPid) { - return info.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; - } - } - } - return false; - } -} diff --git a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/LatchResult.java b/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/LatchResult.java deleted file mode 100644 index 7c4c9dfaeafa..000000000000 --- a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/LatchResult.java +++ /dev/null @@ -1,33 +0,0 @@ -package io.flutter.plugins.firebasemessaging; - -import io.flutter.plugin.common.MethodChannel; -import java.util.concurrent.CountDownLatch; - -public class LatchResult { - - private MethodChannel.Result result; - - public LatchResult(final CountDownLatch latch) { - result = - new MethodChannel.Result() { - @Override - public void success(Object result) { - latch.countDown(); - } - - @Override - public void error(String errorCode, String errorMessage, Object errorDetails) { - latch.countDown(); - } - - @Override - public void notImplemented() { - latch.countDown(); - } - }; - } - - public MethodChannel.Result getResult() { - return result; - } -} diff --git a/packages/firebase_messaging/example/android/app/google-services.json b/packages/firebase_messaging/example/android/app/google-services.json deleted file mode 100644 index 625037ce8a98..000000000000 --- a/packages/firebase_messaging/example/android/app/google-services.json +++ /dev/null @@ -1,93 +0,0 @@ -{ - "project_info": { - "project_number": "3401408244", - "firebase_url": "https://test-project-cloud-messaging.firebaseio.com", - "project_id": "test-project-cloud-messaging", - "storage_bucket": "test-project-cloud-messaging.appspot.com" - }, - "client": [ - { - "client_info": { - "mobilesdk_app_id": "1:3401408244:android:5920de3503a2d18c", - "android_client_info": { - "package_name": "com.google.firebase.quickstart.fcm" - } - }, - "oauth_client": [ - { - "client_id": "3401408244-s01tdnd5guf3vrrn4n780j22sm20gob8.apps.googleusercontent.com", - "client_type": 1, - "android_info": { - "package_name": "com.google.firebase.quickstart.fcm", - "certificate_hash": "a647cb35cdac21e3a26f2b5697c5b789cde3b5a0" - } - }, - { - "client_id": "3401408244-m5m1eph6d37rrp71hj33hcttffmmeat0.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyBMV3_n5xWH-rzyxsUWbvPPo7-_ZgESSOo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 2, - "other_platform_oauth_client": [ - { - "client_id": "3401408244-kpa4vjuab7s8gjut5gjdf3hku8gbdp1c.apps.googleusercontent.com", - "client_type": 2, - "ios_info": { - "bundle_id": "io.flutter.plugins.firebase-messaging" - } - }, - { - "client_id": "3401408244-m5m1eph6d37rrp71hj33hcttffmmeat0.apps.googleusercontent.com", - "client_type": 3 - } - ] - }, - "ads_service": { - "status": 2 - } - } - }, - { - "client_info": { - "mobilesdk_app_id": "1:3401408244:android:fc026014a0a3ac30", - "android_client_info": { - "package_name": "io.flutter.plugins.firebasemessagingexample" - } - }, - "oauth_client": [ - { - "client_id": "3401408244-m5m1eph6d37rrp71hj33hcttffmmeat0.apps.googleusercontent.com", - "client_type": 3 - } - ], - "api_key": [ - { - "current_key": "AIzaSyBMV3_n5xWH-rzyxsUWbvPPo7-_ZgESSOo" - } - ], - "services": { - "analytics_service": { - "status": 1 - }, - "appinvite_service": { - "status": 1, - "other_platform_oauth_client": [] - }, - "ads_service": { - "status": 2 - } - } - } - ], - "configuration_version": "1" -} diff --git a/packages/firebase_messaging/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/messaging/EmbeddingV1ActivityTest.java b/packages/firebase_messaging/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/messaging/EmbeddingV1ActivityTest.java deleted file mode 100644 index 8358f5620ca6..000000000000 --- a/packages/firebase_messaging/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/messaging/EmbeddingV1ActivityTest.java +++ /dev/null @@ -1,14 +0,0 @@ -package io.flutter.plugins.firebase.messaging; - -import androidx.test.rule.ActivityTestRule; -import dev.flutter.plugins.e2e.FlutterTestRunner; -import io.flutter.plugins.firebasemessagingexample.EmbeddingV1Activity; -import org.junit.Rule; -import org.junit.runner.RunWith; - -@RunWith(FlutterTestRunner.class) -public class EmbeddingV1ActivityTest { - @Rule - public ActivityTestRule rule = - new ActivityTestRule<>(EmbeddingV1Activity.class); -} diff --git a/packages/firebase_messaging/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/messaging/MainActivityTest.java b/packages/firebase_messaging/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/messaging/MainActivityTest.java deleted file mode 100644 index 89048a06637f..000000000000 --- a/packages/firebase_messaging/example/android/app/src/androidTest/java/io/flutter/plugins/firebase/messaging/MainActivityTest.java +++ /dev/null @@ -1,12 +0,0 @@ -package io.flutter.plugins.firebase.messaging; - -import androidx.test.rule.ActivityTestRule; -import dev.flutter.plugins.e2e.FlutterTestRunner; -import io.flutter.plugins.firebasemessagingexample.MainActivity; -import org.junit.Rule; -import org.junit.runner.RunWith; - -@RunWith(FlutterTestRunner.class) -public class MainActivityTest { - @Rule public ActivityTestRule rule = new ActivityTestRule<>(MainActivity.class); -} diff --git a/packages/firebase_messaging/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_messaging/example/android/app/src/main/AndroidManifest.xml deleted file mode 100644 index b1f013664cbe..000000000000 --- a/packages/firebase_messaging/example/android/app/src/main/AndroidManifest.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - diff --git a/packages/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/.gitignore b/packages/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/.gitignore deleted file mode 100644 index 9eb4563d2ae1..000000000000 --- a/packages/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/.gitignore +++ /dev/null @@ -1 +0,0 @@ -GeneratedPluginRegistrant.java diff --git a/packages/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/firebasemessagingexample/EmbeddingV1Activity.java b/packages/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/firebasemessagingexample/EmbeddingV1Activity.java deleted file mode 100644 index cf57491a87f2..000000000000 --- a/packages/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/firebasemessagingexample/EmbeddingV1Activity.java +++ /dev/null @@ -1,13 +0,0 @@ -package io.flutter.plugins.firebasemessagingexample; - -import android.os.Bundle; -import io.flutter.app.FlutterActivity; -import io.flutter.plugins.GeneratedPluginRegistrant; - -public class EmbeddingV1Activity extends FlutterActivity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - GeneratedPluginRegistrant.registerWith(this); - } -} diff --git a/packages/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/firebasemessagingexample/MainActivity.java b/packages/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/firebasemessagingexample/MainActivity.java deleted file mode 100644 index 733f2218974e..000000000000 --- a/packages/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/firebasemessagingexample/MainActivity.java +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -package io.flutter.plugins.firebasemessagingexample; - -import dev.flutter.plugins.e2e.E2EPlugin; -import io.flutter.embedding.android.FlutterActivity; -import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.plugins.firebase.core.FlutterFirebaseCorePlugin; -import io.flutter.plugins.firebasemessaging.FirebaseMessagingPlugin; - -public class MainActivity extends FlutterActivity { - - // TODO(kroikie): Remove this once v2 of GeneratedPluginRegistrant rolls to stable. https://github.com/flutter/flutter/issues/42694 - @Override - public void configureFlutterEngine(FlutterEngine flutterEngine) { - flutterEngine.getPlugins().add(new FirebaseMessagingPlugin()); - flutterEngine.getPlugins().add(new FlutterFirebaseCorePlugin()); - flutterEngine.getPlugins().add(new E2EPlugin()); - } -} diff --git a/packages/firebase_messaging/example/android/gradle/wrapper/gradle-wrapper.properties b/packages/firebase_messaging/example/android/gradle/wrapper/gradle-wrapper.properties deleted file mode 100644 index 384299ed705b..000000000000 --- a/packages/firebase_messaging/example/android/gradle/wrapper/gradle-wrapper.properties +++ /dev/null @@ -1,6 +0,0 @@ -#Thu Oct 03 14:07:08 BST 2019 -distributionBase=GRADLE_USER_HOME -distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME -zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/packages/firebase_messaging/example/android/settings.gradle b/packages/firebase_messaging/example/android/settings.gradle deleted file mode 100644 index 115da6cb4f4d..000000000000 --- a/packages/firebase_messaging/example/android/settings.gradle +++ /dev/null @@ -1,15 +0,0 @@ -include ':app' - -def flutterProjectRoot = rootProject.projectDir.parentFile.toPath() - -def plugins = new Properties() -def pluginsFile = new File(flutterProjectRoot.toFile(), '.flutter-plugins') -if (pluginsFile.exists()) { - pluginsFile.withInputStream { stream -> plugins.load(stream) } -} - -plugins.each { name, path -> - def pluginDirectory = flutterProjectRoot.resolve(path).resolve('android').toFile() - include ":$name" - project(":$name").projectDir = pluginDirectory -} diff --git a/packages/firebase_messaging/example/ios/Runner/.gitignore b/packages/firebase_messaging/example/ios/Runner/.gitignore deleted file mode 100644 index 0cab08d0bdd7..000000000000 --- a/packages/firebase_messaging/example/ios/Runner/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -GeneratedPluginRegistrant.h -GeneratedPluginRegistrant.m diff --git a/packages/firebase_messaging/example/ios/Runner/AppDelegate.h b/packages/firebase_messaging/example/ios/Runner/AppDelegate.h deleted file mode 100644 index d9e18e990f2e..000000000000 --- a/packages/firebase_messaging/example/ios/Runner/AppDelegate.h +++ /dev/null @@ -1,10 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import -#import - -@interface AppDelegate : FlutterAppDelegate - -@end diff --git a/packages/firebase_messaging/example/ios/Runner/AppDelegate.m b/packages/firebase_messaging/example/ios/Runner/AppDelegate.m deleted file mode 100644 index bddc7fe2249f..000000000000 --- a/packages/firebase_messaging/example/ios/Runner/AppDelegate.m +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "AppDelegate.h" -#include "GeneratedPluginRegistrant.h" - -@implementation AppDelegate - -- (BOOL)application:(UIApplication *)application - didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - if (@available(iOS 10.0, *)) { - [UNUserNotificationCenter currentNotificationCenter].delegate = - (id)self; - } - [GeneratedPluginRegistrant registerWithRegistry:self]; - return [super application:application didFinishLaunchingWithOptions:launchOptions]; -} - -@end diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json deleted file mode 100644 index d22f10b2ab63..000000000000 --- a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json +++ /dev/null @@ -1,116 +0,0 @@ -{ - "images" : [ - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "20x20", - "idiom" : "iphone", - "filename" : "Icon-App-20x20@3x.png", - "scale" : "3x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "iphone", - "filename" : "Icon-App-29x29@3x.png", - "scale" : "3x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "iphone", - "filename" : "Icon-App-40x40@3x.png", - "scale" : "3x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@2x.png", - "scale" : "2x" - }, - { - "size" : "60x60", - "idiom" : "iphone", - "filename" : "Icon-App-60x60@3x.png", - "scale" : "3x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@1x.png", - "scale" : "1x" - }, - { - "size" : "20x20", - "idiom" : "ipad", - "filename" : "Icon-App-20x20@2x.png", - "scale" : "2x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@1x.png", - "scale" : "1x" - }, - { - "size" : "29x29", - "idiom" : "ipad", - "filename" : "Icon-App-29x29@2x.png", - "scale" : "2x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@1x.png", - "scale" : "1x" - }, - { - "size" : "40x40", - "idiom" : "ipad", - "filename" : "Icon-App-40x40@2x.png", - "scale" : "2x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@1x.png", - "scale" : "1x" - }, - { - "size" : "76x76", - "idiom" : "ipad", - "filename" : "Icon-App-76x76@2x.png", - "scale" : "2x" - }, - { - "size" : "83.5x83.5", - "idiom" : "ipad", - "filename" : "Icon-App-83.5x83.5@2x.png", - "scale" : "2x" - } - ], - "info" : { - "version" : 1, - "author" : "xcode" - } -} diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png deleted file mode 100644 index 28c6bf03016f..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@1x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png deleted file mode 100644 index 2ccbfd967d96..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@2x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png deleted file mode 100644 index f091b6b0bca8..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-20x20@3x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png deleted file mode 100644 index 4cde12118dda..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@1x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png deleted file mode 100644 index d0ef06e7edb8..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@2x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png deleted file mode 100644 index dcdc2306c285..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-29x29@3x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png deleted file mode 100644 index 2ccbfd967d96..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@1x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png deleted file mode 100644 index c8f9ed8f5cee..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@2x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png deleted file mode 100644 index a6d6b8609df0..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-40x40@3x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png deleted file mode 100644 index a6d6b8609df0..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@2x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png deleted file mode 100644 index 75b2d164a5a9..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-60x60@3x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png deleted file mode 100644 index c4df70d39da7..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@1x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png deleted file mode 100644 index 6a84f41e14e2..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-76x76@2x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png b/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png deleted file mode 100644 index d0e1f5853602..000000000000 Binary files a/packages/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Icon-App-83.5x83.5@2x.png and /dev/null differ diff --git a/packages/firebase_messaging/example/ios/Runner/GoogleService-Info.plist b/packages/firebase_messaging/example/ios/Runner/GoogleService-Info.plist deleted file mode 100644 index aae17f35f51a..000000000000 --- a/packages/firebase_messaging/example/ios/Runner/GoogleService-Info.plist +++ /dev/null @@ -1,40 +0,0 @@ - - - - - AD_UNIT_ID_FOR_BANNER_TEST - ca-app-pub-3940256099942544/2934735716 - AD_UNIT_ID_FOR_INTERSTITIAL_TEST - ca-app-pub-3940256099942544/4411468910 - CLIENT_ID - 3401408244-kpa4vjuab7s8gjut5gjdf3hku8gbdp1c.apps.googleusercontent.com - REVERSED_CLIENT_ID - com.googleusercontent.apps.3401408244-kpa4vjuab7s8gjut5gjdf3hku8gbdp1c - API_KEY - AIzaSyCwJxWJTafbmE8PNdWEbU5Ciw5z1R5Fl3k - GCM_SENDER_ID - 3401408244 - PLIST_VERSION - 1 - BUNDLE_ID - io.flutter.plugins.firebase-messaging - PROJECT_ID - test-project-cloud-messaging - STORAGE_BUCKET - test-project-cloud-messaging.appspot.com - IS_ADS_ENABLED - - IS_ANALYTICS_ENABLED - - IS_APPINVITE_ENABLED - - IS_GCM_ENABLED - - IS_SIGNIN_ENABLED - - GOOGLE_APP_ID - 1:3401408244:ios:d297dea19b53a7da - DATABASE_URL - https://test-project-cloud-messaging.firebaseio.com - - \ No newline at end of file diff --git a/packages/firebase_messaging/example/lib/main.dart b/packages/firebase_messaging/example/lib/main.dart deleted file mode 100644 index d248b9c5cbc6..000000000000 --- a/packages/firebase_messaging/example/lib/main.dart +++ /dev/null @@ -1,244 +0,0 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter/material.dart'; - -final Map _items = {}; -Item _itemForMessage(Map message) { - final dynamic data = message['data'] ?? message; - final String itemId = data['id']; - final Item item = _items.putIfAbsent(itemId, () => Item(itemId: itemId)) - ..status = data['status']; - return item; -} - -class Item { - Item({this.itemId}); - final String itemId; - - StreamController _controller = StreamController.broadcast(); - Stream get onChanged => _controller.stream; - - String _status; - String get status => _status; - set status(String value) { - _status = value; - _controller.add(this); - } - - static final Map> routes = >{}; - Route get route { - final String routeName = '/detail/$itemId'; - return routes.putIfAbsent( - routeName, - () => MaterialPageRoute( - settings: RouteSettings(name: routeName), - builder: (BuildContext context) => DetailPage(itemId), - ), - ); - } -} - -class DetailPage extends StatefulWidget { - DetailPage(this.itemId); - final String itemId; - @override - _DetailPageState createState() => _DetailPageState(); -} - -class _DetailPageState extends State { - Item _item; - StreamSubscription _subscription; - - @override - void initState() { - super.initState(); - _item = _items[widget.itemId]; - _subscription = _item.onChanged.listen((Item item) { - if (!mounted) { - _subscription.cancel(); - } else { - setState(() { - _item = item; - }); - } - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: Text("Item ${_item.itemId}"), - ), - body: Material( - child: Center(child: Text("Item status: ${_item.status}")), - ), - ); - } -} - -class PushMessagingExample extends StatefulWidget { - @override - _PushMessagingExampleState createState() => _PushMessagingExampleState(); -} - -class _PushMessagingExampleState extends State { - String _homeScreenText = "Waiting for token..."; - bool _topicButtonsDisabled = false; - - final FirebaseMessaging _firebaseMessaging = FirebaseMessaging(); - final TextEditingController _topicController = - TextEditingController(text: 'topic'); - - Widget _buildDialog(BuildContext context, Item item) { - return AlertDialog( - content: Text("Item ${item.itemId} has been updated"), - actions: [ - FlatButton( - child: const Text('CLOSE'), - onPressed: () { - Navigator.pop(context, false); - }, - ), - FlatButton( - child: const Text('SHOW'), - onPressed: () { - Navigator.pop(context, true); - }, - ), - ], - ); - } - - void _showItemDialog(Map message) { - showDialog( - context: context, - builder: (_) => _buildDialog(context, _itemForMessage(message)), - ).then((bool shouldNavigate) { - if (shouldNavigate == true) { - _navigateToItemDetail(message); - } - }); - } - - void _navigateToItemDetail(Map message) { - final Item item = _itemForMessage(message); - // Clear away dialogs - Navigator.popUntil(context, (Route route) => route is PageRoute); - if (!item.route.isCurrent) { - Navigator.push(context, item.route); - } - } - - @override - void initState() { - super.initState(); - _firebaseMessaging.configure( - onMessage: (Map message) async { - print("onMessage: $message"); - _showItemDialog(message); - }, - onLaunch: (Map message) async { - print("onLaunch: $message"); - _navigateToItemDetail(message); - }, - onResume: (Map message) async { - print("onResume: $message"); - _navigateToItemDetail(message); - }, - ); - _firebaseMessaging.requestNotificationPermissions( - const IosNotificationSettings( - sound: true, badge: true, alert: true, provisional: true)); - _firebaseMessaging.onIosSettingsRegistered - .listen((IosNotificationSettings settings) { - print("Settings registered: $settings"); - }); - _firebaseMessaging.getToken().then((String token) { - assert(token != null); - setState(() { - _homeScreenText = "Push Messaging token: $token"; - }); - print(_homeScreenText); - }); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - title: const Text('Push Messaging Demo'), - ), - // For testing -- simulate a message being received - floatingActionButton: FloatingActionButton( - onPressed: () => _showItemDialog({ - "data": { - "id": "2", - "status": "out of stock", - }, - }), - tooltip: 'Simulate Message', - child: const Icon(Icons.message), - ), - body: Material( - child: Column( - children: [ - Center( - child: Text(_homeScreenText), - ), - Row(children: [ - Expanded( - child: TextField( - controller: _topicController, - onChanged: (String v) { - setState(() { - _topicButtonsDisabled = v.isEmpty; - }); - }), - ), - FlatButton( - child: const Text("subscribe"), - onPressed: _topicButtonsDisabled - ? null - : () { - _firebaseMessaging - .subscribeToTopic(_topicController.text); - _clearTopicText(); - }, - ), - FlatButton( - child: const Text("unsubscribe"), - onPressed: _topicButtonsDisabled - ? null - : () { - _firebaseMessaging - .unsubscribeFromTopic(_topicController.text); - _clearTopicText(); - }, - ), - ]) - ], - ), - )); - } - - void _clearTopicText() { - setState(() { - _topicController.text = ""; - _topicButtonsDisabled = true; - }); - } -} - -void main() { - runApp( - MaterialApp( - home: PushMessagingExample(), - ), - ); -} diff --git a/packages/firebase_messaging/example/test_driver/firebase_messaging_e2e.dart b/packages/firebase_messaging/example/test_driver/firebase_messaging_e2e.dart deleted file mode 100644 index bcc40fa5bd7d..000000000000 --- a/packages/firebase_messaging/example/test_driver/firebase_messaging_e2e.dart +++ /dev/null @@ -1,42 +0,0 @@ -// Copyright 2020, the Chromium project authors. Please see the AUTHORS file -// for details. All rights reserved. Use of this source code is governed by a -// BSD-style license that can be found in the LICENSE file. - -import 'package:e2e/e2e.dart'; -import 'package:firebase_core/firebase_core.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; - -void main() { - E2EWidgetsFlutterBinding.ensureInitialized(); - - group('$FirebaseMessaging', () { - FirebaseMessaging firebaseMessaging; - setUp(() async { - await Firebase.initializeApp(); - firebaseMessaging = await FirebaseMessaging(); - }); - - testWidgets('autoInitEnabled', (WidgetTester tester) async { - await firebaseMessaging.setAutoInitEnabled(false); - expect(await firebaseMessaging.autoInitEnabled(), false); - await firebaseMessaging.setAutoInitEnabled(true); - expect(await firebaseMessaging.autoInitEnabled(), true); - }); - - // TODO(jackson): token retrieval isn't working on test devices yet - testWidgets('subscribeToTopic', (WidgetTester tester) async { - await firebaseMessaging.subscribeToTopic('foo'); - }, skip: true); - - // TODO(jackson): token retrieval isn't working on test devices yet - testWidgets('unsubscribeFromTopic', (WidgetTester tester) async { - await firebaseMessaging.unsubscribeFromTopic('foo'); - }, skip: true); - - testWidgets('deleteInstanceID', (WidgetTester tester) async { - final bool result = await firebaseMessaging.deleteInstanceID(); - expect(result, isTrue); - }); - }); -} diff --git a/packages/firebase_messaging/CHANGELOG.md b/packages/firebase_messaging/firebase_messaging/CHANGELOG.md similarity index 63% rename from packages/firebase_messaging/CHANGELOG.md rename to packages/firebase_messaging/firebase_messaging/CHANGELOG.md index 834f14e6c54d..f8ac59b75d07 100644 --- a/packages/firebase_messaging/CHANGELOG.md +++ b/packages/firebase_messaging/firebase_messaging/CHANGELOG.md @@ -1,3 +1,101 @@ +## 8.0.0-dev.3 + + - **FIX**: assert. + - **FEAT**: notification persistence. + - **FEAT**: add senderId (use iid on Android to support it). + +## 8.0.0-dev.2 + + - **FEAT**: bump firebase sdk version to 6.33.0. + - **DOCS**: typos. + +## 8.0.0-dev.1 + +This plugin is now federated to allow integration with other platforms, along with upgrading underlying SDK versions. + +We've also added lots of features which can be seen in the changelog below, however notably the biggest changes are: + +- Removed all manual native code changes that were originally required for integration - this plugin works + out of the box once configured with Firebase & APNs. +- Support for macOS. +- iOS background handler support. +- Android background handler debugging and logging support. +- Android V2 embedding support. +- Reworked API for message handling (Streams + explicit handlers). +- Fully typed Message & Notification classes (vs raw Maps). +- New Apple notification permissions & support. +- Detailed documentation. + +- **`FirebaseMessaging`**: + + - **BREAKING**: `configure()` has been removed in favor of calling specific static methods which return Streams. + - **Why?**: The previous implementation of `configure()` caused unintended side effects if called multiple + times (either to register a different handler, or remove handlers). This change allows developers to be more + explicit about registering handlers and removing them without effecting others via Streams. + - **DEPRECATED**: Calling `FirebaseMessaging()` has been deprecated in favor of `FirebaseMessaging.instance` + & `FirebaseMessaging.instanceFor()`. + - **DEPRECATED**: `requestNotificationPermissions()` has been deprecated in favor of `requestPermission()`. + - **DEPRECATED**: `deleteInstanceID()` has been deprecated in favor of `deleteToken()`. + - **DEPRECATED**: `autoInitEnabled()` has been deprecated in favor of `isAutoInitEnabled`. + - **NEW**: Added support for `isAutoInitEnabled` as a synchronous getter. + - **NEW**: Added support for `getInitialMessage()`. This API has been added to detect whether a messaging containing + a notification has caused the application to be opened via users interaction. + - **NEW**: Added support for `deleteToken()`. + - **NEW**: Added support for `getToken()`. + - **NEW**: [Apple] Added support for `getAPNSToken()`. + - **NEW**: [Apple] Added support for `getNotificationSettings()`. See `NotificationSettings` below. + - **NEW**: [Apple] Added support for `requestPermission()`. See `NotificationSettings` below. New permissions such + as `carPlay`, `crtiticalAlert`, `announcement` are now supported. + - **NEW**: [Android] Added support for `sendMessage()`. The `sendMessage()` API enables support for sending FCM + payloads back to a custom server from the device. + - **NEW**: [Android] When receiving background messages on the separate background Dart executor whilst in debug, + you should now see flutter logs and be able to debug/add breakpoints your Dart background message handler. + - **NEW**: [Apple] Added support for `setForegroundNotificationPresentationOptions()`. By default, iOS devices will + not show notifications in the foreground. Use this API to override the defaults. See documentation for Android + foreground notifications. + - **NEW** - [Android] Firebase Cloud Messages that contain a notification are now always sent to Dart regardless of + whether the app was in the foreground or background. Previously, if a message came through that contained a + notification whilst your app was in the foreground then FCM would not notify the plugin messaging service of the + message (and subsequently your handlers in Dart) until the user interacted with it. + +- **Event handling**: + + - Event handling has been reworked to provide a more intuitive API for developers. Foreground based events can now + be accessed via Streams: + - **NEW**: `FirebaseMessaging.onMessage` Returns a Stream that is called when an incoming FCM payload is + received whilst the Flutter instance is in the foreground, containing a [RemoteMessage]. + - **NEW**: `FirebaseMessaging.onMessageOpenedApp` Returns a [Stream] that is called when a user presses a + notification displayed via FCM. This replaces the previous `onLaunch` and `onResume` handlers. + - **NEW**: `FirebaseMessaging.onBackgroundMessage()` Sets a background message handler to trigger when the app + is in the background or terminated. + +- `IosNotificationSettings`: + + - **DEPRECATED**: Usage of the `IosNotificationSettings` class is now deprecated (currently used with the now + deprecated `requestNotificationPermissions()` method). + - Instead of this class, use named arguments when calling `requestPermission()` and read the permissions back + via the returned `NotificationSettings` instance. + +- `NotificationSettings`: + + - **NEW**: A `NotificationSettings` class is returned from calls to `requestPermission()` + and `getNotificationSettings()`. It contains information such as the authorization status, along with the platform + specific settings. + +- `RemoteMessage`: + + - **NEW**: Incoming FCM payloads are now represented as a `RemoteMessage` rather than a raw `Map`. + +- `RemoteNotification`: + - **NEW**:When a message includes a notification payload, the `RemoteMessage` includes a `RemoteNotification` rather + than a raw `Map`. + +- **Other**: + + - Additional types are available throughout messaging to aid with the latest changes: + - `BackgroundMessageHandler`, `AppleNotificationSetting`, `AppleShowPreviewSetting`, `AuthorizationStatus` + , `AndroidNotificationPriority`, `AndroidNotificationVisibility` + ## 7.0.3 - Update a dependency to the latest release. diff --git a/packages/firebase_messaging/LICENSE b/packages/firebase_messaging/firebase_messaging/LICENSE similarity index 100% rename from packages/firebase_messaging/LICENSE rename to packages/firebase_messaging/firebase_messaging/LICENSE diff --git a/packages/firebase_messaging/firebase_messaging/README.md b/packages/firebase_messaging/firebase_messaging/README.md new file mode 100644 index 000000000000..fb75dc639507 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/README.md @@ -0,0 +1,26 @@ +# Firebase Messaging Plugin for Flutter + +A Flutter plugin to use the [Firebase Cloud Messaging API](https://firebase.google.com/docs/cloud-messaging). + +To learn more about Firebase Cloud Messaging, please visit the [Firebase website](https://firebase.google.com/products/cloud-messaging) + +[![pub package](https://img.shields.io/pub/v/firebase_messaging.svg)](https://pub.dev/packages/firebase_messaging) + +## Getting Started + +To get started with Firebase Cloud Messaging for Flutter, please [see the documentation](https://firebase.flutter.dev/docs/messaging/overview) available + at [https://firebase.flutter.dev](https://firebase.flutter.dev) + +## Usage + +To use this plugin, please visit the [Cloud Messaging Usage documentation](https://firebase.flutter.dev/docs/messaging/usage) + +## Issues and feedback + +Please file FlutterFire specific issues, bugs, or feature requests in our [issue tracker](https://github.com/FirebaseExtended/flutterfire/issues/new). + +Plugin issues that are not specific to FlutterFire can be filed in the [Flutter issue tracker](https://github.com/flutter/flutter/issues/new). + +To contribute a change to this plugin, +please review our [contribution guide](https://github.com/FirebaseExtended/flutterfire/blob/master/CONTRIBUTING.md) +and open a [pull request](https://github.com/FirebaseExtended/flutterfire/pulls). diff --git a/packages/firebase_messaging/firebase_messaging/android/build.gradle b/packages/firebase_messaging/firebase_messaging/android/build.gradle new file mode 100644 index 000000000000..5f95103e062a --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/android/build.gradle @@ -0,0 +1,59 @@ +group 'io.flutter.plugins.firebasemessaging' +version '1.0-SNAPSHOT' + +buildscript { + repositories { + google() + jcenter() + } + + dependencies { + classpath 'com.android.tools.build:gradle:3.5.4' + } +} + +rootProject.allprojects { + repositories { + google() + jcenter() + } +} + +apply plugin: 'com.android.library' + +def firebaseCoreProject = findProject(':firebase_core') +if (firebaseCoreProject == null) { + throw new GradleException('Could not find the firebase_core FlutterFire plugin, have you added it as a dependency in your pubspec?') +} else if (!firebaseCoreProject.properties['FirebaseSDKVersion']) { + throw new GradleException('A newer version of the firebase_core FlutterFire plugin is required, please update your firebase_core pubspec dependency.') +} + +def getRootProjectExtOrCoreProperty(name, firebaseCoreProject) { + if (!rootProject.ext.has('FlutterFire')) return firebaseCoreProject.properties[name] + if (!rootProject.ext.get('FlutterFire')[name]) return firebaseCoreProject.properties[name] + return rootProject.ext.get('FlutterFire').get(name) +} + +android { + compileSdkVersion 29 + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + defaultConfig { + minSdkVersion 16 + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + lintOptions { + disable 'InvalidPackage' + } + dependencies { + api firebaseCoreProject + implementation platform("com.google.firebase:firebase-bom:${getRootProjectExtOrCoreProperty("FirebaseSDKVersion", firebaseCoreProject)}") + implementation 'com.google.firebase:firebase-messaging' + implementation 'androidx.localbroadcastmanager:localbroadcastmanager:1.0.0' + implementation 'androidx.annotation:annotation:1.1.0' + } +} + +apply from: file("./user-agent.gradle") diff --git a/packages/firebase_messaging/android/settings.gradle b/packages/firebase_messaging/firebase_messaging/android/settings.gradle similarity index 100% rename from packages/firebase_messaging/android/settings.gradle rename to packages/firebase_messaging/firebase_messaging/android/settings.gradle diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/AndroidManifest.xml b/packages/firebase_messaging/firebase_messaging/android/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..fa64b98881f7 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/AndroidManifest.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/ContextHolder.java b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/ContextHolder.java new file mode 100644 index 000000000000..fc28ef1d0659 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/ContextHolder.java @@ -0,0 +1,21 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.messaging; + +import android.content.Context; +import android.util.Log; + +public class ContextHolder { + private static Context applicationContext; + + public static Context getApplicationContext() { + return applicationContext; + } + + public static void setApplicationContext(Context applicationContext) { + Log.d("FLTFireContextHolder", "received application context."); + ContextHolder.applicationContext = applicationContext; + } +} diff --git a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseAppRegistrar.java b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseAppRegistrar.java similarity index 85% rename from packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseAppRegistrar.java rename to packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseAppRegistrar.java index e5192b615ba4..2f21b2c6ea84 100644 --- a/packages/firebase_messaging/android/src/main/java/io/flutter/plugins/firebasemessaging/FlutterFirebaseAppRegistrar.java +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseAppRegistrar.java @@ -1,8 +1,8 @@ -// Copyright 2019 The Chromium Authors. All rights reserved. +// Copyright 2020 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -package io.flutter.plugins.firebasemessaging; +package io.flutter.plugins.firebase.messaging; import androidx.annotation.Keep; import com.google.firebase.components.Component; diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingBackgroundExecutor.java b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingBackgroundExecutor.java new file mode 100644 index 000000000000..1aeb040b2de7 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingBackgroundExecutor.java @@ -0,0 +1,297 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.messaging; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.AssetManager; +import android.util.Log; +import androidx.annotation.NonNull; +import com.google.firebase.messaging.RemoteMessage; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.FlutterShellArgs; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.dart.DartExecutor.DartCallback; +import io.flutter.embedding.engine.plugins.shim.ShimPluginRegistry; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.view.FlutterCallbackInformation; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * An background execution abstraction which handles initializing a background isolate running a + * callback dispatcher, used to invoke Dart callbacks while backgrounded. + */ +public class FlutterFirebaseMessagingBackgroundExecutor implements MethodCallHandler { + private static final String TAG = "FLTFireBGExecutor"; + private static final String CALLBACK_HANDLE_KEY = "callback_handle"; + private static final String USER_CALLBACK_HANDLE_KEY = "user_callback_handle"; + + private static io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback + pluginRegistrantCallback; + private final AtomicBoolean isCallbackDispatcherReady = new AtomicBoolean(false); + /** + * The {@link MethodChannel} that connects the Android side of this plugin with the background + * Dart isolate that was created by this plugin. + */ + private MethodChannel backgroundChannel; + + private FlutterEngine backgroundFlutterEngine; + + /** + * Sets the {@code io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback} used to + * register plugins with the newly spawned isolate. + * + *

Note: this is only necessary for applications using the V1 engine embedding API as plugins + * are automatically registered via reflection in the V2 engine embedding API. If not set, + * background message callbacks will not be able to utilize functionality from other plugins. + */ + public static void setPluginRegistrant( + io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback callback) { + pluginRegistrantCallback = callback; + } + + /** + * Sets the Dart callback handle for the Dart method that is responsible for initializing the + * background Dart isolate, preparing it to receive Dart callback tasks requests. + */ + public static void setCallbackDispatcher(long callbackHandle) { + Context context = ContextHolder.getApplicationContext(); + SharedPreferences prefs = + context.getSharedPreferences(FlutterFirebaseMessagingUtils.SHARED_PREFERENCES_KEY, 0); + prefs.edit().putLong(CALLBACK_HANDLE_KEY, callbackHandle).apply(); + } + + /** + * Returns true when the background isolate has started and is ready to handle background + * messages. + */ + public boolean isNotRunning() { + return !isCallbackDispatcherReady.get(); + } + + private void onInitialized() { + isCallbackDispatcherReady.set(true); + FlutterFirebaseMessagingBackgroundService.onInitialized(); + } + + @Override + public void onMethodCall(MethodCall call, @NonNull Result result) { + String method = call.method; + try { + if (method.equals("MessagingBackground#initialized")) { + // This message is sent by the background method channel as soon as the background isolate + // is running. From this point forward, the Android side of this plugin can send + // callback handles through the background method channel, and the Dart side will execute + // the Dart methods corresponding to those callback handles. + onInitialized(); + result.success(true); + } else { + result.notImplemented(); + } + } catch (PluginRegistrantException e) { + result.error("error", "Flutter FCM error: " + e.getMessage(), null); + } + } + + /** + * Starts running a background Dart isolate within a new {@link FlutterEngine} using a previously + * used entrypoint. + * + *

The isolate is configured as follows: + * + *

    + *
  • Bundle Path: {@code io.flutter.view.FlutterMain.findAppBundlePath(context)}. + *
  • Entrypoint: The Dart method used the last time this plugin was initialized in the + * foreground. + *
  • Run args: none. + *
+ * + *

Preconditions: + * + *

    + *
  • The given callback must correspond to a registered Dart callback. If the handle does not + * resolve to a Dart callback then this method does nothing. + *
  • A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link + * PluginRegistrantException} will be thrown. + *
+ */ + public void startBackgroundIsolate() { + if (isNotRunning()) { + long callbackHandle = getPluginCallbackHandle(); + if (callbackHandle != 0) { + startBackgroundIsolate(callbackHandle, null); + } + } + } + + /** + * Starts running a background Dart isolate within a new {@link FlutterEngine}. + * + *

The isolate is configured as follows: + * + *

    + *
  • Bundle Path: {@code io.flutter.view.FlutterMain.findAppBundlePath(context)}. + *
  • Entrypoint: The Dart method represented by {@code callbackHandle}. + *
  • Run args: none. + *
+ * + *

Preconditions: + * + *

    + *
  • The given {@code callbackHandle} must correspond to a registered Dart callback. If the + * handle does not resolve to a Dart callback then this method does nothing. + *
  • A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link + * PluginRegistrantException} will be thrown. + *
+ */ + public void startBackgroundIsolate(long callbackHandle, FlutterShellArgs shellArgs) { + if (backgroundFlutterEngine != null) { + Log.e(TAG, "Background isolate already started."); + return; + } + + Context context = ContextHolder.getApplicationContext(); + String appBundlePath = io.flutter.view.FlutterMain.findAppBundlePath(); + AssetManager assets = context.getAssets(); + if (isNotRunning()) { + if (shellArgs != null) { + Log.i( + TAG, + "Creating background FlutterEngine instance, with args: " + + Arrays.toString(shellArgs.toArray())); + backgroundFlutterEngine = new FlutterEngine(context, shellArgs.toArray()); + } else { + Log.i(TAG, "Creating background FlutterEngine instance."); + backgroundFlutterEngine = new FlutterEngine(context); + } + // We need to create an instance of `FlutterEngine` before looking up the + // callback. If we don't, the callback cache won't be initialized and the + // lookup will fail. + FlutterCallbackInformation flutterCallback = + FlutterCallbackInformation.lookupCallbackInformation(callbackHandle); + DartExecutor executor = backgroundFlutterEngine.getDartExecutor(); + initializeMethodChannel(executor); + DartCallback dartCallback = new DartCallback(assets, appBundlePath, flutterCallback); + + executor.executeDartCallback(dartCallback); + + // The pluginRegistrantCallback should only be set in the V1 embedding as + // plugin registration is done via reflection in the V2 embedding. + if (pluginRegistrantCallback != null) { + pluginRegistrantCallback.registerWith(new ShimPluginRegistry(backgroundFlutterEngine)); + } + } + } + + boolean isDartBackgroundHandlerRegistered() { + return getPluginCallbackHandle() != 0; + } + + /** + * Executes the desired Dart callback in a background Dart isolate. + * + *

The given {@code intent} should contain a {@code long} extra called "callbackHandle", which + * corresponds to a callback registered with the Dart VM. + */ + public void executeDartCallbackInBackgroundIsolate(Intent intent, final CountDownLatch latch) { + if (backgroundFlutterEngine == null) { + Log.i( + TAG, + "A background message could not be handled in Dart as no onBackgroundMessage handler has been registered."); + return; + } + + Result result = null; + if (latch != null) { + result = + new Result() { + @Override + public void success(Object result) { + // If another thread is waiting, then wake that thread when the callback returns a result. + latch.countDown(); + } + + @Override + public void error(String errorCode, String errorMessage, Object errorDetails) { + latch.countDown(); + } + + @Override + public void notImplemented() { + latch.countDown(); + } + }; + } + + // Handle the message event in Dart. + RemoteMessage remoteMessage = + intent.getParcelableExtra(FlutterFirebaseMessagingUtils.EXTRA_REMOTE_MESSAGE); + if (remoteMessage != null) { + Map remoteMessageMap = + FlutterFirebaseMessagingUtils.remoteMessageToMap(remoteMessage); + backgroundChannel.invokeMethod( + "MessagingBackground#onMessage", + new HashMap() { + { + put("userCallbackHandle", getUserCallbackHandle()); + put("message", remoteMessageMap); + } + }, + result); + } else { + Log.e(TAG, "RemoteMessage instance not found in Intent."); + } + } + + /** + * Get the users registered Dart callback handle for background messaging. Returns 0 if not set. + */ + private long getUserCallbackHandle() { + SharedPreferences prefs = + ContextHolder.getApplicationContext() + .getSharedPreferences(FlutterFirebaseMessagingUtils.SHARED_PREFERENCES_KEY, 0); + return prefs.getLong(USER_CALLBACK_HANDLE_KEY, 0); + } + + /** Get the registered Dart callback handle for the messaging plugin. Returns 0 if not set. */ + private long getPluginCallbackHandle() { + SharedPreferences prefs = + ContextHolder.getApplicationContext() + .getSharedPreferences(FlutterFirebaseMessagingUtils.SHARED_PREFERENCES_KEY, 0); + return prefs.getLong(CALLBACK_HANDLE_KEY, 0); + } + + /** + * Sets the Dart callback handle for the users Dart handler that is responsible for handling + * messaging events in the background. + */ + public static void setUserCallbackHandle(long callbackHandle) { + Context context = ContextHolder.getApplicationContext(); + SharedPreferences prefs = + context.getSharedPreferences(FlutterFirebaseMessagingUtils.SHARED_PREFERENCES_KEY, 0); + prefs.edit().putLong(USER_CALLBACK_HANDLE_KEY, callbackHandle).apply(); + } + + private void initializeMethodChannel(BinaryMessenger isolate) { + // backgroundChannel is the channel responsible for receiving the following messages from + // the background isolate that was setup by this plugin method call: + // - "FirebaseBackgroundMessaging#initialized" + // + // This channel is also responsible for sending requests from Android to Dart to execute Dart + // callbacks in the background isolate. + backgroundChannel = + new MethodChannel(isolate, "plugins.flutter.io/firebase_messaging_background"); + backgroundChannel.setMethodCallHandler(this); + } +} diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingBackgroundService.java b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingBackgroundService.java new file mode 100644 index 000000000000..e6c1987c5037 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingBackgroundService.java @@ -0,0 +1,168 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.messaging; + +import android.content.Context; +import android.content.Intent; +import android.os.Handler; +import android.util.Log; +import androidx.annotation.NonNull; +import androidx.core.app.JobIntentService; +import io.flutter.embedding.engine.FlutterShellArgs; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.CountDownLatch; + +public class FlutterFirebaseMessagingBackgroundService extends JobIntentService { + private static final String TAG = "FLTFireMsgService"; + + private static final List messagingQueue = + Collections.synchronizedList(new LinkedList<>()); + + /** Background Dart execution context. */ + private static FlutterFirebaseMessagingBackgroundExecutor flutterBackgroundExecutor; + + /** + * Schedule the message to be handled by the {@link FlutterFirebaseMessagingBackgroundService}. + */ + public static void enqueueMessageProcessing(Context context, Intent messageIntent) { + enqueueWork( + context, + FlutterFirebaseMessagingBackgroundService.class, + FlutterFirebaseMessagingUtils.JOB_ID, + messageIntent); + } + + /** + * Starts the background isolate for the {@link FlutterFirebaseMessagingBackgroundService}. + * + *

Preconditions: + * + *

    + *
  • The given {@code callbackHandle} must correspond to a registered Dart callback. If the + * handle does not resolve to a Dart callback then this method does nothing. + *
  • A static {@link #pluginRegistrantCallback} must exist, otherwise a {@link + * PluginRegistrantException} will be thrown. + *
+ */ + @SuppressWarnings("JavadocReference") + public static void startBackgroundIsolate(long callbackHandle, FlutterShellArgs shellArgs) { + if (flutterBackgroundExecutor != null) { + Log.w(TAG, "Attempted to start a duplicate background isolate. Returning..."); + return; + } + flutterBackgroundExecutor = new FlutterFirebaseMessagingBackgroundExecutor(); + flutterBackgroundExecutor.startBackgroundIsolate(callbackHandle, shellArgs); + } + + /** + * Called once the Dart isolate ({@code flutterBackgroundExecutor}) has finished initializing. + * + *

Invoked by {@link FlutterFirebaseMessagingPlugin} when it receives the {@code + * FirebaseMessaging.initialized} message. Processes all messaging events that came in while the + * isolate was starting. + */ + /* package */ + static void onInitialized() { + Log.i(TAG, "FlutterFirebaseMessagingBackgroundService started!"); + synchronized (messagingQueue) { + // Handle all the message events received before the Dart isolate was + // initialized, then clear the queue. + for (Intent intent : messagingQueue) { + flutterBackgroundExecutor.executeDartCallbackInBackgroundIsolate(intent, null); + } + messagingQueue.clear(); + } + } + + /** + * Sets the Dart callback handle for the Dart method that is responsible for initializing the + * background Dart isolate, preparing it to receive Dart callback tasks requests. + */ + public static void setCallbackDispatcher(long callbackHandle) { + FlutterFirebaseMessagingBackgroundExecutor.setCallbackDispatcher(callbackHandle); + } + + /** + * Sets the Dart callback handle for the users Dart handler that is responsible for handling + * messaging events in the background. + */ + public static void setUserCallbackHandle(long callbackHandle) { + FlutterFirebaseMessagingBackgroundExecutor.setUserCallbackHandle(callbackHandle); + } + + /** + * Sets the {@link io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback} used to + * register the plugins used by an application with the newly spawned background isolate. + * + *

This should be invoked in {@link MainApplication.onCreate} with {@link + * GeneratedPluginRegistrant} in applications using the V1 embedding API in order to use other + * plugins in the background isolate. For applications using the V2 embedding API, it is not + * necessary to set a {@link io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback} as + * plugins are registered automatically. + */ + @SuppressWarnings({"deprecation", "JavadocReference"}) + public static void setPluginRegistrant( + io.flutter.plugin.common.PluginRegistry.PluginRegistrantCallback callback) { + // Indirectly set in FlutterFirebaseMessagingBackgroundExecutor for backwards compatibility. + FlutterFirebaseMessagingBackgroundExecutor.setPluginRegistrant(callback); + } + + @Override + public void onCreate() { + super.onCreate(); + if (flutterBackgroundExecutor == null) { + flutterBackgroundExecutor = new FlutterFirebaseMessagingBackgroundExecutor(); + } + flutterBackgroundExecutor.startBackgroundIsolate(); + } + + /** + * Executes a Dart callback, as specified within the incoming {@code intent}. + * + *

Invoked by our {@link JobIntentService} superclass after a call to {@link + * JobIntentService#enqueueWork(Context, Class, int, Intent);}. + * + *

If there are no pre-existing callback execution requests, other than the incoming {@code + * intent}, then the desired Dart callback is invoked immediately. + * + *

If there are any pre-existing callback requests that have yet to be executed, the incoming + * {@code intent} is added to the {@link #messagingQueue} to be invoked later, after all + * pre-existing callbacks have been executed. + */ + @Override + protected void onHandleWork(@NonNull final Intent intent) { + if (!flutterBackgroundExecutor.isDartBackgroundHandlerRegistered()) { + Log.w( + TAG, + "A background message could not be handled in Dart as no onBackgroundMessage handler has been registered."); + return; + } + + // If we're in the middle of processing queued messages, add the incoming + // intent to the queue and return. + synchronized (messagingQueue) { + if (flutterBackgroundExecutor.isNotRunning()) { + Log.i(TAG, "Service has not yet started, messages will be queued."); + messagingQueue.add(intent); + return; + } + } + + // There were no pre-existing callback requests. Execute the callback + // specified by the incoming intent. + final CountDownLatch latch = new CountDownLatch(1); + new Handler(getMainLooper()) + .post( + () -> flutterBackgroundExecutor.executeDartCallbackInBackgroundIsolate(intent, latch)); + + try { + latch.await(); + } catch (InterruptedException ex) { + Log.i(TAG, "Exception waiting to execute Dart callback", ex); + } + } +} diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingInitProvider.java b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingInitProvider.java new file mode 100644 index 000000000000..622faf71412f --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingInitProvider.java @@ -0,0 +1,67 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.messaging; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.content.Context; +import android.content.pm.ProviderInfo; +import android.database.Cursor; +import android.net.Uri; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +public class FlutterFirebaseMessagingInitProvider extends ContentProvider { + @Override + public void attachInfo(Context context, ProviderInfo info) { + super.attachInfo(context, info); + } + + @Override + public boolean onCreate() { + if (ContextHolder.getApplicationContext() == null) { + Context context = getContext(); + if (context != null && context.getApplicationContext() != null) { + context = context.getApplicationContext(); + } + ContextHolder.setApplicationContext(context); + } + return false; + } + + @Nullable + @Override + public Cursor query( + @NonNull Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { + return null; + } + + @Nullable + @Override + public String getType(@NonNull Uri uri) { + return null; + } + + @Nullable + @Override + public Uri insert(@NonNull Uri uri, ContentValues values) { + return null; + } + + @Override + public int delete(@NonNull Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public int update( + @NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { + return 0; + } +} diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingPlugin.java b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingPlugin.java new file mode 100644 index 000000000000..73eaaa66246b --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingPlugin.java @@ -0,0 +1,407 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.messaging; + +import static io.flutter.plugins.firebase.core.FlutterFirebasePluginRegistry.registerPlugin; + +import android.app.Activity; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import com.google.android.gms.tasks.Task; +import com.google.android.gms.tasks.Tasks; +import com.google.firebase.FirebaseApp; +import com.google.firebase.iid.FirebaseInstanceId; +import com.google.firebase.iid.Metadata; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.RemoteMessage; +import io.flutter.embedding.engine.FlutterShellArgs; +import io.flutter.embedding.engine.plugins.FlutterPlugin; +import io.flutter.embedding.engine.plugins.activity.ActivityAware; +import io.flutter.embedding.engine.plugins.activity.ActivityPluginBinding; +import io.flutter.plugin.common.BinaryMessenger; +import io.flutter.plugin.common.MethodCall; +import io.flutter.plugin.common.MethodChannel; +import io.flutter.plugin.common.MethodChannel.MethodCallHandler; +import io.flutter.plugin.common.MethodChannel.Result; +import io.flutter.plugin.common.PluginRegistry.NewIntentListener; +import io.flutter.plugin.common.PluginRegistry.Registrar; +import io.flutter.plugins.firebase.core.FlutterFirebasePlugin; +import java.util.HashMap; +import java.util.Map; +import java.util.Objects; + +/** FlutterFirebaseMessagingPlugin */ +public class FlutterFirebaseMessagingPlugin extends BroadcastReceiver + implements FlutterFirebasePlugin, + MethodCallHandler, + NewIntentListener, + FlutterPlugin, + ActivityAware { + + private final HashMap consumedInitialMessages = new HashMap<>(); + private MethodChannel channel; + private Activity mainActivity; + private RemoteMessage initialMessage; + + @SuppressWarnings("unused") + public static void registerWith(Registrar registrar) { + FlutterFirebaseMessagingPlugin instance = new FlutterFirebaseMessagingPlugin(); + instance.setActivity(registrar.activity()); + registrar.addNewIntentListener(instance); + instance.initInstance(registrar.messenger()); + } + + private void initInstance(BinaryMessenger messenger) { + String channelName = "plugins.flutter.io/firebase_messaging"; + channel = new MethodChannel(messenger, channelName); + channel.setMethodCallHandler(this); + + // Register broadcast receiver + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(FlutterFirebaseMessagingUtils.ACTION_TOKEN); + intentFilter.addAction(FlutterFirebaseMessagingUtils.ACTION_REMOTE_MESSAGE); + LocalBroadcastManager manager = + LocalBroadcastManager.getInstance(ContextHolder.getApplicationContext()); + manager.registerReceiver(this, intentFilter); + + registerPlugin(channelName, this); + } + + private void onAttachedToEngine(Context context, BinaryMessenger binaryMessenger) { + initInstance(binaryMessenger); + } + + private void setActivity(Activity flutterActivity) { + this.mainActivity = flutterActivity; + } + + @Override + public void onAttachedToEngine(FlutterPluginBinding binding) { + onAttachedToEngine(binding.getApplicationContext(), binding.getBinaryMessenger()); + } + + @Override + public void onDetachedFromEngine(@NonNull FlutterPluginBinding binding) { + LocalBroadcastManager.getInstance(ContextHolder.getApplicationContext()) + .unregisterReceiver(this); + } + + @Override + public void onAttachedToActivity(ActivityPluginBinding binding) { + binding.addOnNewIntentListener(this); + this.mainActivity = binding.getActivity(); + if (mainActivity.getIntent() != null && mainActivity.getIntent().getExtras() != null) { + if ((mainActivity.getIntent().getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) + != Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) { + onNewIntent(mainActivity.getIntent()); + } + } + } + + @Override + public void onDetachedFromActivityForConfigChanges() { + this.mainActivity = null; + } + + @Override + public void onReattachedToActivityForConfigChanges(ActivityPluginBinding binding) { + binding.addOnNewIntentListener(this); + this.mainActivity = binding.getActivity(); + } + + @Override + public void onDetachedFromActivity() { + this.mainActivity = null; + } + + // BroadcastReceiver implementation. + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + + if (action == null) { + return; + } + + if (action.equals(FlutterFirebaseMessagingUtils.ACTION_TOKEN)) { + String token = intent.getStringExtra(FlutterFirebaseMessagingUtils.EXTRA_TOKEN); + channel.invokeMethod("Messaging#onTokenRefresh", token); + } else if (action.equals(FlutterFirebaseMessagingUtils.ACTION_REMOTE_MESSAGE)) { + RemoteMessage message = + intent.getParcelableExtra(FlutterFirebaseMessagingUtils.EXTRA_REMOTE_MESSAGE); + if (message == null) return; + Map content = FlutterFirebaseMessagingUtils.remoteMessageToMap(message); + channel.invokeMethod("Messaging#onMessage", content); + } + } + + private Task deleteToken(Map arguments) { + return Tasks.call( + cachedThreadPool, + () -> { + String senderId = + arguments.get("senderId") != null + ? (String) arguments.get("senderId") + : Metadata.getDefaultSenderId(FirebaseApp.getInstance()); + FirebaseInstanceId.getInstance().deleteToken(senderId, "*"); + return null; + }); + } + + private Task getToken(Map arguments) { + return Tasks.call( + cachedThreadPool, + () -> { + String senderId = + arguments.get("senderId") != null + ? (String) arguments.get("senderId") + : Metadata.getDefaultSenderId(FirebaseApp.getInstance()); + return FirebaseInstanceId.getInstance().getToken(senderId, "*"); + }); + } + + private Task subscribeToTopic(Map arguments) { + return Tasks.call( + cachedThreadPool, + () -> { + FirebaseMessaging firebaseMessaging = + FlutterFirebaseMessagingUtils.getFirebaseMessagingForArguments(arguments); + String topic = (String) Objects.requireNonNull(arguments.get("topic")); + Tasks.await(firebaseMessaging.subscribeToTopic(topic)); + return null; + }); + } + + private Task unsubscribeFromTopic(Map arguments) { + return Tasks.call( + cachedThreadPool, + () -> { + FirebaseMessaging firebaseMessaging = + FlutterFirebaseMessagingUtils.getFirebaseMessagingForArguments(arguments); + String topic = (String) Objects.requireNonNull(arguments.get("topic")); + Tasks.await(firebaseMessaging.unsubscribeFromTopic(topic)); + return null; + }); + } + + private Task sendMessage(Map arguments) { + return Tasks.call( + cachedThreadPool, + () -> { + FirebaseMessaging firebaseMessaging = + FlutterFirebaseMessagingUtils.getFirebaseMessagingForArguments(arguments); + RemoteMessage remoteMessage = + FlutterFirebaseMessagingUtils.getRemoteMessageForArguments(arguments); + firebaseMessaging.send(remoteMessage); + return null; + }); + } + + private Task> setAutoInitEnabled(Map arguments) { + return Tasks.call( + cachedThreadPool, + () -> { + FirebaseMessaging firebaseMessaging = + FlutterFirebaseMessagingUtils.getFirebaseMessagingForArguments(arguments); + Boolean enabled = (Boolean) Objects.requireNonNull(arguments.get("enabled")); + firebaseMessaging.setAutoInitEnabled(enabled); + return new HashMap() { + { + put( + FlutterFirebaseMessagingUtils.IS_AUTO_INIT_ENABLED, + firebaseMessaging.isAutoInitEnabled()); + } + }; + }); + } + + private Task> getInitialMessage(Map arguments) { + return Tasks.call( + cachedThreadPool, + () -> { + if (initialMessage != null) { + Map remoteMessageMap = + FlutterFirebaseMessagingUtils.remoteMessageToMap(initialMessage); + initialMessage = null; + return remoteMessageMap; + } + + if (mainActivity == null) { + return null; + } + + Intent intent = mainActivity.getIntent(); + + if (intent == null || intent.getExtras() == null) { + return null; + } + + // Remote Message ID can be either one of the following... + String messageId = intent.getExtras().getString("google.message_id"); + if (messageId == null) messageId = intent.getExtras().getString("message_id"); + + // We only want to handle non-consumed initial messages. + if (messageId == null || consumedInitialMessages.get(messageId) != null) { + return null; + } + + RemoteMessage remoteMessage = + FlutterFirebaseMessagingReceiver.notifications.get(messageId); + + // If we can't find a copy of the remote message in memory then check from our persisted store. + if (remoteMessage == null) { + remoteMessage = + FlutterFirebaseMessagingStore.getInstance().getFirebaseMessage(messageId); + FlutterFirebaseMessagingStore.getInstance().removeFirebaseMessage(messageId); + } + + if (remoteMessage == null) { + return null; + } + + consumedInitialMessages.put(messageId, true); + return FlutterFirebaseMessagingUtils.remoteMessageToMap(remoteMessage); + }); + } + + @Override + public void onMethodCall(final MethodCall call, @NonNull final Result result) { + Task methodCallTask; + + switch (call.method) { + // This message is sent when the Dart side of this plugin is told to initialize. + // In response, this (native) side of the plugin needs to spin up a background + // Dart isolate by using the given pluginCallbackHandle, and then setup a background + // method channel to communicate with the new background isolate. Once completed, + // this onMethodCall() method will receive messages from both the primary and background + // method channels. + case "Messaging#startBackgroundIsolate": + @SuppressWarnings("unchecked") + long pluginCallbackHandle = + (long) ((Map) call.arguments).get("pluginCallbackHandle"); + @SuppressWarnings("unchecked") + long userCallbackHandle = + (long) ((Map) call.arguments).get("userCallbackHandle"); + + FlutterShellArgs shellArgs = null; + if (mainActivity != null) { + shellArgs = + ((io.flutter.embedding.android.FlutterActivity) mainActivity).getFlutterShellArgs(); + } + + FlutterFirebaseMessagingBackgroundService.setCallbackDispatcher(pluginCallbackHandle); + FlutterFirebaseMessagingBackgroundService.setUserCallbackHandle(userCallbackHandle); + FlutterFirebaseMessagingBackgroundService.startBackgroundIsolate( + pluginCallbackHandle, shellArgs); + case "Messaging#getInitialMessage": + methodCallTask = getInitialMessage(call.arguments()); + break; + case "Messaging#deleteToken": + methodCallTask = deleteToken(call.arguments()); + break; + case "Messaging#getToken": + methodCallTask = getToken(call.arguments()); + break; + case "Messaging#subscribeToTopic": + methodCallTask = subscribeToTopic(call.arguments()); + break; + case "Messaging#unsubscribeFromTopic": + methodCallTask = unsubscribeFromTopic(call.arguments()); + break; + case "Messaging#sendMessage": + methodCallTask = sendMessage(call.arguments()); + break; + case "Messaging#setAutoInitEnabled": + methodCallTask = setAutoInitEnabled(call.arguments()); + break; + default: + result.notImplemented(); + return; + } + + methodCallTask.addOnCompleteListener( + task -> { + if (task.isSuccessful()) { + result.success(task.getResult()); + } else { + Exception exception = task.getException(); + result.error( + "firebase_messaging", + exception != null ? exception.getMessage() : null, + getExceptionDetails(exception)); + } + }); + } + + private Map getExceptionDetails(@Nullable Exception exception) { + Map details = new HashMap<>(); + details.put("code", "unknown"); + if (exception != null) { + details.put("message", exception.getMessage()); + } else { + details.put("message", "An unknown error has occurred."); + } + details.put("additionalData", new HashMap<>()); + return details; + } + + @Override + public boolean onNewIntent(Intent intent) { + if (intent == null || intent.getExtras() == null) { + return false; + } + + // Remote Message ID can be either one of the following... + String messageId = intent.getExtras().getString("google.message_id"); + if (messageId == null) messageId = intent.getExtras().getString("message_id"); + if (messageId == null) { + return false; + } + + RemoteMessage remoteMessage = FlutterFirebaseMessagingReceiver.notifications.get(messageId); + + // If we can't find a copy of the remote message in memory then check from our persisted store. + if (remoteMessage == null) { + remoteMessage = FlutterFirebaseMessagingStore.getInstance().getFirebaseMessage(messageId); + // Note we don't remove it here as the user may still call getInitialMessage. + } + + if (remoteMessage == null) { + return false; + } + + // Store this message for later use by getInitialMessage. + initialMessage = remoteMessage; + + FlutterFirebaseMessagingReceiver.notifications.remove(messageId); + channel.invokeMethod( + "Messaging#onMessageOpenedApp", + FlutterFirebaseMessagingUtils.remoteMessageToMap(remoteMessage)); + mainActivity.setIntent(intent); + return true; + } + + @Override + public Task> getPluginConstantsForFirebaseApp(FirebaseApp firebaseApp) { + return Tasks.call( + cachedThreadPool, + () -> { + Map constants = new HashMap<>(); + FirebaseMessaging firebaseMessaging = FirebaseMessaging.getInstance(); + constants.put("AUTO_INIT_ENABLED", firebaseMessaging.isAutoInitEnabled()); + return constants; + }); + } + + @Override + public Task didReinitializeFirebaseCore() { + return Tasks.call(cachedThreadPool, () -> null); + } +} diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingReceiver.java b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingReceiver.java new file mode 100644 index 000000000000..53333a45fc1d --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingReceiver.java @@ -0,0 +1,54 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.messaging; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.util.Log; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import com.google.firebase.messaging.RemoteMessage; +import java.util.HashMap; + +public class FlutterFirebaseMessagingReceiver extends BroadcastReceiver { + private static final String TAG = "FLTFireMsgReceiver"; + static HashMap notifications = new HashMap<>(); + + @Override + public void onReceive(Context context, Intent intent) { + Log.d(TAG, "broadcast received for message"); + if (ContextHolder.getApplicationContext() == null) { + ContextHolder.setApplicationContext(context.getApplicationContext()); + } + + RemoteMessage remoteMessage = new RemoteMessage(intent.getExtras()); + + // Store the RemoteMessage if the message contains a notification payload. + if (remoteMessage.getNotification() != null) { + notifications.put(remoteMessage.getMessageId(), remoteMessage); + FlutterFirebaseMessagingStore.getInstance().storeFirebaseMessage(remoteMessage); + } + + // |-> --------------------- + // App in Foreground + // ------------------------ + if (FlutterFirebaseMessagingUtils.isApplicationForeground(context)) { + Intent onMessageIntent = new Intent(FlutterFirebaseMessagingUtils.ACTION_REMOTE_MESSAGE); + onMessageIntent.putExtra(FlutterFirebaseMessagingUtils.EXTRA_REMOTE_MESSAGE, remoteMessage); + LocalBroadcastManager.getInstance(context).sendBroadcast(onMessageIntent); + return; + } + + // |-> --------------------- + // App in Background/Quit + // ------------------------ + Intent onBackgroundMessageIntent = + new Intent(context, FlutterFirebaseMessagingBackgroundService.class); + onBackgroundMessageIntent.putExtra( + FlutterFirebaseMessagingUtils.EXTRA_REMOTE_MESSAGE, remoteMessage); + FlutterFirebaseMessagingBackgroundService.enqueueMessageProcessing( + context, onBackgroundMessageIntent); + } +} diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingService.java b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingService.java new file mode 100644 index 000000000000..8d7f6c8c7549 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingService.java @@ -0,0 +1,26 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.messaging; + +import android.content.Intent; +import androidx.annotation.NonNull; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; +import com.google.firebase.messaging.FirebaseMessagingService; +import com.google.firebase.messaging.RemoteMessage; + +public class FlutterFirebaseMessagingService extends FirebaseMessagingService { + @Override + public void onNewToken(@NonNull String token) { + Intent onMessageIntent = new Intent(FlutterFirebaseMessagingUtils.ACTION_TOKEN); + onMessageIntent.putExtra(FlutterFirebaseMessagingUtils.EXTRA_TOKEN, token); + LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(onMessageIntent); + } + + @Override + public void onMessageReceived(@NonNull RemoteMessage remoteMessage) { + // Added for commenting purposes; + // We don't handle the message here as we already handle it in the receiver and don't want to duplicate. + } +} diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingStore.java b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingStore.java new file mode 100644 index 000000000000..6ab2ef247599 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingStore.java @@ -0,0 +1,130 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.messaging; + +import android.content.Context; +import android.content.SharedPreferences; +import com.google.firebase.messaging.RemoteMessage; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +public class FlutterFirebaseMessagingStore { + private static final String PREFERENCES_FILE = "io.flutter.plugins.firebase.messaging"; + private static final String KEY_NOTIFICATION_IDS = "notification_ids"; + private static final int MAX_SIZE_NOTIFICATIONS = 20; + private static FlutterFirebaseMessagingStore instance; + private final String DELIMITER = ","; + private SharedPreferences preferences; + + public static FlutterFirebaseMessagingStore getInstance() { + if (instance == null) { + instance = new FlutterFirebaseMessagingStore(); + } + return instance; + } + + private SharedPreferences getPreferences() { + if (preferences == null) { + preferences = + ContextHolder.getApplicationContext() + .getSharedPreferences(PREFERENCES_FILE, Context.MODE_PRIVATE); + } + return preferences; + } + + public void setPreferencesStringValue(String key, String value) { + getPreferences().edit().putString(key, value).apply(); + } + + public String getPreferencesStringValue(String key, String defaultValue) { + return getPreferences().getString(key, defaultValue); + } + + public void storeFirebaseMessage(RemoteMessage remoteMessage) { + String remoteMessageString = + new JSONObject(FlutterFirebaseMessagingUtils.remoteMessageToMap(remoteMessage)).toString(); + setPreferencesStringValue(remoteMessage.getMessageId(), remoteMessageString); + + // Save new notification id. + // Note that this is using a comma delimited string to preserve ordering. We could use a String Set + // on SharedPreferences but this won't guarantee ordering when we want to remove the oldest added ids. + String notifications = getPreferencesStringValue(KEY_NOTIFICATION_IDS, ""); + notifications += remoteMessage.getMessageId() + DELIMITER; // append to last + + // Check and remove old notification messages. + List allNotificationList = + new ArrayList<>(Arrays.asList(notifications.split(DELIMITER))); + if (allNotificationList.size() > MAX_SIZE_NOTIFICATIONS) { + String firstRemoteMessageId = allNotificationList.get(0); + getPreferences().edit().remove(firstRemoteMessageId).apply(); + notifications = notifications.replace(firstRemoteMessageId + DELIMITER, ""); + } + + setPreferencesStringValue(KEY_NOTIFICATION_IDS, notifications); + } + + public RemoteMessage getFirebaseMessage(String remoteMessageId) { + String remoteMessageString = getPreferencesStringValue(remoteMessageId, null); + if (remoteMessageString != null) { + try { + Map argumentsMap = new HashMap<>(1); + Map messageOutMap = jsonObjectToMap(new JSONObject(remoteMessageString)); + // Add a fake 'to' - as it's required to construct a RemoteMessage instance. + messageOutMap.put("to", remoteMessageId); + argumentsMap.put("message", messageOutMap); + return FlutterFirebaseMessagingUtils.getRemoteMessageForArguments(argumentsMap); + } catch (JSONException e) { + e.printStackTrace(); + } + } + return null; + } + + public void removeFirebaseMessage(String remoteMessageId) { + getPreferences().edit().remove(remoteMessageId).apply(); + String notifications = getPreferencesStringValue(KEY_NOTIFICATION_IDS, ""); + if (!notifications.isEmpty()) { + notifications = notifications.replace(remoteMessageId + DELIMITER, ""); + setPreferencesStringValue(KEY_NOTIFICATION_IDS, notifications); + } + } + + private Map jsonObjectToMap(JSONObject jsonObject) throws JSONException { + Map map = new HashMap<>(); + Iterator keys = jsonObject.keys(); + while (keys.hasNext()) { + String key = keys.next(); + Object value = jsonObject.get(key); + if (value instanceof JSONArray) { + value = jsonArrayToList((JSONArray) value); + } else if (value instanceof JSONObject) { + value = jsonObjectToMap((JSONObject) value); + } + map.put(key, value); + } + return map; + } + + public List jsonArrayToList(JSONArray array) throws JSONException { + List list = new ArrayList<>(); + for (int i = 0; i < array.length(); i++) { + Object value = array.get(i); + if (value instanceof JSONArray) { + value = jsonArrayToList((JSONArray) value); + } else if (value instanceof JSONObject) { + value = jsonObjectToMap((JSONObject) value); + } + list.add(value); + } + return list; + } +} diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingUtils.java b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingUtils.java new file mode 100644 index 000000000000..eb385b3234d9 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/FlutterFirebaseMessagingUtils.java @@ -0,0 +1,241 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.messaging; + +import android.app.ActivityManager; +import android.app.KeyguardManager; +import android.content.Context; +import com.google.firebase.messaging.FirebaseMessaging; +import com.google.firebase.messaging.RemoteMessage; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; + +class FlutterFirebaseMessagingUtils { + static final String IS_AUTO_INIT_ENABLED = "isAutoInitEnabled"; + static final String SHARED_PREFERENCES_KEY = "io.flutter.firebase.messaging.callback"; + static final String ACTION_REMOTE_MESSAGE = "io.flutter.plugins.firebase.messaging.NOTIFICATION"; + static final String EXTRA_REMOTE_MESSAGE = "notification"; + static final String ACTION_TOKEN = "io.flutter.plugins.firebase.messaging.TOKEN"; + static final String EXTRA_TOKEN = "token"; + static final int JOB_ID = 2020; + private static final String KEY_COLLAPSE_KEY = "collapseKey"; + private static final String KEY_DATA = "data"; + private static final String KEY_FROM = "from"; + private static final String KEY_MESSAGE_ID = "messageId"; + private static final String KEY_MESSAGE_TYPE = "messageType"; + private static final String KEY_SENT_TIME = "sentTime"; + private static final String KEY_TO = "to"; + private static final String KEY_TTL = "ttl"; + + static Map remoteMessageToMap(RemoteMessage remoteMessage) { + Map messageMap = new HashMap<>(); + Map dataMap = new HashMap<>(); + + if (remoteMessage.getCollapseKey() != null) { + messageMap.put(KEY_COLLAPSE_KEY, remoteMessage.getCollapseKey()); + } + + if (remoteMessage.getFrom() != null) { + messageMap.put(KEY_FROM, remoteMessage.getFrom()); + } + + if (remoteMessage.getTo() != null) { + messageMap.put(KEY_TO, remoteMessage.getTo()); + } + + if (remoteMessage.getMessageId() != null) { + messageMap.put(KEY_MESSAGE_ID, remoteMessage.getMessageId()); + } + + if (remoteMessage.getMessageType() != null) { + messageMap.put(KEY_MESSAGE_TYPE, remoteMessage.getMessageType()); + } + + if (remoteMessage.getData().size() > 0) { + Set> entries = remoteMessage.getData().entrySet(); + for (Map.Entry entry : entries) { + dataMap.put(entry.getKey(), entry.getValue()); + } + } + + messageMap.put(KEY_DATA, dataMap); + messageMap.put(KEY_TTL, remoteMessage.getTtl()); + messageMap.put(KEY_SENT_TIME, remoteMessage.getSentTime()); + + if (remoteMessage.getNotification() != null) { + messageMap.put( + "notification", remoteMessageNotificationToMap(remoteMessage.getNotification())); + } + + return messageMap; + } + + private static Map remoteMessageNotificationToMap( + RemoteMessage.Notification notification) { + Map notificationMap = new HashMap<>(); + Map androidNotificationMap = new HashMap<>(); + + if (notification.getTitle() != null) { + notificationMap.put("title", notification.getTitle()); + } + + if (notification.getTitleLocalizationKey() != null) { + notificationMap.put("titleLocKey", notification.getTitleLocalizationKey()); + } + + if (notification.getTitleLocalizationArgs() != null) { + notificationMap.put("titleLocArgs", notification.getTitleLocalizationArgs()); + } + + if (notification.getBody() != null) { + notificationMap.put("body", notification.getBody()); + } + + if (notification.getBodyLocalizationKey() != null) { + notificationMap.put("bodyLocKey", notification.getBodyLocalizationKey()); + } + + if (notification.getBodyLocalizationArgs() != null) { + notificationMap.put("bodyLocArgs", notification.getBodyLocalizationArgs()); + } + + if (notification.getChannelId() != null) { + androidNotificationMap.put("channelId", notification.getChannelId()); + } + + if (notification.getClickAction() != null) { + androidNotificationMap.put("clickAction", notification.getClickAction()); + } + + if (notification.getColor() != null) { + androidNotificationMap.put("color", notification.getColor()); + } + + if (notification.getIcon() != null) { + androidNotificationMap.put("smallIcon", notification.getIcon()); + } + + if (notification.getImageUrl() != null) { + androidNotificationMap.put("imageUrl", notification.getImageUrl().toString()); + } + + if (notification.getLink() != null) { + androidNotificationMap.put("link", notification.getLink().toString()); + } + + if (notification.getNotificationCount() != null) { + androidNotificationMap.put("count", notification.getNotificationCount()); + } + + if (notification.getNotificationPriority() != null) { + androidNotificationMap.put("priority", notification.getNotificationPriority()); + } + + if (notification.getSound() != null) { + androidNotificationMap.put("sound", notification.getSound()); + } + + if (notification.getTicker() != null) { + androidNotificationMap.put("ticker", notification.getTicker()); + } + + if (notification.getVisibility() != null) { + androidNotificationMap.put("visibility", notification.getVisibility()); + } + + notificationMap.put("android", androidNotificationMap); + return notificationMap; + } + + /** + * Identify if the application is currently in a state where user interaction is possible. This + * method is called when a remote message is received to determine how the incoming message should + * be handled. + * + * @param context context. + * @return True if the application is currently in a state where user interaction is possible, + * false otherwise. + */ + static boolean isApplicationForeground(Context context) { + KeyguardManager keyguardManager = + (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); + + if (keyguardManager != null && keyguardManager.isKeyguardLocked()) { + return false; + } + + ActivityManager activityManager = + (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); + if (activityManager == null) return false; + + List appProcesses = + activityManager.getRunningAppProcesses(); + if (appProcesses == null) return false; + + final String packageName = context.getPackageName(); + for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) { + if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND + && appProcess.processName.equals(packageName)) { + return true; + } + } + + return false; + } + + // Extracted to handle multi-app support in the future. + // arguments.get("appName") - to get the Firebase app name. + static FirebaseMessaging getFirebaseMessagingForArguments(Map arguments) { + return FirebaseMessaging.getInstance(); + } + + /** + * Builds an instance of {@link RemoteMessage} from Flutter method channel call arguments. + * + * @param arguments Method channel call arguments. + * @return RemoteMessage + */ + static RemoteMessage getRemoteMessageForArguments(Map arguments) { + @SuppressWarnings("unchecked") + Map messageMap = + (Map) Objects.requireNonNull(arguments.get("message")); + + String to = (String) Objects.requireNonNull(messageMap.get("to")); + RemoteMessage.Builder builder = new RemoteMessage.Builder(to); + + String collapseKey = (String) messageMap.get("collapseKey"); + String messageId = (String) messageMap.get("messageId"); + String messageType = (String) messageMap.get("messageType"); + Integer ttl = (Integer) messageMap.get("ttl"); + + @SuppressWarnings("unchecked") + Map data = (Map) messageMap.get("data"); + + if (collapseKey != null) { + builder.setCollapseKey(collapseKey); + } + + if (messageType != null) { + builder.setMessageType(messageType); + } + + if (messageId != null) { + builder.setMessageId(messageId); + } + + if (ttl != null) { + builder.setTtl(ttl); + } + + if (data != null) { + builder.setData(data); + } + + return builder.build(); + } +} diff --git a/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/PluginRegistrantException.java b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/PluginRegistrantException.java new file mode 100644 index 000000000000..4787b643784c --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/android/src/main/java/io/flutter/plugins/firebase/messaging/PluginRegistrantException.java @@ -0,0 +1,14 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.plugins.firebase.messaging; + +// Only applicable to v1 embedding applications. +class PluginRegistrantException extends RuntimeException { + public PluginRegistrantException() { + super( + "PluginRegistrantCallback is not set. Did you forget to call " + + "FlutterFirebaseMessagingBackgroundService.setPluginRegistrant? See the documentation for instructions."); + } +} diff --git a/packages/firebase_messaging/android/user-agent.gradle b/packages/firebase_messaging/firebase_messaging/android/user-agent.gradle similarity index 100% rename from packages/firebase_messaging/android/user-agent.gradle rename to packages/firebase_messaging/firebase_messaging/android/user-agent.gradle diff --git a/packages/firebase_messaging/firebase_messaging/example/.gitignore b/packages/firebase_messaging/firebase_messaging/example/.gitignore new file mode 100644 index 000000000000..9d532b18a01f --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/.gitignore @@ -0,0 +1,41 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +**/ios/Flutter/.last_build_id +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +/build/ + +# Web related +lib/generated_plugin_registrant.dart + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/packages/firebase_messaging/firebase_messaging/example/.metadata b/packages/firebase_messaging/firebase_messaging/example/.metadata new file mode 100644 index 000000000000..ee5762c1c79f --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: d408d302e22179d598f467e11da5dd968dbdc9ec + channel: stable + +project_type: app diff --git a/packages/firebase_messaging/example/README.md b/packages/firebase_messaging/firebase_messaging/example/README.md similarity index 100% rename from packages/firebase_messaging/example/README.md rename to packages/firebase_messaging/firebase_messaging/example/README.md diff --git a/packages/firebase_messaging/firebase_messaging/example/android/.gitignore b/packages/firebase_messaging/firebase_messaging/example/android/.gitignore new file mode 100644 index 000000000000..0a741cb43d66 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/android/.gitignore @@ -0,0 +1,11 @@ +gradle-wrapper.jar +/.gradle +/captures/ +/gradlew +/gradlew.bat +/local.properties +GeneratedPluginRegistrant.java + +# Remember to never publicly share your keystore. +# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +key.properties diff --git a/packages/firebase_messaging/example/android/app/build.gradle b/packages/firebase_messaging/firebase_messaging/example/android/app/build.gradle similarity index 77% rename from packages/firebase_messaging/example/android/app/build.gradle rename to packages/firebase_messaging/firebase_messaging/example/android/app/build.gradle index 366883500124..eafb3fda7e96 100644 --- a/packages/firebase_messaging/example/android/app/build.gradle +++ b/packages/firebase_messaging/firebase_messaging/example/android/app/build.gradle @@ -22,8 +22,16 @@ if (flutterVersionName == null) { } apply plugin: 'com.android.application' +apply plugin: 'com.google.gms.google-services' apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" +rootProject.ext { + set('FlutterFire', [ + FirebaseSDKVersion: '25.12.0' + ]) +} + + android { compileSdkVersion 29 @@ -32,12 +40,12 @@ android { } defaultConfig { - applicationId 'io.flutter.plugins.firebasemessagingexample' + // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). + applicationId "io.flutter.plugins.firebase.messaging.example" minSdkVersion 16 - targetSdkVersion 28 + targetSdkVersion 29 versionCode flutterVersionCode.toInteger() versionName flutterVersionName - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } buildTypes { @@ -52,12 +60,3 @@ android { flutter { source '../..' } - -dependencies { - testImplementation 'junit:junit:4.12' - androidTestImplementation 'androidx.test:runner:1.2.0' - androidTestImplementation 'androidx.test:rules:1.2.0' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' -} - -apply plugin: 'com.google.gms.google-services' diff --git a/packages/firebase_messaging/firebase_messaging/example/android/app/google-services.json b/packages/firebase_messaging/firebase_messaging/example/android/app/google-services.json new file mode 100644 index 000000000000..fe43a56b87a8 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/android/app/google-services.json @@ -0,0 +1,931 @@ +{ + "project_info": { + "project_number": "448618578101", + "firebase_url": "https://react-native-firebase-testing.firebaseio.com", + "project_id": "react-native-firebase-testing", + "storage_bucket": "react-native-firebase-testing.appspot.com" + }, + "client": [ + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:cadfc9e23089d00eac3efc", + "android_client_info": { + "package_name": "com.dackers.demo" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:f97c4f71d274e363ac3efc", + "android_client_info": { + "package_name": "com.example.crash_test_2" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:e708991417cc7542ac3efc", + "android_client_info": { + "package_name": "com.example.testapp" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:78350c93b1c60b39ac3efc", + "android_client_info": { + "package_name": "com.example.weather_application" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-r9nu34bp51ji8dve73mr8toori9nv3fp.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.example.weather_application", + "certificate_hash": "909ca1482ef022bbae45a2db6b6d05d807a4c4aa" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:cc6c1dc7a65cc83c", + "android_client_info": { + "package_name": "com.invertase.testing" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-h0o9b94jnhcoal2qgjn7s7ckkc2n7okq.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.invertase.testing", + "certificate_hash": "909ca1482ef022bbae45a2db6b6d05d807a4c4aa" + } + }, + { + "client_id": "448618578101-gva3jv7cr8qquj04k0o7cni674j65kha.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.invertase.testing", + "certificate_hash": "5e8f16062ea3cd2c4a0d547876baa6f38cabf625" + } + }, + { + "client_id": "448618578101-a9p7bj5jlakabp22fo3cbkj7nsmag24e.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.invertase.testing", + "certificate_hash": "889b4292c735f371168a372cc7778992cd8a5052" + } + }, + { + "client_id": "448618578101-pdjje2lkv3p941e03hkrhfa7459cr2v8.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.invertase.testing", + "certificate_hash": "992e468b990cc418f306d0131be61ecfad800ac1" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:cc5ce91648e65dbeac3efc", + "android_client_info": { + "package_name": "com.notifeetestapp" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-j9nluebtat700ua550esfvaf64gbo5l5.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.notifeetestapp", + "certificate_hash": "5e8f16062ea3cd2c4a0d547876baa6f38cabf625" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:6aa085e64d694703ac3efc", + "android_client_info": { + "package_name": "com.rnfbdemo" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-a8tk83htomfvrelhu71j93safubvipdj.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.rnfbdemo", + "certificate_hash": "909ca1482ef022bbae45a2db6b6d05d807a4c4aa" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:c017beb5f26b66fcac3efc", + "android_client_info": { + "package_name": "com.testapp" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-q1ffje1ttluf8vfhi4aa50pag231q9dt.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "com.testapp", + "certificate_hash": "909ca1482ef022bbae45a2db6b6d05d807a4c4aa" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:ad3f78043b0f1078ac3efc", + "android_client_info": { + "package_name": "crashlytics.v4" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:770b4d1c54259e4cac3efc", + "android_client_info": { + "package_name": "crashlytics.v5" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:349f77433954456eac3efc", + "android_client_info": { + "package_name": "crashlytics.v6" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:275b7c40f21f5a6dac3efc", + "android_client_info": { + "package_name": "crashlytics.v7" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:6bf0706c1f27114dac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebase.cloudfunctions.cloudfunctionsexample" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:5cc04a661d27c405ac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebase.crashlytics.firebasecrashlyticsexample" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:3ad281c0067ccf97ac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebase.firestoreexample" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-npu5ll2fj4emgvtietpb685fvdm7fg3s.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.flutter.plugins.firebase.firestoreexample", + "certificate_hash": "909ca1482ef022bbae45a2db6b6d05d807a4c4aa" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:0568e76dc62e89bcac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebase.messaging.example" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:9d44a7b85d1ab0baac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebaseauthexample" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-qd7qb4i251kmq2ju79bl7sif96si0ve3.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.flutter.plugins.firebaseauthexample", + "certificate_hash": "909ca1482ef022bbae45a2db6b6d05d807a4c4aa" + } + }, + { + "client_id": "448618578101-velutq65ok2dr5ohh0oi1q62irr920ss.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.flutter.plugins.firebaseauthexample", + "certificate_hash": "29142b8612b4b6a0ba0fefd1dbf65ab565fb2cbd" + } + }, + { + "client_id": "448618578101-2brn4509s8513kjf9a0i6kvtb9qnght5.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.flutter.plugins.firebaseauthexample", + "certificate_hash": "e1604565b51994d10886de1d91da9968dfec02ed" + } + }, + { + "client_id": "448618578101-2kqet2itdu42g0avs2v3fbsmtf6a45gg.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.flutter.plugins.firebaseauthexample", + "certificate_hash": "939efbe8eaa5aaf50396b19fe980ef4a8df1c6e7" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:29bf96f913c195f5ac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebasecrashlyticsexample" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:d767a536227718bcac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebasemessagingexample" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-rthm8sh42ifgn9vp60914nol7u8h7mgq.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.flutter.plugins.firebasemessagingexample", + "certificate_hash": "29142b8612b4b6a0ba0fefd1dbf65ab565fb2cbd" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:553625b1be8cf2efac3efc", + "android_client_info": { + "package_name": "io.flutter.plugins.firebasestorageexample" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:670b9e77ba703236ac3efc", + "android_client_info": { + "package_name": "io.invertase.flutter.ui.auth.example" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-vh59lklbaigpccaugknk3j0g1pp81kr2.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "io.invertase.flutter.ui.auth.example", + "certificate_hash": "e1604565b51994d10886de1d91da9968dfec02ed" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + }, + { + "client_info": { + "mobilesdk_app_id": "1:448618578101:android:b90da4e868ff1aafac3efc", + "android_client_info": { + "package_name": "org.reactjs.native.example.rnfbTestApplication" + } + }, + "oauth_client": [ + { + "client_id": "448618578101-fft7llalbhpetiiuqg4uk19jfp0tfefe.apps.googleusercontent.com", + "client_type": 1, + "android_info": { + "package_name": "org.reactjs.native.example.rnfbTestApplication", + "certificate_hash": "909ca1482ef022bbae45a2db6b6d05d807a4c4aa" + } + }, + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + } + ], + "api_key": [ + { + "current_key": "AIzaSyCuu4tbv9CwwTudNOweMNstzZHIDBhgJxA" + } + ], + "services": { + "appinvite_service": { + "other_platform_oauth_client": [ + { + "client_id": "448618578101-sg12d2qin42cpr00f8b0gehs5s7inm0v.apps.googleusercontent.com", + "client_type": 3 + }, + { + "client_id": "448618578101-qqah55eossjuaa9np6ppo2l9fe3e7ohh.apps.googleusercontent.com", + "client_type": 2, + "ios_info": { + "bundle_id": "com.crashlytics.test" + } + } + ] + } + } + } + ], + "configuration_version": "1" +} \ No newline at end of file diff --git a/packages/firebase_messaging/firebase_messaging/example/android/app/src/debug/AndroidManifest.xml b/packages/firebase_messaging/firebase_messaging/example/android/app/src/debug/AndroidManifest.xml new file mode 100644 index 000000000000..9cbc491336e9 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/android/app/src/debug/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/AndroidManifest.xml b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000000..62844897c3d5 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,52 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/firebase/messaging/example/MainActivity.java b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/firebase/messaging/example/MainActivity.java new file mode 100644 index 000000000000..503907b11127 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/java/io/flutter/plugins/firebase/messaging/example/MainActivity.java @@ -0,0 +1,5 @@ +package io.flutter.plugins.firebase.messaging.example; + +import io.flutter.embedding.android.FlutterActivity; + +public class MainActivity extends FlutterActivity {} diff --git a/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/drawable/launch_background.xml b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/drawable/launch_background.xml new file mode 100644 index 000000000000..304732f88420 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/drawable/launch_background.xml @@ -0,0 +1,12 @@ + + + + + + + + diff --git a/packages/firebase_messaging/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from packages/firebase_messaging/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png rename to packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/mipmap-hdpi/ic_launcher.png diff --git a/packages/firebase_messaging/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from packages/firebase_messaging/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png rename to packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/mipmap-mdpi/ic_launcher.png diff --git a/packages/firebase_messaging/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from packages/firebase_messaging/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png rename to packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png diff --git a/packages/firebase_messaging/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from packages/firebase_messaging/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png diff --git a/packages/firebase_messaging/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from packages/firebase_messaging/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/values/styles.xml b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/values/styles.xml new file mode 100644 index 000000000000..1f83a33fd4f2 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/android/app/src/main/res/values/styles.xml @@ -0,0 +1,18 @@ + + + + + + + diff --git a/packages/firebase_messaging/firebase_messaging/example/android/app/src/profile/AndroidManifest.xml b/packages/firebase_messaging/firebase_messaging/example/android/app/src/profile/AndroidManifest.xml new file mode 100644 index 000000000000..9cbc491336e9 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/android/app/src/profile/AndroidManifest.xml @@ -0,0 +1,7 @@ + + + + diff --git a/packages/firebase_messaging/example/android/build.gradle b/packages/firebase_messaging/firebase_messaging/example/android/build.gradle similarity index 78% rename from packages/firebase_messaging/example/android/build.gradle rename to packages/firebase_messaging/firebase_messaging/example/android/build.gradle index e7c4702904a4..92e0e4a394d1 100644 --- a/packages/firebase_messaging/example/android/build.gradle +++ b/packages/firebase_messaging/firebase_messaging/example/android/build.gradle @@ -5,8 +5,8 @@ buildscript { } dependencies { - classpath 'com.android.tools.build:gradle:3.5.3' - classpath 'com.google.gms:google-services:4.3.2' + classpath 'com.android.tools.build:gradle:3.5.0' + classpath 'com.google.gms:google-services:4.3.4' } } diff --git a/packages/firebase_messaging/example/android/gradle.properties b/packages/firebase_messaging/firebase_messaging/example/android/gradle.properties similarity index 100% rename from packages/firebase_messaging/example/android/gradle.properties rename to packages/firebase_messaging/firebase_messaging/example/android/gradle.properties index d2032bce8be6..a6738207fd15 100644 --- a/packages/firebase_messaging/example/android/gradle.properties +++ b/packages/firebase_messaging/firebase_messaging/example/android/gradle.properties @@ -1,4 +1,4 @@ org.gradle.jvmargs=-Xmx1536M -android.enableJetifier=true android.useAndroidX=true +android.enableJetifier=true android.enableR8=true diff --git a/packages/firebase_messaging/example/android/app/gradle/wrapper/gradle-wrapper.properties b/packages/firebase_messaging/firebase_messaging/example/android/gradle/wrapper/gradle-wrapper.properties similarity index 79% rename from packages/firebase_messaging/example/android/app/gradle/wrapper/gradle-wrapper.properties rename to packages/firebase_messaging/firebase_messaging/example/android/gradle/wrapper/gradle-wrapper.properties index 9a4163a4f5ee..296b146b7318 100644 --- a/packages/firebase_messaging/example/android/app/gradle/wrapper/gradle-wrapper.properties +++ b/packages/firebase_messaging/firebase_messaging/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,6 @@ +#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-4.6-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/packages/firebase_messaging/firebase_messaging/example/android/settings.gradle b/packages/firebase_messaging/firebase_messaging/example/android/settings.gradle new file mode 100644 index 000000000000..44e62bcf06ae --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/android/settings.gradle @@ -0,0 +1,11 @@ +include ':app' + +def localPropertiesFile = new File(rootProject.projectDir, "local.properties") +def properties = new Properties() + +assert localPropertiesFile.exists() +localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + +def flutterSdkPath = properties.getProperty("flutter.sdk") +assert flutterSdkPath != null, "flutter.sdk not set in local.properties" +apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/.gitignore b/packages/firebase_messaging/firebase_messaging/example/ios/.gitignore new file mode 100644 index 000000000000..e96ef602b8d1 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/ios/.gitignore @@ -0,0 +1,32 @@ +*.mode1v3 +*.mode2v3 +*.moved-aside +*.pbxuser +*.perspectivev3 +**/*sync/ +.sconsign.dblite +.tags* +**/.vagrant/ +**/DerivedData/ +Icon? +**/Pods/ +**/.symlinks/ +profile +xcuserdata +**/.generated/ +Flutter/App.framework +Flutter/Flutter.framework +Flutter/Flutter.podspec +Flutter/Generated.xcconfig +Flutter/app.flx +Flutter/app.zip +Flutter/flutter_assets/ +Flutter/flutter_export_environment.sh +ServiceDefinitions.json +Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!default.mode1v3 +!default.mode2v3 +!default.pbxuser +!default.perspectivev3 diff --git a/packages/firebase_messaging/example/ios/Flutter/AppFrameworkInfo.plist b/packages/firebase_messaging/firebase_messaging/example/ios/Flutter/AppFrameworkInfo.plist similarity index 84% rename from packages/firebase_messaging/example/ios/Flutter/AppFrameworkInfo.plist rename to packages/firebase_messaging/firebase_messaging/example/ios/Flutter/AppFrameworkInfo.plist index 6c2de8086bcd..f2872cf474ee 100644 --- a/packages/firebase_messaging/example/ios/Flutter/AppFrameworkInfo.plist +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Flutter/AppFrameworkInfo.plist @@ -3,7 +3,7 @@ CFBundleDevelopmentRegion - en + $(DEVELOPMENT_LANGUAGE) CFBundleExecutable App CFBundleIdentifier @@ -20,11 +20,7 @@ ???? CFBundleVersion 1.0 - UIRequiredDeviceCapabilities - - arm64 - MinimumOSVersion - 8.0 + 9.0 diff --git a/packages/firebase_messaging/example/ios/Flutter/Debug.xcconfig b/packages/firebase_messaging/firebase_messaging/example/ios/Flutter/Debug.xcconfig similarity index 100% rename from packages/firebase_messaging/example/ios/Flutter/Debug.xcconfig rename to packages/firebase_messaging/firebase_messaging/example/ios/Flutter/Debug.xcconfig index 9803018ca79d..e8efba114687 100644 --- a/packages/firebase_messaging/example/ios/Flutter/Debug.xcconfig +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Flutter/Debug.xcconfig @@ -1,2 +1,2 @@ -#include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/firebase_messaging/example/ios/Flutter/Release.xcconfig b/packages/firebase_messaging/firebase_messaging/example/ios/Flutter/Release.xcconfig similarity index 100% rename from packages/firebase_messaging/example/ios/Flutter/Release.xcconfig rename to packages/firebase_messaging/firebase_messaging/example/ios/Flutter/Release.xcconfig index a4a8c604e13d..399e9340e6f6 100644 --- a/packages/firebase_messaging/example/ios/Flutter/Release.xcconfig +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Flutter/Release.xcconfig @@ -1,2 +1,2 @@ -#include "Generated.xcconfig" #include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "Generated.xcconfig" diff --git a/packages/firebase_messaging/example/ios/Runner.xcodeproj/project.pbxproj b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/project.pbxproj similarity index 64% rename from packages/firebase_messaging/example/ios/Runner.xcodeproj/project.pbxproj rename to packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/project.pbxproj index 4eba4df46fa0..22d7922fb8e4 100644 --- a/packages/firebase_messaging/example/ios/Runner.xcodeproj/project.pbxproj +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/project.pbxproj @@ -3,16 +3,14 @@ archiveVersion = 1; classes = { }; - objectVersion = 46; + objectVersion = 51; objects = { /* Begin PBXBuildFile section */ - 2EF3EFF8E25860E112E91756 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 799CAB03D87EEEE1FF65E7BE /* libPods-Runner.a */; }; + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; }; + 275EF1BD2530B4BC008E79D3 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 275EF1BC2530B4BC008E79D3 /* GoogleService-Info.plist */; }; 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; }; - 5C5A74FB1EAA74D700FE37CF /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 5C5A74FA1EAA74D700FE37CF /* GoogleService-Info.plist */; }; - 5C8223C01EC265DD00E12B15 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 5C8223BF1EC265DD00E12B15 /* GeneratedPluginRegistrant.m */; }; - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB21CF90195004384FC /* Debug.xcconfig */; }; - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */ = {isa = PBXBuildFile; fileRef = 9740EEB31CF90195004384FC /* Generated.xcconfig */; }; + 88A1BA888F20E378BC794541 /* libPods-Runner.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 61672002202DF3E143D4F450 /* libPods-Runner.a */; }; 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */; }; 97C146F31CF9000F007C117D /* main.m in Sources */ = {isa = PBXBuildFile; fileRef = 97C146F21CF9000F007C117D /* main.m */; }; 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; @@ -34,25 +32,26 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - 3AE5C9160747E692296DA524 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; + 275EF1BC2530B4BC008E79D3 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + 27715A442538A1AE00757C2A /* Firebase Cloud Messaging Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Firebase Cloud Messaging Example.entitlements"; sourceTree = ""; }; 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = ""; }; - 5C5A74FA1EAA74D700FE37CF /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; - 5C8223BE1EC265DD00E12B15 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; - 5C8223BF1EC265DD00E12B15 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; - 5CDE2B941EA81E0D006C00D0 /* Runner.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Runner.entitlements; sourceTree = ""; }; - 799CAB03D87EEEE1FF65E7BE /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 5213D4DB21693B7FDB92C6A0 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 61672002202DF3E143D4F450 /* libPods-Runner.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-Runner.a"; sourceTree = BUILT_PRODUCTS_DIR; }; 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = ""; }; 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppDelegate.m; sourceTree = ""; }; - 84DCF961D7629C483D05ED3D /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = ""; }; 9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = ""; }; - 97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 97C146EE1CF9000F007C117D /* Firebase Cloud Messaging Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Firebase Cloud Messaging Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 97C146F21CF9000F007C117D /* main.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = main.m; sourceTree = ""; }; 97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 9E2B97414E4FE1879B08F585 /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + D9E24403570AB85E2ED73605 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -60,20 +59,21 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 2EF3EFF8E25860E112E91756 /* libPods-Runner.a in Frameworks */, + 88A1BA888F20E378BC794541 /* libPods-Runner.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 840012C8B5EDBCF56B0E4AC1 /* Pods */ = { + 8C972AC0F42819024E4367EC /* Pods */ = { isa = PBXGroup; children = ( - 84DCF961D7629C483D05ED3D /* Pods-Runner.debug.xcconfig */, - 3AE5C9160747E692296DA524 /* Pods-Runner.release.xcconfig */, + 5213D4DB21693B7FDB92C6A0 /* Pods-Runner.debug.xcconfig */, + 9E2B97414E4FE1879B08F585 /* Pods-Runner.release.xcconfig */, + D9E24403570AB85E2ED73605 /* Pods-Runner.profile.xcconfig */, ); - name = Pods; + path = Pods; sourceTree = ""; }; 9740EEB11CF90186004384FC /* Flutter */ = { @@ -93,15 +93,15 @@ 9740EEB11CF90186004384FC /* Flutter */, 97C146F01CF9000F007C117D /* Runner */, 97C146EF1CF9000F007C117D /* Products */, - 840012C8B5EDBCF56B0E4AC1 /* Pods */, - CF3B75C9A7D2FA2A4C99F110 /* Frameworks */, + 8C972AC0F42819024E4367EC /* Pods */, + C6DFB291D4DE268ECF1E3926 /* Frameworks */, ); sourceTree = ""; }; 97C146EF1CF9000F007C117D /* Products */ = { isa = PBXGroup; children = ( - 97C146EE1CF9000F007C117D /* Runner.app */, + 97C146EE1CF9000F007C117D /* Firebase Cloud Messaging Example.app */, ); name = Products; sourceTree = ""; @@ -109,17 +109,17 @@ 97C146F01CF9000F007C117D /* Runner */ = { isa = PBXGroup; children = ( - 5C8223BE1EC265DD00E12B15 /* GeneratedPluginRegistrant.h */, - 5C8223BF1EC265DD00E12B15 /* GeneratedPluginRegistrant.m */, - 5CDE2B941EA81E0D006C00D0 /* Runner.entitlements */, + 27715A442538A1AE00757C2A /* Firebase Cloud Messaging Example.entitlements */, + 275EF1BC2530B4BC008E79D3 /* GoogleService-Info.plist */, 7AFFD8ED1D35381100E5BB4D /* AppDelegate.h */, 7AFFD8EE1D35381100E5BB4D /* AppDelegate.m */, - 5C5A74FA1EAA74D700FE37CF /* GoogleService-Info.plist */, 97C146FA1CF9000F007C117D /* Main.storyboard */, 97C146FD1CF9000F007C117D /* Assets.xcassets */, 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */, 97C147021CF9000F007C117D /* Info.plist */, 97C146F11CF9000F007C117D /* Supporting Files */, + 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */, + 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */, ); path = Runner; sourceTree = ""; @@ -132,10 +132,10 @@ name = "Supporting Files"; sourceTree = ""; }; - CF3B75C9A7D2FA2A4C99F110 /* Frameworks */ = { + C6DFB291D4DE268ECF1E3926 /* Frameworks */ = { isa = PBXGroup; children = ( - 799CAB03D87EEEE1FF65E7BE /* libPods-Runner.a */, + 61672002202DF3E143D4F450 /* libPods-Runner.a */, ); name = Frameworks; sourceTree = ""; @@ -147,14 +147,14 @@ isa = PBXNativeTarget; buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */; buildPhases = ( - AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */, + 095EEB27D2A4AE080033180A /* [CP] Check Pods Manifest.lock */, 9740EEB61CF901F6004384FC /* Run Script */, 97C146EA1CF9000F007C117D /* Sources */, 97C146EB1CF9000F007C117D /* Frameworks */, 97C146EC1CF9000F007C117D /* Resources */, 9705A1C41CF9048500538489 /* Embed Frameworks */, - 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */, 3B06AD1E1E4923F5004D2608 /* Thin Binary */, + 104FB7D8BEBAD0A7568938E9 /* [CP] Embed Pods Frameworks */, ); buildRules = ( ); @@ -162,7 +162,7 @@ ); name = Runner; productName = Runner; - productReference = 97C146EE1CF9000F007C117D /* Runner.app */; + productReference = 97C146EE1CF9000F007C117D /* Firebase Cloud Messaging Example.app */; productType = "com.apple.product-type.application"; }; /* End PBXNativeTarget section */ @@ -171,22 +171,17 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0830; - ORGANIZATIONNAME = "The Chromium Authors"; + LastUpgradeCheck = 1020; + ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { CreatedOnToolsVersion = 7.3.1; - SystemCapabilities = { - com.apple.Push = { - enabled = 1; - }; - }; }; }; }; buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */; - compatibilityVersion = "Xcode 3.2"; - developmentRegion = English; + compatibilityVersion = "Xcode 9.3"; + developmentRegion = en; hasScannedForEncodings = 0; knownRegions = ( en, @@ -207,11 +202,9 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5C5A74FB1EAA74D700FE37CF /* GoogleService-Info.plist in Resources */, 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */, - 9740EEB51CF90195004384FC /* Generated.xcconfig in Resources */, 3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */, - 9740EEB41CF90195004384FC /* Debug.xcconfig in Resources */, + 275EF1BD2530B4BC008E79D3 /* GoogleService-Info.plist in Resources */, 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */, 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */, ); @@ -220,69 +213,72 @@ /* End PBXResourcesBuildPhase section */ /* Begin PBXShellScriptBuildPhase section */ - 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { + 095EEB27D2A4AE080033180A /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); + inputFileListPaths = ( + ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( ); - name = "Thin Binary"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; }; - 95BB15E9E1769C0D146AA592 /* [CP] Embed Pods Frameworks */ = { + 104FB7D8BEBAD0A7568938E9 /* [CP] Embed Pods Frameworks */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); - inputPaths = ( - "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh", - "${PODS_ROOT}/../Flutter/Flutter.framework", + inputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist", ); name = "[CP] Embed Pods Frameworks"; - outputPaths = ( - "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Flutter.framework", + outputFileListPaths = ( + "${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; - 9740EEB61CF901F6004384FC /* Run Script */ = { + 3B06AD1E1E4923F5004D2608 /* Thin Binary */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( ); - name = "Run Script"; + name = "Thin Binary"; outputPaths = ( ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin"; }; - AB1344B0443C71CD721E1BB7 /* [CP] Check Pods Manifest.lock */ = { + 9740EEB61CF901F6004384FC /* Run Script */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; files = ( ); inputPaths = ( - "${PODS_PODFILE_DIR_PATH}/Podfile.lock", - "${PODS_ROOT}/Manifest.lock", ); - name = "[CP] Check Pods Manifest.lock"; + name = "Run Script"; outputPaths = ( - "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; - showEnvVarsInLog = 0; + shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build"; }; /* End PBXShellScriptBuildPhase section */ @@ -293,7 +289,7 @@ files = ( 978B8F6F1D3862AE00F588F7 /* AppDelegate.m in Sources */, 97C146F31CF9000F007C117D /* main.m in Sources */, - 5C8223C01EC265DD00E12B15 /* GeneratedPluginRegistrant.m in Sources */, + 1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -319,9 +315,90 @@ /* End PBXVariantGroup section */ /* Begin XCBuildConfiguration section */ + 249021D3217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + }; + name = Profile; + }; + 249021D4217E4FDB00AE95B9 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CODE_SIGN_ENTITLEMENTS = "Runner/Firebase Cloud Messaging Example.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = YYX2P3XVJ7; + ENABLE_BITCODE = NO; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.firebase.messaging; + PRODUCT_NAME = "Firebase Cloud Messaging Example"; + PROVISIONING_PROFILE_SPECIFIER = ""; + VERSIONING_SYSTEM = "apple-generic"; + }; + name = Profile; + }; 97C147031CF9000F007C117D /* Debug */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -329,14 +406,22 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -359,7 +444,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -369,7 +454,6 @@ }; 97C147041CF9000F007C117D /* Release */ = { isa = XCBuildConfiguration; - baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; CLANG_ANALYZER_NONNULL = YES; @@ -377,14 +461,22 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -401,9 +493,10 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.0; + IPHONEOS_DEPLOYMENT_TARGET = 9.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; + SUPPORTED_PLATFORMS = iphoneos; TARGETED_DEVICE_FAMILY = "1,2"; VALIDATE_PRODUCT = YES; }; @@ -413,22 +506,31 @@ isa = XCBuildConfiguration; baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; buildSettings = { - ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_ENTITLEMENTS = "Runner/Firebase Cloud Messaging Example.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = YYX2P3XVJ7; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.plugins.firebase-messaging"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.firebase.messaging; + PRODUCT_NAME = "Firebase Cloud Messaging Example"; + PROVISIONING_PROFILE_SPECIFIER = ""; + VERSIONING_SYSTEM = "apple-generic"; }; name = Debug; }; @@ -436,22 +538,31 @@ isa = XCBuildConfiguration; baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; buildSettings = { - ARCHS = arm64; ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements; + CODE_SIGN_ENTITLEMENTS = "Runner/Firebase Cloud Messaging Example.entitlements"; + CODE_SIGN_IDENTITY = "Apple Development"; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)"; + DEVELOPMENT_TEAM = YYX2P3XVJ7; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); INFOPLIST_FILE = Runner/Info.plist; - LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + ); LIBRARY_SEARCH_PATHS = ( "$(inherited)", "$(PROJECT_DIR)/Flutter", ); - PRODUCT_BUNDLE_IDENTIFIER = "io.flutter.plugins.firebase-messaging"; - PRODUCT_NAME = "$(TARGET_NAME)"; + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.firebase.messaging; + PRODUCT_NAME = "Firebase Cloud Messaging Example"; + PROVISIONING_PROFILE_SPECIFIER = ""; + VERSIONING_SYSTEM = "apple-generic"; }; name = Release; }; @@ -463,6 +574,7 @@ buildConfigurations = ( 97C147031CF9000F007C117D /* Debug */, 97C147041CF9000F007C117D /* Release */, + 249021D3217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; @@ -472,6 +584,7 @@ buildConfigurations = ( 97C147061CF9000F007C117D /* Debug */, 97C147071CF9000F007C117D /* Release */, + 249021D4217E4FDB00AE95B9 /* Profile */, ); defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..1d526a16ed0f --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000000..f9b0d7c5ea15 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/firebase_messaging/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme similarity index 89% rename from packages/firebase_messaging/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme rename to packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index 1c9580788197..15b1d08a9b4e 100644 --- a/packages/firebase_messaging/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ @@ -27,19 +27,17 @@ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" shouldUseLaunchSchemeArgsEnv = "YES"> - - - - + + - - diff --git a/packages/firebase_messaging/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/firebase_messaging/example/ios/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata rename to packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings new file mode 100644 index 000000000000..f9b0d7c5ea15 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings @@ -0,0 +1,8 @@ + + + + + PreviewsEnabled + + + diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.h b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.h new file mode 100644 index 000000000000..36e21bbf9cf4 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.h @@ -0,0 +1,6 @@ +#import +#import + +@interface AppDelegate : FlutterAppDelegate + +@end diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.m b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.m new file mode 100644 index 000000000000..70e83933db14 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/AppDelegate.m @@ -0,0 +1,13 @@ +#import "AppDelegate.h" +#import "GeneratedPluginRegistrant.h" + +@implementation AppDelegate + +- (BOOL)application:(UIApplication *)application + didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { + [GeneratedPluginRegistrant registerWithRegistry:self]; + // Override point for customization after application launch. + return [super application:application didFinishLaunchingWithOptions:launchOptions]; +} + +@end diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000000..83e0eacdeaf6 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,116 @@ +{ + "images" : [ + { + "size" : "20x20", + "idiom": "iphone", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-20@2x.png", + "scale": "2x" + }, + { + "size" : "20x20", + "idiom": "iphone", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-20@3x.png", + "scale": "3x" + }, + { + "size" : "20x20", + "idiom": "ipad", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-20.png", + "scale": "1x" + }, + { + "size" : "20x20", + "idiom": "ipad", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-20@2x.png", + "scale": "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-29@2x.png", + "scale" : "2x" + }, + { + "size" : "29x29", + "idiom" : "iphone", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-29@3x.png", + "scale" : "3x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-40@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "iphone", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-40@3x.png", + "scale" : "3x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-60@2x.png", + "scale" : "2x" + }, + { + "size" : "60x60", + "idiom" : "iphone", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-60@3x.png", + "scale" : "3x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-29.png", + "scale" : "1x" + }, + { + "size" : "29x29", + "idiom" : "ipad", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-29@2x.png", + "scale" : "2x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-40.png", + "scale" : "1x" + }, + { + "size" : "40x40", + "idiom" : "ipad", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-40@2x.png", + "scale" : "2x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-76.png", + "scale" : "1x" + }, + { + "size" : "76x76", + "idiom" : "ipad", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-76@2x.png", + "scale" : "2x" + }, + { + "size" : "83.5x83.5", + "idiom" : "ipad", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-83.5@2x.png", + "scale" : "2x" + }, + { + "size" : "1024x1024", + "idiom" : "ios-marketing", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-1024.png", + "scale" : "1x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-1024.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-1024.png new file mode 100644 index 000000000000..3ab3bb42be8d Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-1024.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-20.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-20.png new file mode 100644 index 000000000000..a640ee1d709e Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-20.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-20@2x.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-20@2x.png new file mode 100644 index 000000000000..e24d176da367 Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-20@2x.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-20@3x.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-20@3x.png new file mode 100644 index 000000000000..16c3e03bd7d4 Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-20@3x.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-29.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-29.png new file mode 100644 index 000000000000..2e82fb760fdd Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-29.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-29@2x.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-29@2x.png new file mode 100644 index 000000000000..062585ef25a4 Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-29@2x.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-29@3x.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-29@3x.png new file mode 100644 index 000000000000..1e25c100dce8 Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-29@3x.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-40.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-40.png new file mode 100644 index 000000000000..e24d176da367 Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-40.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-40@2x.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-40@2x.png new file mode 100644 index 000000000000..ecb61d2b212b Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-40@2x.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-40@3x.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-40@3x.png new file mode 100644 index 000000000000..f69a48c1e2cd Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-40@3x.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-60@2x.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-60@2x.png new file mode 100644 index 000000000000..f69a48c1e2cd Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-60@2x.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-60@3x.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-60@3x.png new file mode 100644 index 000000000000..d66dc1ac5254 Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-60@3x.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-76.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-76.png new file mode 100644 index 000000000000..96f5ae2779c7 Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-76.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-76@2x.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-76@2x.png new file mode 100644 index 000000000000..07eff0d07efc Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-76@2x.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-83.5@2x.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-83.5@2x.png new file mode 100644 index 000000000000..ed4fddaa16e8 Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-83.5@2x.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/Contents.json b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/Contents.json new file mode 100644 index 000000000000..73c00596a7fc --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json new file mode 100644 index 000000000000..0bedcf2fd467 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "idiom" : "universal", + "filename" : "LaunchImage.png", + "scale" : "1x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@2x.png", + "scale" : "2x" + }, + { + "idiom" : "universal", + "filename" : "LaunchImage@3x.png", + "scale" : "3x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@2x.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png new file mode 100644 index 000000000000..9da19eacad3b Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/LaunchImage@3x.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md new file mode 100644 index 000000000000..89c2725b70f1 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Assets.xcassets/LaunchImage.imageset/README.md @@ -0,0 +1,5 @@ +# Launch Screen Assets + +You can customize the launch screen with your own desired assets by replacing the image files in this directory. + +You can also do it by opening your Flutter project's Xcode project with `open ios/Runner.xcworkspace`, selecting `Runner/Assets.xcassets` in the Project Navigator and dropping in the desired images. \ No newline at end of file diff --git a/packages/firebase_messaging/example/ios/Runner/Base.lproj/LaunchScreen.storyboard b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Base.lproj/LaunchScreen.storyboard similarity index 51% rename from packages/firebase_messaging/example/ios/Runner/Base.lproj/LaunchScreen.storyboard rename to packages/firebase_messaging/firebase_messaging/example/ios/Runner/Base.lproj/LaunchScreen.storyboard index ebf48f603974..f2e259c7c939 100644 --- a/packages/firebase_messaging/example/ios/Runner/Base.lproj/LaunchScreen.storyboard +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Base.lproj/LaunchScreen.storyboard @@ -1,8 +1,8 @@ - + - + @@ -10,13 +10,20 @@ - - + + - - + + + + + + + + + @@ -24,4 +31,7 @@ + + + diff --git a/packages/firebase_messaging/example/ios/Runner/Base.lproj/Main.storyboard b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Base.lproj/Main.storyboard similarity index 100% rename from packages/firebase_messaging/example/ios/Runner/Base.lproj/Main.storyboard rename to packages/firebase_messaging/firebase_messaging/example/ios/Runner/Base.lproj/Main.storyboard diff --git a/packages/firebase_messaging/example/ios/Runner/Runner.entitlements b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Firebase Cloud Messaging Example.entitlements similarity index 100% rename from packages/firebase_messaging/example/ios/Runner/Runner.entitlements rename to packages/firebase_messaging/firebase_messaging/example/ios/Runner/Firebase Cloud Messaging Example.entitlements diff --git a/packages/firebase_messaging/firebase_messaging/example/ios/Runner/GoogleService-Info.plist b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/GoogleService-Info.plist new file mode 100644 index 000000000000..6c36b98465c4 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/GoogleService-Info.plist @@ -0,0 +1,38 @@ + + + + + CLIENT_ID + 448618578101-evbjdqq9co9v29pi8jcua8bm7kr4smuu.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.448618578101-evbjdqq9co9v29pi8jcua8bm7kr4smuu + ANDROID_CLIENT_ID + 448618578101-npu5ll2fj4emgvtietpb685fvdm7fg3s.apps.googleusercontent.com + API_KEY + AIzaSyAHAsf51D0A407EklG1bs-5wA7EbyfNFg0 + GCM_SENDER_ID + 448618578101 + PLIST_VERSION + 1 + BUNDLE_ID + io.flutter.plugins.firebase.messaging + PROJECT_ID + react-native-firebase-testing + STORAGE_BUCKET + react-native-firebase-testing.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:448618578101:ios:0b11ed8263232715ac3efc + DATABASE_URL + https://react-native-firebase-testing.firebaseio.com + + \ No newline at end of file diff --git a/packages/firebase_messaging/example/ios/Runner/Info.plist b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Info.plist similarity index 84% rename from packages/firebase_messaging/example/ios/Runner/Info.plist rename to packages/firebase_messaging/firebase_messaging/example/ios/Runner/Info.plist index ededae49efe7..5e8c92f7877f 100644 --- a/packages/firebase_messaging/example/ios/Runner/Info.plist +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/Info.plist @@ -3,9 +3,7 @@ CFBundleDevelopmentRegion - en - CFBundleDisplayName - Firebase Cloud Messaging Example + $(DEVELOPMENT_LANGUAGE) CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier @@ -13,25 +11,26 @@ CFBundleInfoDictionaryVersion 6.0 CFBundleName - firebase_messaging_example + Cloud Messaging CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + $(FLUTTER_BUILD_NAME) CFBundleSignature ???? CFBundleVersion - 1 + $(FLUTTER_BUILD_NUMBER) LSRequiresIPhoneOS + UIBackgroundModes + + processing + remote-notification + UILaunchStoryboardName LaunchScreen UIMainStoryboardFile Main - UIRequiredDeviceCapabilities - - arm64 - UISupportedInterfaceOrientations UIInterfaceOrientationPortrait diff --git a/packages/firebase_messaging/example/ios/Runner/main.m b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/main.m similarity index 57% rename from packages/firebase_messaging/example/ios/Runner/main.m rename to packages/firebase_messaging/firebase_messaging/example/ios/Runner/main.m index bec320c0bee0..dff6597e4513 100644 --- a/packages/firebase_messaging/example/ios/Runner/main.m +++ b/packages/firebase_messaging/firebase_messaging/example/ios/Runner/main.m @@ -1,7 +1,3 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - #import #import #import "AppDelegate.h" diff --git a/packages/firebase_messaging/firebase_messaging/example/lib/main.dart b/packages/firebase_messaging/firebase_messaging/example/lib/main.dart new file mode 100644 index 000000000000..fb6c30e500d0 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/lib/main.dart @@ -0,0 +1,225 @@ +// Copyright 2019 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; +import 'dart:convert'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:http/http.dart' as http; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +import 'message.dart'; +import 'message_list.dart'; +import 'permissions.dart'; +import 'token_monitor.dart'; + +/// Define a top-level named handler which background/terminated messages will +/// call. +/// +/// To verify things are working, check out the native platform logs. +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + // If you're going to use other Firebase services in the background, such as Firestore, + // make sure you call `initializeApp` before using other Firebase services. + await Firebase.initializeApp(); + print("Handling a background message ${message.messageId}"); +} + +/// Create a [AndroidNotificationChannel] for heads up notifications +const AndroidNotificationChannel channel = AndroidNotificationChannel( + 'high_importance_channel', // id + 'High Importance Notifications', // title + 'This channel is used for important notifications.', // description + importance: Importance.high, + enableVibration: true, + playSound: true, +); + +/// Initalize the [FlutterLocalNotificationsPlugin] package. +final FlutterLocalNotificationsPlugin flutterLocalNotificationsPlugin = + FlutterLocalNotificationsPlugin(); + +void main() async { + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp(); + + // Set the background messaging handler early on, as a named top-level function + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + + /// Create an Android Notification Channel. + /// + /// We use this channel in the `AndroidManifest.xml` file to override the + /// default FCM channel to enable heads up notifications. + await flutterLocalNotificationsPlugin + .resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.createNotificationChannel(channel); + + /// Update the iOS foreground notification presentation options to allow + /// heads up notifications. + await FirebaseMessaging.instance.setForegroundNotificationPresentationOptions( + alert: true, + badge: true, + sound: true, + ); + + runApp(MessagingExampleApp()); +} + +/// Entry point for the example application. +class MessagingExampleApp extends StatelessWidget { + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Messaging Example App', + theme: ThemeData.dark(), + routes: { + '/': (context) => Application(), + '/message': (context) => MessageView(), + }, + ); + } +} + +// Crude counter to make messages unique +int _messageCount = 0; + +/// The API endpoint here accepts a raw FCM payload for demonstration purposes. +String constructFCMPayload(String token) { + _messageCount++; + return jsonEncode({ + 'token': token, + 'data': { + 'via': 'FlutterFire Cloud Messaging!!!', + 'count': _messageCount.toString(), + }, + 'notification': { + 'title': 'Hello FlutterFire!', + 'body': 'This notification (#$_messageCount) was created via FCM!', + }, + }); +} + +/// Renders the example application. +class Application extends StatefulWidget { + @override + State createState() => _Application(); +} + +class _Application extends State { + String _token; + + @override + void initState() { + super.initState(); + FirebaseMessaging.instance + .getInitialMessage() + .then((RemoteMessage message) { + if (message != null) { + Navigator.pushNamed(context, '/message', + arguments: MessageArguments(message, true)); + } + }); + + FirebaseMessaging.onMessage.listen((RemoteMessage message) { + RemoteNotification notification = message.notification; + AndroidNotification android = message.notification?.android; + + if (notification != null && android != null) { + flutterLocalNotificationsPlugin.show( + notification.hashCode, + notification.title, + notification.body, + NotificationDetails( + android: AndroidNotificationDetails( + channel.id, + channel.name, + channel.description, + icon: android?.smallIcon, + ), + )); + } + }); + + FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { + print('A new onMessageOpenedApp event was published!'); + Navigator.pushNamed(context, '/message', + arguments: MessageArguments(message, true)); + }); + } + + Future sendPushMessage() async { + if (_token == null) { + print('Unable to send FCM message, no token exists.'); + return; + } + + try { + await http.post( + 'https://api.rnfirebase.io/messaging/send', + headers: { + 'Content-Type': 'application/json; charset=UTF-8', + }, + body: constructFCMPayload(_token), + ); + print('FCM request for device sent!'); + } catch (e) { + print(e); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text("Cloud Messaging"), + ), + floatingActionButton: Builder( + builder: (context) => FloatingActionButton( + onPressed: () => sendPushMessage(), + child: Icon(Icons.send), + backgroundColor: Colors.white, + ), + ), + body: SingleChildScrollView( + child: Column(children: [ + MetaCard("Permissions", Permissions()), + MetaCard("FCM Token", TokenMonitor((token) { + _token = token; + return token == null + ? CircularProgressIndicator() + : Text(token, style: TextStyle(fontSize: 12)); + })), + MetaCard("Message Stream", MessageList()), + ]), + ), + ); + } +} + +/// UI Widget for displaying metadata. +class MetaCard extends StatelessWidget { + final String _title; + final Widget _children; + + // ignore: public_member_api_docs + MetaCard(this._title, this._children); + + @override + Widget build(BuildContext context) { + return Container( + width: double.infinity, + margin: EdgeInsets.only(left: 8, right: 8, top: 8), + child: Card( + child: Padding( + padding: EdgeInsets.all(16), + child: Column(children: [ + Container( + margin: EdgeInsets.only(bottom: 16), + child: Text(_title, style: TextStyle(fontSize: 18))), + _children, + ])))); + } +} diff --git a/packages/firebase_messaging/firebase_messaging/example/lib/message.dart b/packages/firebase_messaging/firebase_messaging/example/lib/message.dart new file mode 100644 index 000000000000..16c66d9c7cca --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/lib/message.dart @@ -0,0 +1,149 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; + +/// Message route arguments. +class MessageArguments { + /// The RemoteMessage + final RemoteMessage message; + + /// Whether this message caused the application to open. + final bool openedApplication; + + // ignore: public_member_api_docs + MessageArguments(this.message, this.openedApplication) + : assert(message != null); +} + +/// Displays information about a [RemoteMessage]. +class MessageView extends StatelessWidget { + /// A single data row. + Widget row(String title, String value) { + return Padding( + padding: const EdgeInsets.only(left: 8.0, right: 8.0, top: 8.0), + child: Row(children: [ + Text("$title: "), + Text(value ?? "N/A"), + ]), + ); + } + + @override + Widget build(BuildContext context) { + final MessageArguments args = ModalRoute.of(context).settings.arguments; + RemoteMessage message = args.message; + RemoteNotification notification = message.notification; + + return Scaffold( + appBar: AppBar( + title: Text(message.messageId), + ), + body: SingleChildScrollView( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column(children: [ + row("Triggered application open", args.openedApplication.toString()), + row("Message ID", message.messageId), + row("Sender ID", message.senderId), + row("Category", message.category), + row("Collapse Key", message.collapseKey), + row("Content Available", message.contentAvailable.toString()), + row("Data", message.data.toString()), + row("From", message.from), + row("Message ID", message.messageId), + row("Sent Time", message.sentTime?.toString()), + row("Thread ID", message.threadId), + row("Time to Live (TTL)", message.ttl?.toString()), + if (notification != null) ...[ + Padding( + padding: const EdgeInsets.only(top: 16.0), + child: Container( + child: Column(children: [ + Text( + "Remote Notification", + style: TextStyle(fontSize: 18), + ), + row( + "Title", + notification.title, + ), + row( + "Body", + notification.body, + ), + if (notification.android != null) ...[ + Text( + "Android Properties", + style: TextStyle(fontSize: 18), + ), + row( + "Channel ID", + notification.android.channelId, + ), + row( + "Click Action", + notification.android.clickAction, + ), + row( + "Color", + notification.android.color, + ), + row( + "Count", + notification.android.count?.toString(), + ), + row( + "Image URL", + notification.android.imageUrl, + ), + row( + "Link", + notification.android.link, + ), + row( + "Priority", + notification.android.priority?.toString(), + ), + row( + "Small Icon", + notification.android.smallIcon, + ), + row( + "Sound", + notification.android.sound, + ), + row( + "Ticker", + notification.android.ticker, + ), + row( + "Visibility", + notification.android.visibility?.toString(), + ), + ], + if (notification.apple != null) ...[ + Text( + "Apple Properties", + style: TextStyle(fontSize: 18), + ), + row( + "Subtitle", + notification.apple.subtitle, + ), + row( + "Badge", + notification.apple.badge, + ), + row( + "Sound", + notification.apple.sound?.name, + ), + ] + ]), + ), + ) + ] + ]), + )), + ); + } +} diff --git a/packages/firebase_messaging/firebase_messaging/example/lib/message_list.dart b/packages/firebase_messaging/firebase_messaging/example/lib/message_list.dart new file mode 100644 index 000000000000..03a5c5a4f6ff --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/lib/message_list.dart @@ -0,0 +1,45 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; + +import 'message.dart'; + +/// Listens for incoming foreground messages and displays them in a list. +class MessageList extends StatefulWidget { + @override + State createState() => _MessageList(); +} + +class _MessageList extends State { + List _messages = []; + + @override + void initState() { + super.initState(); + FirebaseMessaging.onMessage.listen((RemoteMessage message) { + setState(() { + _messages = [..._messages, message]; + }); + }); + } + + @override + Widget build(BuildContext context) { + if (_messages.isEmpty) { + return Text("No messages received"); + } + + return ListView.builder( + shrinkWrap: true, + itemCount: _messages.length, + itemBuilder: (context, index) { + RemoteMessage message = _messages[index]; + + return ListTile( + title: Text(message.messageId), + subtitle: Text(message.sentTime?.toString() ?? 'N/A'), + onTap: () => Navigator.pushNamed(context, '/message', + arguments: MessageArguments(message, false)), + ); + }); + } +} diff --git a/packages/firebase_messaging/firebase_messaging/example/lib/permissions.dart b/packages/firebase_messaging/firebase_messaging/example/lib/permissions.dart new file mode 100644 index 000000000000..77b8a6c101e9 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/lib/permissions.dart @@ -0,0 +1,104 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; + +/// Requests & displays the current user permissions for this device. +class Permissions extends StatefulWidget { + @override + State createState() => _Permissions(); +} + +class _Permissions extends State { + bool _requested = false; + bool _fetching = false; + NotificationSettings _settings; + + void requestPermissions() async { + setState(() { + _fetching = true; + }); + + NotificationSettings settings = + await FirebaseMessaging.instance.requestPermission( + alert: true, + announcement: true, + badge: true, + carPlay: true, + criticalAlert: true, + // This will ensure the popup shows for users + provisional: false, + ); + + setState(() { + _requested = true; + _fetching = false; + _settings = settings; + }); + } + + Widget row(String title, String value) { + return Container( + margin: EdgeInsets.only(bottom: 8), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text("$title:", style: TextStyle(fontWeight: FontWeight.bold)), + Text(value), + ], + ), + ); + } + + @override + Widget build(BuildContext context) { + if (_fetching) { + return Container(child: CircularProgressIndicator()); + } + + if (!_requested) { + return RaisedButton( + onPressed: requestPermissions, child: Text("Request Permissions")); + } + + return (Column(children: [ + row("Authorization Status", statusMap[_settings.authorizationStatus]), + if (defaultTargetPlatform == TargetPlatform.iOS) ...[ + row("Alert", settingsMap[_settings.alert]), + row("Announcement", settingsMap[_settings.announcement]), + row("Badge", settingsMap[_settings.badge]), + row("Car Play", settingsMap[_settings.carPlay]), + row("Lock Screen", settingsMap[_settings.lockScreen]), + row("Notification Center", settingsMap[_settings.notificationCenter]), + row("Show Previews", previewMap[_settings.showPreviews]), + row("Sound", settingsMap[_settings.sound]), + ], + Container( + child: RaisedButton( + onPressed: () => {}, child: Text("Reload Permissions")), + ) + ])); + } +} + +/// Maps a [AuthorizationStatus] to a string value. +const statusMap = { + AuthorizationStatus.authorized: 'Authorized', + AuthorizationStatus.denied: 'Denied', + AuthorizationStatus.notDetermined: 'Not Determined', + AuthorizationStatus.provisional: 'Provisional', +}; + +/// Maps a [AppleNotificationSetting] to a string value. +const settingsMap = { + AppleNotificationSetting.disabled: 'Disabled', + AppleNotificationSetting.enabled: 'Enabled', + AppleNotificationSetting.notSupported: 'Not Supported', +}; + +/// Maps a [AppleShowPreviewSetting] to a string value. +const previewMap = { + AppleShowPreviewSetting.always: 'Always', + AppleShowPreviewSetting.never: 'Never', + AppleShowPreviewSetting.notSupported: 'Not Supported', + AppleShowPreviewSetting.whenAuthenticated: 'Only When Authenticated', +}; diff --git a/packages/firebase_messaging/firebase_messaging/example/lib/token_monitor.dart b/packages/firebase_messaging/firebase_messaging/example/lib/token_monitor.dart new file mode 100644 index 000000000000..a1c25080d1d6 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/lib/token_monitor.dart @@ -0,0 +1,40 @@ +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; + +/// Manages & returns the users FCM token. +/// +/// Also monitors token refreshes and updates state. +class TokenMonitor extends StatefulWidget { + // ignore: public_member_api_docs + TokenMonitor(this._builder); + + final Widget Function(String token) _builder; + + @override + State createState() => _TokenMonitor(); +} + +class _TokenMonitor extends State { + String _token; + Stream _tokenStream; + + void setToken(String token) { + print('FCM Token: $token'); + setState(() { + _token = token; + }); + } + + @override + void initState() { + super.initState(); + FirebaseMessaging.instance.getToken().then(setToken); + _tokenStream = FirebaseMessaging.instance.onTokenRefresh; + _tokenStream.listen(setToken); + } + + @override + Widget build(BuildContext context) { + return widget._builder(_token); + } +} diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/.gitignore b/packages/firebase_messaging/firebase_messaging/example/macos/.gitignore new file mode 100644 index 000000000000..d2fd3772308c --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/.gitignore @@ -0,0 +1,6 @@ +# Flutter-related +**/Flutter/ephemeral/ +**/Pods/ + +# Xcode-related +**/xcuserdata/ diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Flutter/Flutter-Debug.xcconfig b/packages/firebase_messaging/firebase_messaging/example/macos/Flutter/Flutter-Debug.xcconfig new file mode 100644 index 000000000000..785633d3a86b --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Flutter/Flutter-Debug.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Flutter/Flutter-Release.xcconfig b/packages/firebase_messaging/firebase_messaging/example/macos/Flutter/Flutter-Release.xcconfig new file mode 100644 index 000000000000..5fba960c3af2 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Flutter/Flutter-Release.xcconfig @@ -0,0 +1,2 @@ +#include "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig" +#include "ephemeral/Flutter-Generated.xcconfig" diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcodeproj/project.pbxproj b/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcodeproj/project.pbxproj new file mode 100644 index 000000000000..6baa2e94a6a0 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcodeproj/project.pbxproj @@ -0,0 +1,669 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 51; + objects = { + +/* Begin PBXAggregateTarget section */ + 33CC111A2044C6BA0003C045 /* Flutter Assemble */ = { + isa = PBXAggregateTarget; + buildConfigurationList = 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */; + buildPhases = ( + 33CC111E2044C6BF0003C045 /* ShellScript */, + ); + dependencies = ( + ); + name = "Flutter Assemble"; + productName = FLX; + }; +/* End PBXAggregateTarget section */ + +/* Begin PBXBuildFile section */ + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */ = {isa = PBXBuildFile; fileRef = 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */; }; + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC10F02044A3C60003C045 /* AppDelegate.swift */; }; + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F22044A3C60003C045 /* Assets.xcassets */; }; + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 33CC10F42044A3C60003C045 /* MainMenu.xib */; }; + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */; }; + 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; }; + 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + B550B1FC23F53648007DADD5 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = B550B1FB23F53648007DADD5 /* GoogleService-Info.plist */; }; + B6036D992F5B77F0D48D7883 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 1026236A547BC5196614E954 /* Pods_Runner.framework */; }; + D73912F022F37F9E000D13A0 /* App.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; }; + D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */ = {isa = PBXBuildFile; fileRef = D73912EF22F37F9E000D13A0 /* App.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 33CC10E52044A3C60003C045 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 33CC111A2044C6BA0003C045; + remoteInfo = FLX; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXCopyFilesBuildPhase section */ + 33CC110E2044A8840003C045 /* Bundle Framework */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + D73912F222F3801D000D13A0 /* App.framework in Bundle Framework */, + 33D1A10522148B93006C7A3E /* FlutterMacOS.framework in Bundle Framework */, + ); + name = "Bundle Framework"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + +/* Begin PBXFileReference section */ + 0B4CF9B1CA3F6E07FE2F953C /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; + 1026236A547BC5196614E954 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 1E5E064344044E24673A33BD /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = ""; }; + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Warnings.xcconfig; sourceTree = ""; }; + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneratedPluginRegistrant.swift; sourceTree = ""; }; + 33CC10ED2044A3C60003C045 /* Firebase Cloud Messaging Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Firebase Cloud Messaging Example.app"; sourceTree = BUILT_PRODUCTS_DIR; }; + 33CC10F02044A3C60003C045 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 33CC10F22044A3C60003C045 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = Assets.xcassets; path = Runner/Assets.xcassets; sourceTree = ""; }; + 33CC10F52044A3C60003C045 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; + 33CC10F72044A3C60003C045 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = Info.plist; path = Runner/Info.plist; sourceTree = ""; }; + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainFlutterWindow.swift; sourceTree = ""; }; + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Debug.xcconfig"; sourceTree = ""; }; + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = "Flutter-Release.xcconfig"; sourceTree = ""; }; + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = "Flutter-Generated.xcconfig"; path = "ephemeral/Flutter-Generated.xcconfig"; sourceTree = ""; }; + 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = FlutterMacOS.framework; path = Flutter/ephemeral/FlutterMacOS.framework; sourceTree = SOURCE_ROOT; }; + 33E51913231747F40026EE4D /* DebugProfile.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = DebugProfile.entitlements; sourceTree = ""; }; + 33E51914231749380026EE4D /* Release.entitlements */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.entitlements; path = Release.entitlements; sourceTree = ""; }; + 33E5194F232828860026EE4D /* AppInfo.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = AppInfo.xcconfig; sourceTree = ""; }; + 3D7BD4B06D0869EA1407E048 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Release.xcconfig; sourceTree = ""; }; + 9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; path = Debug.xcconfig; sourceTree = ""; }; + B550B1FB23F53648007DADD5 /* GoogleService-Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = "GoogleService-Info.plist"; sourceTree = ""; }; + D73912EF22F37F9E000D13A0 /* App.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = App.framework; path = Flutter/ephemeral/App.framework; sourceTree = SOURCE_ROOT; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 33CC10EA2044A3C60003C045 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D73912F022F37F9E000D13A0 /* App.framework in Frameworks */, + 33D1A10422148B71006C7A3E /* FlutterMacOS.framework in Frameworks */, + B6036D992F5B77F0D48D7883 /* Pods_Runner.framework in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 286E7513A68DD39907D77423 /* Pods */ = { + isa = PBXGroup; + children = ( + 3D7BD4B06D0869EA1407E048 /* Pods-Runner.debug.xcconfig */, + 1E5E064344044E24673A33BD /* Pods-Runner.release.xcconfig */, + 0B4CF9B1CA3F6E07FE2F953C /* Pods-Runner.profile.xcconfig */, + ); + path = Pods; + sourceTree = ""; + }; + 33BA886A226E78AF003329D5 /* Configs */ = { + isa = PBXGroup; + children = ( + 33E5194F232828860026EE4D /* AppInfo.xcconfig */, + 9740EEB21CF90195004384FC /* Debug.xcconfig */, + 7AFA3C8E1D35360C0083082E /* Release.xcconfig */, + 333000ED22D3DE5D00554162 /* Warnings.xcconfig */, + ); + path = Configs; + sourceTree = ""; + }; + 33CC10E42044A3C60003C045 = { + isa = PBXGroup; + children = ( + 33FAB671232836740065AC1E /* Runner */, + 33CEB47122A05771004F2AC0 /* Flutter */, + 33CC10EE2044A3C60003C045 /* Products */, + D73912EC22F37F3D000D13A0 /* Frameworks */, + 286E7513A68DD39907D77423 /* Pods */, + ); + sourceTree = ""; + }; + 33CC10EE2044A3C60003C045 /* Products */ = { + isa = PBXGroup; + children = ( + 33CC10ED2044A3C60003C045 /* Firebase Cloud Messaging Example.app */, + ); + name = Products; + sourceTree = ""; + }; + 33CC11242044D66E0003C045 /* Resources */ = { + isa = PBXGroup; + children = ( + 33CC10F22044A3C60003C045 /* Assets.xcassets */, + 33CC10F42044A3C60003C045 /* MainMenu.xib */, + 33CC10F72044A3C60003C045 /* Info.plist */, + ); + name = Resources; + path = ..; + sourceTree = ""; + }; + 33CEB47122A05771004F2AC0 /* Flutter */ = { + isa = PBXGroup; + children = ( + 335BBD1A22A9A15E00E9071D /* GeneratedPluginRegistrant.swift */, + 33CEB47222A05771004F2AC0 /* Flutter-Debug.xcconfig */, + 33CEB47422A05771004F2AC0 /* Flutter-Release.xcconfig */, + 33CEB47722A0578A004F2AC0 /* Flutter-Generated.xcconfig */, + D73912EF22F37F9E000D13A0 /* App.framework */, + 33D1A10322148B71006C7A3E /* FlutterMacOS.framework */, + ); + path = Flutter; + sourceTree = ""; + }; + 33FAB671232836740065AC1E /* Runner */ = { + isa = PBXGroup; + children = ( + B550B1FB23F53648007DADD5 /* GoogleService-Info.plist */, + 33CC10F02044A3C60003C045 /* AppDelegate.swift */, + 33CC11122044BFA00003C045 /* MainFlutterWindow.swift */, + 33E51913231747F40026EE4D /* DebugProfile.entitlements */, + 33E51914231749380026EE4D /* Release.entitlements */, + 33CC11242044D66E0003C045 /* Resources */, + 33BA886A226E78AF003329D5 /* Configs */, + ); + path = Runner; + sourceTree = ""; + }; + D73912EC22F37F3D000D13A0 /* Frameworks */ = { + isa = PBXGroup; + children = ( + 1026236A547BC5196614E954 /* Pods_Runner.framework */, + ); + name = Frameworks; + sourceTree = ""; + }; +/* End PBXGroup section */ + +/* Begin PBXNativeTarget section */ + 33CC10EC2044A3C60003C045 /* Runner */ = { + isa = PBXNativeTarget; + buildConfigurationList = 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */; + buildPhases = ( + 9C4FABD4FD7B8936CFFEAF31 /* [CP] Check Pods Manifest.lock */, + 33CC10E92044A3C60003C045 /* Sources */, + 33CC10EA2044A3C60003C045 /* Frameworks */, + 33CC10EB2044A3C60003C045 /* Resources */, + 33CC110E2044A8840003C045 /* Bundle Framework */, + 3399D490228B24CF009A79C7 /* ShellScript */, + E033F9E34514FF7F419D8FF5 /* [CP] Embed Pods Frameworks */, + ); + buildRules = ( + ); + dependencies = ( + 33CC11202044C79F0003C045 /* PBXTargetDependency */, + ); + name = Runner; + productName = Runner; + productReference = 33CC10ED2044A3C60003C045 /* Firebase Cloud Messaging Example.app */; + productType = "com.apple.product-type.application"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 33CC10E52044A3C60003C045 /* Project object */ = { + isa = PBXProject; + attributes = { + LastSwiftUpdateCheck = 0920; + LastUpgradeCheck = 0930; + ORGANIZATIONNAME = "The Flutter Authors"; + TargetAttributes = { + 33CC10EC2044A3C60003C045 = { + CreatedOnToolsVersion = 9.2; + LastSwiftMigration = 1100; + ProvisioningStyle = Automatic; + SystemCapabilities = { + com.apple.Sandbox = { + enabled = 1; + }; + }; + }; + 33CC111A2044C6BA0003C045 = { + CreatedOnToolsVersion = 9.2; + ProvisioningStyle = Manual; + }; + }; + }; + buildConfigurationList = 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */; + compatibilityVersion = "Xcode 8.0"; + developmentRegion = en; + hasScannedForEncodings = 0; + knownRegions = ( + en, + Base, + ); + mainGroup = 33CC10E42044A3C60003C045; + productRefGroup = 33CC10EE2044A3C60003C045 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 33CC10EC2044A3C60003C045 /* Runner */, + 33CC111A2044C6BA0003C045 /* Flutter Assemble */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 33CC10EB2044A3C60003C045 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC10F32044A3C60003C045 /* Assets.xcassets in Resources */, + 33CC10F62044A3C60003C045 /* MainMenu.xib in Resources */, + B550B1FC23F53648007DADD5 /* GoogleService-Info.plist in Resources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 3399D490228B24CF009A79C7 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "echo \"$PRODUCT_NAME.app\" > \"$PROJECT_DIR\"/Flutter/ephemeral/.app_filename\n"; + }; + 33CC111E2044C6BF0003C045 /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + Flutter/ephemeral/FlutterInputs.xcfilelist, + ); + inputPaths = ( + Flutter/ephemeral/tripwire, + ); + outputFileListPaths = ( + Flutter/ephemeral/FlutterOutputs.xcfilelist, + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"$FLUTTER_ROOT\"/packages/flutter_tools/bin/macos_assemble.sh\ntouch Flutter/ephemeral/tripwire\n"; + }; + 9C4FABD4FD7B8936CFFEAF31 /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", + ); + name = "[CP] Check Pods Manifest.lock"; + outputFileListPaths = ( + ); + outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt", + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; + showEnvVarsInLog = 0; + }; + E033F9E34514FF7F419D8FF5 /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputFileListPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 33CC10E92044A3C60003C045 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 33CC11132044BFA00003C045 /* MainFlutterWindow.swift in Sources */, + 33CC10F12044A3C60003C045 /* AppDelegate.swift in Sources */, + 335BBD1B22A9A15E00E9071D /* GeneratedPluginRegistrant.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 33CC11202044C79F0003C045 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 33CC111A2044C6BA0003C045 /* Flutter Assemble */; + targetProxy = 33CC111F2044C79F0003C045 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin PBXVariantGroup section */ + 33CC10F42044A3C60003C045 /* MainMenu.xib */ = { + isa = PBXVariantGroup; + children = ( + 33CC10F52044A3C60003C045 /* Base */, + ); + name = MainMenu.xib; + path = Runner; + sourceTree = ""; + }; +/* End PBXVariantGroup section */ + +/* Begin XCBuildConfiguration section */ + 338D0CE9231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Profile; + }; + 338D0CEA231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = YYX2P3XVJ7; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter/ephemeral", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.firebase.messaging; + PRODUCT_NAME = "Firebase Cloud Messaging Example"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Profile; + }; + 338D0CEB231458BD00FA5F75 /* Profile */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Profile; + }; + 33CC10F92044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = macosx; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + }; + name = Debug; + }; + 33CC10FA2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CODE_SIGN_IDENTITY = "-"; + COPY_PHASE_STRIP = NO; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu11; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + MACOSX_DEPLOYMENT_TARGET = 10.11; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = macosx; + SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_OPTIMIZATION_LEVEL = "-O"; + }; + name = Release; + }; + 33CC10FC2044A3C60003C045 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/DebugProfile.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = YYX2P3XVJ7; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter/ephemeral", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.firebase.messaging; + PRODUCT_NAME = "Firebase Cloud Messaging Example"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 5.0; + }; + name = Debug; + }; + 33CC10FD2044A3C60003C045 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 33E5194F232828860026EE4D /* AppInfo.xcconfig */; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; + CLANG_ENABLE_MODULES = YES; + CODE_SIGN_ENTITLEMENTS = Runner/Release.entitlements; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; + COMBINE_HIDPI_IMAGES = YES; + DEVELOPMENT_TEAM = YYX2P3XVJ7; + FRAMEWORK_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/Flutter/ephemeral", + ); + INFOPLIST_FILE = Runner/Info.plist; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/../Frameworks", + ); + PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.firebase.messaging; + PRODUCT_NAME = "Firebase Cloud Messaging Example"; + PROVISIONING_PROFILE_SPECIFIER = ""; + SWIFT_VERSION = 5.0; + }; + name = Release; + }; + 33CC111C2044C6BA0003C045 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Manual; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 33CC111D2044C6BA0003C045 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CODE_SIGN_STYLE = Automatic; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 33CC10E82044A3C60003C045 /* Build configuration list for PBXProject "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10F92044A3C60003C045 /* Debug */, + 33CC10FA2044A3C60003C045 /* Release */, + 338D0CE9231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC10FB2044A3C60003C045 /* Build configuration list for PBXNativeTarget "Runner" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC10FC2044A3C60003C045 /* Debug */, + 33CC10FD2044A3C60003C045 /* Release */, + 338D0CEA231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 33CC111B2044C6BA0003C045 /* Build configuration list for PBXAggregateTarget "Flutter Assemble" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 33CC111C2044C6BA0003C045 /* Debug */, + 33CC111D2044C6BA0003C045 /* Release */, + 338D0CEB231458BD00FA5F75 /* Profile */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 33CC10E52044A3C60003C045 /* Project object */; +} diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata b/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata new file mode 100644 index 000000000000..764c74b8df34 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcodeproj/project.xcworkspace/contents.xcworkspacedata @@ -0,0 +1,7 @@ + + + + + diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme new file mode 100644 index 000000000000..625b146836c3 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -0,0 +1,97 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/firebase_messaging/example/ios/Runner.xcworkspace/contents.xcworkspacedata b/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcworkspace/contents.xcworkspacedata similarity index 100% rename from packages/firebase_messaging/example/ios/Runner.xcworkspace/contents.xcworkspacedata rename to packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcworkspace/contents.xcworkspacedata diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000000..18d981003d68 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/AppDelegate.swift b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/AppDelegate.swift new file mode 100644 index 000000000000..d53ef6437726 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/AppDelegate.swift @@ -0,0 +1,9 @@ +import Cocoa +import FlutterMacOS + +@NSApplicationMain +class AppDelegate: FlutterAppDelegate { + override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { + return true + } +} diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json new file mode 100644 index 000000000000..0c3c9becd350 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -0,0 +1,68 @@ +{ + "images" : [ + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-16.png", + "scale" : "1x" + }, + { + "size" : "16x16", + "idiom" : "mac", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-32.png", + "scale" : "2x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-32.png", + "scale" : "1x" + }, + { + "size" : "32x32", + "idiom" : "mac", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-64.png", + "scale" : "2x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-128.png", + "scale" : "1x" + }, + { + "size" : "128x128", + "idiom" : "mac", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-256.png", + "scale" : "2x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-256.png", + "scale" : "1x" + }, + { + "size" : "256x256", + "idiom" : "mac", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-512.png", + "scale" : "2x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-512.png", + "scale" : "1x" + }, + { + "size" : "512x512", + "idiom" : "mac", + "filename" : "Firebase Cloud Messaging (1- Icon, Light)-1024.png", + "scale" : "2x" + } + ], + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-1024.png b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-1024.png new file mode 100644 index 000000000000..3ab3bb42be8d Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-1024.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-128.png b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-128.png new file mode 100644 index 000000000000..b42a8e5a9838 Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-128.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-16.png b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-16.png new file mode 100644 index 000000000000..b78425418515 Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-16.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-256.png b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-256.png new file mode 100644 index 000000000000..fa9b209178e5 Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-256.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-32.png b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-32.png new file mode 100644 index 000000000000..ecf26fd2767a Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-32.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-512.png b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-512.png new file mode 100644 index 000000000000..b1a06d1bf2f9 Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-512.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-64.png b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-64.png new file mode 100644 index 000000000000..23224c836a53 Binary files /dev/null and b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Assets.xcassets/AppIcon.appiconset/Firebase Cloud Messaging (1- Icon, Light)-64.png differ diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Base.lproj/MainMenu.xib b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Base.lproj/MainMenu.xib new file mode 100644 index 000000000000..537341abf994 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Base.lproj/MainMenu.xib @@ -0,0 +1,339 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Configs/AppInfo.xcconfig b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Configs/AppInfo.xcconfig new file mode 100644 index 000000000000..f653d20264ba --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Configs/AppInfo.xcconfig @@ -0,0 +1,14 @@ +// Application-level settings for the Runner target. +// +// This may be replaced with something auto-generated from metadata (e.g., pubspec.yaml) in the +// future. If not, the values below would default to using the project name when this becomes a +// 'flutter create' template. + +// The application's name. By default this is also the title of the Flutter window. +PRODUCT_NAME = example + +// The application's bundle identifier +PRODUCT_BUNDLE_IDENTIFIER = io.flutter.plugins.example + +// The copyright displayed in application information +PRODUCT_COPYRIGHT = Copyright © 2020 io.flutter.plugins. All rights reserved. diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Configs/Debug.xcconfig b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Configs/Debug.xcconfig new file mode 100644 index 000000000000..36b0fd9464f4 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Configs/Debug.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Debug.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Configs/Release.xcconfig b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Configs/Release.xcconfig new file mode 100644 index 000000000000..dff4f49561c8 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Configs/Release.xcconfig @@ -0,0 +1,2 @@ +#include "../../Flutter/Flutter-Release.xcconfig" +#include "Warnings.xcconfig" diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Configs/Warnings.xcconfig b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Configs/Warnings.xcconfig new file mode 100644 index 000000000000..42bcbf4780b1 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Configs/Warnings.xcconfig @@ -0,0 +1,13 @@ +WARNING_CFLAGS = -Wall -Wconditional-uninitialized -Wnullable-to-nonnull-conversion -Wmissing-method-return-type -Woverlength-strings +GCC_WARN_UNDECLARED_SELECTOR = YES +CLANG_UNDEFINED_BEHAVIOR_SANITIZER_NULLABILITY = YES +CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE +CLANG_WARN__DUPLICATE_METHOD_MATCH = YES +CLANG_WARN_PRAGMA_PACK = YES +CLANG_WARN_STRICT_PROTOTYPES = YES +CLANG_WARN_COMMA = YES +GCC_WARN_STRICT_SELECTOR_MATCH = YES +CLANG_WARN_OBJC_REPEATED_USE_OF_WEAK = YES +CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES +GCC_WARN_SHADOW = YES +CLANG_WARN_UNREACHABLE_CODE = YES diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/DebugProfile.entitlements b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/DebugProfile.entitlements new file mode 100644 index 000000000000..b76b509eebbf --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/DebugProfile.entitlements @@ -0,0 +1,16 @@ + + + + + com.apple.developer.aps-environment + development + com.apple.security.app-sandbox + + com.apple.security.cs.allow-jit + + com.apple.security.network.client + + com.apple.security.network.server + + + diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/GoogleService-Info.plist b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/GoogleService-Info.plist new file mode 100644 index 000000000000..0b158f509537 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/GoogleService-Info.plist @@ -0,0 +1,38 @@ + + + + + CLIENT_ID + 448618578101-evbjdqq9co9v29pi8jcua8bm7kr4smuu.apps.googleusercontent.com + REVERSED_CLIENT_ID + com.googleusercontent.apps.448618578101-evbjdqq9co9v29pi8jcua8bm7kr4smuu + ANDROID_CLIENT_ID + 448618578101-npu5ll2fj4emgvtietpb685fvdm7fg3s.apps.googleusercontent.com + API_KEY + AIzaSyAHAsf51D0A407EklG1bs-5wA7EbyfNFg0 + GCM_SENDER_ID + 448618578101 + PLIST_VERSION + 1 + BUNDLE_ID + io.flutter.plugins.firebase.messaging + PROJECT_ID + react-native-firebase-testing + STORAGE_BUCKET + react-native-firebase-testing.appspot.com + IS_ADS_ENABLED + + IS_ANALYTICS_ENABLED + + IS_APPINVITE_ENABLED + + IS_GCM_ENABLED + + IS_SIGNIN_ENABLED + + GOOGLE_APP_ID + 1:448618578101:ios:0b11ed8263232715ac3efc + DATABASE_URL + https://react-native-firebase-testing.firebaseio.com + + diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Info.plist b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Info.plist new file mode 100644 index 000000000000..a3ca3d6eac3e --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Info.plist @@ -0,0 +1,43 @@ + + + + + CFBundleDevelopmentRegion + $(DEVELOPMENT_LANGUAGE) + CFBundleExecutable + $(EXECUTABLE_NAME) + CFBundleIconFile + + CFBundleIdentifier + $(PRODUCT_BUNDLE_IDENTIFIER) + CFBundleInfoDictionaryVersion + 6.0 + CFBundleName + $(PRODUCT_NAME) + CFBundlePackageType + APPL + CFBundleShortVersionString + $(FLUTTER_BUILD_NAME) + CFBundleURLTypes + + + CFBundleTypeRole + Editor + CFBundleURLSchemes + + com.googleusercontent.apps.448618578101-ja1be10uicsa2dvss16gh4hkqks0vq61 + + + + CFBundleVersion + $(FLUTTER_BUILD_NUMBER) + LSMinimumSystemVersion + $(MACOSX_DEPLOYMENT_TARGET) + NSHumanReadableCopyright + $(PRODUCT_COPYRIGHT) + NSMainNibFile + MainMenu + NSPrincipalClass + NSApplication + + diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/MainFlutterWindow.swift b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/MainFlutterWindow.swift new file mode 100644 index 000000000000..2722837ec918 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/MainFlutterWindow.swift @@ -0,0 +1,15 @@ +import Cocoa +import FlutterMacOS + +class MainFlutterWindow: NSWindow { + override func awakeFromNib() { + let flutterViewController = FlutterViewController.init() + let windowFrame = self.frame + self.contentViewController = flutterViewController + self.setFrame(windowFrame, display: true) + + RegisterGeneratedPlugins(registry: flutterViewController) + + super.awakeFromNib() + } +} diff --git a/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Release.entitlements b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Release.entitlements new file mode 100644 index 000000000000..39bde1896e64 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/macos/Runner/Release.entitlements @@ -0,0 +1,12 @@ + + + + + com.apple.developer.aps-environment + development + com.apple.security.app-sandbox + + com.apple.security.network.client + + + diff --git a/packages/firebase_messaging/example/pubspec.yaml b/packages/firebase_messaging/firebase_messaging/example/pubspec.yaml similarity index 52% rename from packages/firebase_messaging/example/pubspec.yaml rename to packages/firebase_messaging/firebase_messaging/example/pubspec.yaml index 928ded9de680..f5f9b3feb6f4 100644 --- a/packages/firebase_messaging/example/pubspec.yaml +++ b/packages/firebase_messaging/firebase_messaging/example/pubspec.yaml @@ -2,26 +2,30 @@ name: firebase_messaging_example description: Demonstrates how to use the firebase_messaging plugin. dependencies: - firebase_analytics: ^6.0.2 flutter: sdk: flutter firebase_messaging: path: ../ - firebase_core: - path: ../../firebase_core/firebase_core + firebase_core: + path: ../../../firebase_core/firebase_core + http: 0.12.2 + flutter_local_notifications: ^3.0.0 dependency_overrides: firebase_core: - path: ../../firebase_core/firebase_core + path: ../../../firebase_core/firebase_core + firebase_messaging: + path: ../ + firebase_messaging_platform_interface: + path: ../../firebase_messaging_platform_interface dev_dependencies: pedantic: ^1.8.0 - flutter_test: - sdk: flutter - e2e: ^0.6.1 + drive: 0.0.1-6.0.pre flutter_driver: sdk: flutter test: any + e2e: ^0.6.1 flutter: uses-material-design: true diff --git a/packages/firebase_messaging/firebase_messaging/example/test_driver/firebase_messaging_e2e.dart b/packages/firebase_messaging/firebase_messaging/example/test_driver/firebase_messaging_e2e.dart new file mode 100644 index 000000000000..9b065692aa62 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/test_driver/firebase_messaging_e2e.dart @@ -0,0 +1,22 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:drive/drive.dart' as drive; +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'instance_e2e.dart'; + +// Requires that an emulator is running locally +bool USE_EMULATOR = false; + +void testsMain() { + setUpAll(() async { + await Firebase.initializeApp(); + }); + + runInstanceTests(); +} + +void main() => drive.main(testsMain); diff --git a/packages/firebase_messaging/example/test_driver/firebase_messaging_e2e_test.dart b/packages/firebase_messaging/firebase_messaging/example/test_driver/firebase_messaging_e2e_test.dart similarity index 73% rename from packages/firebase_messaging/example/test_driver/firebase_messaging_e2e_test.dart rename to packages/firebase_messaging/firebase_messaging/example/test_driver/firebase_messaging_e2e_test.dart index 27e264e5475b..f9c0fa99a519 100644 --- a/packages/firebase_messaging/example/test_driver/firebase_messaging_e2e_test.dart +++ b/packages/firebase_messaging/firebase_messaging/example/test_driver/firebase_messaging_e2e_test.dart @@ -2,6 +2,6 @@ // for details. All rights reserved. Use of this source code is governed by a // BSD-style license that can be found in the LICENSE file. -import 'package:e2e/e2e_driver.dart' as e2e; +import 'package:drive/drive_driver.dart' as drive; -void main() => e2e.main(); +void main() => drive.main(); diff --git a/packages/firebase_messaging/firebase_messaging/example/test_driver/instance_e2e.dart b/packages/firebase_messaging/firebase_messaging/example/test_driver/instance_e2e.dart new file mode 100644 index 000000000000..b11685334463 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/test_driver/instance_e2e.dart @@ -0,0 +1,131 @@ +// Copyright 2020, the Chromium project messagingors. Please see the MESSAGINGORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +const bool SKIP_MANUAL_TESTS = bool.fromEnvironment('CI', defaultValue: false); + +void runInstanceTests() { + group('$FirebaseMessaging.instance', () { + FirebaseApp app; + FirebaseMessaging messaging; + + setUpAll(() async { + app = await Firebase.initializeApp(); + messaging = FirebaseMessaging.instance; + }); + + tearDownAll(() {}); + + test('instance', () { + expect(messaging, isA()); + expect(messaging.app, isA()); + expect(messaging.app.name, defaultFirebaseAppName); + }); + + group('app', () { + test('accessible from messaging.app', () { + expect(messaging.app, isA()); + expect(messaging.app.name, app.name); + }); + }); + + group('setAutoInitEnabled()', () { + test('sets the value', () async { + expect(messaging.isAutoInitEnabled, isTrue); + await messaging.setAutoInitEnabled(false); + expect(messaging.isAutoInitEnabled, isFalse); + }); + }); + + group('requestPermission', () { + test( + 'authorizationStatus returns AuthorizationStatus.authorized on Android', + () async { + final result = await messaging.requestPermission(); + expect(result, isA()); + expect(result.authorizationStatus, AuthorizationStatus.authorized); + }, skip: defaultTargetPlatform != TargetPlatform.android); + }); + + group('getAPNSToken', () { + test('resolves null on android', () async { + expect(await messaging.getAPNSToken(), null); + }, skip: defaultTargetPlatform != TargetPlatform.android); + + test('resolves null on ios if using simulator', () async { + expect(await messaging.getAPNSToken(), null); + }, + skip: !(defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform != TargetPlatform.macOS)); + }); + + group('getInitialMessage', () { + test('returns null when no initial message', () async { + expect(await messaging.getInitialMessage(), null); + }); + }); + + group('getToken()', () { + test('returns a token', () async { + final result = await messaging.getToken(); + expect(result, isA()); + }); + }, skip: SKIP_MANUAL_TESTS); // only run for manual testing + + group('deleteToken()', () { + test('generate a new token after deleting', () async { + final token1 = await messaging.getToken(); + await messaging.deleteToken(); + final token2 = await messaging.getToken(); + expect(token1, isA()); + expect(token2, isA()); + expect(token1, isNot(token2)); + }, skip: SKIP_MANUAL_TESTS); // only run for manual testing + }); + + group('subscribeToTopic()', () { + test('successfully subscribes from topic', () async { + final topic = 'test-topic'; + await messaging.subscribeToTopic(topic); + }); + }); + + group('unsubscribeFromTopic()', () { + test('successfully unsubscribes from topic', () async { + final topic = 'test-topic'; + await messaging.unsubscribeFromTopic(topic); + }); + }); + + // deprecated methods + group('FirebaseMessaging (deprecated)', () { + test('returns an instance with the current [FirebaseApp]', () async { + // ignore: deprecated_member_use + final testInstance = FirebaseMessaging(); + expect(testInstance, isA()); + expect(testInstance.app, isA()); + expect(testInstance.app.name, defaultFirebaseAppName); + }); + }); + + group('autoInitEnabled (deprecated)', () { + test('returns correct value', () async { + // should now be false due to previous setAutoInitEnabled test. + expect(messaging.isAutoInitEnabled, isFalse); + // ignore: deprecated_member_use + expect(await messaging.autoInitEnabled(), messaging.isAutoInitEnabled); + + await messaging.setAutoInitEnabled(true); + + expect(messaging.isAutoInitEnabled, isTrue); + // ignore: deprecated_member_use + expect(await messaging.autoInitEnabled(), messaging.isAutoInitEnabled); + }); + }); + }); +} diff --git a/packages/firebase_messaging/firebase_messaging/example/test_driver/test_utils.dart b/packages/firebase_messaging/firebase_messaging/example/test_driver/test_utils.dart new file mode 100644 index 000000000000..b74e8cf8e864 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/example/test_driver/test_utils.dart @@ -0,0 +1,30 @@ +import 'package:firebase_core/firebase_core.dart'; + +import 'dart:io'; + +// Initializes a secondary app for testing +Future testInitializeSecondaryApp() async { + final String testAppName = 'testapp'; + + FirebaseOptions testAppOptions; + if (Platform.isIOS || Platform.isMacOS) { + testAppOptions = FirebaseOptions( + appId: '1:448618578101:ios:0b650370bb29e29cac3efc', + apiKey: 'AIzaSyAgUhHU8wSJgO5MVNy95tMT07NEjzMOfz0', + projectId: 'react-native-firebase-testing', + messagingSenderId: '448618578101', + iosBundleId: 'io.flutter.plugins.firebasecoreexample', + storageBucket: null, + ); + } else { + testAppOptions = FirebaseOptions( + appId: '1:448618578101:web:0b650370bb29e29cac3efc', + apiKey: 'AIzaSyAgUhHU8wSJgO5MVNy95tMT07NEjzMOfz0', + projectId: 'react-native-firebase-testing', + messagingSenderId: '448618578101', + storageBucket: null, + ); + } + + return Firebase.initializeApp(name: testAppName, options: testAppOptions); +} diff --git a/packages/firebase_messaging/ios/Assets/.gitkeep b/packages/firebase_messaging/firebase_messaging/ios/Assets/.gitkeep similarity index 100% rename from packages/firebase_messaging/ios/Assets/.gitkeep rename to packages/firebase_messaging/firebase_messaging/ios/Assets/.gitkeep diff --git a/packages/firebase_messaging/firebase_messaging/ios/Classes/FLTFirebaseMessagingPlugin.h b/packages/firebase_messaging/firebase_messaging/ios/Classes/FLTFirebaseMessagingPlugin.h new file mode 100644 index 000000000000..6e3ab6c65a1c --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/ios/Classes/FLTFirebaseMessagingPlugin.h @@ -0,0 +1,50 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#if TARGET_OS_OSX +#import +#else +#import +#endif + +#import +#import +#import +#import + +#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 +#define __FF_NOTIFICATIONS_SUPPORTED_PLATFORM +#elif defined(__MAC_10_14) +#define __FF_NOTIFICATIONS_SUPPORTED_PLATFORM +#endif + +// Suppress warning - use can add the Flutter plugin for Firebase Analytics. +#define FIREBASE_ANALYTICS_SUPPRESS_WARNING + +#if TARGET_OS_OSX +#ifdef __FF_NOTIFICATIONS_SUPPORTED_PLATFORM +@interface FLTFirebaseMessagingPlugin : FLTFirebasePlugin +#else +@interface FLTFirebaseMessagingPlugin : FLTFirebasePlugin +#endif +#else +#ifdef __FF_NOTIFICATIONS_SUPPORTED_PLATFORM +API_AVAILABLE(ios(10.0)) +@interface FLTFirebaseMessagingPlugin : FLTFirebasePlugin +#else +@interface FLTFirebaseMessagingPlugin + : FLTFirebasePlugin +#endif +#endif +@end diff --git a/packages/firebase_messaging/firebase_messaging/ios/Classes/FLTFirebaseMessagingPlugin.m b/packages/firebase_messaging/firebase_messaging/ios/Classes/FLTFirebaseMessagingPlugin.m new file mode 100644 index 000000000000..00c61545043d --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/ios/Classes/FLTFirebaseMessagingPlugin.m @@ -0,0 +1,988 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#import +#import +#import + +#import "FLTFirebaseMessagingPlugin.h" + +NSString *const kFLTFirebaseMessagingChannelName = @"plugins.flutter.io/firebase_messaging"; + +NSString *const kMessagingArgumentCode = @"code"; +NSString *const kMessagingArgumentMessage = @"message"; +NSString *const kMessagingArgumentAdditionalData = @"additionalData"; +NSString *const kMessagingPresentationOptionsUserDefaults = + @"flutter_firebase_messaging_presentation_options"; + +@implementation FLTFirebaseMessagingPlugin { + FlutterMethodChannel *_channel; + NSObject *_registrar; + NSDictionary *_initialNotification; + +#ifdef __FF_NOTIFICATIONS_SUPPORTED_PLATFORM + API_AVAILABLE(ios(10), macosx(10.14)) + __weak id _originalNotificationCenterDelegate; + API_AVAILABLE(ios(10), macosx(10.14)) + struct { + unsigned int willPresentNotification : 1; + unsigned int didReceiveNotificationResponse : 1; + unsigned int openSettingsForNotification : 1; + } _originalNotificationCenterDelegateRespondsTo; +#endif +} + +#pragma mark - FlutterPlugin + +- (instancetype)initWithFlutterMethodChannel:(FlutterMethodChannel *)channel + andFlutterPluginRegistrar:(NSObject *)registrar { + self = [super init]; + if (self) { + _channel = channel; + _registrar = registrar; + + // Application + // Dart -> `getInitialNotification` + // ObjC -> Initialize other delegates & observers + [[NSNotificationCenter defaultCenter] + addObserver:self + selector:@selector(application_onDidFinishLaunchingNotification:) +#if TARGET_OS_OSX + name:NSApplicationDidFinishLaunchingNotification +#else + name:UIApplicationDidFinishLaunchingNotification +#endif + object:nil]; + } + return self; +} + ++ (void)registerWithRegistrar:(NSObject *)registrar { + FlutterMethodChannel *channel = + [FlutterMethodChannel methodChannelWithName:kFLTFirebaseMessagingChannelName + binaryMessenger:[registrar messenger]]; + id instance = [[FLTFirebaseMessagingPlugin alloc] initWithFlutterMethodChannel:channel + andFlutterPluginRegistrar:registrar]; + // Register with internal FlutterFire plugin registry. + [[FLTFirebasePluginRegistry sharedInstance] registerFirebasePlugin:instance]; + + [registrar addMethodCallDelegate:instance channel:channel]; +#if !TARGET_OS_OSX + [registrar publish:instance]; // iOS only supported +#endif +} + +- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)flutterResult { + FLTFirebaseMethodCallErrorBlock errorBlock = ^( + NSString *_Nullable code, NSString *_Nullable message, NSDictionary *_Nullable details, + NSError *_Nullable error) { + if (code == nil) { + NSDictionary *errorDetails = [self NSDictionaryForNSError:error]; + code = errorDetails[kMessagingArgumentCode]; + message = errorDetails[kMessagingArgumentMessage]; + details = errorDetails; + } else { + details = @{ + kMessagingArgumentCode : code, + kMessagingArgumentMessage : message, + kMessagingArgumentAdditionalData : @{}, + }; + } + + if ([@"unknown" isEqualToString:code]) { + NSLog(@"FLTFirebaseMessaging: An error occurred while calling method %@, errorOrNil => %@", + call.method, [error userInfo]); + } + + flutterResult([FLTFirebasePlugin createFlutterErrorFromCode:code + message:message + optionalDetails:details + andOptionalNSError:error]); + }; + + FLTFirebaseMethodCallResult *methodCallResult = + [FLTFirebaseMethodCallResult createWithSuccess:flutterResult andErrorBlock:errorBlock]; + + if ([@"Messaging#getInitialMessage" isEqualToString:call.method]) { + methodCallResult.success([self copyInitialNotification]); + } else if ([@"Messaging#deleteToken" isEqualToString:call.method]) { + [self messagingDeleteToken:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"Messaging#getAPNSToken" isEqualToString:call.method]) { + [self messagingGetAPNSToken:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"Messaging#setForegroundNotificationPresentationOptions" + isEqualToString:call.method]) { + [self messagingSetForegroundNotificationPresentationOptions:call.arguments + withMethodCallResult:methodCallResult]; + } + if ([@"Messaging#getToken" isEqualToString:call.method]) { + [self messagingGetToken:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"Messaging#getNotificationSettings" isEqualToString:call.method]) { + if (@available(iOS 10, macOS 10.14, *)) { + [self messagingGetNotificationSettings:call.arguments withMethodCallResult:methodCallResult]; + } else { + // Defaults handled in Dart. + methodCallResult.success(@{}); + } + } else if ([@"Messaging#requestPermission" isEqualToString:call.method]) { + if (@available(iOS 10, macOS 10.14, *)) { + [self messagingRequestPermission:call.arguments withMethodCallResult:methodCallResult]; + } else { + // Defaults handled in Dart. + methodCallResult.success(@{}); + } + } else if ([@"Messaging#setAutoInitEnabled" isEqualToString:call.method]) { + [self messagingSetAutoInitEnabled:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"Messaging#subscribeToTopic" isEqualToString:call.method]) { + [self messagingSubscribeToTopic:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"Messaging#unsubscribeFromTopic" isEqualToString:call.method]) { + [self messagingUnsubscribeFromTopic:call.arguments withMethodCallResult:methodCallResult]; + } else if ([@"Messaging#startBackgroundIsolate" isEqualToString:call.method]) { + methodCallResult.success(nil); + } else { + methodCallResult.success(FlutterMethodNotImplemented); + } +} +- (void)messagingSetForegroundNotificationPresentationOptions:(id)arguments + withMethodCallResult: + (FLTFirebaseMethodCallResult *)result { + NSMutableDictionary *persistedOptions = [NSMutableDictionary dictionary]; + if ([arguments[@"alert"] isEqual:@(YES)]) { + persistedOptions[@"alert"] = @YES; + } + if ([arguments[@"badge"] isEqual:@(YES)]) { + persistedOptions[@"badge"] = @YES; + } + if ([arguments[@"sound"] isEqual:@(YES)]) { + persistedOptions[@"sound"] = @YES; + } + + [[NSUserDefaults standardUserDefaults] setObject:persistedOptions + forKey:kMessagingPresentationOptionsUserDefaults]; + result.success(nil); +} + +#pragma mark - Firebase Messaging Delegate + +- (void)messaging:(nonnull FIRMessaging *)messaging + didReceiveRegistrationToken:(nonnull NSString *)fcmToken { + // Don't crash if the token is reset. + if (fcmToken == nil) { + return; + } + + // Send to Dart. + [_channel invokeMethod:@"Messaging#onTokenRefresh" arguments:fcmToken]; + + // If the users AppDelegate implements messaging:didReceiveRegistrationToken: then call it as well + // so we don't break other libraries. + SEL messaging_didReceiveRegistrationTokenSelector = + NSSelectorFromString(@"messaging:didReceiveRegistrationToken:"); + if ([[GULAppDelegateSwizzler sharedApplication].delegate + respondsToSelector:messaging_didReceiveRegistrationTokenSelector]) { + void (*usersDidReceiveRegistrationTokenIMP)(id, SEL, FIRMessaging *, NSString *) = + (typeof(usersDidReceiveRegistrationTokenIMP)) & objc_msgSend; + usersDidReceiveRegistrationTokenIMP([GULAppDelegateSwizzler sharedApplication].delegate, + messaging_didReceiveRegistrationTokenSelector, messaging, + fcmToken); + } +} + +#pragma mark - NSNotificationCenter Observers + +- (void)application_onDidFinishLaunchingNotification:(nonnull NSNotification *)notification { + // Setup UIApplicationDelegate. +#if TARGET_OS_OSX + // For macOS we use swizzling to intercept as addApplicationDelegate does not exist on the macOS + // registrar Flutter implementation. + [GULAppDelegateSwizzler registerAppDelegateInterceptor:self]; + [GULAppDelegateSwizzler proxyOriginalDelegateIncludingAPNSMethods]; + + SEL didReceiveRemoteNotificationWithCompletionSEL = + NSSelectorFromString(@"application:didReceiveRemoteNotification:fetchCompletionHandler:"); + if ([[GULAppDelegateSwizzler sharedApplication].delegate + respondsToSelector:didReceiveRemoteNotificationWithCompletionSEL]) { + // noop - user has own implementation of this method in their AppDelegate, this + // means GULAppDelegateSwizzler will have already replaced it with a donor method + } else { + // add our own donor implementation of + // application:didReceiveRemoteNotification:fetchCompletionHandler: + Method donorMethod = class_getInstanceMethod(object_getClass(self), + didReceiveRemoteNotificationWithCompletionSEL); + class_addMethod(object_getClass([GULAppDelegateSwizzler sharedApplication].delegate), + didReceiveRemoteNotificationWithCompletionSEL, + method_getImplementation(donorMethod), method_getTypeEncoding(donorMethod)); + } +#else + [_registrar addApplicationDelegate:self]; +#endif + + // Set UNUserNotificationCenter but preserve original delegate if necessary. + if (@available(iOS 10.0, macOS 10.14, *)) { + UNUserNotificationCenter *notificationCenter = + [UNUserNotificationCenter currentNotificationCenter]; + if (notificationCenter.delegate != nil) { + _originalNotificationCenterDelegate = notificationCenter.delegate; + _originalNotificationCenterDelegateRespondsTo.openSettingsForNotification = + (unsigned int)[_originalNotificationCenterDelegate + respondsToSelector:@selector(userNotificationCenter:openSettingsForNotification:)]; + _originalNotificationCenterDelegateRespondsTo.willPresentNotification = + (unsigned int)[_originalNotificationCenterDelegate + respondsToSelector:@selector(userNotificationCenter: + willPresentNotification:withCompletionHandler:)]; + _originalNotificationCenterDelegateRespondsTo.didReceiveNotificationResponse = + (unsigned int)[_originalNotificationCenterDelegate + respondsToSelector:@selector(userNotificationCenter: + didReceiveNotificationResponse:withCompletionHandler:)]; + } + __strong FLTFirebasePlugin *strongSelf = self; + notificationCenter.delegate = strongSelf; + } + + // We automatically register for remote notifications as + // application:didReceiveRemoteNotification:fetchCompletionHandler: will not get called unless + // registerForRemoteNotifications is called early on during app initialization, calling this from + // Dart would be too late. +#if TARGET_OS_OSX + if (@available(macOS 10.14, *)) { + [[NSApplication sharedApplication] registerForRemoteNotifications]; + } +#else + [[UIApplication sharedApplication] registerForRemoteNotifications]; +#endif +} + +#pragma mark - UNUserNotificationCenter Delegate Methods + +#ifdef __FF_NOTIFICATIONS_SUPPORTED_PLATFORM +// Called when a notification is received whilst the app is in the foreground. +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + willPresentNotification:(UNNotification *)notification + withCompletionHandler: + (void (^)(UNNotificationPresentationOptions options))completionHandler + API_AVAILABLE(macos(10.14), ios(10.0)) { + // We only want to handle FCM notifications. + if (notification.request.content.userInfo[@"gcm.message_id"]) { + NSDictionary *notificationDict = + [FLTFirebaseMessagingPlugin NSDictionaryFromUNNotification:notification]; + + // Don't send an event if contentAvailable is true - application:didReceiveRemoteNotification + // will send the event for us, we don't want to duplicate them. + if (!notificationDict[@"contentAvailable"]) { + [_channel invokeMethod:@"Messaging#onMessage" arguments:notificationDict]; + } + } + + // Forward on to any other delegates amd allow them to control presentation behavior. + if (_originalNotificationCenterDelegate != nil && + _originalNotificationCenterDelegateRespondsTo.willPresentNotification) { + [_originalNotificationCenterDelegate userNotificationCenter:center + willPresentNotification:notification + withCompletionHandler:completionHandler]; + } else { + UNNotificationPresentationOptions presentationOptions = UNNotificationPresentationOptionNone; + NSDictionary *persistedOptions = [[NSUserDefaults standardUserDefaults] + dictionaryForKey:kMessagingPresentationOptionsUserDefaults]; + if (persistedOptions != nil) { + if ([persistedOptions[@"alert"] isEqual:@(YES)]) { + presentationOptions |= UNNotificationPresentationOptionAlert; + } + if ([persistedOptions[@"badge"] isEqual:@(YES)]) { + presentationOptions |= UNNotificationPresentationOptionBadge; + } + if ([persistedOptions[@"sound"] isEqual:@(YES)]) { + presentationOptions |= UNNotificationPresentationOptionSound; + } + } + completionHandler(presentationOptions); + } +} + +// Called when a use interacts with a notification. +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + didReceiveNotificationResponse:(UNNotificationResponse *)response + withCompletionHandler:(void (^)(void))completionHandler + API_AVAILABLE(macos(10.14), ios(10.0)) { + NSDictionary *remoteNotification = response.notification.request.content.userInfo; + // We only want to handle FCM notifications. + if (remoteNotification[@"gcm.message_id"]) { + NSDictionary *notificationDict = + [FLTFirebaseMessagingPlugin remoteMessageUserInfoToDict:remoteNotification]; + [_channel invokeMethod:@"Messaging#onMessageOpenedApp" arguments:notificationDict]; + @synchronized(self) { + _initialNotification = notificationDict; + } + } + + // Forward on to any other delegates. + if (_originalNotificationCenterDelegate != nil && + _originalNotificationCenterDelegateRespondsTo.didReceiveNotificationResponse) { + [_originalNotificationCenterDelegate userNotificationCenter:center + didReceiveNotificationResponse:response + withCompletionHandler:completionHandler]; + } else { + completionHandler(); + } +} + +// We don't use this for FlutterFire, but for the purpose of forwarding to any original delegates we +// implement this. +- (void)userNotificationCenter:(UNUserNotificationCenter *)center + openSettingsForNotification:(nullable UNNotification *)notification + API_AVAILABLE(macos(10.14), ios(10.0)) { + // Forward on to any other delegates. + if (_originalNotificationCenterDelegate != nil && + _originalNotificationCenterDelegateRespondsTo.openSettingsForNotification) { +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunguarded-availability-new" + [_originalNotificationCenterDelegate userNotificationCenter:center + openSettingsForNotification:notification]; +#pragma clang diagnostic pop + } +} + +#endif + +#pragma mark - AppDelegate Methods + +#if TARGET_OS_OSX +// Called when `registerForRemoteNotifications` completes successfully. +- (void)application:(NSApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { +#else +- (void)application:(UIApplication *)application + didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { +#endif +#ifdef DEBUG + [[FIRMessaging messaging] setAPNSToken:deviceToken type:FIRMessagingAPNSTokenTypeSandbox]; +#else + [[FIRMessaging messaging] setAPNSToken:deviceToken type:FIRMessagingAPNSTokenTypeProd]; +#endif +} + +#if TARGET_OS_OSX +// Called when `registerForRemoteNotifications` fails to complete. +- (void)application:(NSApplication *)application + didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { +#else +- (void)application:(UIApplication *)application + didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { +#endif + NSLog(@"%@", error.localizedDescription); +} + +// Called when a remote notification is received via APNs. +#if TARGET_OS_OSX +- (void)application:(NSApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo { +#if __has_include() + if ([[FIRAuth auth] canHandleNotification:userInfo]) { + return YES; + } +#endif + // Only handle notifications from FCM. + if (userInfo[@"gcm.message_id"]) { + NSDictionary *notificationDict = + [FLTFirebaseMessagingPlugin remoteMessageUserInfoToDict:userInfo]; + + if ([NSApplication sharedApplication].isActive) { + [_channel invokeMethod:@"Messaging#onMessage" arguments:notificationDict]; + } else { + [_channel invokeMethod:@"Messaging#onBackgroundMessage" arguments:notificationDict]; + } + } +} +#endif + +#if !TARGET_OS_OSX +- (BOOL)application:(UIApplication *)application + didReceiveRemoteNotification:(NSDictionary *)userInfo + fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { +#if __has_include() + if ([[FIRAuth auth] canHandleNotification:userInfo]) { + completionHandler(UIBackgroundFetchResultNoData); + return YES; + } +#endif + + // Only handle notifications from FCM. + if (userInfo[@"gcm.message_id"]) { + NSDictionary *notificationDict = + [FLTFirebaseMessagingPlugin remoteMessageUserInfoToDict:userInfo]; + + if ([UIApplication sharedApplication].applicationState == UIApplicationStateBackground) { + __block BOOL completed = NO; + + // If app is in background state, register background task to guarantee async queues aren't + // frozen. + UIBackgroundTaskIdentifier __block backgroundTaskId = + [application beginBackgroundTaskWithExpirationHandler:^{ + @synchronized(self) { + if (completed == NO) { + completed = YES; + completionHandler(UIBackgroundFetchResultNewData); + if (backgroundTaskId != UIBackgroundTaskInvalid) { + [application endBackgroundTask:backgroundTaskId]; + backgroundTaskId = UIBackgroundTaskInvalid; + } + } + } + }]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(25 * NSEC_PER_SEC)), + dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{ + @synchronized(self) { + if (completed == NO) { + completed = YES; + completionHandler(UIBackgroundFetchResultNewData); + if (backgroundTaskId != UIBackgroundTaskInvalid) { + [application endBackgroundTask:backgroundTaskId]; + backgroundTaskId = UIBackgroundTaskInvalid; + } + } + } + }); + + [_channel invokeMethod:@"Messaging#onBackgroundMessage" + arguments:notificationDict + result:^(id _Nullable result) { + @synchronized(self) { + if (completed == NO) { + completed = YES; + completionHandler(UIBackgroundFetchResultNewData); + if (backgroundTaskId != UIBackgroundTaskInvalid) { + [application endBackgroundTask:backgroundTaskId]; + backgroundTaskId = UIBackgroundTaskInvalid; + } + } + } + }]; + } else { + [_channel invokeMethod:@"Messaging#onMessage" arguments:notificationDict]; + completionHandler(UIBackgroundFetchResultNoData); + } + + return YES; + } // if (userInfo[@"gcm.message_id"]) + return NO; +} // didReceiveRemoteNotification +#endif + +#pragma mark - Firebase Messaging API + +- (void)messagingUnsubscribeFromTopic:(id)arguments + withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRMessaging *messaging = [FIRMessaging messaging]; + NSString *topic = arguments[@"topic"]; + [messaging unsubscribeFromTopic:topic + completion:^(NSError *error) { + if (error != nil) { + result.error(nil, nil, nil, error); + } else { + result.success(nil); + } + }]; +} + +- (void)messagingSubscribeToTopic:(id)arguments + withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRMessaging *messaging = [FIRMessaging messaging]; + NSString *topic = arguments[@"topic"]; + [messaging subscribeToTopic:topic + completion:^(NSError *error) { + if (error != nil) { + result.error(nil, nil, nil, error); + } else { + result.success(nil); + } + }]; +} + +- (void)messagingSetAutoInitEnabled:(id)arguments + withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRMessaging *messaging = [FIRMessaging messaging]; + messaging.autoInitEnabled = [arguments[@"enabled"] boolValue]; + result.success(@{ + @"isAutoInitEnabled" : @(messaging.isAutoInitEnabled), + }); +} + +- (void)messagingRequestPermission:(id)arguments + withMethodCallResult:(FLTFirebaseMethodCallResult *)result + API_AVAILABLE(ios(10), macosx(10.14)) { + NSDictionary *permissions = arguments[@"permissions"]; + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + + UNAuthorizationOptions options = UNAuthorizationOptionNone; + + if ([permissions[@"alert"] isEqual:@(YES)]) { + options |= UNAuthorizationOptionAlert; + } + + if ([permissions[@"badge"] isEqual:@(YES)]) { + options |= UNAuthorizationOptionBadge; + } + + if ([permissions[@"sound"] isEqual:@(YES)]) { + options |= UNAuthorizationOptionSound; + } + + if ([permissions[@"provisional"] isEqual:@(YES)]) { + if (@available(iOS 12.0, *)) { + options |= UNAuthorizationOptionProvisional; + } + } + + if ([permissions[@"announcement"] isEqual:@(YES)]) { + if (@available(iOS 13.0, *)) { + // TODO not available in iOS9 deployment target - enable once iOS10+ deployment target + // specified in podspec. options |= UNAuthorizationOptionAnnouncement; + } + } + + if ([permissions[@"carPlay"] isEqual:@(YES)]) { + options |= UNAuthorizationOptionCarPlay; + } + + if ([permissions[@"criticalAlert"] isEqual:@(YES)]) { + if (@available(iOS 12.0, *)) { + options |= UNAuthorizationOptionCriticalAlert; + } + } + + id handler = ^(BOOL granted, NSError *_Nullable error) { + if (error != nil) { + result.error(nil, nil, nil, error); + } else { + [center getNotificationSettingsWithCompletionHandler:^( + UNNotificationSettings *_Nonnull settings) { + result.success( + [FLTFirebaseMessagingPlugin NSDictionaryFromUNNotificationSettings:settings]); + }]; + } + }; + + [center requestAuthorizationWithOptions:options completionHandler:handler]; +} + +- (void)messagingGetNotificationSettings:(id)arguments + withMethodCallResult:(FLTFirebaseMethodCallResult *)result + API_AVAILABLE(ios(10), macos(10.14)) { + UNUserNotificationCenter *center = [UNUserNotificationCenter currentNotificationCenter]; + [center getNotificationSettingsWithCompletionHandler:^( + UNNotificationSettings *_Nonnull settings) { + result.success([FLTFirebaseMessagingPlugin NSDictionaryFromUNNotificationSettings:settings]); + }]; +} + +- (void)messagingGetToken:(id)arguments withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRMessaging *messaging = [FIRMessaging messaging]; + NSString *senderId = arguments[@"senderId"] ?: [FIRApp defaultApp].options.GCMSenderID; + [messaging retrieveFCMTokenForSenderID:senderId + completion:^(NSString *token, NSError *error) { + if (error != nil) { + result.error(nil, nil, nil, error); + } else { + result.success(token); + } + }]; +} + +- (void)messagingGetAPNSToken:(id)arguments + withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + NSData *apnsToken = [FIRMessaging messaging].APNSToken; + if (apnsToken) { + result.success([FLTFirebaseMessagingPlugin APNSTokenFromNSData:apnsToken]); + } else { + result.success([NSNull null]); + } +} + +- (void)messagingDeleteToken:(id)arguments + withMethodCallResult:(FLTFirebaseMethodCallResult *)result { + FIRMessaging *messaging = [FIRMessaging messaging]; + NSString *senderId = arguments[@"senderId"] ?: [FIRApp defaultApp].options.GCMSenderID; + [messaging deleteFCMTokenForSenderID:senderId + completion:^(NSError *error) { + if (error != nil) { + result.error(nil, nil, nil, error); + } else { + result.success(nil); + } + }]; +} + +#pragma mark - FLTFirebasePlugin + +- (void)didReinitializeFirebaseCore:(void (^)(void))completion { + completion(); +} + +- (NSDictionary *_Nonnull)pluginConstantsForFIRApp:(FIRApp *)firebase_app { + return @{ + @"AUTO_INIT_ENABLED" : @([FIRMessaging messaging].isAutoInitEnabled), + }; +} + +- (NSString *_Nonnull)firebaseLibraryName { + return LIBRARY_NAME; +} + +- (NSString *_Nonnull)firebaseLibraryVersion { + return LIBRARY_VERSION; +} + +- (NSString *_Nonnull)flutterChannelName { + return kFLTFirebaseMessagingChannelName; +} + +#pragma mark - Utilities + ++ (NSDictionary *)NSDictionaryFromUNNotificationSettings:(UNNotificationSettings *_Nonnull)settings + API_AVAILABLE(ios(10), macos(10.14)) { + NSMutableDictionary *settingsDictionary = [NSMutableDictionary dictionary]; + + // authorizedStatus + NSNumber *authorizedStatus = @-1; + if (settings.authorizationStatus == UNAuthorizationStatusNotDetermined) { + authorizedStatus = @-1; + } else if (settings.authorizationStatus == UNAuthorizationStatusDenied) { + authorizedStatus = @0; + } else if (settings.authorizationStatus == UNAuthorizationStatusAuthorized) { + authorizedStatus = @1; + } + + if (@available(iOS 12.0, *)) { + if (settings.authorizationStatus == UNAuthorizationStatusProvisional) { + authorizedStatus = @2; + } + } + + NSNumber *showPreviews = @-1; + if (@available(iOS 11.0, *)) { + if (settings.showPreviewsSetting == UNShowPreviewsSettingNever) { + showPreviews = @0; + } else if (settings.showPreviewsSetting == UNShowPreviewsSettingAlways) { + showPreviews = @1; + } else if (settings.showPreviewsSetting == UNShowPreviewsSettingWhenAuthenticated) { + showPreviews = @2; + } + } + +#pragma clang diagnostic push +#pragma ide diagnostic ignored "OCSimplifyInspectionLegacy" + if (@available(iOS 13.0, *)) { + // TODO not available in iOS9 deployment target - enable once iOS10+ deployment target specified + // in podspec. settingsDictionary[@"announcement"] = + // [FLTFirebaseMessagingPlugin NSNumberForUNNotificationSetting:settings.announcementSetting]; + settingsDictionary[@"announcement"] = @-1; + } else { + settingsDictionary[@"announcement"] = @-1; + } +#pragma clang diagnostic pop + + if (@available(iOS 12.0, *)) { + settingsDictionary[@"criticalAlert"] = + [FLTFirebaseMessagingPlugin NSNumberForUNNotificationSetting:settings.criticalAlertSetting]; + } else { + settingsDictionary[@"criticalAlert"] = @-1; + } + + settingsDictionary[@"showPreviews"] = showPreviews; + settingsDictionary[@"authorizationStatus"] = authorizedStatus; + settingsDictionary[@"alert"] = + [FLTFirebaseMessagingPlugin NSNumberForUNNotificationSetting:settings.alertSetting]; + settingsDictionary[@"badge"] = + [FLTFirebaseMessagingPlugin NSNumberForUNNotificationSetting:settings.badgeSetting]; + settingsDictionary[@"sound"] = + [FLTFirebaseMessagingPlugin NSNumberForUNNotificationSetting:settings.soundSetting]; +#if TARGET_OS_OSX + settingsDictionary[@"carPlay"] = @-1; +#else + settingsDictionary[@"carPlay"] = + [FLTFirebaseMessagingPlugin NSNumberForUNNotificationSetting:settings.carPlaySetting]; +#endif + settingsDictionary[@"lockScreen"] = + [FLTFirebaseMessagingPlugin NSNumberForUNNotificationSetting:settings.lockScreenSetting]; + settingsDictionary[@"notificationCenter"] = [FLTFirebaseMessagingPlugin + NSNumberForUNNotificationSetting:settings.notificationCenterSetting]; + return settingsDictionary; +} + ++ (NSNumber *)NSNumberForUNNotificationSetting:(UNNotificationSetting)setting + API_AVAILABLE(ios(10), macos(10.14)) { + NSNumber *asNumber = @-1; + + if (setting == UNNotificationSettingNotSupported) { + asNumber = @-1; + } else if (setting == UNNotificationSettingDisabled) { + asNumber = @0; + } else if (setting == UNNotificationSettingEnabled) { + asNumber = @1; + } + return asNumber; +} + ++ (NSString *)APNSTokenFromNSData:(NSData *)tokenData { + const char *data = [tokenData bytes]; + + NSMutableString *token = [NSMutableString string]; + for (NSInteger i = 0; i < tokenData.length; i++) { + [token appendFormat:@"%02.2hhX", data[i]]; + } + + return [token copy]; +} + +#if TARGET_OS_OSX ++ (NSDictionary *)NSDictionaryFromUNNotification:(UNNotification *)notification + API_AVAILABLE(macos(10.14)) { +#else ++ (NSDictionary *)NSDictionaryFromUNNotification:(UNNotification *)notification { +#endif + return [self remoteMessageUserInfoToDict:notification.request.content.userInfo]; +} + ++ (NSDictionary *)remoteMessageUserInfoToDict:(NSDictionary *)userInfo { + NSMutableDictionary *message = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *data = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *notification = [[NSMutableDictionary alloc] init]; + NSMutableDictionary *notificationIOS = [[NSMutableDictionary alloc] init]; + + // message.data + for (id key in userInfo) { + // message.messageId + if ([key isEqualToString:@"gcm.message_id"] || [key isEqualToString:@"google.message_id"] || + [key isEqualToString:@"message_id"]) { + message[@"messageId"] = userInfo[key]; + continue; + } + + // message.messageType + if ([key isEqualToString:@"message_type"]) { + message[@"messageType"] = userInfo[key]; + continue; + } + + // message.collapseKey + if ([key isEqualToString:@"collapse_key"]) { + message[@"collapseKey"] = userInfo[key]; + continue; + } + + // message.from + if ([key isEqualToString:@"from"]) { + message[@"from"] = userInfo[key]; + continue; + } + + // message.sentTime + if ([key isEqualToString:@"google.c.a.ts"]) { + message[@"sentTime"] = userInfo[key]; + continue; + } + + // message.to + if ([key isEqualToString:@"to"] || [key isEqualToString:@"google.to"]) { + message[@"to"] = userInfo[key]; + continue; + } + + // build data dict from remaining keys but skip keys that shouldn't be included in data + if ([key isEqualToString:@"aps"] || [key hasPrefix:@"gcm."] || [key hasPrefix:@"google."]) { + continue; + } + data[key] = userInfo[key]; + } + message[@"data"] = data; + + if (userInfo[@"aps"] != nil) { + NSDictionary *apsDict = userInfo[@"aps"]; + // message.category + if (apsDict[@"category"] != nil) { + message[@"category"] = apsDict[@"category"]; + } + + // message.threadId + if (apsDict[@"thread-id"] != nil) { + message[@"threadId"] = apsDict[@"thread-id"]; + } + + // message.contentAvailable + if (apsDict[@"content-available"] != nil) { + message[@"contentAvailable"] = @([apsDict[@"content-available"] boolValue]); + } + + // message.mutableContent + if (apsDict[@"mutable-content"] != nil && [apsDict[@"mutable-content"] intValue] == 1) { + message[@"mutableContent"] = @([apsDict[@"mutable-content"] boolValue]); + } + + // message.notification.* + if (apsDict[@"alert"] != nil) { + // can be a string or dictionary + if ([apsDict[@"alert"] isKindOfClass:[NSString class]]) { + // message.notification.title + notification[@"title"] = apsDict[@"alert"]; + } else if ([apsDict[@"alert"] isKindOfClass:[NSDictionary class]]) { + NSDictionary *apsAlertDict = apsDict[@"alert"]; + + // message.notification.title + if (apsAlertDict[@"title"] != nil) { + notification[@"title"] = apsAlertDict[@"title"]; + } + + // message.notification.titleLocKey + if (apsAlertDict[@"title-loc-key"] != nil) { + notification[@"titleLocKey"] = apsAlertDict[@"title-loc-key"]; + } + + // message.notification.titleLocArgs + if (apsAlertDict[@"title-loc-args"] != nil) { + notification[@"titleLocArgs"] = apsAlertDict[@"title-loc-args"]; + } + + // message.notification.body + if (apsAlertDict[@"body"] != nil) { + notification[@"body"] = apsAlertDict[@"body"]; + } + + // message.notification.bodyLocKey + if (apsAlertDict[@"loc-key"] != nil) { + notification[@"bodyLocKey"] = apsAlertDict[@"loc-key"]; + } + + // message.notification.bodyLocArgs + if (apsAlertDict[@"loc-args"] != nil) { + notification[@"bodyLocArgs"] = apsAlertDict[@"loc-args"]; + } + + // Apple only + // message.notification.apple.subtitle + if (apsAlertDict[@"subtitle"] != nil) { + notificationIOS[@"subtitle"] = apsAlertDict[@"subtitle"]; + } + + // Apple only + // message.notification.apple.subtitleLocKey + if (apsAlertDict[@"subtitle-loc-key"] != nil) { + notificationIOS[@"subtitleLocKey"] = apsAlertDict[@"subtitle-loc-key"]; + } + + // Apple only + // message.notification.apple.subtitleLocArgs + if (apsAlertDict[@"subtitle-loc-args"] != nil) { + notificationIOS[@"subtitleLocArgs"] = apsAlertDict[@"subtitle-loc-args"]; + } + + // Apple only + // message.notification.apple.badge + if (apsAlertDict[@"badge"] != nil) { + notificationIOS[@"badge"] = apsAlertDict[@"badge"]; + } + } + + notification[@"apple"] = notificationIOS; + message[@"notification"] = notification; + } + + // message.notification.apple.sound + if (apsDict[@"sound"] != nil) { + if ([apsDict[@"sound"] isKindOfClass:[NSString class]]) { + // message.notification.apple.sound + notification[@"sound"] = @{ + @"name" : apsDict[@"sound"], + @"critical" : @NO, + @"volume" : @1, + }; + } else if ([apsDict[@"sound"] isKindOfClass:[NSDictionary class]]) { + NSDictionary *apsSoundDict = apsDict[@"sound"]; + NSMutableDictionary *notificationIOSSound = [[NSMutableDictionary alloc] init]; + + // message.notification.apple.sound.name String + if (apsSoundDict[@"name"] != nil) { + notificationIOSSound[@"name"] = apsSoundDict[@"name"]; + } + + // message.notification.apple.sound.critical Boolean + if (apsSoundDict[@"critical"] != nil) { + notificationIOSSound[@"critical"] = @([apsSoundDict[@"critical"] boolValue]); + } + + // message.notification.apple.sound.volume Number + if (apsSoundDict[@"volume"] != nil) { + notificationIOSSound[@"volume"] = apsSoundDict[@"volume"]; + } + + // message.notification.apple.sound + notificationIOS[@"sound"] = notificationIOSSound; + } + + notification[@"apple"] = notificationIOS; + message[@"notification"] = notification; + } + } + + return message; +} + +- (nullable NSDictionary *)copyInitialNotification { + @synchronized(self) { + if (_initialNotification != nil) { + NSDictionary *initialNotificationCopy = [_initialNotification copy]; + _initialNotification = nil; + return initialNotificationCopy; + } + } + + return nil; +} + +- (NSDictionary *)NSDictionaryForNSError:(NSError *)error { + NSString *code = @"unknown"; + NSString *message = @"An unknown error has occurred."; + + if (error == nil) { + return @{ + kMessagingArgumentCode : code, + kMessagingArgumentMessage : message, + kMessagingArgumentAdditionalData : @{}, + }; + } + + // code - codes from taken from NSError+FIRMessaging.h + if (error.code == 4) { + code = @"unavailable"; + } else if (error.code == 7) { + code = @"invalid-request"; + } else if (error.code == 8) { + code = @"invalid-argument"; + } else if (error.code == 501) { + code = @"missing-device-id"; + } else if (error.code == 1001) { + code = @"unavailable"; + } else if (error.code == 1003) { + code = @"invalid-argument"; + } else if (error.code == 1004) { + code = @"save-failed"; + } else if (error.code == 1005) { + code = @"invalid-argument"; + } else if (error.code == 2001) { + code = @"already-connected"; + } else if (error.code == 3005) { + code = @"pubsub-operation-cancelled"; + } + + // message + if ([error userInfo][NSLocalizedDescriptionKey] != nil) { + message = [error userInfo][NSLocalizedDescriptionKey]; + } + + return @{ + kMessagingArgumentCode : code, + kMessagingArgumentMessage : message, + kMessagingArgumentAdditionalData : @{}, + }; +} + +@end diff --git a/packages/firebase_messaging/ios/firebase_messaging.podspec b/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging.podspec similarity index 97% rename from packages/firebase_messaging/ios/firebase_messaging.podspec rename to packages/firebase_messaging/firebase_messaging/ios/firebase_messaging.podspec index 3ec4ae3d95c5..947762dcbe49 100644 --- a/packages/firebase_messaging/ios/firebase_messaging.podspec +++ b/packages/firebase_messaging/firebase_messaging/ios/firebase_messaging.podspec @@ -25,17 +25,17 @@ Pod::Spec.new do |s| s.license = { :file => '../LICENSE' } s.authors = 'The Chromium Authors' s.source = { :path => '.' } - + s.source_files = 'Classes/**/*.{h,m}' s.public_header_files = 'Classes/*.h' - s.ios.deployment_target = '8.0' + s.ios.deployment_target = '9.0' + s.dependency 'Flutter' s.dependency 'firebase_core' s.dependency 'Firebase/CoreOnly', "~> #{firebase_sdk_version}" s.dependency 'Firebase/Messaging', "~> #{firebase_sdk_version}" - s.static_framework = true s.pod_target_xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => "LIBRARY_VERSION=\\@\\\"#{library_version}\\\" LIBRARY_NAME=\\@\\\"flutter-fire-fcm\\\"", diff --git a/packages/firebase_messaging/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/firebase_messaging/lib/firebase_messaging.dart new file mode 100644 index 000000000000..9dbb03dd3072 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/lib/firebase_messaging.dart @@ -0,0 +1,31 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +library firebase_messaging; + +import 'dart:async'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart' + show FirebasePluginPlatform; +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; + +export 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart' + show + BackgroundMessageHandler, + AppleShowPreviewSetting, + AppleNotification, + AppleNotificationSetting, + AppleNotificationSound, + AuthorizationStatus, + NotificationSettings, + // ignore: deprecated_member_use, deprecated_member_use_from_same_package + IosNotificationSettings, + AndroidNotification, + AndroidNotificationPriority, + AndroidNotificationVisibility, + RemoteMessage, + RemoteNotification; + +part 'src/messaging.dart'; diff --git a/packages/firebase_messaging/firebase_messaging/lib/src/messaging.dart b/packages/firebase_messaging/firebase_messaging/lib/src/messaging.dart new file mode 100644 index 000000000000..e78b1e964f87 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/lib/src/messaging.dart @@ -0,0 +1,368 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +part of firebase_messaging; + +/// The [FirebaseMessaging] entry point. +/// +/// To get a new instance, call [FirebaseMessaging.instance]. +class FirebaseMessaging extends FirebasePluginPlatform { + // Cached and lazily loaded instance of [FirebaseMessagingPlatform] to avoid + // creating a [MethodChannelFirebaseMessaging] when not needed or creating an + // instance with the default app before a user specifies an app. + FirebaseMessagingPlatform _delegatePackingProperty; + + FirebaseMessagingPlatform get _delegate { + if (_delegatePackingProperty == null) { + _delegatePackingProperty = FirebaseMessagingPlatform.instanceFor( + app: app, pluginConstants: pluginConstants); + } + return _delegatePackingProperty; + } + + /// The [FirebaseApp] for this current [FirebaseMessaging] instance. + FirebaseApp app; + + FirebaseMessaging._({this.app}) + : super(app.name, 'plugins.flutter.io/firebase_messaging'); + + /// Returns an instance using the default [FirebaseApp]. + static FirebaseMessaging get instance { + return FirebaseMessaging._(app: Firebase.app()); + } + + /// Returns an instance using a specified [FirebaseApp] + /// + /// If [app] is not provided, the default Firebase app will be used. + // TODO: messaging does not yet support multiple Firebase Apps. Default app only. + // static FirebaseMessaging instanceFor({ + // FirebaseApp app, + // }) { + // app ??= Firebase.app(); + // assert(app != null); + // + // String key = '${app.name}'; + // if (_cachedInstances.containsKey(key)) { + // return _cachedInstances[key]; + // } + // + // FirebaseMessaging newInstance = FirebaseMessaging._(app: app); + // _cachedInstances[key] = newInstance; + // + // return newInstance; + // } + // + // static final Map _cachedInstances = {}; + + /// Returns a Stream that is called when an incoming FCM payload is received whilst + /// the Flutter instance is in the foreground. + /// + /// The Stream contains the [RemoteMessage]. + /// + /// To handle messages whilst the app is in the background or terminated, + /// see [onBackgroundMessage]. + static Stream get onMessage { + Stream onMessageStream = + FirebaseMessagingPlatform.onMessage.stream; + + StreamController streamController; + streamController = StreamController.broadcast(onListen: () { + onMessageStream.pipe(streamController); + }); + + return streamController.stream; + } + + /// Returns a [Stream] that is called when a user presses a notification message displayed + /// via FCM. + /// + /// A Stream event will be sent if the app has opened from a background state + /// (not terminated). + /// + /// If your app is opened via a notification whilst the app is terminated, + /// see [getInitialMessage]. + static Stream get onMessageOpenedApp { + Stream onMessageOpenedAppStream = + FirebaseMessagingPlatform.onMessageOpenedApp.stream; + + StreamController streamController; + streamController = StreamController.broadcast(onListen: () { + onMessageOpenedAppStream.pipe(streamController); + }); + + return streamController.stream; + } + + /// Set a message handler function which is called when the app is in the + /// background or terminated. + /// + /// This provided handler must be a top-level function and cannot be + /// anonymous otherwise an [ArgumentError] will be thrown. + static void onBackgroundMessage(BackgroundMessageHandler handler) { + FirebaseMessagingPlatform.onBackgroundMessage = handler; + } + + // ignore: public_member_api_docs + @Deprecated( + "Constructing Messaging is deprecated, use 'FirebaseMessaging.instance' instead") + factory FirebaseMessaging() { + return FirebaseMessaging.instance; + } + + /// Returns whether messaging auto initialization is enabled or disabled for the device. + bool get isAutoInitEnabled { + return _delegate.isAutoInitEnabled; + } + + /// If the application has been opened from a terminated state via a [RemoteMessage] + /// (containing a [Notification]), it will be returned, otherwise it will be `null`. + /// + /// Once the [RemoteMesage] has been consumed, it will be removed and further + /// calls to [getInitialMessage] will be `null`. + /// + /// This should be used to determine whether specific notification interaction + /// should open the app with a specific purpose (e.g. opening a chat message, + /// specific screen etc). + Future getInitialMessage() { + return _delegate.getInitialMessage(); + } + + /// Removes access to an FCM token previously authorized with optional [senderId]. + /// + /// Messages sent by the server to this token will fail. + Future deleteToken() { + return _delegate.deleteToken(); + } + + /// On iOS/MacOS, it is possible to get the users APNs token. + /// + /// This may be required if you want to send messages to your iOS/MacOS devices + /// without using the FCM service. + /// + /// On Android & web, this returns `null`. + Future getAPNSToken() { + return _delegate.getAPNSToken(); + } + + /// Returns the default FCM token for this device and optionally a [senderId]. + Future getToken({ + String vapidKey, + }) { + return _delegate.getToken( + vapidKey: vapidKey, + ); + } + + /// Fires when a new FCM token is generated. + Stream get onTokenRefresh { + return _delegate.onTokenRefresh; + } + + /// Returns the current [NotificationSettings]. + /// + /// To request permissions, call [requestPermission]. + Future getNotificationSettings() { + return _delegate.getNotificationSettings(); + } + + /// Prompts the user for notification permissions. + /// + /// - On iOS, a dialog is shown requesting the users permission. + /// - On macOS, a notification will appear asking to grant permission. + /// - On Android, is it not required to call this method. If called however, + /// a [NotificationSettings] class will be returned with + /// [NotificationSettings.authorizationStatus] returning + /// [AuthorizationStatus.authorized]. + /// - On Web, a popup requesting the users permission is shown using the native browser API. + /// + /// Note that on iOS, if [provisional] is set to `true`, silent notification permissions will be + /// automatically granted. When notifications are delivered to the device, the + /// user will be presented with an option to disable notifications, keep receiving + /// them silently or enable prominent notifications. + Future requestPermission({ + /// Request permission to display alerts. Defaults to `true`. + /// + /// iOS/macOS only. + bool alert = true, + + /// Request permission for Siri to automatically read out notification messages over AirPods. + /// Defaults to `false`. + /// + /// iOS only. + bool announcement = false, + + /// Request permission to update the application badge. Defaults to `true`. + /// + /// iOS/macOS only. + bool badge = true, + + /// Request permission to display notifications in a CarPlay environment. + /// Defaults to `false`. + /// + /// iOS only. + bool carPlay = false, + + /// Request permission for critical alerts. Defaults to `false`. + /// + /// Note; your application must explicitly state reasoning for enabling + /// critical alerts during the App Store review process or your may be + /// rejected. + /// + /// iOS only. + bool criticalAlert = false, + + /// Request permission to provisionally create non-interrupting notifications. + /// Defaults to `false`. + /// + /// iOS only. + bool provisional = false, + + /// Request permission to play sounds. Defaults to `true`. + /// + /// iOS/macOS only. + bool sound = true, + }) { + return _delegate.requestPermission( + alert: alert, + announcement: announcement, + badge: badge, + carPlay: carPlay, + criticalAlert: criticalAlert, + provisional: provisional, + sound: sound, + ); + } + + /// Prompts the user for notification permissions. + /// + /// On iOS, a dialog is shown requesting the users permission. + /// + /// On Android, permissions are not required and `true` is returned. + /// + /// On Web, a popup requesting the users permission is shown using the native + /// browser API. + @Deprecated( + "requestNotificationPermissions() is deprecated in favor of requestPermission()") + Future requestNotificationPermissions( + [IosNotificationSettings iosSettings]) async { + iosSettings ??= const IosNotificationSettings(); + AuthorizationStatus status = (await requestPermission( + sound: iosSettings.sound, + alert: iosSettings.alert, + badge: iosSettings.badge, + provisional: iosSettings.provisional, + )) + .authorizationStatus; + + return status == AuthorizationStatus.authorized || + status == AuthorizationStatus.provisional; + } + + /// Send a new [RemoteMessage] to the FCM server. Android only. + Future sendMessage({ + String to, + Map data, + String collapseKey, + String messageId, + String messageType, + int ttl, + }) { + assert(to != null); + if (ttl != null) { + assert(ttl >= 0); + } + return _delegate.sendMessage( + to: to ?? '${app.options.messagingSenderId}@fcm.googleapis.com', + data: data, + collapseKey: collapseKey, + messageId: messageId, + messageType: messageType, + ttl: ttl, + ); + } + + /// Enable or disable auto-initialization of Firebase Cloud Messaging. + Future setAutoInitEnabled(bool enabled) async { + assert(enabled != null); + return _delegate.setAutoInitEnabled(enabled); + } + + /// Sets the presentation options for Apple notifications when received in + /// the foreground. + /// + /// By default, on Apple devices notification messages are only shown when + /// the application is in the background or terminated. Calling this method + /// updates these options to allow customizing notification presentation behaviour whilst + /// the application is in the foreground. + /// + /// Important: The requested permissions and those set by the user take priority + /// over these settings. + /// + /// - [alert] Causes a notification message to display in the foreground, overlaying + /// the current application (heads up mode). + /// - [badge] The application badge count will be updated if the application is + /// in the foreground. + /// - [sound] The device will trigger a sound if the application is in the foreground. + /// + /// If all arguments are `false` or are omitted, a notification will not be displayed in the + /// foreground, however you will still receive events relating to the notification. + Future setForegroundNotificationPresentationOptions({ + bool alert = false, + bool badge = false, + bool sound = false, + }) { + return _delegate.setForegroundNotificationPresentationOptions( + alert: alert, + badge: badge, + sound: sound, + ); + } + + /// Subscribe to topic in background. + /// + /// [topic] must match the following regular expression: + /// "[a-zA-Z0-9-_.~%]{1,900}". + Future subscribeToTopic(String topic) { + _assertTopicName(topic); + return _delegate.subscribeToTopic(topic); + } + + /// Unsubscribe from topic in background. + Future unsubscribeFromTopic(String topic) { + _assertTopicName(topic); + return _delegate.unsubscribeFromTopic(topic); + } + + /// Resets Instance ID and revokes all tokens. + /// + /// A new Instance ID is generated asynchronously if Firebase Cloud Messaging + /// auto-init is enabled. + /// + /// Returns `true` if the operations executed successfully and `false` if + /// an error occurred. + @Deprecated('Use [deleteToken] instead.') + Future deleteInstanceID() async { + try { + await deleteToken(); + return true; + } catch (e) { + return false; + } + } + + /// Determine whether FCM auto-initialization is enabled or disabled. + @Deprecated( + "autoInitEnabled() is deprecated. Use [isAutoInitEnabled] instead") + Future autoInitEnabled() async { + return isAutoInitEnabled; + } +} + +_assertTopicName(String topic) { + assert(topic != null); + + bool isValidTopic = RegExp(r"^[a-zA-Z0-9-_.~%]{1,900}$").hasMatch(topic); + + assert(isValidTopic); +} diff --git a/packages/firebase_messaging/firebase_messaging/macos/Assets/.gitkeep b/packages/firebase_messaging/firebase_messaging/macos/Assets/.gitkeep new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/packages/firebase_messaging/firebase_messaging/macos/Classes/FLTFirebaseMessagingPlugin.h b/packages/firebase_messaging/firebase_messaging/macos/Classes/FLTFirebaseMessagingPlugin.h new file mode 120000 index 000000000000..4e47236d9093 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/macos/Classes/FLTFirebaseMessagingPlugin.h @@ -0,0 +1 @@ +../../ios/Classes/FLTFirebaseMessagingPlugin.h \ No newline at end of file diff --git a/packages/firebase_messaging/firebase_messaging/macos/Classes/FLTFirebaseMessagingPlugin.m b/packages/firebase_messaging/firebase_messaging/macos/Classes/FLTFirebaseMessagingPlugin.m new file mode 120000 index 000000000000..1ae4068172a2 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/macos/Classes/FLTFirebaseMessagingPlugin.m @@ -0,0 +1 @@ +../../ios/Classes/FLTFirebaseMessagingPlugin.m \ No newline at end of file diff --git a/packages/firebase_messaging/firebase_messaging/macos/firebase_messaging.podspec b/packages/firebase_messaging/firebase_messaging/macos/firebase_messaging.podspec new file mode 100644 index 000000000000..be4637908c19 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/macos/firebase_messaging.podspec @@ -0,0 +1,47 @@ +require 'yaml' + +pubspec = YAML.load_file(File.join('..', 'pubspec.yaml')) +library_version = pubspec['version'].gsub('+', '-') + +firebase_sdk_version = '6.33.0' +if defined?($FirebaseSDKVersion) + Pod::UI.puts "#{pubspec['name']}: Using user specified Firebase SDK version '#{$FirebaseSDKVersion}'" + firebase_sdk_version = $FirebaseSDKVersion +else + firebase_core_script = File.join(File.expand_path('..', File.expand_path('..', File.dirname(__FILE__))), 'firebase_core/ios/firebase_sdk_version.rb') + if File.exist?(firebase_core_script) + require firebase_core_script + firebase_sdk_version = firebase_sdk_version! + Pod::UI.puts "#{pubspec['name']}: Using Firebase SDK version '#{firebase_sdk_version}' defined in 'firebase_core'" + end +end + +Pod::Spec.new do |s| + s.name = pubspec['name'] + s.version = library_version + s.summary = pubspec['description'] + s.description = pubspec['description'] + s.homepage = pubspec['homepage'] + s.license = { :file => '../LICENSE' } + s.authors = 'The Chromium Authors' + s.source = { :path => '.' } + + s.source_files = 'Classes/**/*.{h,m}' + s.public_header_files = 'Classes/**/*.h' + + s.platform = :osx, '10.11' + + # Flutter dependencies + s.dependency 'FlutterMacOS' + + # Firebase dependencies + s.dependency 'firebase_core' + s.dependency 'Firebase/CoreOnly', "~> #{firebase_sdk_version}" + s.dependency 'Firebase/Messaging', "~> #{firebase_sdk_version}" + + s.static_framework = true + s.pod_target_xcconfig = { + 'GCC_PREPROCESSOR_DEFINITIONS' => "LIBRARY_VERSION=\\@\\\"#{library_version}\\\" LIBRARY_NAME=\\@\\\"flutter-fire-fcm\\\"", + 'DEFINES_MODULE' => 'YES' + } +end diff --git a/packages/firebase_messaging/pubspec.yaml b/packages/firebase_messaging/firebase_messaging/pubspec.yaml similarity index 61% rename from packages/firebase_messaging/pubspec.yaml rename to packages/firebase_messaging/firebase_messaging/pubspec.yaml index 44261112906b..37a161748b3f 100644 --- a/packages/firebase_messaging/pubspec.yaml +++ b/packages/firebase_messaging/firebase_messaging/pubspec.yaml @@ -2,31 +2,37 @@ name: firebase_messaging description: Flutter plugin for Firebase Cloud Messaging, a cross-platform messaging solution that lets you reliably deliver messages on Android and iOS. homepage: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_messaging -version: 7.0.3 +version: 8.0.0-dev.3 flutter: plugin: platforms: android: - package: io.flutter.plugins.firebasemessaging - pluginClass: FirebaseMessagingPlugin + package: io.flutter.plugins.firebase.messaging + pluginClass: FlutterFirebaseMessagingPlugin ios: pluginClass: FLTFirebaseMessagingPlugin + macos: + pluginClass: FLTFirebaseMessagingPlugin dependencies: meta: ^1.0.4 flutter: sdk: flutter firebase_core: ^0.5.0+1 + firebase_core_platform_interface: ^2.0.0 + firebase_messaging_platform_interface: ^1.0.0-dev.2 dev_dependencies: pedantic: ^1.8.0 - test: ^1.3.0 - mockito: ^3.0.0 flutter_test: sdk: flutter flutter_driver: sdk: flutter + test: any + mockito: ^4.1.1 + plugin_platform_interface: ^1.0.2 + async: ^2.4.2 environment: - sdk: ">=2.0.0 <3.0.0" + sdk: ">=2.1.0 <3.0.0" flutter: ">=1.12.13+hotfix.5 <2.0.0" diff --git a/packages/firebase_messaging/firebase_messaging/test/firebase_messaging_test.dart b/packages/firebase_messaging/firebase_messaging/test/firebase_messaging_test.dart new file mode 100644 index 000000000000..edcf3f1a598f --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/test/firebase_messaging_test.dart @@ -0,0 +1,245 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:async/async.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; +import 'package:firebase_messaging_platform_interface/src/utils.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; + +import './mock.dart'; + +void main() { + setupFirebaseMessagingMocks(); + FirebaseMessaging messaging; + + group('$FirebaseMessaging', () { + setUpAll(() async { + await Firebase.initializeApp(); + FirebaseMessagingPlatform.instance = kMockMessagingPlatform; + messaging = FirebaseMessaging.instance; + }); + group('instance', () { + test('returns an instance', () async { + expect(messaging, isA()); + }); + + test('returns the correct $FirebaseApp', () { + expect(messaging.app, isA()); + expect(messaging.app.name, defaultFirebaseAppName); + }); + }); + + group('get.isAutoInitEnabled', () { + test('verify delegate method is called', () { + // verify isAutoInitEnabled returns true + when(kMockMessagingPlatform.isAutoInitEnabled).thenReturn(true); + var result = messaging.isAutoInitEnabled; + + expect(result, isA()); + expect(result, isTrue); + verify(kMockMessagingPlatform.isAutoInitEnabled); + + // verify isAutoInitEnabled returns false + when(kMockMessagingPlatform.isAutoInitEnabled).thenReturn(false); + result = messaging.isAutoInitEnabled; + + expect(result, isA()); + expect(result, isFalse); + verify(kMockMessagingPlatform.isAutoInitEnabled); + }); + }); + + group('initialNotification', () { + test('verify delegate method is called', () async { + const senderId = 'test-notification'; + RemoteMessage message = RemoteMessage(senderId: senderId); + when(kMockMessagingPlatform.getInitialMessage()) + .thenAnswer((_) => Future.value(message)); + + final result = await messaging.getInitialMessage(); + + expect(result, isA()); + expect(result.senderId, senderId); + + verify(kMockMessagingPlatform.getInitialMessage()); + }); + }); + + group('deleteToken', () { + test('verify delegate method is called with correct args', () async { + when(kMockMessagingPlatform.deleteToken()) + .thenAnswer((_) => Future.value(null)); + + await messaging.deleteToken(); + + verify(kMockMessagingPlatform.deleteToken()); + }); + }); + + group('getAPNSToken', () { + test('verify delegate method is called', () async { + const apnsToken = 'test-apns'; + when(kMockMessagingPlatform.getAPNSToken()) + .thenAnswer((_) => Future.value(apnsToken)); + + await messaging.getAPNSToken(); + + verify(kMockMessagingPlatform.getAPNSToken()); + }); + }); + group('getToken', () { + test('verify delegate method is called with correct args', () async { + const vapidKey = 'test-vapid-key'; + when(kMockMessagingPlatform.getToken(vapidKey: anyNamed('vapidKey'))) + .thenReturn(null); + + await messaging.getToken(vapidKey: vapidKey); + + verify(kMockMessagingPlatform.getToken(vapidKey: vapidKey)); + }); + }); + + group('onTokenRefresh', () { + test('verify delegate method is called', () async { + const token = 'test-token'; + + when(kMockMessagingPlatform.onTokenRefresh) + .thenAnswer((_) => Stream.fromIterable([token])); + + final StreamQueue changes = + StreamQueue(messaging.onTokenRefresh); + expect(await changes.next, isA()); + + verify(kMockMessagingPlatform.onTokenRefresh); + }); + }); + group('requestPermission', () { + test('verify delegate method is called with correct args', () async { + when(kMockMessagingPlatform.requestPermission( + alert: anyNamed('alert'), + announcement: anyNamed('announcement'), + badge: anyNamed('badge'), + carPlay: anyNamed('carPlay'), + criticalAlert: anyNamed('criticalAlert'), + provisional: anyNamed('provisional'), + sound: anyNamed('sound'), + )).thenAnswer((_) => Future.value(androidNotificationSettings)); + + // true values + await messaging.requestPermission( + alert: true, + announcement: true, + badge: true, + carPlay: true, + criticalAlert: true, + provisional: true, + sound: true); + + verify(kMockMessagingPlatform.requestPermission( + alert: true, + announcement: true, + badge: true, + carPlay: true, + criticalAlert: true, + provisional: true, + sound: true)); + + // false values + await messaging.requestPermission( + alert: false, + announcement: false, + badge: false, + carPlay: false, + criticalAlert: false, + provisional: false, + sound: false); + + verify(kMockMessagingPlatform.requestPermission( + alert: false, + announcement: false, + badge: false, + carPlay: false, + criticalAlert: false, + provisional: false, + sound: false)); + + // default values + await messaging.requestPermission(); + + verify(kMockMessagingPlatform.requestPermission( + alert: true, + announcement: false, + badge: true, + carPlay: false, + criticalAlert: false, + provisional: false, + sound: true)); + }); + }); + + group('setAutoInitEnabled', () { + test('verify delegate method is called with correct args', () async { + when(kMockMessagingPlatform.setAutoInitEnabled(any)) + .thenAnswer((_) => null); + + await messaging.setAutoInitEnabled(false); + verify(kMockMessagingPlatform.setAutoInitEnabled(false)); + + await messaging.setAutoInitEnabled(true); + verify(kMockMessagingPlatform.setAutoInitEnabled(true)); + }); + + test('asserts [ttl] is more than 0 if not null', () { + expect(() => messaging.setAutoInitEnabled(null), throwsAssertionError); + }); + }); + group('subscribeToTopic', () { + setUpAll(() { + when(kMockMessagingPlatform.subscribeToTopic(any)) + .thenAnswer((_) => null); + }); + + test('throws AssertionError if topic is invalid', () async { + final invalidTopic = 'test invalid = topic'; + + expect(() => messaging.subscribeToTopic(invalidTopic), + throwsAssertionError); + }); + + test('verify delegate method is called with correct args', () async { + final topic = 'test-topic'; + + await messaging.subscribeToTopic(topic); + verify(kMockMessagingPlatform.subscribeToTopic(topic)); + }); + + test('throws AssertionError for invalid topic name', () { + expect( + () => messaging.unsubscribeFromTopic(null), throwsAssertionError); + verifyNever(kMockMessagingPlatform.unsubscribeFromTopic(any)); + }); + }); + group('unsubscribeFromTopic', () { + when(kMockMessagingPlatform.unsubscribeFromTopic(any)) + .thenAnswer((_) => null); + test('verify delegate method is called with correct args', () async { + final topic = 'test-topic'; + + await messaging.unsubscribeFromTopic(topic); + verify(kMockMessagingPlatform.unsubscribeFromTopic(topic)); + }); + + test('throws AssertionError for invalid topic name', () { + expect( + () => messaging.unsubscribeFromTopic(null), throwsAssertionError); + verifyNever(kMockMessagingPlatform.unsubscribeFromTopic(any)); + }); + }); + }); +} diff --git a/packages/firebase_messaging/firebase_messaging/test/mock.dart b/packages/firebase_messaging/firebase_messaging/test/mock.dart new file mode 100644 index 000000000000..d4f810fd7ca8 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging/test/mock.dart @@ -0,0 +1,72 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mockito/mockito.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +typedef Callback(MethodCall call); + +final String kTestString = 'Hello World'; + +final MockFirebaseMessaging kMockMessagingPlatform = MockFirebaseMessaging(); + +setupFirebaseMessagingMocks() { + TestWidgetsFlutterBinding.ensureInitialized(); + + MethodChannelFirebase.channel.setMockMethodCallHandler((call) async { + if (call.method == 'Firebase#initializeCore') { + return [ + { + 'name': defaultFirebaseAppName, + 'options': { + 'apiKey': '123', + 'appId': '123', + 'messagingSenderId': '123', + 'projectId': '123', + }, + 'pluginConstants': {}, + } + ]; + } + + if (call.method == 'Firebase#initializeApp') { + return { + 'name': call.arguments['appName'], + 'options': call.arguments['options'], + 'pluginConstants': {}, + }; + } + + return null; + }); + + // Mock Platform Interface Methods + // ignore: invalid_use_of_protected_member + when(kMockMessagingPlatform.delegateFor(app: anyNamed("app"))) + .thenReturn(kMockMessagingPlatform); + // ignore: invalid_use_of_protected_member + when(kMockMessagingPlatform.setInitialValues( + isAutoInitEnabled: anyNamed("isAutoInitEnabled"), + )).thenReturn(kMockMessagingPlatform); +} + +// Platform Interface Mock Classes + +// FirebaseMessagingPlatform Mock +class MockFirebaseMessaging extends Mock + with MockPlatformInterfaceMixin + implements FirebaseMessagingPlatform { + MockFirebaseMessaging() { + TestFirebaseMessagingPlatform(); + } +} + +class TestFirebaseMessagingPlatform extends FirebaseMessagingPlatform { + TestFirebaseMessagingPlatform() : super(); +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/.gitignore b/packages/firebase_messaging/firebase_messaging_platform_interface/.gitignore new file mode 100644 index 000000000000..8665f160f3b4 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/.gitignore @@ -0,0 +1,76 @@ +# Miscellaneous +*.class +*.log +*.pyc +*.swp +.DS_Store +.atom/ +.buildlog/ +.history +.svn/ + +# IntelliJ related +*.iml +*.ipr +*.iws +.idea/ +.metadata + +# The .vscode folder contains launch configuration and tasks you configure in +# VS Code which you may wish to be included in version control, so this line +# is commented out by default. +#.vscode/ + +# Flutter/Dart/Pub related +**/doc/api/ +.dart_tool/ +.flutter-plugins +.flutter-plugins-dependencies +.packages +.pub-cache/ +.pub/ +build/ + +# Android related +**/android/**/gradle-wrapper.jar +**/android/.gradle +**/android/captures/ +**/android/gradlew +**/android/gradlew.bat +**/android/local.properties +**/android/**/GeneratedPluginRegistrant.java + +# iOS/XCode related +**/ios/**/*.mode1v3 +**/ios/**/*.mode2v3 +**/ios/**/*.moved-aside +**/ios/**/*.pbxuser +**/ios/**/*.perspectivev3 +**/ios/**/*sync/ +**/ios/**/.sconsign.dblite +**/ios/**/.tags* +**/ios/**/.vagrant/ +**/ios/**/DerivedData/ +**/ios/**/Icon? +**/ios/**/Pods/ +**/ios/**/.symlinks/ +**/ios/**/profile +**/ios/**/xcuserdata +**/ios/.generated/ +**/ios/Flutter/App.framework +**/ios/Flutter/Flutter.framework +**/ios/Flutter/Flutter.podspec +**/ios/Flutter/Generated.xcconfig +**/ios/Flutter/app.flx +**/ios/Flutter/app.zip +**/ios/Flutter/flutter_assets/ +**/ios/Flutter/flutter_export_environment.sh +**/ios/ServiceDefinitions.json +**/ios/Runner/GeneratedPluginRegistrant.* + +# Exceptions to above rules. +!**/ios/**/default.mode1v3 +!**/ios/**/default.mode2v3 +!**/ios/**/default.pbxuser +!**/ios/**/default.perspectivev3 +!/packages/flutter_tools/test/data/dart_dependencies_test/**/.packages \ No newline at end of file diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/CHANGELOG.md b/packages/firebase_messaging/firebase_messaging_platform_interface/CHANGELOG.md new file mode 100644 index 000000000000..9ffa5a93c328 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/CHANGELOG.md @@ -0,0 +1,7 @@ +## 1.0.0-dev.2 + + - **FEAT**: add senderId (use iid on Android to support it). + +## 1.0.0-dev.1 + +- Initial release. diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/LICENSE b/packages/firebase_messaging/firebase_messaging_platform_interface/LICENSE new file mode 100644 index 000000000000..9d40552c335e --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/LICENSE @@ -0,0 +1,26 @@ +Copyright 2020, the Chromium project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/README.md b/packages/firebase_messaging/firebase_messaging_platform_interface/README.md new file mode 100644 index 000000000000..12e65c4c7da0 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/README.md @@ -0,0 +1,26 @@ +# firebase_messaging_platform_interface + +A common platform interface for the [`firebase_messaging`][1] plugin. + +This interface allows platform-specific implementations of the `firebase_messaging` +plugin, as well as the plugin itself, to ensure they are supporting the +same interface. + +## Usage + +To implement a new platform-specific implementation of `firebase_messaging`, extend +[`FirebaseMessagingPlatform`][2] with an implementation that performs the +platform-specific behavior, and when you register your plugin, set the default +`FirebaseMessagingPlatform` by calling +`FirebaseMessagingPlatform.instance = MyMessaging()`. + +## Note on breaking changes + +Strongly prefer non-breaking changes (such as adding a method to the interface) +over breaking changes for this package. + +See https://flutter.dev/go/platform-interface-breaking-changes for a discussion +on why a less-clean interface is preferable to a breaking change. + +[1]: ../firebase_messaging +[2]: lib/firebase_messaging_platform_interface.dart \ No newline at end of file diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/firebase_messaging_platform_interface.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/firebase_messaging_platform_interface.dart new file mode 100644 index 000000000000..f4c1bd2f8e04 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/firebase_messaging_platform_interface.dart @@ -0,0 +1,13 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +library firebase_messaging_platform_interface; + +export 'src/platform_interface/platform_interface_messaging.dart'; + +export 'src/notification_settings.dart'; +export 'src/ios_notification_settings.dart'; +export 'src/types.dart'; +export 'src/remote_message.dart'; +export 'src/remote_notification.dart'; diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/ios_notification_settings.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/ios_notification_settings.dart new file mode 100644 index 000000000000..9f5537500b82 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/ios_notification_settings.dart @@ -0,0 +1,42 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +/// A class representing iOS specific permissions which can be requested by your +/// application. +@Deprecated( + "Using [IosNotificationSettings] is deprecated. Instead, call [requestPermission] directly with named arguments") +class IosNotificationSettings { + // ignore: public_member_api_docs + const IosNotificationSettings({ + this.sound = true, + this.alert = true, + this.badge = true, + this.provisional = false, + }); + + /// Request permission to play sounds. + final bool sound; + + /// Request permission to display alerts. + final bool alert; + + /// Request permission to update the application badge. + final bool badge; + + /// Request permission to provisionally create non-interrupting notifications. + final bool provisional; + + /// Converts the settings into a [Map]. + Map toMap() { + return { + 'sound': sound, + 'alert': alert, + 'badge': badge, + 'provisional': provisional + }; + } + + @override + String toString() => 'IosNotificationSettings(${toMap()})'; +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/method_channel/method_channel_messaging.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/method_channel/method_channel_messaging.dart new file mode 100644 index 000000000000..67299c33e394 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/method_channel/method_channel_messaging.dart @@ -0,0 +1,378 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +// ignore_for_file: deprecated_member_use_from_same_package + +import 'dart:async'; +import 'dart:io'; +import 'dart:ui'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; + +import '../utils.dart'; +import 'utils/exception.dart'; + +// This is the entrypoint for the background isolate. Since we can only enter +// an isolate once, we setup a MethodChannel to listen for method invocations +// from the native portion of the plugin. This allows for the plugin to perform +// any necessary processing in Dart (e.g., populating a custom object) before +// invoking the provided callback. +void _firebaseMessagingCallbackDispatcher() { + // Initialize state necessary for MethodChannels. + WidgetsFlutterBinding.ensureInitialized(); + + const MethodChannel _channel = MethodChannel( + 'plugins.flutter.io/firebase_messaging_background', + ); + + // This is where we handle background events from the native portion of the plugin. + _channel.setMethodCallHandler((MethodCall call) async { + if (call.method == "MessagingBackground#onMessage") { + final CallbackHandle handle = + CallbackHandle.fromRawHandle(call.arguments["userCallbackHandle"]); + + // PluginUtilities.getCallbackFromHandle performs a lookup based on the + // callback handle and returns a tear-off of the original callback. + final Function closure = PluginUtilities.getCallbackFromHandle(handle); + + if (closure == null) { + print('Fatal: could not find user callback'); + exit(-1); + } + + try { + Map messageMap = + Map.from(call.arguments["message"]); + final RemoteMessage remoteMessage = RemoteMessage.fromMap(messageMap); + await closure(remoteMessage); + } catch (e) { + print( + "FlutterFire Messaging: An error occurred in your background messaging handler:"); + print(e); + } + } else { + throw UnimplementedError("${call.method} has not been implemented"); + } + }); + + // Once we've finished initializing, let the native portion of the plugin + // know that it can start scheduling alarms. + _channel.invokeMethod("MessagingBackground#initialized"); +} + +/// The entry point for accessing a Messaging. +/// +/// You can get an instance by calling [FirebaseMessaging.instance]. +class MethodChannelFirebaseMessaging extends FirebaseMessagingPlatform { + /// Create an instance of [MethodChannelFirebaseMessaging] with optional [FirebaseApp] + MethodChannelFirebaseMessaging({FirebaseApp app}) : super(appInstance: app) { + if (_initialized) return; + channel.setMethodCallHandler((MethodCall call) async { + switch (call.method) { + case "Messaging#onTokenRefresh": + _tokenStreamController.add(call.arguments as String); + break; + case "Messaging#onMessage": + print(call.arguments); + Map messageMap = + Map.from(call.arguments); + FirebaseMessagingPlatform.onMessage + .add(RemoteMessage.fromMap(messageMap)); + break; + case "Messaging#onMessageOpenedApp": + Map messageMap = + Map.from(call.arguments); + FirebaseMessagingPlatform.onMessageOpenedApp + .add(RemoteMessage.fromMap(messageMap)); + break; + case "Messaging#onBackgroundMessage": + // Apple only. Android calls via separate background channel. + Map messageMap = + Map.from(call.arguments); + return FirebaseMessagingPlatform.onBackgroundMessage + ?.call(RemoteMessage.fromMap(messageMap)); + default: + throw UnimplementedError("${call.method} has not been implemented"); + } + }); + _initialized = true; + } + + bool _autoInitEnabled; + + static bool _initialized = false; + static bool _bgHandlerInitialized = false; + + /// Returns a stub instance to allow the platform interface to access + /// the class instance statically. + static MethodChannelFirebaseMessaging get instance { + return MethodChannelFirebaseMessaging._(); + } + + /// Internal stub class initializer. + /// + /// When the user code calls an auth method, the real instance is + /// then initialized via the [delegateFor] method. + MethodChannelFirebaseMessaging._() : super(appInstance: null); + + /// The [MethodChannel] to which calls will be delegated. + @visibleForTesting + static const MethodChannel channel = MethodChannel( + 'plugins.flutter.io/firebase_messaging', + ); + + final StreamController _tokenStreamController = + StreamController.broadcast(); + + @override + FirebaseMessagingPlatform delegateFor({FirebaseApp app}) { + return MethodChannelFirebaseMessaging(app: app); + } + + @override + FirebaseMessagingPlatform setInitialValues({bool isAutoInitEnabled}) { + _autoInitEnabled = isAutoInitEnabled ?? false; + return this; + } + + @override + bool get isAutoInitEnabled { + return _autoInitEnabled; + } + + @override + Future getInitialMessage() async { + try { + Map remoteMessageMap = await channel + .invokeMapMethod('Messaging#getInitialMessage', { + 'appName': app.name, + }); + + if (remoteMessageMap == null) { + return null; + } + + return RemoteMessage.fromMap(remoteMessageMap); + } catch (e) { + throw convertPlatformException(e); + } + } + + @override + void registerBackgroundMessageHandler( + BackgroundMessageHandler handler) async { + if (handler == null || defaultTargetPlatform != TargetPlatform.android) { + return; + } + + if (!_bgHandlerInitialized) { + _bgHandlerInitialized = true; + final CallbackHandle bgHandle = PluginUtilities.getCallbackHandle( + _firebaseMessagingCallbackDispatcher); + final CallbackHandle userHandle = + PluginUtilities.getCallbackHandle(handler); + await channel.invokeMethod('Messaging#startBackgroundIsolate', { + 'pluginCallbackHandle': bgHandle.toRawHandle(), + 'userCallbackHandle': userHandle.toRawHandle(), + }); + } + } + + @override + Future deleteToken({ + String senderId, + }) async { + try { + await channel.invokeMethod( + 'Messaging#deleteToken', {'appName': app.name, 'senderId': senderId}); + } catch (e) { + throw convertPlatformException(e); + } + } + + @override + Future getAPNSToken() async { + if (defaultTargetPlatform != TargetPlatform.iOS && + defaultTargetPlatform != TargetPlatform.macOS) { + return null; + } + + try { + return await channel.invokeMethod('Messaging#getAPNSToken', { + 'appName': app.name, + }); + } catch (e) { + throw convertPlatformException(e); + } + } + + @override + Future getToken({ + String senderId, + String vapidKey, // not used yet; web only property + }) async { + try { + return await channel.invokeMethod('Messaging#getToken', { + 'appName': app.name, + 'senderId': senderId, + }); + } catch (e) { + throw convertPlatformException(e); + } + } + + @override + Future getNotificationSettings() async { + if (defaultTargetPlatform != TargetPlatform.iOS && + defaultTargetPlatform != TargetPlatform.macOS) { + return androidNotificationSettings; + } + + try { + Map response = await channel + .invokeMapMethod('Messaging#getNotificationSettings', { + 'appName': app.name, + }); + + return convertToNotificationSettings(response); + } catch (e) { + throw convertPlatformException(e); + } + } + + @override + Future requestPermission( + {bool alert = true, + bool announcement = false, + bool badge = true, + bool carPlay = false, + bool criticalAlert = false, + bool provisional = false, + bool sound = true}) async { + if (defaultTargetPlatform != TargetPlatform.iOS && + defaultTargetPlatform != TargetPlatform.macOS) { + return androidNotificationSettings; + } + + try { + Map response = await channel + .invokeMapMethod('Messaging#requestPermission', { + 'appName': app.name, + 'permissions': { + 'alert': alert, + 'announcement': announcement, + 'badge': badge, + 'carPlay': carPlay, + 'criticalAlert': criticalAlert, + 'provisional': provisional, + 'sound': sound, + } + }); + + return convertToNotificationSettings(response); + } catch (e) { + throw convertPlatformException(e); + } + } + + @override + Future setAutoInitEnabled(bool enabled) async { + try { + Map data = await channel + .invokeMapMethod('Messaging#setAutoInitEnabled', { + 'appName': app.name, + 'enabled': enabled, + }); + _autoInitEnabled = data['isAutoInitEnabled']; + } catch (e) { + throw convertPlatformException(e); + } + } + + @override + Stream get onTokenRefresh { + return _tokenStreamController.stream; + } + + @override + Future setForegroundNotificationPresentationOptions({ + bool alert, + bool badge, + bool sound, + }) async { + if (defaultTargetPlatform != TargetPlatform.iOS && + defaultTargetPlatform != TargetPlatform.macOS) { + return; + } + + try { + await channel.invokeMethod( + 'Messaging#setForegroundNotificationPresentationOptions', { + 'appName': app.name, + 'alert': alert, + 'badge': badge, + 'sound': sound, + }); + } catch (e) { + throw convertPlatformException(e); + } + } + + @override + Future sendMessage({ + String to, + Map data, + String collapseKey, + String messageId, + String messageType, + int ttl, + }) async { + if (defaultTargetPlatform != TargetPlatform.android) { + throw UnimplementedError( + "Sending of messages from the Firebase Messaging SDK is only supported on Android devices."); + } + + try { + await channel.invokeMethod('Messaging#sendMessage', { + 'appName': app.name, + 'to': to, + 'data': data, + 'collapseKey': collapseKey, + 'messageId': messageId, + 'messageType': messageType, + 'ttl': ttl, + }); + } catch (e) { + throw convertPlatformException(e); + } + } + + @override + Future subscribeToTopic(String topic) async { + try { + await channel.invokeMethod('Messaging#subscribeToTopic', { + 'appName': app.name, + 'topic': topic, + }); + } catch (e) { + throw convertPlatformException(e); + } + } + + @override + Future unsubscribeFromTopic(String topic) async { + try { + await channel.invokeMethod('Messaging#unsubscribeFromTopic', { + 'appName': app.name, + 'topic': topic, + }); + } catch (e) { + throw convertPlatformException(e); + } + } +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/method_channel/utils/exception.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/method_channel/utils/exception.dart new file mode 100644 index 000000000000..ca674529a667 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/method_channel/utils/exception.dart @@ -0,0 +1,39 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_core/firebase_core.dart'; +import 'package:flutter/services.dart'; + +/// Catches a [PlatformException] and converts it into a [FirebaseException] if +/// it was intentionally caught on the native platform. +Exception convertPlatformException(Object exception) { + if (exception is! Exception || exception is! PlatformException) { + throw exception; + } + + return platformExceptionToFirebaseException(exception as PlatformException); +} + +/// Converts a [PlatformException] into a [FirebaseException]. +/// +/// A [PlatformException] can only be converted to a [FirebaseException] if the +/// `details` of the exception exist. Firebase returns specific codes and messages +/// which can be converted into user friendly exceptions. +FirebaseException platformExceptionToFirebaseException( + PlatformException platformException) { + Map details = platformException.details != null + ? Map.from(platformException.details) + : null; + + String code = 'unknown'; + String message = platformException.message; + + if (details != null) { + code = details['code'] ?? code; + message = details['message'] ?? message; + } + + return FirebaseException( + plugin: 'firebase_messaging', code: code, message: message); +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/notification_settings.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/notification_settings.dart new file mode 100644 index 000000000000..5d6f5ef3c45f --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/notification_settings.dart @@ -0,0 +1,68 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; + +/// Represents the devices notification settings. +class NotificationSettings { + // ignore: public_member_api_docs + const NotificationSettings( + {this.alert, + this.announcement, + this.authorizationStatus, + this.badge, + this.carPlay, + this.lockScreen, + this.notificationCenter, + this.showPreviews, + this.sound}); + + /// Whether or not messages containing a notification will alert the user. + /// + /// Apple devices only. + final AppleNotificationSetting alert; + + /// Whether or not messages containing a notification be announced to the user + /// via 3rd party services such as Siri. + /// + /// Apple devices only. + final AppleNotificationSetting announcement; + + /// The overall notification authorization status for the user. + final AuthorizationStatus authorizationStatus; + + /// Whether or not messages containing a notification can update the application badge. + /// + /// Apple devices only. + final AppleNotificationSetting badge; + + /// Whether or not messages containing a notification will be displayed in a + /// CarPlay environment. + /// + /// Apple devices only. + final AppleNotificationSetting carPlay; + + /// Whether or not messages containing a notification will be displayed on the + /// device lock screen. + /// + /// Apple devices only. + final AppleNotificationSetting lockScreen; + + /// Whether or not messages containing a notification will be displayed in the + /// device notification center. + /// + /// Apple devices only. + final AppleNotificationSetting notificationCenter; + + /// Whether or not messages containing a notification can displayed a previewed + /// version to users. + /// + /// Apple devices only. + final AppleShowPreviewSetting showPreviews; + + /// Whether or not messages containing a notification will trigger a sound. + /// + /// Apple devices only. + final AppleNotificationSetting sound; +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/platform_interface/platform_interface_messaging.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/platform_interface/platform_interface_messaging.dart new file mode 100644 index 000000000000..dd824051f73e --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/platform_interface/platform_interface_messaging.dart @@ -0,0 +1,309 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'dart:async'; + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; +import 'package:meta/meta.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../method_channel/method_channel_messaging.dart'; + +/// Defines an interface to work with Messaging on web and mobile. +abstract class FirebaseMessagingPlatform extends PlatformInterface { + /// The [FirebaseApp] this instance was initialized with. + @protected + final FirebaseApp appInstance; + + /// Create an instance using [app]. + FirebaseMessagingPlatform({this.appInstance}) : super(token: _token); + + /// Returns the [FirebaseApp] for the current instance. + FirebaseApp get app { + if (appInstance == null) { + return Firebase.app(); + } + + return appInstance; + } + + static final Object _token = Object(); + + /// Create an instance with a [FirebaseApp] using an existing instance. + factory FirebaseMessagingPlatform.instanceFor( + {FirebaseApp app, Map pluginConstants}) { + return FirebaseMessagingPlatform.instance + .delegateFor(app: app) + .setInitialValues( + isAutoInitEnabled: pluginConstants['AUTO_INIT_ENABLED'], + ); + } + + static FirebaseMessagingPlatform _instance; + + /// The current default [FirebaseMessagingPlatform] instance. + /// + /// It will always default to [MethodChannelFirebaseMessaging] + /// if no other implementation was provided. + static FirebaseMessagingPlatform get instance { + if (_instance == null) { + _instance = MethodChannelFirebaseMessaging.instance; + } + return _instance; + } + + /// Sets the [FirebaseMessagingPlatform.instance] + static set instance(FirebaseMessagingPlatform instance) { + assert(instance != null); + PlatformInterface.verifyToken(instance, _token); + _instance = instance; + } + + static StreamController _onMessageStreamController; + + /// Returns a Stream that is called when an incoming FCM payload is received whilst + /// the Flutter instance is in the foreground. + /// + /// To handle messages whilst the app is in the background or terminated, + /// see [onBackgroundMessage]. + static StreamController get onMessage { + return _onMessageStreamController ??= + StreamController.broadcast(); + } + + static StreamController _onMessageOpenedAppStreamController; + + /// Returns a [Stream] that is called when a user presses a notification displayed + /// via FCM. + /// + /// A Stream event will be sent if the app has opened from a background state + /// (not terminated). + /// + /// If your app is opened via a notification whilst the app is terminated, + /// see [getInitialMessage]. + static StreamController get onMessageOpenedApp { + return _onMessageOpenedAppStreamController ??= + StreamController.broadcast(); + } + + static BackgroundMessageHandler _onBackgroundMessageHandler; + + /// Set a message handler function which is called when the app is in the + /// background or terminated. + /// + /// This provided handler must be a top-level function and cannot be + /// anonymous otherwise an [ArgumentError] will be thrown. + static BackgroundMessageHandler get onBackgroundMessage { + return _onBackgroundMessageHandler; + } + + /// Allows the background message handler to be created and calls the + /// instance delegate [registerBackgroundMessageHandler] to perform any + /// platform specific registration logic. + static set onBackgroundMessage(BackgroundMessageHandler handler) { + _onBackgroundMessageHandler = handler; + instance.registerBackgroundMessageHandler(handler); + } + + /// Enables delegates to create new instances of themselves if a none default + /// [FirebaseApp] instance is required by the user. + @protected + FirebaseMessagingPlatform delegateFor({FirebaseApp app}) { + throw UnimplementedError('delegateFor() is not implemented'); + } + + /// Sets any initial values on the instance. + /// + /// Platforms with Method Channels can provide constant values to be available + /// before the instance has initialized to prevent any unnecessary async + /// calls. + @protected + FirebaseMessagingPlatform setInitialValues({ + bool isAutoInitEnabled, + }) { + throw UnimplementedError('setInitialValues() is not implemented'); + } + + /// Returns whether messaging auto initialization is enabled or disabled for the device. + bool get isAutoInitEnabled { + throw UnimplementedError('isAutoInitEnabled is not implemented'); + } + + /// If the application has been opened from a terminated state via a [RemoteMessage] + /// (containing a [Notification]), it will be returned, otherwise it will be `null`. + /// + /// Once the [Notification] has been consumed, it will be removed and further + /// calls to [getInitialMessage] will be `null`. + /// + /// This should be used to determine whether specific notification interaction + /// should open the app with a specific purpose (e.g. opening a chat message, + /// specific screen etc). + Future getInitialMessage() { + throw UnimplementedError('getInitialMessage() is not implemented'); + } + + /// Allows delegates to create a background message handler implementation. + /// + /// For example, on native platforms this could be to setup an isolate, whereas + /// on web a service worker can be registered. + void registerBackgroundMessageHandler(BackgroundMessageHandler handler) { + throw UnimplementedError( + 'registerBackgroundMessageHandler() is not implemented'); + } + + /// Removes access to an FCM token previously authorized with optional [senderId]. + /// + /// Messages sent by the server to this token will fail. + Future deleteToken({ + String senderId, + }) { + throw UnimplementedError('deleteToken() is not implemented'); + } + + /// On iOS & MacOS, it is possible to get the users APNs token. This may be required + /// if you want to send messages to your iOS devices without using the FCM service. + Future getAPNSToken() { + throw UnimplementedError('getAPNSToken() is not implemented'); + } + + /// Returns the default FCM token for this device and optionally [senderId]. + Future getToken({ + String senderId, + String vapidKey, + }) { + throw UnimplementedError('getToken() is not implemented'); + } + + /// Fires when a new FCM token is generated. + Stream get onTokenRefresh { + throw UnimplementedError('onTokenRefresh is not implemented'); + } + + /// Returns the current [NotificationSettings]. + /// + /// To request permissions, call [requestPermission]. + Future getNotificationSettings() { + throw UnimplementedError('getNotificationSettings() is not implemented'); + } + + /// Prompts the user for notification permissions. + /// + /// On iOS, a dialog is shown requesting the users permission. + /// If [provisional] is set to `true`, silent notification permissions will be + /// automatically granted. When notifications are delivered to the device, the + /// user will be presented with an option to disable notifications, keep receiving + /// them silently or enable prominent notifications. + /// + /// On Android, is it not required to call this method. If called however, + /// a [NotificationSettings] class will be returned with + /// [NotificationSettings.authorizationStatus] returning + /// [AuthorizationStatus.authorized]. + /// + /// On Web, a popup requesting the users permission is shown using the native + /// browser API. + Future requestPermission({ + /// Request permission to display alerts. Defaults to `true`. + /// + /// iOS only. + bool alert = true, + + /// Request permission for Siri to automatically read out notification messages over AirPods. + /// Defaults to `false`. + /// + /// iOS only. + bool announcement = false, + + /// Request permission to update the application badge. Defaults to `true`. + /// + /// iOS only. + bool badge = true, + + /// Request permission to display notifications in a CarPlay environment. + /// Defaults to `false`. + /// + /// iOS only. + bool carPlay = false, + + /// Request permission for critical alerts. Defaults to `false`. + /// + /// Note; your application must explicitly state reasoning for enabling + /// critical alerts during the App Store review process or your may be + /// rejected. + /// + /// iOS only. + bool criticalAlert = false, + + /// Request permission to provisionally create non-interrupting notifications. + /// Defaults to `false`. + /// + /// iOS only. + bool provisional = false, + + /// Request permission to play sounds. Defaults to `true`. + /// + /// iOS only. + bool sound = true, + }) { + throw UnimplementedError('requestPermission() is not implemented'); + } + + /// Enable or disable auto-initialization of Firebase Cloud Messaging. + Future setAutoInitEnabled(bool enabled) async { + throw UnimplementedError('setAutoInitEnabled() is not implemented'); + } + + /// Sets the presentation options for iOS based notifications when received in + /// the foreground. + /// + /// By default, on iOS devices notification messages are only shown when + /// the application is in the background or terminated. Calling this method + /// updates these settings to allow a notification to trigger feedback to the + /// user. + /// + /// Important: The requested permissions and those set by the user take priority + /// over these settings. + /// + /// - [alert] Causes a notification message to display in the foreground, overlaying + /// the current application (heads up mode). + /// - [badge] The application badge count will be updated if the application is + /// in the foreground. + /// - [sound] The device will trigger a sound if the application is in the foreground. + /// + /// If all arguments are `false`, a notification message will not be displayed in the + /// foreground. + Future setForegroundNotificationPresentationOptions({ + bool alert, + bool badge, + bool sound, + }) { + throw UnimplementedError( + 'setForegroundNotificationPresentationOptions() is not implemented'); + } + + /// Send a new [RemoteMessage] to the FCM server. + Future sendMessage({ + String to, + Map data, + String collapseKey, + String messageId, + String messageType, + int ttl, + }) { + throw UnimplementedError('sendMessage() is not implemented'); + } + + /// Subscribe to topic in background. + /// + /// [topic] must match the following regular expression: + /// "[a-zA-Z0-9-_.~%]{1,900}". + Future subscribeToTopic(String topic) { + throw UnimplementedError('subscribeToTopic() is not implemented'); + } + + /// Unsubscribe from topic in background. + Future unsubscribeFromTopic(String topic) { + throw UnimplementedError('unsubscribeFromTopic() is not implemented'); + } +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/remote_message.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/remote_message.dart new file mode 100644 index 000000000000..7b967ba5f852 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/remote_message.dart @@ -0,0 +1,89 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; + +/// A class representing a message sent from Firebase Cloud Messaging. +class RemoteMessage { + // ignore: public_member_api_docs + const RemoteMessage( + {this.senderId, + this.category, + this.collapseKey, + this.contentAvailable, + this.data, + this.from, + this.messageId, + this.messageType, + this.mutableContent, + this.notification, + this.sentTime, + this.threadId, + this.ttl}); + + /// Constructs a [RemoteMessage] from a raw Map. + factory RemoteMessage.fromMap(Map map) { + return RemoteMessage( + senderId: map['senderId'], + category: map['category'], + collapseKey: map['collapseKey'], + contentAvailable: map['contentAvailable'] ?? false, + data: map['data'] == null + ? {} + : Map.from(map['data']), + from: map['from'], + messageId: map['messageId'], + mutableContent: map['mutableContent'] ?? false, + notification: map['notification'] == null + ? null + : RemoteNotification.fromMap( + Map.from(map['notification'])), + sentTime: map['sentTime'] == null + ? null + : DateTime.fromMillisecondsSinceEpoch(map['sentTime']), + threadId: map['threadId'], + ttl: map['ttl'], + ); + } + + /// The ID of the upstream sender location. + final String senderId; + + /// The iOS category this notification is assigned to. + final String category; + + /// The collapse key a message was sent with. Used to override existing messages with the same key. + final String collapseKey; + + /// Whether the iOS APNs message was configured as a background update notification. + final bool contentAvailable; + + /// Any additional data sent with the message. + final Map data; + + /// The topic name or message identifier. + final String from; + + /// A unique ID assigned to every message. + final String messageId; + + /// The message type of the message. + final String messageType; + + /// Whether the iOS APNs `mutable-content` property on the message was set + /// allowing the app to modify the notification via app extensions. + final bool mutableContent; + + /// Additional Notification data sent with the message. + final RemoteNotification notification; + + /// The time the message was sent, represented as a [DateTime]. + final DateTime sentTime; + + /// An iOS app specific identifier used for notification grouping. + final String threadId; + + /// The time to live for the message in seconds. + final int ttl; +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/remote_notification.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/remote_notification.dart new file mode 100644 index 000000000000..27b3df62a6b4 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/remote_notification.dart @@ -0,0 +1,200 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; +import 'utils.dart'; + +/// A class representing a notification which has been construted and sent to the +/// device via FCM. +/// +/// This class can be accessed via a [RemoteMessage.notification]. +class RemoteNotification { + // ignore: public_member_api_docs + const RemoteNotification( + {this.android, + this.apple, + this.title, + this.titleLocArgs, + this.titleLocKey, + this.body, + this.bodyLocArgs, + this.bodyLocKey}); + + /// Constructs a [RemoteNotification] from a raw Map. + factory RemoteNotification.fromMap(Map map) { + AndroidNotification _android; + AppleNotification _apple; + + if (map['android'] != null) { + _android = AndroidNotification( + channelId: map['android']['channelId'], + clickAction: map['android']['clickAction'], + color: map['android']['color'], + count: map['android']['count'], + imageUrl: map['android']['imageUrl'], + link: map['android']['link'], + priority: + convertToAndroidNotificationPriority(map['android']['priority']), + smallIcon: map['android']['smallIcon'], + sound: map['android']['sound'], + ticker: map['android']['ticker'], + visibility: convertToAndroidNotificationVisibility( + map['android']['visibility']), + ); + } + + if (map['apple'] != null) { + _apple = AppleNotification( + badge: map['apple']['badge'], + subtitle: map['apple']['subtitle'], + subtitleLocArgs: map['apple']['subtitleLocArgs'] ?? [], + subtitleLocKey: map['apple']['subtitleLocKey'], + sound: map['apple']['sound'] == null + ? null + : AppleNotificationSound( + critical: map['apple']['criticalSound']['critical'], + name: map['apple']['criticalSound']['name'], + volume: map['apple']['criticalSound']['volume'])); + } + + return RemoteNotification( + title: map['title'], + titleLocArgs: map['titleLocArgs'] ?? [], + titleLocKey: map['titleLocKey'], + body: map['body'], + bodyLocArgs: map['bodyLocArgs'] ?? [], + bodyLocKey: map['bodyLocKey'], + android: _android, + apple: _apple, + ); + } + + /// Android specific notification properties. + final AndroidNotification android; + + /// Apple specific notification properties. + final AppleNotification apple; + + /// The notification title. + final String title; + + /// Any arguments that should be formatted into the resource specified by titleLocKey. + final List titleLocArgs; + + /// The native localization key for the notification title. + final String titleLocKey; + + /// The notification body content. + final String body; + + /// Any arguments that should be formatted into the resource specified by bodyLocKey. + final List bodyLocArgs; + + /// The native localization key for the notification body content. + final String bodyLocKey; +} + +/// Android specific properties of a [RemoteNotification]. +/// +/// This will only be populated if the current device is Android. +class AndroidNotification { + // ignore: public_member_api_docs + const AndroidNotification( + {this.channelId, + this.clickAction, + this.color, + this.count, + this.imageUrl, + this.link, + this.priority, + this.smallIcon, + this.sound, + this.ticker, + this.visibility}); + + /// The channel the notification is delivered on. + final String channelId; + + /// A spcific click action was defined for the notification. + /// + /// This property is not required to handle user interaction. + final String clickAction; + + /// The color of the notification. + final String color; + + /// The current notification count for the application. + final int count; + + /// The image URL for the notification. + /// + /// Will be `null` if the notification did not include an image. + final String imageUrl; + + // ignore: public_member_api_docs + final String link; + + /// The priority for the notifcation. + /// + /// This property only has impact on devices running Android 8.0 (API level 26) +. + /// Later than this, they use the channel importance instead. + final AndroidNotificationPriority priority; + + /// The resource file name of the small icon shown in the notification. + final String smallIcon; + + /// The resource file name of the sound used to alert users to the incoming notification. + final String sound; + + /// Ticker text for the notification, used for accessibility purposes. + final String ticker; + + /// The visibility level of the notification. + final AndroidNotificationVisibility visibility; +} + +/// Apple specific properties of a [RemoteNotification]. +/// +/// This will only be populated if the current device is Apple based (iOS/MacOS). +class AppleNotification { + // ignore: public_member_api_docs + const AppleNotification( + {this.badge, + this.sound, + this.subtitle, + this.subtitleLocArgs, + this.subtitleLocKey}); + + /// The value which sets the application badge. + final String badge; + + /// Sound values for the incoming notification. + final AppleNotificationSound sound; + + /// Any subtile text on the notification. + final String subtitle; + + /// Any arguments that should be formatted into the resource specified by subtitleLocKey. + final List subtitleLocArgs; + + /// The native localization key for the notification subtitle. + final String subtitleLocKey; +} + +/// Represents the sound property for [AppleNotification] +class AppleNotificationSound { + // ignore: public_member_api_docs + const AppleNotificationSound({this.critical, this.name, this.volume}); + + /// Whether or not the notification sound was critical. + final bool critical; + + /// The resource name of the sound played. + final String name; + + /// The volume of the sound. + /// + /// This value is a number between 0.0 & 1.0. + final num volume; +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/types.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/types.dart new file mode 100644 index 000000000000..600a487713df --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/types.dart @@ -0,0 +1,98 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; + +/// Defines a handler for incoming remote message payloads. +typedef Future BackgroundMessageHandler(RemoteMessage message); + +/// An enum representing a notification setting for this app on the device. +enum AppleNotificationSetting { + /// This setting is currently disabled by the user. + disabled, + + /// This setting is currently enabled. + enabled, + + /// This setting is not supported on this device. + /// + /// Usually this means that the iOS version required for this setting has not been met, + /// or the platform is not Apple. + notSupported, +} + +/// An enum representing the show previews notification setting for this app on the device. +enum AppleShowPreviewSetting { + /// Always show previews even if the device is currently locked. + always, + + /// Never show previews. + never, + + /// This setting is not supported on this device. + /// + /// Usually this means that the iOS version required for this setting (iOS 11+) has not been met, + /// or the platform is not Apple. + notSupported, + + /// Only show previews when the device is unlocked. + whenAuthenticated, +} + +/// Represents the current status of the platforms notification permissions. +enum AuthorizationStatus { + /// The app is authorized to create notifications. + authorized, + + /// The app is not authorized to create notifications. + denied, + + /// The app user has not yet chosen whether to allow the application to create + /// notifications. Usually this status is returned prior to the first call + /// of [requestPermission]. + notDetermined, + + /// The app is currently authorized to post non-interrupting user notifications. + provisional, +} + +/// An enum representing a notification priority on Android. +/// +/// Note; on devices which have channel support (Android 8.0 (API level 26) +), +/// this value will be ignored. Instead, the channel "importance" level is used. +enum AndroidNotificationPriority { + /// The application small icon will not show up in the status bar, or alert the user. The notification + /// will be in a collapsed state in the notification shade and placed at the bottom of the list. + minimumPriority, + + /// The application small icon will show in the device status bar, however the notification will + /// not alert the user (no sound or vibration). The notification will show in it's expanded state + /// when the notification shade is pulled down. + lowPriority, + + /// When a notification is received, the device smallIcon will appear in the notification shade. + /// When the user pulls down the notification shade, the content of the notification will be shown + /// in it's expanded state. + defaultPriority, + + /// Notifications will appear on-top of applications, allowing direct interaction without pulling + /// own the notification shade. This level is used for urgent notifications, such as + /// incoming phone calls, messages etc, which require immediate attention. + highPriority, + + /// The highest priority level a notification can be set to. + maximumPriority, +} + +/// An enum representing the visibility level of a notification on Android. +enum AndroidNotificationVisibility { + /// Do not reveal any part of this notification on a secure lock-screen. + secret, + + /// Show this notification on all lock-screens, but conceal sensitive or private information on secure lock-screens. + private, + + /// Show this notification in its entirety on all lock-screens. + public, +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/utils.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/utils.dart new file mode 100644 index 000000000000..82ffe01877bf --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/lib/src/utils.dart @@ -0,0 +1,122 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; + +/// Converts an [int] into it's [AndroidNotificationPriority] representation. +AndroidNotificationPriority convertToAndroidNotificationPriority(int priority) { + switch (priority) { + case -2: + return AndroidNotificationPriority.minimumPriority; + case -1: + return AndroidNotificationPriority.lowPriority; + case 0: + return AndroidNotificationPriority.defaultPriority; + case 1: + return AndroidNotificationPriority.highPriority; + case 2: + return AndroidNotificationPriority.maximumPriority; + default: + return AndroidNotificationPriority.defaultPriority; + } +} + +/// Converts an [int] into it's [AndroidNotificationVisibility] representation. +AndroidNotificationVisibility convertToAndroidNotificationVisibility( + int visibility) { + switch (visibility) { + case -1: + return AndroidNotificationVisibility.secret; + case 0: + return AndroidNotificationVisibility.private; + case 1: + return AndroidNotificationVisibility.public; + default: + return AndroidNotificationVisibility.private; + } +} + +/// Converts an [int] into it's [AuthorizationStatus] representation. +AuthorizationStatus convertToAuthorizationStatus(int status) { + // Can be null on unsupported platforms, e.g. iOS < 10. + if (status == null) { + return AuthorizationStatus.notDetermined; + } + switch (status) { + case -1: + return AuthorizationStatus.notDetermined; + case 0: + return AuthorizationStatus.denied; + case 1: + return AuthorizationStatus.authorized; + case 2: + return AuthorizationStatus.provisional; + default: + return AuthorizationStatus.notDetermined; + } +} + +/// Converts an [int] into it's [AppleNotificationSetting] representation. +AppleNotificationSetting convertToAppleNotificationSetting(int status) { + // Can be null on unsupported platforms, e.g. iOS < 10. + if (status == null) { + return AppleNotificationSetting.notSupported; + } + switch (status) { + case -1: + return AppleNotificationSetting.notSupported; + case 0: + return AppleNotificationSetting.disabled; + case 1: + return AppleNotificationSetting.enabled; + default: + return AppleNotificationSetting.notSupported; + } +} + +/// Converts an [int] into its [AppleShowPreviewSetting] representation. +AppleShowPreviewSetting convertToAppleShowPreviewSetting(int status) { + switch (status) { + case -1: + return AppleShowPreviewSetting.notSupported; + case 0: + return AppleShowPreviewSetting.never; + case 1: + return AppleShowPreviewSetting.always; + case 2: + return AppleShowPreviewSetting.whenAuthenticated; + default: + return AppleShowPreviewSetting.notSupported; + } +} + +/// Converts a [Map] into it's [NotificationSettings] representation. +NotificationSettings convertToNotificationSettings(Map map) { + return NotificationSettings( + authorizationStatus: + convertToAuthorizationStatus(map['authorizationStatus']), + alert: convertToAppleNotificationSetting(map['alert']), + announcement: convertToAppleNotificationSetting(map['announcement']), + badge: convertToAppleNotificationSetting(map['badge']), + carPlay: convertToAppleNotificationSetting(map['carPlay']), + lockScreen: convertToAppleNotificationSetting(map['lockScreen']), + notificationCenter: + convertToAppleNotificationSetting(map['notificationCenter']), + showPreviews: convertToAppleShowPreviewSetting(map['showPreviews']), + sound: convertToAppleNotificationSetting(map['sound']), + ); +} + +/// Used to return [NotificationSettings] for all Android devices. +final NotificationSettings androidNotificationSettings = NotificationSettings( + authorizationStatus: AuthorizationStatus.authorized, + alert: AppleNotificationSetting.notSupported, + announcement: AppleNotificationSetting.notSupported, + badge: AppleNotificationSetting.notSupported, + carPlay: AppleNotificationSetting.notSupported, + lockScreen: AppleNotificationSetting.notSupported, + notificationCenter: AppleNotificationSetting.notSupported, + showPreviews: AppleShowPreviewSetting.notSupported, + sound: AppleNotificationSetting.notSupported, +); diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/pubspec.yaml b/packages/firebase_messaging/firebase_messaging_platform_interface/pubspec.yaml new file mode 100644 index 000000000000..8e5a6f2c4ff1 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/pubspec.yaml @@ -0,0 +1,22 @@ +name: firebase_messaging_platform_interface +description: A common platform interface for the firebase_messaging plugin. +version: 1.0.0-dev.2 +homepage: https://github.com/FirebaseExtended/flutterfire/tree/master/packages/firebase_messaging/firebase_messaging_platform_interface + +dependencies: + flutter: + sdk: flutter + meta: ^1.0.5 + firebase_core: ">=0.5.0 <0.6.0" + plugin_platform_interface: ^1.0.0 + +dev_dependencies: + firebase_core_platform_interface: ">=2.0.0 <2.1.0" + pedantic: ^1.8.0 + flutter_test: + sdk: flutter + mockito: ^4.1.1 + +environment: + sdk: ">=2.1.0 <3.0.0" + flutter: ">=1.9.1+hotfix.5 <2.0.0" diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/test/ios_notification_settings_test.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/test/ios_notification_settings_test.dart new file mode 100644 index 000000000000..8d35dd37d410 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/test/ios_notification_settings_test.dart @@ -0,0 +1,19 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:firebase_messaging_platform_interface/src/ios_notification_settings.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('IosNotificationSettings', () { + test('toMap()', () { + // ignore: deprecated_member_use_from_same_package + final settings = IosNotificationSettings(sound: false, alert: false); + final settingsMap = settings.toMap(); + expect(settingsMap, isA>()); + expect(settingsMap['sound'], isFalse); + expect(settingsMap['alert'], isFalse); + }); + }); +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/test/method_channel_tests/method_channel_messaging_test.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/test/method_channel_tests/method_channel_messaging_test.dart new file mode 100644 index 000000000000..e1a16d80d4b5 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/test/method_channel_tests/method_channel_messaging_test.dart @@ -0,0 +1,271 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:firebase_messaging_platform_interface/src/method_channel/method_channel_messaging.dart'; +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/services.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:mockito/mockito.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../mock.dart'; + +void main() { + setupFirebaseMessagingMocks(); + + FirebaseApp app; + FirebaseMessagingPlatform messaging; + final List log = []; + + group('$MethodChannelFirebaseMessaging', () { + setUpAll(() async { + app = await Firebase.initializeApp(); + + handleMethodCall((call) async { + log.add(call); + + switch (call.method) { + case 'Messaging#deleteToken': + case 'Messaging#sendMessage': + case 'Messaging#subscribeToTopic': + case 'Messaging#unsubscribeFromTopic': + return null; + case 'Messaging#getAPNSToken': + case 'Messaging#getToken': + return 'test_token'; + case 'Messaging#hasPermission': + case 'Messaging#requestPermission': + return { + 'authorizationStatus': 1, + 'alert': 1, + 'announcement': 0, + 'badge': 1, + 'carPlay': 0, + 'criticalAlert': 0, + 'provisional': 0, + 'sound': 1, + }; + case 'Messaging#setAutoInitEnabled': + return { + 'isAutoInitEnabled': true, + }; + case 'Messaging#deleteInstanceID': + return true; + default: + return {}; + } + }); + }); + + setUp(() { + log.clear(); + messaging = MethodChannelFirebaseMessaging(app: app); + }); + + group('$FirebaseMessagingPlatform()', () { + test('$MethodChannelFirebaseMessaging is the default instance', () { + expect(FirebaseMessagingPlatform.instance, + isA()); + }); + + test('Cannot be implemented with `implements`', () { + expect(() { + FirebaseMessagingPlatform.instance = + ImplementsFirebaseMessagingPlatform(); + }, throwsAssertionError); + }); + + test('Can be extended', () { + FirebaseMessagingPlatform.instance = ExtendsFirebaseMessagingPlatform(); + }); + + test('Can be mocked with `implements`', () { + final FirebaseMessagingPlatform mock = MocksFirebaseMessagingPlatform(); + FirebaseMessagingPlatform.instance = mock; + }); + }); + + test('delegateFor()', () { + final testMessaging = TestMethodChannelFirebaseMessaging(Firebase.app()); + final result = testMessaging.delegateFor(app: Firebase.app()); + + expect(result, isA()); + expect(result.app, isA()); + }); + + group('setInitialValues()', () { + test('when isAutoInitEnabled is false', () { + final testMessaging = + TestMethodChannelFirebaseMessaging(Firebase.app()); + final result = testMessaging.setInitialValues(isAutoInitEnabled: false); + expect(result, isA()); + expect(result.isAutoInitEnabled, isFalse); + }); + + test('when isAutoInitEnabled is true', () { + final testMessaging = + TestMethodChannelFirebaseMessaging(Firebase.app()); + final result = testMessaging.setInitialValues(isAutoInitEnabled: true); + expect(result, isA()); + expect(result.isAutoInitEnabled, isTrue); + }); + }); + + test('isAutoInitEnabled', () { + // ignore: invalid_use_of_protected_member + messaging.setInitialValues(isAutoInitEnabled: true); + expect(messaging.isAutoInitEnabled, isTrue); + }); + + test('deleteToken', () async { + await messaging.deleteToken(); + + // check native method was called + expect(log, [ + isMethodCall( + 'Messaging#deleteToken', + arguments: { + 'appName': defaultFirebaseAppName, + 'senderId': null, + }, + ), + ]); + }); + + test('getAPNSToken', () async { + // not applicable to android + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + await messaging.getAPNSToken(); + + // check native method was called + expect(log, [ + isMethodCall( + 'Messaging#getAPNSToken', + arguments: { + 'appName': defaultFirebaseAppName, + }, + ), + ]); + }); + + test('getToken', () async { + await messaging.getToken(); + + // check native method was called + expect(log, [ + isMethodCall( + 'Messaging#getToken', + arguments: { + 'appName': defaultFirebaseAppName, + 'senderId': null, + }, + ), + ]); + }); + + test('requestPermission', () async { + // test android response + final androidPermissions = await messaging.requestPermission(); + expect(androidPermissions.authorizationStatus, + equals(AuthorizationStatus.authorized)); + // clear log + log.clear(); + + // test other platforms + debugDefaultTargetPlatformOverride = TargetPlatform.iOS; + final iosStatus = await messaging.requestPermission(); + expect(iosStatus.authorizationStatus, isA()); + expect(iosStatus.authorizationStatus, + equals(AuthorizationStatus.authorized)); + + // check native method was called + expect(log, [ + isMethodCall( + 'Messaging#requestPermission', + arguments: { + 'appName': defaultFirebaseAppName, + 'permissions': { + 'alert': true, + 'announcement': false, + 'badge': true, + 'carPlay': false, + 'criticalAlert': false, + 'provisional': false, + 'sound': true, + } + }, + ), + ]); + }); + + test('setAutoInitEnabled', () async { + expect(messaging.isAutoInitEnabled, isNull); + await messaging.setAutoInitEnabled(true); + expect(messaging.isAutoInitEnabled, isTrue); + + // check native method was called + expect(log, [ + isMethodCall( + 'Messaging#setAutoInitEnabled', + arguments: { + 'appName': defaultFirebaseAppName, + 'enabled': true + }, + ), + ]); + }); + + test('onTokenRefresh', () { + expect(messaging.onTokenRefresh, isA>()); + }); + + test('subscribeToTopic', () async { + const topic = 'test-topic'; + await messaging.subscribeToTopic(topic); + + // check native method was called + expect(log, [ + isMethodCall( + 'Messaging#subscribeToTopic', + arguments: { + 'appName': defaultFirebaseAppName, + 'topic': topic, + }, + ), + ]); + }); + + test('unsubscribeFromTopic', () async { + const topic = 'test-topic'; + await messaging.unsubscribeFromTopic(topic); + + // check native method was called + expect(log, [ + isMethodCall( + 'Messaging#unsubscribeFromTopic', + arguments: { + 'appName': defaultFirebaseAppName, + 'topic': topic, + }, + ), + ]); + }); + }); +} + +class ImplementsFirebaseMessagingPlatform extends Mock + implements FirebaseMessagingPlatform {} + +class MocksFirebaseMessagingPlatform extends Mock + with MockPlatformInterfaceMixin + implements FirebaseMessagingPlatform {} + +class ExtendsFirebaseMessagingPlatform extends FirebaseMessagingPlatform {} + +class TestMethodChannelFirebaseMessaging + extends MethodChannelFirebaseMessaging { + TestMethodChannelFirebaseMessaging(FirebaseApp app) : super(app: app); +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/test/mock.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/test/mock.dart new file mode 100644 index 000000000000..1ba01450e71e --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/test/mock.dart @@ -0,0 +1,64 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_messaging_platform_interface/src/method_channel/method_channel_messaging.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:firebase_core_platform_interface/firebase_core_platform_interface.dart'; + +typedef MethodCallCallback = dynamic Function(MethodCall methodCall); +typedef Callback(MethodCall call); + +void setupFirebaseMessagingMocks([Callback customHandlers]) { + TestWidgetsFlutterBinding.ensureInitialized(); + MethodChannelFirebase.channel.setMockMethodCallHandler((call) async { + if (call.method == 'Firebase#initializeCore') { + return [ + { + 'name': defaultFirebaseAppName, + 'options': { + 'apiKey': '123', + 'appId': '123', + 'messagingSenderId': '123', + 'projectId': '123', + }, + 'pluginConstants': {}, + } + ]; + } + if (call.method == 'Firebase#initializeApp') { + return { + 'name': call.arguments['appName'], + 'options': call.arguments['options'], + 'pluginConstants': {}, + }; + } + + if (customHandlers != null) { + customHandlers(call); + } + + return null; + }); +} + +void handleMethodCall(MethodCallCallback methodCallCallback) => + MethodChannelFirebaseMessaging.channel + .setMockMethodCallHandler((call) async { + return await methodCallCallback(call); + }); + +Future testExceptionHandling(String type, Function testMethod) async { + try { + await testMethod(); + } on FirebaseException catch (_) { + if (type == 'PLATFORM' || type == 'EXCEPTION') { + return; + } + fail( + 'testExceptionHandling: ${testMethod} threw unexpected FirebaseException'); + } catch (e) { + fail('testExceptionHandling: ${testMethod} threw invalid exception ${e}'); + } +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/test/notification_test.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/test/notification_test.dart new file mode 100644 index 000000000000..f8d22e42b8e8 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/test/notification_test.dart @@ -0,0 +1,42 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Notification', () { + test('new instance', () { + const testTitle = 'test notification'; + final notification = RemoteNotification(title: testTitle); + expect(notification.title, equals(testTitle)); + }); + }); + + group('AndroidNotification', () { + test('new instance', () { + const testChannelId = 'fooId'; + final notification = AndroidNotification( + channelId: testChannelId, + priority: AndroidNotificationPriority.lowPriority); + expect(notification.channelId, equals(testChannelId)); + }); + }); + + group('AppleNotification', () { + test('new instance', () { + const testSubtitle = 'bar'; + final notification = AppleNotification(subtitle: testSubtitle); + expect(notification.subtitle, equals(testSubtitle)); + }); + }); + + group('AppleNotificationSound', () { + test('new instance', () { + const testCritical = false; + final iosSound = AppleNotificationSound(critical: testCritical); + expect(iosSound.critical, equals(testCritical)); + }); + }); +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/test/platform_interface_tests/platform_interface_messaging_test.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/test/platform_interface_tests/platform_interface_messaging_test.dart new file mode 100644 index 000000000000..8abda280f3d0 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/test/platform_interface_tests/platform_interface_messaging_test.dart @@ -0,0 +1,215 @@ +// Copyright 2020, the Chromium project authors. Please see the AUTHORS file +// for details. All rights reserved. Use of this source code is governed by a +// BSD-style license that can be found in the LICENSE file. + +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:plugin_platform_interface/plugin_platform_interface.dart'; + +import '../mock.dart'; + +void main() { + setupFirebaseMessagingMocks(); + + TestFirebaseMessagingPlatform firebaseMessagingPlatform; + FirebaseApp app; + FirebaseApp secondaryApp; + + group('$FirebaseMessagingPlatform()', () { + setUpAll(() async { + app = await Firebase.initializeApp(); + + secondaryApp = await Firebase.initializeApp( + name: 'testApp2', + options: const FirebaseOptions( + appId: '1:1234567890:ios:42424242424242', + apiKey: '123', + projectId: '123', + messagingSenderId: '1234567890', + ), + ); + + firebaseMessagingPlatform = TestFirebaseMessagingPlatform( + app, + ); + + handleMethodCall((call) async { + switch (call.method) { + default: + return null; + } + }); + }); + + test('Constructor', () { + expect(firebaseMessagingPlatform, isA()); + expect(firebaseMessagingPlatform, isA()); + }); + + test('instanceFor', () { + final result = FirebaseMessagingPlatform.instanceFor( + app: app, + pluginConstants: { + 'AUTO_INIT_ENABLED': true, + }); + expect(result, isA()); + expect(result.isAutoInitEnabled, isA()); + }); + + test('get.instance', () { + expect( + FirebaseMessagingPlatform.instance, isA()); + expect(FirebaseMessagingPlatform.instance.app.name, + equals(defaultFirebaseAppName)); + }); + + group('set.instance', () { + test('sets the current instance', () { + FirebaseMessagingPlatform.instance = + TestFirebaseMessagingPlatform(secondaryApp); + + expect(FirebaseMessagingPlatform.instance, + isA()); + expect(FirebaseMessagingPlatform.instance.app.name, equals('testApp2')); + }); + + test('throws an [AssertionError] if instance is null', () { + expect(() => FirebaseMessagingPlatform.instance = null, + throwsAssertionError); + }); + }); + + test('throws if delegateFor', () { + try { + firebaseMessagingPlatform.testDelegateFor(); + } on UnimplementedError catch (e) { + expect(e.message, equals('delegateFor() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if setInitialValues', () { + try { + firebaseMessagingPlatform.testSetInitialValues(); + } on UnimplementedError catch (e) { + expect(e.message, equals('setInitialValues() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if isAutoInitEnabled', () { + try { + firebaseMessagingPlatform.isAutoInitEnabled; + } on UnimplementedError catch (e) { + expect(e.message, equals('isAutoInitEnabled is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if getInitialMessage', () { + try { + firebaseMessagingPlatform.getInitialMessage(); + } on UnimplementedError catch (e) { + expect(e.message, equals('getInitialMessage() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if deleteToken()', () async { + try { + await firebaseMessagingPlatform.deleteToken(); + } on UnimplementedError catch (e) { + expect(e.message, equals('deleteToken() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if getAPNSToken()', () async { + try { + await firebaseMessagingPlatform.getAPNSToken(); + } on UnimplementedError catch (e) { + expect(e.message, equals('getAPNSToken() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if getToken()', () async { + try { + await firebaseMessagingPlatform.getToken(); + } on UnimplementedError catch (e) { + expect(e.message, equals('getToken() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if onTokenRefresh', () { + try { + firebaseMessagingPlatform.onTokenRefresh; + } on UnimplementedError catch (e) { + expect(e.message, equals('onTokenRefresh is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if requestPermission()', () async { + try { + await firebaseMessagingPlatform.requestPermission(); + } on UnimplementedError catch (e) { + expect(e.message, equals('requestPermission() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if setAutoInitEnabled()', () async { + try { + await firebaseMessagingPlatform.setAutoInitEnabled(true); + } on UnimplementedError catch (e) { + expect(e.message, equals('setAutoInitEnabled() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if subscribeToTopic()', () async { + try { + await firebaseMessagingPlatform.subscribeToTopic('foo'); + } on UnimplementedError catch (e) { + expect(e.message, equals('subscribeToTopic() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + + test('throws if unsubscribeFromTopic()', () async { + try { + await firebaseMessagingPlatform.unsubscribeFromTopic('foo'); + } on UnimplementedError catch (e) { + expect(e.message, equals('unsubscribeFromTopic() is not implemented')); + return; + } + fail('Should have thrown an [UnimplementedError]'); + }); + }); +} + +class TestFirebaseMessagingPlatform extends FirebaseMessagingPlatform { + TestFirebaseMessagingPlatform(FirebaseApp app) : super(appInstance: app); + + FirebaseMessagingPlatform testDelegateFor({FirebaseApp app}) { + return this.delegateFor(); + } + + FirebaseMessagingPlatform testSetInitialValues() { + return this.setInitialValues(isAutoInitEnabled: true); + } +} diff --git a/packages/firebase_messaging/firebase_messaging_platform_interface/test/utils_test.dart b/packages/firebase_messaging/firebase_messaging_platform_interface/test/utils_test.dart new file mode 100644 index 000000000000..36566ea14e80 --- /dev/null +++ b/packages/firebase_messaging/firebase_messaging_platform_interface/test/utils_test.dart @@ -0,0 +1,25 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:firebase_messaging_platform_interface/firebase_messaging_platform_interface.dart'; +import 'package:firebase_messaging_platform_interface/src/utils.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + group('Utilities', () { + test('convertToNotificationPriority()', () { + expect(convertToAndroidNotificationPriority(1), + isA()); + }); + + test('convertToAndroidNotificationVisibility()', () { + expect(convertToAndroidNotificationVisibility(1), + isA()); + }); + + test('convertToIOSAuthorizationStatus()', () { + expect(convertToAuthorizationStatus(1), isA()); + }); + }); +} diff --git a/packages/firebase_messaging/ios/Classes/FLTFirebaseMessagingPlugin.h b/packages/firebase_messaging/ios/Classes/FLTFirebaseMessagingPlugin.h deleted file mode 100644 index 492be15a4ea6..000000000000 --- a/packages/firebase_messaging/ios/Classes/FLTFirebaseMessagingPlugin.h +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import - -@interface FLTFirebaseMessagingPlugin : NSObject -@end diff --git a/packages/firebase_messaging/ios/Classes/FLTFirebaseMessagingPlugin.m b/packages/firebase_messaging/ios/Classes/FLTFirebaseMessagingPlugin.m deleted file mode 100644 index ec35948f6364..000000000000 --- a/packages/firebase_messaging/ios/Classes/FLTFirebaseMessagingPlugin.m +++ /dev/null @@ -1,294 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#import - -#import "FLTFirebaseMessagingPlugin.h" - -#import "Firebase/Firebase.h" - -NSString *const kGCMMessageIDKey = @"gcm.message_id"; - -#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 -@interface FLTFirebaseMessagingPlugin () -@end -#endif - -static FlutterError *getFlutterError(NSError *error) { - if (error == nil) return nil; - return [FlutterError errorWithCode:[NSString stringWithFormat:@"Error %ld", (long)error.code] - message:error.domain - details:error.localizedDescription]; -} - -static NSObject *_registrar; - -@implementation FLTFirebaseMessagingPlugin { - FlutterMethodChannel *_channel; - NSDictionary *_launchNotification; - BOOL _resumingFromBackground; -} - -+ (void)registerWithRegistrar:(NSObject *)registrar { - _registrar = registrar; - FlutterMethodChannel *channel = - [FlutterMethodChannel methodChannelWithName:@"plugins.flutter.io/firebase_messaging" - binaryMessenger:[registrar messenger]]; - FLTFirebaseMessagingPlugin *instance = - [[FLTFirebaseMessagingPlugin alloc] initWithChannel:channel]; - [registrar addApplicationDelegate:instance]; - [registrar addMethodCallDelegate:instance channel:channel]; - - SEL sel = NSSelectorFromString(@"registerLibrary:withVersion:"); - if ([FIRApp respondsToSelector:sel]) { - [FIRApp performSelector:sel withObject:LIBRARY_NAME withObject:LIBRARY_VERSION]; - } -} - -- (instancetype)initWithChannel:(FlutterMethodChannel *)channel { - self = [super init]; - - if (self) { - _channel = channel; - _resumingFromBackground = NO; - [FIRMessaging messaging].delegate = self; - } - return self; -} - -- (void)handleMethodCall:(FlutterMethodCall *)call result:(FlutterResult)result { - NSString *method = call.method; - if ([@"requestNotificationPermissions" isEqualToString:method]) { - NSDictionary *arguments = call.arguments; - if (@available(iOS 10.0, *)) { - UNAuthorizationOptions authOptions = 0; - NSNumber *provisional = arguments[@"provisional"]; - if ([arguments[@"sound"] boolValue]) { - authOptions |= UNAuthorizationOptionSound; - } - if ([arguments[@"alert"] boolValue]) { - authOptions |= UNAuthorizationOptionAlert; - } - if ([arguments[@"badge"] boolValue]) { - authOptions |= UNAuthorizationOptionBadge; - } - - NSNumber *isAtLeastVersion12; - if (@available(iOS 12, *)) { - isAtLeastVersion12 = [NSNumber numberWithBool:YES]; - if ([provisional boolValue]) authOptions |= UNAuthorizationOptionProvisional; - } else { - isAtLeastVersion12 = [NSNumber numberWithBool:NO]; - } - - [[UNUserNotificationCenter currentNotificationCenter] - requestAuthorizationWithOptions:authOptions - completionHandler:^(BOOL granted, NSError *_Nullable error) { - if (error) { - result(getFlutterError(error)); - return; - } - // This works for iOS >= 10. See - // [UIApplication:didRegisterUserNotificationSettings:notificationSettings] - // for ios < 10. - [[UNUserNotificationCenter currentNotificationCenter] - getNotificationSettingsWithCompletionHandler:^( - UNNotificationSettings *_Nonnull settings) { - NSDictionary *settingsDictionary = @{ - @"sound" : [NSNumber numberWithBool:settings.soundSetting == - UNNotificationSettingEnabled], - @"badge" : [NSNumber numberWithBool:settings.badgeSetting == - UNNotificationSettingEnabled], - @"alert" : [NSNumber numberWithBool:settings.alertSetting == - UNNotificationSettingEnabled], - @"provisional" : - [NSNumber numberWithBool:granted && [provisional boolValue] && - isAtLeastVersion12], - }; - [self->_channel invokeMethod:@"onIosSettingsRegistered" - arguments:settingsDictionary]; - }]; - result([NSNumber numberWithBool:granted]); - }]; - - [[UIApplication sharedApplication] registerForRemoteNotifications]; - } else { - UIUserNotificationType notificationTypes = 0; - if ([arguments[@"sound"] boolValue]) { - notificationTypes |= UIUserNotificationTypeSound; - } - if ([arguments[@"alert"] boolValue]) { - notificationTypes |= UIUserNotificationTypeAlert; - } - if ([arguments[@"badge"] boolValue]) { - notificationTypes |= UIUserNotificationTypeBadge; - } - - UIUserNotificationSettings *settings = - [UIUserNotificationSettings settingsForTypes:notificationTypes categories:nil]; - [[UIApplication sharedApplication] registerUserNotificationSettings:settings]; - - [[UIApplication sharedApplication] registerForRemoteNotifications]; - result([NSNumber numberWithBool:YES]); - } - } else if ([@"configure" isEqualToString:method]) { - [FIRMessaging messaging].shouldEstablishDirectChannel = true; - [[UIApplication sharedApplication] registerForRemoteNotifications]; - if (_launchNotification != nil && _launchNotification[kGCMMessageIDKey]) { - [_channel invokeMethod:@"onLaunch" arguments:_launchNotification]; - } - result(nil); - } else if ([@"subscribeToTopic" isEqualToString:method]) { - NSString *topic = call.arguments; - [[FIRMessaging messaging] subscribeToTopic:topic - completion:^(NSError *error) { - result(getFlutterError(error)); - }]; - } else if ([@"unsubscribeFromTopic" isEqualToString:method]) { - NSString *topic = call.arguments; - [[FIRMessaging messaging] unsubscribeFromTopic:topic - completion:^(NSError *error) { - result(getFlutterError(error)); - }]; - } else if ([@"getToken" isEqualToString:method]) { - [[FIRInstanceID instanceID] - instanceIDWithHandler:^(FIRInstanceIDResult *_Nullable instanceIDResult, - NSError *_Nullable error) { - if (error != nil) { - NSLog(@"getToken, error fetching instanceID: %@", error); - result(nil); - } else { - result(instanceIDResult.token); - } - }]; - } else if ([@"deleteInstanceID" isEqualToString:method]) { - [[FIRInstanceID instanceID] deleteIDWithHandler:^void(NSError *_Nullable error) { - if (error.code != 0) { - NSLog(@"deleteInstanceID, error: %@", error); - result([NSNumber numberWithBool:NO]); - } else { - [[UIApplication sharedApplication] unregisterForRemoteNotifications]; - result([NSNumber numberWithBool:YES]); - } - }]; - } else if ([@"autoInitEnabled" isEqualToString:method]) { - BOOL value = [[FIRMessaging messaging] isAutoInitEnabled]; - result([NSNumber numberWithBool:value]); - } else if ([@"setAutoInitEnabled" isEqualToString:method]) { - NSNumber *value = call.arguments; - [FIRMessaging messaging].autoInitEnabled = value.boolValue; - result(nil); - } else { - result(FlutterMethodNotImplemented); - } -} - -#if defined(__IPHONE_10_0) && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_10_0 -// Received data message on iOS 10 devices while app is in the foreground. -// Only invoked if method swizzling is enabled. -- (void)applicationReceivedRemoteMessage:(FIRMessagingRemoteMessage *)remoteMessage { - [self didReceiveRemoteNotification:remoteMessage.appData]; -} - -// Received data message on iOS 10 devices while app is in the foreground. -// Only invoked if method swizzling is disabled and UNUserNotificationCenterDelegate has been -// registered in AppDelegate -- (void)userNotificationCenter:(UNUserNotificationCenter *)center - willPresentNotification:(UNNotification *)notification - withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler - NS_AVAILABLE_IOS(10.0) { - NSDictionary *userInfo = notification.request.content.userInfo; - // Check to key to ensure we only handle messages from Firebase - if (userInfo[kGCMMessageIDKey]) { - [[FIRMessaging messaging] appDidReceiveMessage:userInfo]; - [_channel invokeMethod:@"onMessage" arguments:userInfo]; - completionHandler(UNNotificationPresentationOptionNone); - } -} - -- (void)userNotificationCenter:(UNUserNotificationCenter *)center - didReceiveNotificationResponse:(UNNotificationResponse *)response - withCompletionHandler:(void (^)(void))completionHandler NS_AVAILABLE_IOS(10.0) { - NSDictionary *userInfo = response.notification.request.content.userInfo; - // Check to key to ensure we only handle messages from Firebase - if (userInfo[kGCMMessageIDKey]) { - [_channel invokeMethod:@"onResume" arguments:userInfo]; - completionHandler(); - } -} - -#endif - -- (void)didReceiveRemoteNotification:(NSDictionary *)userInfo { - if (_resumingFromBackground) { - [_channel invokeMethod:@"onResume" arguments:userInfo]; - } else { - [_channel invokeMethod:@"onMessage" arguments:userInfo]; - } -} - -#pragma mark - AppDelegate - -- (BOOL)application:(UIApplication *)application - didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { - if (launchOptions != nil) { - _launchNotification = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey]; - } - return YES; -} - -- (void)applicationDidEnterBackground:(UIApplication *)application { - _resumingFromBackground = YES; -} - -- (void)applicationDidBecomeActive:(UIApplication *)application { - _resumingFromBackground = NO; - // Removes badge number but doesn't clear push notifications, - // helpful when you have valuable info in your push notification - application.applicationIconBadgeNumber = 0; -} - -- (BOOL)application:(UIApplication *)application - didReceiveRemoteNotification:(NSDictionary *)userInfo - fetchCompletionHandler:(void (^)(UIBackgroundFetchResult result))completionHandler { - [self didReceiveRemoteNotification:userInfo]; - completionHandler(UIBackgroundFetchResultNoData); - return YES; -} - -- (void)application:(UIApplication *)application - didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { -#ifdef DEBUG - [[FIRMessaging messaging] setAPNSToken:deviceToken type:FIRMessagingAPNSTokenTypeSandbox]; -#else - [[FIRMessaging messaging] setAPNSToken:deviceToken type:FIRMessagingAPNSTokenTypeProd]; -#endif - - [_channel invokeMethod:@"onToken" arguments:[FIRMessaging messaging].FCMToken]; -} - -// This will only be called for iOS < 10. For iOS >= 10, we make this call when we request -// permissions. -- (void)application:(UIApplication *)application - didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings { - NSDictionary *settingsDictionary = @{ - @"sound" : [NSNumber numberWithBool:notificationSettings.types & UIUserNotificationTypeSound], - @"badge" : [NSNumber numberWithBool:notificationSettings.types & UIUserNotificationTypeBadge], - @"alert" : [NSNumber numberWithBool:notificationSettings.types & UIUserNotificationTypeAlert], - @"provisional" : [NSNumber numberWithBool:NO], - }; - [_channel invokeMethod:@"onIosSettingsRegistered" arguments:settingsDictionary]; -} - -- (void)messaging:(nonnull FIRMessaging *)messaging - didReceiveRegistrationToken:(nonnull NSString *)fcmToken { - [_channel invokeMethod:@"onToken" arguments:fcmToken]; -} - -- (void)messaging:(FIRMessaging *)messaging - didReceiveMessage:(FIRMessagingRemoteMessage *)remoteMessage { - [_channel invokeMethod:@"onMessage" arguments:remoteMessage.appData]; -} - -@end diff --git a/packages/firebase_messaging/lib/firebase_messaging.dart b/packages/firebase_messaging/lib/firebase_messaging.dart deleted file mode 100644 index 81f2018edecd..000000000000 --- a/packages/firebase_messaging/lib/firebase_messaging.dart +++ /dev/null @@ -1,234 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; -import 'dart:ui'; - -import 'package:flutter/services.dart'; -import 'package:flutter/widgets.dart'; -import 'package:meta/meta.dart'; -import 'package:flutter/foundation.dart' - show defaultTargetPlatform, TargetPlatform; - -typedef Future MessageHandler(Map message); - -/// Setup method channel to handle Firebase Cloud Messages received while -/// the Flutter app is not active. The handle for this method is generated -/// and passed to the Android side so that the background isolate knows where -/// to send background messages for processing. -/// -/// Your app should never call this method directly, this is only for use -/// by the firebase_messaging plugin to setup background message handling. -void _fcmSetupBackgroundChannel( - {MethodChannel backgroundChannel = const MethodChannel( - 'plugins.flutter.io/firebase_messaging_background')}) async { - // Setup Flutter state needed for MethodChannels. - WidgetsFlutterBinding.ensureInitialized(); - - // This is where the magic happens and we handle background events from the - // native portion of the plugin. - backgroundChannel.setMethodCallHandler((MethodCall call) async { - if (call.method == 'handleBackgroundMessage') { - final CallbackHandle handle = - CallbackHandle.fromRawHandle(call.arguments['handle']); - final Function handlerFunction = - PluginUtilities.getCallbackFromHandle(handle); - try { - await handlerFunction( - Map.from(call.arguments['message'])); - } catch (e) { - print('Unable to handle incoming background message.'); - print(e); - } - return Future.value(); - } - }); - - // Once we've finished initializing, let the native portion of the plugin - // know that it can start scheduling handling messages. - backgroundChannel.invokeMethod('FcmDartService#initialized'); -} - -/// Implementation of the Firebase Cloud Messaging API for Flutter. -/// -/// Your app should call [requestNotificationPermissions] first and then -/// register handlers for incoming messages with [configure]. -class FirebaseMessaging { - factory FirebaseMessaging() => _instance; - - @visibleForTesting - FirebaseMessaging.private(MethodChannel channel) : _channel = channel; - - static final FirebaseMessaging _instance = FirebaseMessaging.private( - const MethodChannel('plugins.flutter.io/firebase_messaging')); - - final MethodChannel _channel; - - MessageHandler _onMessage; - MessageHandler _onBackgroundMessage; - MessageHandler _onLaunch; - MessageHandler _onResume; - - /// On iOS, prompts the user for notification permissions the first time - /// it is called. - /// - /// Does nothing and returns null on Android. - FutureOr requestNotificationPermissions([ - IosNotificationSettings iosSettings = const IosNotificationSettings(), - ]) { - if (defaultTargetPlatform != TargetPlatform.iOS) { - return null; - } - return _channel.invokeMethod( - 'requestNotificationPermissions', - iosSettings.toMap(), - ); - } - - final StreamController _iosSettingsStreamController = - StreamController.broadcast(); - - /// Stream that fires when the user changes their notification settings. - /// - /// Only fires on iOS. - Stream get onIosSettingsRegistered { - return _iosSettingsStreamController.stream; - } - - /// Sets up [MessageHandler] for incoming messages. - void configure({ - MessageHandler onMessage, - MessageHandler onBackgroundMessage, - MessageHandler onLaunch, - MessageHandler onResume, - }) { - _onMessage = onMessage; - _onLaunch = onLaunch; - _onResume = onResume; - _channel.setMethodCallHandler(_handleMethod); - _channel.invokeMethod('configure'); - if (onBackgroundMessage != null) { - _onBackgroundMessage = onBackgroundMessage; - final CallbackHandle backgroundSetupHandle = - PluginUtilities.getCallbackHandle(_fcmSetupBackgroundChannel); - final CallbackHandle backgroundMessageHandle = - PluginUtilities.getCallbackHandle(_onBackgroundMessage); - - if (backgroundMessageHandle == null) { - throw ArgumentError( - '''Failed to setup background message handler! `onBackgroundMessage` - should be a TOP-LEVEL OR STATIC FUNCTION and should NOT be tied to a - class or an anonymous function.''', - ); - } - - _channel.invokeMethod( - 'FcmDartService#start', - { - 'setupHandle': backgroundSetupHandle.toRawHandle(), - 'backgroundHandle': backgroundMessageHandle.toRawHandle() - }, - ); - } - } - - final StreamController _tokenStreamController = - StreamController.broadcast(); - - /// Fires when a new FCM token is generated. - Stream get onTokenRefresh { - return _tokenStreamController.stream; - } - - /// Returns the FCM token. - Future getToken() async { - return await _channel.invokeMethod('getToken'); - } - - /// Subscribe to topic in background. - /// - /// [topic] must match the following regular expression: - /// "[a-zA-Z0-9-_.~%]{1,900}". - Future subscribeToTopic(String topic) { - return _channel.invokeMethod('subscribeToTopic', topic); - } - - /// Unsubscribe from topic in background. - Future unsubscribeFromTopic(String topic) { - return _channel.invokeMethod('unsubscribeFromTopic', topic); - } - - /// Resets Instance ID and revokes all tokens. In iOS, it also unregisters from remote notifications. - /// - /// A new Instance ID is generated asynchronously if Firebase Cloud Messaging auto-init is enabled. - /// - /// returns true if the operations executed successfully and false if an error ocurred - Future deleteInstanceID() async { - return await _channel.invokeMethod('deleteInstanceID'); - } - - /// Determine whether FCM auto-initialization is enabled or disabled. - Future autoInitEnabled() async { - return await _channel.invokeMethod('autoInitEnabled'); - } - - /// Enable or disable auto-initialization of Firebase Cloud Messaging. - Future setAutoInitEnabled(bool enabled) async { - await _channel.invokeMethod('setAutoInitEnabled', enabled); - } - - Future _handleMethod(MethodCall call) async { - switch (call.method) { - case "onToken": - final String token = call.arguments; - _tokenStreamController.add(token); - return null; - case "onIosSettingsRegistered": - _iosSettingsStreamController.add(IosNotificationSettings._fromMap( - call.arguments.cast())); - return null; - case "onMessage": - return _onMessage(call.arguments.cast()); - case "onLaunch": - return _onLaunch(call.arguments.cast()); - case "onResume": - return _onResume(call.arguments.cast()); - default: - throw UnsupportedError("Unrecognized JSON message"); - } - } -} - -class IosNotificationSettings { - const IosNotificationSettings({ - this.sound = true, - this.alert = true, - this.badge = true, - this.provisional = false, - }); - - IosNotificationSettings._fromMap(Map settings) - : sound = settings['sound'], - alert = settings['alert'], - badge = settings['badge'], - provisional = settings['provisional']; - - final bool sound; - final bool alert; - final bool badge; - final bool provisional; - - @visibleForTesting - Map toMap() { - return { - 'sound': sound, - 'alert': alert, - 'badge': badge, - 'provisional': provisional - }; - } - - @override - String toString() => 'PushNotificationSettings ${toMap()}'; -} diff --git a/packages/firebase_messaging/test/firebase_messaging_test.dart b/packages/firebase_messaging/test/firebase_messaging_test.dart deleted file mode 100644 index 3cc474e676b7..000000000000 --- a/packages/firebase_messaging/test/firebase_messaging_test.dart +++ /dev/null @@ -1,181 +0,0 @@ -// Copyright 2017 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -import 'dart:async'; - -import 'package:flutter/services.dart'; -import 'package:firebase_messaging/firebase_messaging.dart'; -import 'package:flutter_test/flutter_test.dart' show TestWidgetsFlutterBinding; -import 'package:mockito/mockito.dart'; -import 'package:test/test.dart'; -import 'package:flutter/foundation.dart' - show defaultTargetPlatform, TargetPlatform; - -void main() { - TestWidgetsFlutterBinding.ensureInitialized(); - - MockMethodChannel mockChannel; - FirebaseMessaging firebaseMessaging; - - setUp(() { - mockChannel = MockMethodChannel(); - firebaseMessaging = FirebaseMessaging.private(mockChannel); - }); - - test('requestNotificationPermissions on ios with default permissions', () { - firebaseMessaging.requestNotificationPermissions(); - verify(mockChannel.invokeMethod( - 'requestNotificationPermissions', { - 'sound': true, - 'badge': true, - 'alert': true, - 'provisional': false - })); - }, skip: defaultTargetPlatform != TargetPlatform.iOS); - - test('requestNotificationPermissions on ios with custom permissions', () { - firebaseMessaging.requestNotificationPermissions( - const IosNotificationSettings(sound: false, provisional: true)); - verify(mockChannel.invokeMethod( - 'requestNotificationPermissions', { - 'sound': false, - 'badge': true, - 'alert': true, - 'provisional': true - })); - }, skip: defaultTargetPlatform != TargetPlatform.iOS); - - test('configure', () { - firebaseMessaging.configure(); - verify(mockChannel.setMethodCallHandler(any)); - verify(mockChannel.invokeMethod('configure')); - }); - - test('incoming token', () async { - firebaseMessaging.configure(); - final dynamic handler = - verify(mockChannel.setMethodCallHandler(captureAny)).captured.single; - final String token1 = 'I am a super secret token'; - final String token2 = 'I am the new token in town'; - Future tokenFromStream = firebaseMessaging.onTokenRefresh.first; - await handler(MethodCall('onToken', token1)); - - expect(await tokenFromStream, token1); - - tokenFromStream = firebaseMessaging.onTokenRefresh.first; - await handler(MethodCall('onToken', token2)); - - expect(await tokenFromStream, token2); - }); - - test('incoming iOS settings', () async { - firebaseMessaging.configure(); - final dynamic handler = - verify(mockChannel.setMethodCallHandler(captureAny)).captured.single; - IosNotificationSettings iosSettings = const IosNotificationSettings(); - - Future iosSettingsFromStream = - firebaseMessaging.onIosSettingsRegistered.first; - await handler(MethodCall('onIosSettingsRegistered', iosSettings.toMap())); - expect((await iosSettingsFromStream).toMap(), iosSettings.toMap()); - - iosSettings = const IosNotificationSettings(sound: false); - iosSettingsFromStream = firebaseMessaging.onIosSettingsRegistered.first; - await handler(MethodCall('onIosSettingsRegistered', iosSettings.toMap())); - expect((await iosSettingsFromStream).toMap(), iosSettings.toMap()); - }, skip: defaultTargetPlatform != TargetPlatform.iOS); - - test('incoming messages', () async { - final Completer onMessage = Completer(); - final Completer onLaunch = Completer(); - final Completer onResume = Completer(); - - firebaseMessaging.configure( - onMessage: (dynamic m) async { - onMessage.complete(m); - }, - onLaunch: (dynamic m) async { - onLaunch.complete(m); - }, - onResume: (dynamic m) async { - onResume.complete(m); - }, - onBackgroundMessage: validOnBackgroundMessage, - ); - final dynamic handler = - verify(mockChannel.setMethodCallHandler(captureAny)).captured.single; - - final Map onMessageMessage = {}; - final Map onLaunchMessage = {}; - final Map onResumeMessage = {}; - - await handler(MethodCall('onMessage', onMessageMessage)); - expect(await onMessage.future, onMessageMessage); - expect(onLaunch.isCompleted, isFalse); - expect(onResume.isCompleted, isFalse); - - await handler(MethodCall('onLaunch', onLaunchMessage)); - expect(await onLaunch.future, onLaunchMessage); - expect(onResume.isCompleted, isFalse); - - await handler(MethodCall('onResume', onResumeMessage)); - expect(await onResume.future, onResumeMessage); - }); - - const String myTopic = 'Flutter'; - - test('subscribe to topic', () async { - await firebaseMessaging.subscribeToTopic(myTopic); - verify(mockChannel.invokeMethod('subscribeToTopic', myTopic)); - }); - - test('unsubscribe from topic', () async { - await firebaseMessaging.unsubscribeFromTopic(myTopic); - verify(mockChannel.invokeMethod('unsubscribeFromTopic', myTopic)); - }); - - test('getToken', () { - firebaseMessaging.getToken(); - verify(mockChannel.invokeMethod('getToken')); - }); - - test('deleteInstanceID', () { - firebaseMessaging.deleteInstanceID(); - verify(mockChannel.invokeMethod('deleteInstanceID')); - }); - - test('autoInitEnabled', () { - firebaseMessaging.autoInitEnabled(); - verify(mockChannel.invokeMethod('autoInitEnabled')); - }); - - test('setAutoInitEnabled', () { - // assert that we havent called the method yet - verifyNever(firebaseMessaging.setAutoInitEnabled(true)); - - firebaseMessaging.setAutoInitEnabled(true); - - verify(mockChannel.invokeMethod('setAutoInitEnabled', true)); - - // assert that enabled = false was not yet called - verifyNever(firebaseMessaging.setAutoInitEnabled(false)); - - firebaseMessaging.setAutoInitEnabled(false); - - verify(mockChannel.invokeMethod('setAutoInitEnabled', false)); - }); - - test('configure bad onBackgroundMessage', () { - expect( - () => firebaseMessaging.configure( - onBackgroundMessage: (dynamic message) => Future.value(), - ), - throwsArgumentError, - ); - }); -} - -Future validOnBackgroundMessage(Map message) async {} - -class MockMethodChannel extends Mock implements MethodChannel {} diff --git a/website/plugins.js b/website/plugins.js index 070d82e2fded..beaa64135952 100644 --- a/website/plugins.js +++ b/website/plugins.js @@ -56,7 +56,7 @@ module.exports = [ support: { web: false, mobile: true, - macos: false, + macos: true, }, }, {