6

My goal is to send FCM messages to multiple tokens contained in a document map using flutter. The current code uses the 'send all' function and it sends to all as expected. I'm hoping that inserting a widget.document.[token], or similar reference will send only to all those items contained in the document/list. Firebase uses sendAll to send to specific devices so I was hoping this would work.

  • using document(token) reference returns no errors but also no messages

  • using a snapshot that contains only tokens returns an error that only static items can be passed, along with some syntax problems

  • using api/http returns error posturl returns null

In addition to trying the above i've also researched what others have tried.

Here are some of my errors:

  • Tried calling:

  • [ERROR:flutter/lib/ui/ui_dart_state.cc(157)] Unhandled Exception: NoSuchMethodError: The method '[]' was called on null.

  • Tried calling: post("https://fcm.googleapis.com/fcm/send", body: "{\"token\":null,

This is a picture of my database structure:

Firebase Database Structure

Finally, here is my code:

import 'package:chat/screens2/alert_widget.dart';
import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:geo_firestore/geo_firestore.dart';
import 'package:geolocator/geolocator.dart';
import 'package:chat/api/messaging.dart';
import 'package:chat/models/messages.dart';
import 'package:flutter/widgets.dart';


class SendAlert extends StatefulWidget {
  static const String id = 'send_alert';
  final Message message;

  final url;
  final body;
  final title;
  final image;
  final content;

  SendAlert({
    Key key,
    this.title,
    this.url,
    this.body,
    this.message,
    this.image,
    this.content,
    String alertIdd,
  }) : super(key: key);

  get documents => null;

  SendAlertState createState() => SendAlertState();
}

Firestore firestore = Firestore.instance;
GeoFirestore geoFirestore = GeoFirestore(firestore.collection('users'));

class SendAlertState extends State<SendAlert> {
  FirebaseMessaging _firebaseMessaging = FirebaseMessaging();
  TextEditingController roomnameController = TextEditingController();
  final TextEditingController titleController = TextEditingController();
  final TextEditingController bodyController = TextEditingController();
  final List<Message> messages = [];


  TextEditingController citystateController = TextEditingController();

  final db = Firestore.instance;
  get title => null;
  get body => null;
  get uid => null;
  get alertidd => null;
  var currentLocation;
  var clients = [];

  List<Map<String, dynamic>> _documents;

  void onBbackPressed(BuildContext context) => Navigator.pop(context);


  @override
  void initState() {
    super.initState();

        populateClientu();

    Geolocator().getCurrentPosition().then((currloc) {
      setState(() {
        currentLocation = currloc;
      });
    });

    _firebaseMessaging.onTokenRefresh.listen(sendTokenToServer);
    _firebaseMessaging.getToken();

    _firebaseMessaging.subscribeToTopic('all');

    _firebaseMessaging.configure(
      onMessage: (Map<String, dynamic> message) async {
        print("onMessage: $message");
        final notification = message['notification'];
        setState(() {
          messages.add(Message(
              title: notification['title'], body: notification['body']));
        });

      },
      onLaunch: (Map<String, dynamic> message) async {
        print("onLaunch: $message");

        final notification = message['data'];
        setState(() {
          messages.add(Message(
            title: '${notification['title']}',
            body: '${notification['body']}',
          ));
        });

      },
      onResume: (Map<String, dynamic> message) async {
        print("onResume: $message");
      },
    );
    _firebaseMessaging.requestNotificationPermissions(
        const IosNotificationSettings(sound: true, badge: true, alert: true));
  }







  populateClientu() async {
    Position position = await Geolocator()
        .getCurrentPosition(desiredAccuracy: LocationAccuracy.high);
    var queryLocation = GeoPoint(position.latitude, position.longitude);

    List<DocumentSnapshot> snapshots =
        await geoFirestore.getAtLocation(queryLocation, 10.0);

    final documents = snapshots.map((doc) {
      return doc.data;
    }).toList();

    setState(() {
      _documents = documents;
    });

  }




  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          backgroundColor: Color.fromARGB(255, 4, 22, 36),
          title: Text('SEND MSG'),
          leading: IconButton(
            onPressed: () => this.onBbackPressed(context),
            icon: Icon(Icons.arrow_back),
          ),
        ),
        backgroundColor: Color.fromARGB(255, 4, 22, 36),
        body: 

              Container(
                  width: 250,
                  height: 35,
                  margin: EdgeInsets.only(top: 4),
                  child: Opacity(
                      opacity: 0.8,              
              child: FlatButton(

                  color: Color.fromARGB(51, 255, 255, 255),
                  shape: RoundedRectangleBorder(
                    borderRadius: BorderRadius.all(Radius.circular(10)),
                    side: BorderSide(
                      width: 0.75,
                      color: Color.fromARGB(255, 255, 255, 255),
                      style: BorderStyle.solid,
                    ),
                  ),
                  textColor: Color.fromARGB(255, 255, 255, 255),
                  padding: EdgeInsets.all(0),
                  child: Text(
                    "SEND ALERT",
                    style: TextStyle(
                      fontSize: 12,
                      letterSpacing: 2,
                      fontFamily: "Roboto",
                      fontWeight: FontWeight.w500,
                    ),
                    textAlign: TextAlign.left,
                  ),
                  onPressed: () async {



  //                const querySnapshot = await db     <--- I suspect the document map has extra unused data.  I thought maybe FCM will only accept and array of tokens, this did not work either.
    //              .collection('users')
      //            .document()
        //          .collection('token')
          //        .get();




                    sendNotification();

                    Navigator.push(
                        context,
                        MaterialPageRoute(
                            builder: (context) => AlertWidget()));
                  })))
                                );
  }

  void sendTokenToServer(String fcmToken) {
    print('Token: $fcmToken');
  }

  Future sendNotification() async {
  //Future sendNotification(documents(token)) async {   <--- I tried to pass widget.document[token]
    final response = await Messaging.sendToAll(
      title: titleController.text,
      body: bodyController.text,
    );

    if (response.statusCode != 200) {
      Scaffold.of(context).showSnackBar(SnackBar(
        content:
            Text('[${response.statusCode}] Error message: ${response.body}'),
      ));
    }
  }
}

Of course thank you all in advance for your time.

creativecreatorormaybenot
  • 114,516
  • 58
  • 291
  • 402
v i c
  • 139
  • 1
  • 8
  • 2
    It's not very clear what the problem you are trying to solve is. Are you trying to send a push notification to a subset of your users? Do you want the Flutter app to send those push notifications (not recommended because of several security reasons that can easily lead to your entire database being compromised)? If so, you need to get a list of tokens from your list of users, and then POST it to the FCM API. So which part are you having difficulties with? – Ovidiu Jan 20 '20 at 20:29
  • i gps and generate a document map of users. This document is from the database in the picture. So i use the geoHash to gps, in this case i would get 2 users. I want to send gps only those two users. On a larger scale there may be 100 users but only 8 are within the gps filtered range, so I would want to send to those 8 users/tokens only. you mention security issues with sending from flutter and that is what i was trying to do, similar to the send all. I do not know how to use the api to send the document map I currently have. I did try the api unsuccessfully. – v i c Jan 21 '20 at 07:36
  • you can store token in firestore and use firebase function. Check this https://www.skcript.com/svr/sending-push-notification-with-firebase-firestore-cloud-functions/ – mkf Jan 22 '20 at 21:27
  • i'm trying the firebase function method. – v i c Jan 23 '20 at 13:16
  • @vic hi, were you able to find the answer for this? I am trying to do something similar. upon creation of a document, i need to filter users by location and send notification to these users. thank you! – RL Shyam Mar 31 '20 at 16:25

1 Answers1

0
  1. Your firestore instance returns a list of user documents. You need to iterate through the documents to extract only the tokens inside the documents and put them inside a List of Strings that you would then pass on to the FCM.

If you compare your screenshot and the code that should be returning the documents. they don't align. you could have changed it from.

//                const querySnapshot = await db     <--- I suspect the document map has extra unused data.  I thought maybe FCM will only accept and array of tokens, this did not work either.
    //              .collection('users')
      //            .document()
        //          .collection('token')
          //        .get();

To

  //                const querySnapshot = await db     <--- I suspect the document map has extra unused data.  I thought maybe FCM will only accept and array of tokens, this did not work either.
    //              .collection('users')
      //            .document("DOCUMENT ID")
          //        .get();

The above would get a single document. Then you can map the document and get a single token which you can pass into a Message component's token field.

  1. You haven't passed the token into the actual Async function Messaging.sendToAll

    final response = await Messaging.sendToAll( title: titleController.text, body: bodyController.text, );

The above should contain a token string within the body like below.

final response = await Messaging.sendToAll(
      title: titleController.text,
      body: bodyController.text,
      token:fcmTokenReturnedFromFirestore
    );
David Innocent
  • 606
  • 5
  • 16
  • will this allow for multiple tokens to be sent from a doc with multiple tokens or do i have to send this multiple times? – v i c Jan 24 '20 at 14:27