Reputation: 1064
I have a widget, which displays a list of users. When a list element is clicked, a new MaterialPageRoute is created and pushed to the Navigator to open the chat screen of that user displaying the history of messages.
class Users extends StatefulWidget {
final User user;
Users({this.user});
@override
UsersState createState() => new UsersState();
}
class UsersState extends State<Users> {
Map<String, Conversation> _conversations = new Map<String, Conversation>();
void _sendMessage(String message, String username) {
setState(() {
_conversations[username].messages.add(
new Message(text: message, isFromUser: true)
);
});
}
void _openChat(String username) {
Navigator.of(context).push(
new MaterialPageRoute(
builder: (context) {
return new Chat(
user: widget.user,
conversation: _conversations[username],
sendMessage: _sendMessage,
);
}
)
);
}
@override
Widget build(BuildContext context) {
return new Column(
children: _onlineUsers.map((username) {
return new ListTile(
leading: new CircleAvatar(
child: new Text(username[0].toUpperCase())
),
title: new Text(username),
onTap: () => _openChat(username),
);
}).toList()
);
}
}
In the Chat widget, when the send button is pressed, the UsersState._sendMessage
is called, which changes the state by updating the current conversation. Then the build method of the UsersState class is run, however the Chat widget is not updated with the added messages (I guess, because the onTap function was not called again).
I tried to put the page route creation as an immediate function to the build method, but that didn't work either. How do I update the state of the new screen automatically when the state is set in the class that created it?
Upvotes: 3
Views: 2915
Reputation: 5656
Had a similar situation. My solution (adapted to your case):
Chat should extend StatefulWidget
Rather than passing a callback to Chat, let chat call setState() and add message to messages within Chat.
As you've only passed a reference to the conversation to Chat, this should have also updated your map of conversations in Users.
When the Navigator pops the route, the parent Users will be rebuild and thus reflect the updated conversations.
Had the Chat state been unrelated to parent state, I'd imagine you update Chat state and then invoke the sendMessage callback to also update parent state? This would avoid relying on parent state mutation, which some might say is an anti-pattern.
Have approx 2h of flutter experience so far, so I look forward to hear if others will chime in. EDIT: Added pt 4 - thanks for chiming in @rmtmckenzie.
Upvotes: 0
Reputation: 40433
What you want to do is a bit different than what you have; as far as I can tell what you have is a widget that holds the conversations and then creates a new route every time any of the conversations changes.
What I would recommend is looking into using a database to store the messages; at some point you're going to need to do that anyways. If you're just storing on the device, sqflite is a good solution, while if you want it to be saved to a server using FireStore would probably be the easiest.
You can have your main widget that lists the users, but then each chat widget should itself be a stateful widget for which the state changes each time a new message is added. The way you register for changes depends on the plugin you use.
If you really want to do this in memory only for a semi-robust app, I'd recommend looking into a StreamBuilder and using streams to send each message to the individual chats, or writing your own simple publish/subscribe system (i.e. registering to listen when the widget is created, unregistering when the widget is disposed). You'd have to pass the object that manages registration around - I'd recommend instead of using navigator's push, using pushNamed and managing things from the Navigator/MaterialApp/WidgetApp.
For just a quick example though (as you said you're doing in a comment), you could use InheritedWidget to propagate changes downwards. You just have to make sure that the InheritedWidget is in the widget tree for both of your pages; if you'd using a Navigator manually you'd just have to make sure the Navigator is a child of the InheritedWidget, while if you're using a MaterialApp or WidgetsApp you could use the builder
parameter to specify a function which builds the inheritedwidget below the MaterialApp/WidgetsApp but above the navigator.
The stock app example isn't quite what you're doing, but it is an example of an app with a listing page and pages for individual things.
Upvotes: 2