FCM отправляет сообщение в трей только тогда, когда приложение отключено, но мне нужны уведомления со звуком

#android #firebase #flutter #dart #firebase-cloud-messaging

#Android #firebase #флаттер #dart #firebase-облако-обмен сообщениями

Вопрос:

Я использую простой пример обмена сообщениями на firebase, который я нашел в разделе example пакета firebase_messaging.

main.dart

 import 'dart:async';

import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';

final Map<String, Item> _items = <String, Item>{};
Item _itemForMessage(Map<String, dynamic> 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<Item> _controller = StreamController<Item>.broadcast();
  Stream<Item> get onChanged => _controller.stream;

  String _status;
  String get status => _status;
  set status(String value) {
    _status = value;
    _controller.add(this);
  }

  static final Map<String, Route<void>> routes = <String, Route<void>>{};
  Route<void> get route {
    final String routeName = '/detail/$itemId';
    return routes.putIfAbsent(
      routeName,
      () => MaterialPageRoute<void>(
        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<DetailPage> {
  Item _item;
  StreamSubscription<Item> _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<PushMessagingExample> {
  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: <Widget>[
        FlatButton(
          child: const Text('CLOSE'),
          onPressed: () {
            Navigator.pop(context, false);
          },
        ),
        FlatButton(
          child: const Text('SHOW'),
          onPressed: () {
            Navigator.pop(context, true);
          },
        ),
      ],
    );
  }

  void _showItemDialog(Map<String, dynamic> message) {
    showDialog<bool>(
      context: context,
      builder: (_) => _buildDialog(context, _itemForMessage(message)),
    ).then((bool shouldNavigate) {
      if (shouldNavigate == true) {
        _navigateToItemDetail(message);
      }
    });
  }

  void _navigateToItemDetail(Map<String, dynamic> message) {
    final Item item = _itemForMessage(message);
    // Clear away dialogs
    Navigator.popUntil(context, (Route<dynamic> route) => route is PageRoute);
    if (!item.route.isCurrent) {
      Navigator.push(context, item.route);
    }
  }

  @override
  void initState() {
    super.initState();
    _firebaseMessaging.configure(
      onMessage: (Map<String, dynamic> message) async {
        print("onMessage: $message");
        _showItemDialog(message);
      },
      onLaunch: (Map<String, dynamic> message) async {
        print("onLaunch: $message");
        _navigateToItemDetail(message);
      },
      onResume: (Map<String, dynamic> 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(<String, dynamic>{
            "data": <String, String>{
              "id": "2",
              "status": "out of stock",
            },
          }),
          tooltip: 'Simulate Message',
          child: const Icon(Icons.message),
        ),
        body: Material(
          child: Column(
            children: <Widget>[
              Center(
                child: Text(_homeScreenText),
              ),
              Row(children: <Widget>[
                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(),
    ),
  );
}

  

Все работает нормально, но уведомление отображается не так, как я хочу.
Мне нужно уведомление, подобное следующему:

оно появляется примерно на 2-3 секунды, а затем мы можем проверить его в трее, если проигнорируем его. [1]: https://i.stack.imgur.com/vfeSd.png

но вместо этого я просто получаю значок flutter рядом с часами на верхней панели, и мне приходится перетаскивать и проверять трей, чтобы проверить уведомление.

Итак, пожалуйста, дайте мне знать, как показать уведомление со звуком и небольшим баннером вверху, который должен отображаться в течение нескольких секунд, а затем, если уведомление игнорируется, должен оставаться в трее.

Ответ №1:

Вы сделали то, что указано в официальном документе, но, поверьте мне, официальный документ настолько мертв и неполон.

Вам нужно выполнить несколько шагов, чтобы показывать баннеры уведомлений:

Источник и титры: https://github.com/FirebaseExtended/flutterfire/issues/1327#issuecomment-623399564

Добавьте следующее в свой strings.xml . Конечно, вы захотите настроить их для своего собственного приложения и помните, что название канала и описание канала — это то, что пользователь увидит в своих настройках уведомлений.

 <string name="notification_channel_id" translatable="false">my_unique_fcm_id</string>
<string name="notification_channel_name">The name of this notification</string>
<string name="notification_channel_desc">A description of the notification.</string>
  

Зарегистрируйте новый канал уведомлений, добавив следующий код в свое приложение. Это должно быть сделано как можно раньше, например, после вызова super.onCreate в MainActivity.kt (или .java).

 // Create the NotificationChannel, but only on API 26  because
// the NotificationChannel class is new and not in the support library
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
     val channelID = getString(R.string.notification_channel_id)
     val name = getString(R.string.notification_channel_name)
     val descriptionText = getString(R.string.notification_channel_desc)
     val importance = NotificationManager.IMPORTANCE_HIGH
     val channel = NotificationChannel(channelID, name, importance).apply {
          description = descriptionText
      }
      // Register the channel with the system
      val notificationManager: NotificationManager =
           getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
      notificationManager.createNotificationChannel(channel)
}
  

Обратите внимание на строку, которая задает важность, поскольку именно это приводит к тому, что уведомления, отправляемые по этому каналу, отображаются как уведомление «Heads Up».

 val importance = NotificationManager.IMPORTANCE_HIGH
  

Добавьте следующее в свой AndroidManifest.xml под тегом. Это гарантирует, что любые FCM, отправленные без установленного в их полезной нагрузке канала уведомления, будут использовать этот. (optional)

 <meta-data android:name="com.google.firebase.messaging.default_notification_channel_id" android:value="@string/notification_channel_id" />
  

Очистите и перестройте

Отправьте тестовый FCM. Он должен использовать ваш недавно созданный канал по умолчанию и отображаться как уведомление «Heads Up».

(Необязательно) Вы можете повторить эти шаги, чтобы настроить дополнительные каналы уведомлений для каждого типа уведомлений, которые вы планируете отправлять. Просто убедитесь, что у каждого из них есть уникальный идентификатор, и что вы связываете идентификатор канала с полезной нагрузкой уведомления при его отправке. Полные документы находятся здесь.

Комментарии:

1. Но мне нужно динамическое имя и описание @OMiShah. Должен быть способ сделать это без редактирования кода Android. некоторые люди предлагали flutter_local_notifications с firebase_messaging, но я не знаю, как реализовать оба?

2. чувак, процесс, которым я поделился, зарегистрировал канал уведомлений с наивысшим приоритетом, что позволит вам отправлять уведомления из FCM со звуком и уведомлением в заголовке, которое вы ищете. После того, как вы выполнили основное, вам просто нужно передать идентификатор канала с вашей полезной нагрузкой уведомления, которая будет воспроизводить звук и показывать уведомления на устройстве пользователя.

3. Наконец-то это работает! Мне пришлось ознакомиться с документацией с официального сайта kotlin code, также мне пришлось ознакомиться с некоторыми примерами, так что ответ @ OMiShah правильный, но люди, которые ничего не знают об Android, не могут с легкостью разобраться в этом.

4. Слава Богу, ты понял, брат 😃😎