Search code examples
flutterfirebasefirebase-cloud-messagingflutter-alertdialog

How to prevent an Alert Dialog from showing in a certain flutter page when receiving a notification


I have a quite complex flutter app and in my app i have a homepage.dart, where i also have a drawer, inside this drawer i have a list of pages including "chat" when the users click on chat the have a similar interface like the common app whatspp to have a more natural feeling when using this chat function of the app. my goal is when a user receives a notification, i would love to send them directly to the chat screen with the details i am passing through the notification,but i have not being able to achieve that, so now i wanted to focus on the case where the user receives the notification when using the app. in this case i display an alertdialog with the information of the notification an additionally i have a button that sends them directly to the chatscreen.dart.

What i now want is when the users are in the chat page or in the chatscreen page, i don't want to display the alert dialogue because they are already in the chat interface, in my app i still use userpreferences to store the last page the user was so when the last page was the chatpage.dart or the chatscreen.dart page, it recognise that the dialogue shouldn't show, but when i go to the homepage or another page, i see the notification and when i navigate the user to the chat screen and the peer they are talking to writes a message, it not only displays in the chat but also triggers the alert dialogue and as from there no matter the page it is constantly showing the alert dialogue. it is as if alert dialog was not recognizing the page the user is. i am going to post the code of the pages that have to do with the correct functionality of this: chatscreen.dart, main.dart, and because of the boundsries in length i am not able to add the homepage.dart.

chatScreen.dart:


class ChatScreen extends StatefulWidget {
  final String peerId;
  final String peerAvatar;
  final String peerName;
  final String myId;
  final String name;
  static final String routeName = "/chatScreen";
  final String company;
  final String selectedBranch;
  final String myPhoto;

   //ChatScreen({}) : super(key: key);
   
  //static final String routeName = "home";

  ChatScreen({Key? key,required this.peerId, required this.peerAvatar, required this.company, required this.myPhoto, required this.selectedBranch,required this.peerName, required this.myId, required this.name}) : super(key: key);

  @override
  _ChatScreenState createState() => _ChatScreenState();
}

class _ChatScreenState extends State<ChatScreen> {
  TextEditingController messageController = TextEditingController();
  ScrollController listScrollController = ScrollController();
   final prefs = new PreferenciasUsuario();
    bool isTyping = false;

      File? selectedImage; // This will hold the selected image file
ImagePicker picker = ImagePicker(); // Instance of ImagePicker

//ImageProvider<Object>? myPhoto;
String? fileName;
String company = "";
String selectedBranch = "";
String id = "";

String peerId= "";
 String peerAvatar= "";
 String peerName= "";
 String myPhoto= "";
 String name= "";
//AudioRecorder record = AudioRecorder();
late bool _isRecording;
bool showPlayer = false;
late String _audioPath;
String? currentRoute;

   @override
  void initState() {

    company = widget.company;
    selectedBranch = widget.selectedBranch;
    id = widget.myId;
    _isRecording = false;
  _audioPath = '';


peerId= widget.peerId;
peerAvatar= widget.peerAvatar;
peerName= widget.peerName;
myPhoto= widget.myPhoto;
name= widget.name;
currentRoute = ModalRoute.of(context)?.settings.name;
    super.initState();
  }
  @override
  void dispose() {
    
    super.dispose();
  }


  Future<void> sendMessage() async {
  final String messageText = messageController.text.trim();
  if (messageText.isNotEmpty) {
    messageController.clear();
print(" name ${name}");
print("widget.myId ${widget.myId}");
print("peerId ${peerId}");
print("messageText $messageText");
    // Add message to messages subcollection in the chat document
    final HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('sendChat');
    final result = await callable.call(<String, dynamic>{
      "myPhoto": myPhoto,
      "name": name,
      "senderId": widget.myId, // Assume currentUser is available
      "receiverId": peerId,
      "text": messageText,
      "type": "text", // For text messages. You can define other types as needed.
      "company": widget.company,
      "selectedBranch": widget.selectedBranch,
      "peerPhoto": peerAvatar,
      "peerName": peerName,
    });

    if (result.data['success']) {
      if(mounted){

    }
  }
}
}

  // Function to call a Firebase Cloud Function
Future<void> uploadMyPhoto({required Map<String, dynamic> photoData,}) async {
  HttpsCallable callable = FirebaseFunctions.instance.httpsCallable('uploadChatPhoto');
  try {
   
     final HttpsCallableResult result = await callable.call({
    'id': id,
    "name": name,
    "senderId": widget.myId, // Assume currentUser is available
    "receiverId": peerId,
    'company': company,
    'selectedBranch': selectedBranch,
    'photo': photoData,
    "type": "img",
  });
  print("gcame down");
  final String response = result.data['message'];
  print("message : $response");
 
setState(() {
  
});
    Navigator.of(context).pop();
    Navigator.of(context).pop();
  } catch (e) {
    print("Failed to call cloud function: $e");
  }
}

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Color.fromRGBO(20, 33, 61, 1),
        title: Text(peerName),
        actions: [
          CircleAvatar(
            backgroundImage: NetworkImage(peerAvatar),
          ),
          SizedBox(width: 10),
        ],
      ),
      body: Container(
        decoration: BoxDecoration(
    image: DecorationImage(
      image: AssetImage('assets/ChatWatermark.jpg'), // Path to your asset image
      fit: BoxFit.cover, // Adjust the image size to cover the entire container
    ),
  ),
        child: Column(
          children: <Widget>[
            // Expanded to fill available space, leaving room for the message input
            Expanded(
              child: buildMessagesList(),
            ),
            // Message input area
            buildInputArea(),
          ],
        ),
      ),
    );
  }

 // Updated to fetch messages from Firestore
Widget buildMessagesList() {
  final chatId = getChatId(widget.myId, peerId);

  return StreamBuilder<QuerySnapshot>(
    stream: FirebaseFirestore.instance
        .collection('users')
        .doc(widget.myId)
        .collection('chats')
        .doc(chatId)
        .collection('messages')
        .orderBy('timestamp', descending: true)
        .snapshots(),
    builder: (context, snapshot) {
      if (!snapshot.hasData) {
        return Center(child: CircularProgressIndicator());
      } else {
        final documents = snapshot.data!.docs;
        // Group messages by date
        print("about to fuck up");
        Map<String, List<DocumentSnapshot>> groupedMessages = {};
        documents.forEach((doc) {
          
          final timestamp = doc['timestamp'] as Timestamp;
          print("timestamp $timestamp");
          // var timestamp = Timestamp(timestamp['_seconds'], timestamp['_nanoseconds']);
          final DateTime messageDate = timestamp.toDate().toLocal();
          final String formattedDate = formatDate(messageDate);
          print("went th");
          if (groupedMessages.containsKey(formattedDate)) {
            groupedMessages[formattedDate]!.add(doc);
          } else {
            groupedMessages[formattedDate] = [doc];
          }
        });
        print("groupedMessages $groupedMessages");

        return ListView.builder(
          padding: EdgeInsets.all(10.0),
          itemCount: groupedMessages.length,
          itemBuilder: (context, index) {
            final date = groupedMessages.keys.toList()[index];
            final dateFormat = DateFormat('dd/MM/yyyy');
           DateTime dateShow = dateFormat.parse(date);
            print("datetum : $date");
            List<DocumentSnapshot<Object?>> messages = groupedMessages[date]!;
            messages = messages.reversed.toList();
            return Column(
              crossAxisAlignment: CrossAxisAlignment.start,
              children: [
                Padding(
                  padding: EdgeInsets.symmetric(vertical: 10, horizontal: 16),
                  child: Center(
                    child: ClipRRect(
                      borderRadius: BorderRadius.circular(10),
                      child: Container(
                        padding: EdgeInsets.all(5),
                        color: Color.fromRGBO(40, 175, 138, 0.2),
                        child: Text(
                          formatDate(dateShow),
                          style: TextStyle(fontSize: 15, fontWeight: FontWeight.bold, color: Color.fromARGB(255, 4, 75, 36)),
                        ),
                      ),
                    ),
                  ),
                ),
                ...messages.map((message) => buildMessageItem(message.data() as Map<String, dynamic>)).toList(),
              ],
            );
          },
          reverse: true,
          controller: listScrollController,
        );
      }
    },
  );
}


  return Padding(
    padding: EdgeInsets.symmetric(vertical: 8),
    child: Row(
      mainAxisAlignment: isOwnMessage ? MainAxisAlignment.end : MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.end,
      children: [
        Flexible(
          child: GestureDetector(
            onTap: () {
              if (type == "img") {
                Navigator.push(
                  context,
                  MaterialPageRoute(
                    builder: (context) => ImagePage(
                      imgList: ImageConfig(
                        source: "http", // Assuming the source is always "http" for network images
                        path: message["photo"]["url"],
                        ref: null, // Pass reference if applicable
                      ),
                    ),
                  ),
                );
              }
            },
            child: Container(
              padding: EdgeInsets.all(12),
              constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.7),
              decoration: BoxDecoration(
                color: type == "not" ? Colors.purple : (isOwnMessage ? Colors.blue[400] : Colors.grey[200]),
                borderRadius: BorderRadius.only(
                  topLeft: Radius.circular(isOwnMessage ? 20 : 4),
                  topRight: Radius.circular(isOwnMessage ? 4 : 20),
                  bottomLeft: Radius.circular(20),
                  bottomRight: Radius.circular(20),
                ),
              ),
              child: Column(
                crossAxisAlignment: CrossAxisAlignment.start,
                children: [
                  if (type == "text" )
                    Text(
                      message['text'],
                      style: TextStyle(color: isOwnMessage ? Colors.white : Colors.black),
                    )
                  else if (type == "img")
                    Image.network(
                      message["photo"]["url"],
                      height: 150, // Adjust height as needed
                      width: double.infinity,
                      fit: BoxFit.cover,
                    ) else if (type == "not")
                    Text(
                      message['text'],
                      style: TextStyle(color: Colors.white),
                    ),
                  SizedBox(height: 4),
                  if (type == "not")
                  Row(
                    children: [
                      Text(
                        formattedTime,
                        style: TextStyle(color: Colors.white, fontSize: 12),
                      ),
                      Spacer(),
                      Icon(Icons.spatial_audio_off_rounded, color: Colors.white,)
                    ],
                  )
                   else if (type != "not")
                  Text(
                    formattedTime,
                    style: TextStyle(color: isOwnMessage? Colors.white70 : Colors.black54, fontSize: 12),
                  ),
                ],
              ),
            ),
          ),
        ),
      ],
    ),
  );
}
Widget buildInputArea() {

 

  return Container(
    padding: EdgeInsets.all(8.0),
    color: Colors.white,
    child: Row(
      children: <Widget>[
        // Text input
        Expanded(
          child: Container(
            decoration: BoxDecoration(
              color: Colors.grey[200],
              borderRadius: BorderRadius.circular(20),
            ),
            padding: EdgeInsets.symmetric(horizontal: 12),
            child: TextField(
              controller: messageController,
              decoration: InputDecoration(
                hintText: 'Nachricht',
                hintStyle: TextStyle(color: Colors.grey),
                border: InputBorder.none,
                
              ),
                 onChanged: (text) {
                setState(() {
                  isTyping = text.isNotEmpty;
                });
              },
              
            ),
          ),
        ),
        SizedBox(width: 8),
        // Send button
        Material(
          color: Colors.transparent,
          borderRadius: BorderRadius.circular(20),
          child: Row(
            children: [
              Visibility(
                visible: !isTyping, 
                child: IconButton(
                  onPressed: ()=>_showImageOptions(context), 
                icon: Icon(Icons.camera_alt,) ,color: Color.fromRGBO(20, 33, 61, 1)),
              ),
              isTyping ? IconButton(
                icon: Icon(Icons.send, color: Color.fromRGBO(20, 33, 61, 1)),
                onPressed: () => sendMessage(),
              ): Container(),
            ],
          ),
        ),
      ],
    ),
  );
}
void _showImageOptions(BuildContext context) {
  showModalBottomSheet(
    context: context,
    builder: (context) {
      return Column(
        mainAxisSize: MainAxisSize.min,
        children: <Widget>[
          ListTile(
            leading: Icon(Icons.camera,color: Color.fromRGBO(151, 195, 184, 1),),
            title: Text('Kamera'),
            onTap: () {
              Navigator.of(context).pop();
              _processImage(ImageSource.camera);
            },
          ),
          ListTile(
            leading: Icon(Icons.image,color: Color.fromRGBO(151, 195, 184, 1),),
            title: Text('Gallerie'),
            onTap: () {
              Navigator.of(context).pop();
              _processImage(ImageSource.gallery);
            },
          ),
        ],
      );
    },
  );
}

Future<void> _processImage(ImageSource source) async {
  try {
    final pickedFile = await picker.pickImage(source: source);
    if (pickedFile != null) {
      setState(() {
        selectedImage = File(pickedFile.path);
      });
      uploadAndCallFunction(selectedImage!);
    }
  } catch (e) {
    // Handle any exceptions
  }
}
Future<void> uploadAndCallFunction(File imageFile) async {
  try {
    String fileName = path.basename(imageFile.path);
    String destination = "$company/$selectedBranch/chat/images/$id/$fileName";

    // Upload file
    UploadTask task = FirebaseApi.uploadFile(destination, imageFile, context);
    

    // When the upload task is complete, get the download URL
    TaskSnapshot snapshot = await task.whenComplete(() {});
    String urlDownload = await snapshot.ref.getDownloadURL();

    // Prepare data to send to Cloud Function
    Map<String, dynamic> photoData = {
      'url': urlDownload,
      'ref': destination,
    };

    print("Upload complete: $urlDownload");

    // Call the Cloud Function and pass 'photoData' as a parameter
   await uploadMyPhoto(photoData:photoData);
  } catch (e) {
    print("An error occurred during upload or function call: $e");
  }
}
  // Helper method to determine the chatId based on userIds
String getChatId(String userId, String peerId) {
  // Simple rule: smallerId_biggerId
  return peerId;
}

}

main.dart:



final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> chatNavigatorKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> chatScreenNavigatorKey = GlobalKey<NavigatorState>();
 

  void main() async {
  
  WidgetsFlutterBinding.ensureInitialized();
    
  await Firebase.initializeApp(
    
  );
 
    await FirebaseApiNotification().initNotification();


      // Pass data to MyApp widget and runApp
      runApp(MyApp(
       
      ));
    }
  } catch (e) {
    print("Error reading cache: $e");
    // If there's an error reading cache, proceed without cached data
    runApp(MyApp(navigationService: navigationService));
  }
  configLoading();
  } else {
     runApp(MyApp(navigationService: navigationService));
     configLoading();
  }
  
}
Future<void> enableFirestorePersistence() async {
  await FirebaseFirestore.instance.enablePersistence();
}

class MyApp extends StatelessWidget {
  final String? userId;
  final String? company;
  final String? selectedBranch;
  final List<String>? groups;
  final String? role;
  final String? name;
  final NavigationService? navigationService;

   MyApp({Key? key, this.userId, this.selectedBranch,this.navigationService, this.company, this.groups, this.role, this.name}) : super(key: key);
   
 
  final prefs = new PreferenciasUsuario();
  @override
  Widget build(BuildContext context) {
    final prefs = new PreferenciasUsuario();
    print(prefs.token);
    initNotificationListener(context);

 // Define the base text style
    final baseTextStyle = const TextStyle(
      fontFamily: 'RobotoCondensed',
      fontStyle: FontStyle.normal,
    );

   


    return Provider(
      child: GetMaterialApp(
        navigatorKey: navigatorKey,
         
        debugShowCheckedModeBanner: false,

        title: "Agenda-App",

        initialRoute:  prefs.ultimaPagina,

        routes: {

          "register": (BuildContext context) => RegisterScreen(),
          "home": (BuildContext context) => Homepage(),
          "chat": (BuildContext context) => Chat(id: userId, company: company, name: name,role: role, selectedBranch: selectedBranch, groups:groups ),
          "/chatScreen": (BuildContext context) => ChatScreen(company: "",selectedBranch: "",name:"",myId: "",peerId: "", peerName: "", peerAvatar: "", myPhoto: "",),

         
        },
        theme: ThemeData(
          useMaterial3: true,
          scaffoldBackgroundColor: light,
          visualDensity: VisualDensity.adaptivePlatformDensity,
         textTheme: textTheme,
         appBarTheme: AppBarTheme(
          titleTextStyle: TextStyle(color: Colors.white, fontFamily: 'RobotoCondensed',
                        fontStyle: FontStyle.normal,
                        fontWeight: FontWeight.bold,
                        fontSize: 20),
          iconTheme: IconThemeData(color: Colors.white)
          )
         
        ),
        builder: EasyLoading.init(),
      ),
    );

  }
  void initNotificationListener(BuildContext context) {
  final firebaseApiNotification = FirebaseApiNotification();
  firebaseApiNotification.onNotificationReceived = (data) {
    NotificationHandler.handleNotification(context, data);
  };
}

  
}


class NotificationHandler {
  static final List<String> excludedRoutes = ['chat', '/chatScreen'];

  static void handleNotification(BuildContext context, Map<String, dynamic> data) {
    // Get the current route
    String? currentRoute = ModalRoute.of(context)?.settings.name;

    // Check if the current route is in the excluded routes list
    if (!excludedRoutes.contains(currentRoute)) {
      // Show the notification dialog
      _showNotificationDialogRespond(context, data);
    }
  }

  static void _showNotificationDialogRespond(BuildContext context, Map<String, dynamic> notificationData) {
    // Your showDialog logic here
       List<String> titleParts = notificationData['title']?.split('-') ?? "";
  String senderName = titleParts.length > 1 ? titleParts[0].trim() : '';
  String messageTitle = titleParts.length > 1 ? titleParts.sublist(1).join('-').trim() : titleParts[0];
  double screenWidth = MediaQuery.of(context).size.width;
    showDialog(
      context: context,
      barrierDismissible: false,
      builder: (_) {
        // Your AlertDialog widget here
        return AlertDialog(
        shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(20.0))),
        titlePadding: EdgeInsets.fromLTRB(20.0, 20.0, 20.0, 20.0),
        contentPadding: EdgeInsets.symmetric(horizontal: 20.0).copyWith(top: 0),
        title: Align(
          alignment: Alignment.topRight,
          child: IconButton(
            icon: Icon(Icons.close),
            onPressed: () => Navigator.of(context).pop(),
          ),
        ),
       content: SingleChildScrollView(
         child: Column(
            mainAxisSize: MainAxisSize.min,
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Container(
                width: screenWidth - 40, // Subtracting horizontal padding
                padding: EdgeInsets.fromLTRB(20, 10, 20, 10),
                decoration: BoxDecoration(
                  color: Colors.white,
                  borderRadius: BorderRadius.only(
                    topLeft: Radius.circular(20),
                    topRight: Radius.circular(20),
                    bottomLeft: Radius.circular(5),
                    bottomRight: Radius.circular(20),
                  ),
                  boxShadow: [BoxShadow(blurRadius: 3, color: Colors.grey.shade300)],
                ),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Row(
                      children: [
                        CircleAvatar(
                          backgroundImage: notificationData['imageUrl'] != null && notificationData['imageUrl'] !=""? NetworkImage(notificationData['imageUrl']) :  Image.asset("assets/logoApp.png").image, // Replace with sender's photo URL
                          radius: 30,
                        ),
                        SizedBox(width: 10),
                        Expanded(
                          child: Column(
                            crossAxisAlignment: CrossAxisAlignment.start,
                            children: [
                              if (senderName.isNotEmpty) Text(senderName, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 18)),
                              Text(messageTitle, style: TextStyle(fontSize: 18, color: Colors.blueGrey)),
                            ],
                          ),
                        ),
                      ],
                    ),
                    SizedBox(height: 10),
                    Text(notificationData['body'] ?? '....', style: TextStyle(fontSize: 16)),
                    SizedBox(height: 10),
                    //Text(notificationTime, style: TextStyle(fontSize: 12, color: Colors.grey)),
                  ],
                ),
              ),

            ],
          ),
       ),
        
        actions: [
          ElevatedButton(
            onPressed: () {
              Navigator.pop(context);
             // String response = responseController.text;
              Navigator.of(context).pushNamed('/chatScreen',
                arguments: {
                  "peerId": notificationData['id']?? "",
                  "peerAvatar": notificationData['ImageUrl']?? "",
                  "peerName": notificationData['title'] ?? "",
                  "myId": notificationData['receiverId'] ?? "",
                  "name": notificationData['peerName'] ?? "",
                  "company": notificationData['company'],
                  "selectedBranch": notificationData['branch']?? "",
                  "myPhoto": notificationData['peerPhoto'],
                },
              );
            },
            child: Text('zum Chat',style: TextStyle(color: Colors.white),),
            style: ElevatedButton.styleFrom(
              backgroundColor: Color.fromRGBO(40, 175, 138, 1),
              shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
            ),
          ),
        ],
      );
      },
    );
  }
}


Solution

  • You can create a Model class which can be used by a Provider. In this Model class you can create StreamController which listens to your notification and broadcasts it to all its listeners. All the widgets that want to listen to your notification can subscribe to the broadcast stream(via StreamSubscription) and take necessary action.

    class MyCustomModel {
      bool isDisposed = false;
      StreamController controller = StreamController.broadcast();
      final firebaseApiNotification = FirebaseApiNotification();
    
      MyCustomModel() {
        firebaseApiNotification.onNotificationReceived = (data) {
         if(!isDisposed) {
            controller.sink.add(data);
         }
       };
      }
    
      void dispose() {
       isDisposed = true;
       controller.close();
       super.dispose();
      }
      
    }
    

    the isDisposed variable helps us in case Model is disposed and a notification is recieved.

    Now, change your Provider (above GetMaterialApp) like this :

    Provider(
      create: (context) =>  MyCustomModel(),
      dispose: (context, value) => value.dispose(),
      child: const GetMaterialApp(),
    ),
    

    Now, as you mentioned, you want to switch to chatpage as soon as a notification is recieved.

    Make your Route classes(Widgets) Stateful(HomePage..etc) EXCEPT for your chatpage as you dont want to push alert dialog or push another chatpage widget screen on your route stack. Now add didChangeDependencies() method(link) and StreamSubscription variable in their State objects. Use Provider.of (link)

    like this :

    StreamSubscription<E> subscription;
    bool disposed = false;
    bool subscriptionInitialised = false;
    
    @override
    void didChangeDependencies() { 
        if(!subscriptionInitialised) {
            subscription = Provider.of<MyCustomModel>(context, listen: false).controller.listen(triggerNavigation);
        subscriptionInitialised = true;
        }
    }
    
    void triggerNavigation(E data) {
        if(!disposed) {
            /*
                In here, you can use Navigation Api to move directly to your chatpage screen
                
                OR 
                
                you can use a alert dialog box which can handle navigation accordingly.         
            */
        }
    }
    
    @override
    void dispose() {
        disposed = true;
        if(subscriptionInitialised) subscription.cancel();
        super.dispose();
    }
    

    make sure to close streams and subscriptions to avoid memory leaks and handling of stream data.

    For more info on StreamController refer : StreamController

    For more info on StreamSubscription refer : StreamSubscription