skip to Main Content

I’m using Firebase Realtime database to add chat features to my app. In the database I have the following data:

{
    events: {
        "event-uuid1": {
            "chat-uuid1": {
                "message": "hey"
            },
            "chat-uuid2": {
                "message": "hey again"
            }
        }
    }
}

In my Flutter app, I have this StreamBuilder (I know this is lengthy, I’m not sure where the problem is so providing more rather than less):

class _EventChatScreenState extends ConsumerState<EventChatScreen> {
    FirebaseDatabase dbInstance = FirebaseDatabase.instance;
    late TextEditingController _messageFieldController;
    late DatabaseReference eventDbRef;

    @override
    void initState() {
        super.initState();
        _messageFieldController = TextEditingController();
        eventDbRef = dbInstance.ref("none");
    }

    @override
    void dispose() {
        _messageFieldController.dispose();
        super.dispose();
    }

    Map<String, ChatMessage> chatMessages = {};

    @override
    Widget build(BuildContext context) {
        final user = ref.watch(userProvider);
        final event = ModalRoute.of(context)!.settings.arguments as EventRepository;
        if (eventDbRef.path == "none") {
            print("IT IS NONE");
            eventDbRef = dbInstance.ref("/events/${event.event.eventId}/");
            print(eventDbRef.path); // Print's correct value
        }
        
        return StreamBuilder(
            stream: eventDbRef.onChildAdded,
            builder: (context, snapshot) {
                if (chatMessages == {}) {
                    return const Text("Loading...");
                }

                DatabaseEvent data;

                if (snapshot.hasData) {
                    data = snapshot.data as DatabaseEvent;
                    ChatMessage newChatMessage = ChatMessage(
                        chatMessageId: "",
                        userId: "",
                        displayname: "",
                        message: "",
                        datetime: "",
                    );

                    for (var child in data.snapshot.children) {
                        switch (child.key) {
                            case "chatMessageId":
                                newChatMessage.chatMessageId = child.value.toString();
                            break;
                        case "userId":
                            newChatMessage.userId = child.value.toString();
                            break;
                        case "displayName":
                            newChatMessage.displayname = child.value.toString();
                            break;
                        case "message":
                            newChatMessage.message = child.value.toString();
                            break;
                        case "datetime":
                            final datetime = DateTime.parse(child.value.toString());
                            final DateFormat formatter = DateFormat('h:mm aa');
                            final String formatted = formatter.format(datetime);
                            newChatMessage.datetime = formatted;
                            break;
                        default:
                    }
                }
   
                if (chatMessages[data.snapshot.key] == null) {
                    chatMessages[data.snapshot.key!] = newChatMessage;
                }
            }

            return ListView.builder(
                itemCount: chatMessages.length,
            itemBuilder: (context, index) {
                String key = chatMessages.keys.elementAt(index);
                if (chatMessages[key]!.userId == user.user.userId) {
                    return UnconstrainedBox(
                        alignment: Alignment.centerRight,
                        child: Container(
                            margin: const EdgeInsets.symmetric(vertical: 5),
                            child: Column(
                                crossAxisAlignment: CrossAxisAlignment.start,
                                children: [
                                    Container(
                                        padding: const EdgeInsets.only(left: 10),
                                        child: Text(chatMessages[key]!.displayname),
                                    ),
                                    Container(
                                        padding: const EdgeInsets.symmetric(vertical: 5, horizontal: 15),
                                        decoration: BoxDecoration(
                                            borderRadius: const BorderRadius.all(
                                                Radius.circular(20),
                                            ),
                                            color: Theme.of(context).colorScheme.primary,
                                        ),
                                        child: Text(chatMessages[key]!.message,
                                        style: TextStyle(
                                        color: Theme.of(context).colorScheme.onPrimary,
                                    ),
                                ),
                            ),
                            Container(
                                padding: const EdgeInsets.only(left: 10),
                                child: Text(chatMessages[key]!.datetime),
                            ),
                        ],
                    ),
                ),
            );
        }
    },
);
},
),

The problem is that when the user goes to the chat screen one of the messages will already be present in the chat. I would expect there to be nothing since I am not setting any initial data anywhere, not using Realtime Database’s persistence, and not using my own local database yet.

My understanding of StreamBuilders is that they only get new data as it comes in, not data that may already exist and is thus not sent through it (Ie. when a new chat message is sent the stream should receive it, which works, but it should not receive chat message messages already in the database). If that understanding is wrong then why am I only getting one message despite there being 2, 3, 4, etc. in the database?

Perhaps I’m understanding/using StreamBuilders, Firebase Realtime Database, or both incorrectly?

2

Answers


  1. Assign this eventDbRef.onChildAdded to a variable in initState and then use the variable as your stream parameter in streambuilder. Having the db call in Streambuilder causes it to be rerun everytime the widget tree builds.

    Login or Signup to reply.
  2. Maybe your understanding about streambuilder is wrong.

    Lets say you use FutureBuilder, It’ll wait till the future is over and then builds the widget accordingly but It’ll not build again if something changes in your database, but for StreamBuilder, It’ll basically listen (and get initial data from stream, here your db) to the stream and build whenever it changes or a new data is added to stream (here database) it will get the updated data and build the widget again.

    Read here:
    https://firebase.flutter.dev/docs/firestore/usage/#realtime-changes

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search