# #firebase #flutter #dart #google-cloud-firestore #flutter-provider
Вопрос:
Я пишу приложение Flutter, которое использует то, что, как я полагаю, вероятно, не является правильным способом навигации по экранам. Я внедряю некоторые поставщики, поэтому стараюсь избегать декларативного стиля навигации Navigator.push, и это привело к тому, что я, по сути, использую индексы, чтобы указать приложению, какой из нескольких экранов отображать в любой момент времени. В приведенном ниже примере экран сообщений представляет собой вкладку внутри индексированного пакета, и когда выбран конкретный «чат», он изменяет индекс сообщений, изменяя возвращаемое значение из списка всех чатов на отображение конкретного чата.
Экран сообщений со списком всех чатов:
class MessagesScreen extends StatefulWidget {
final Function(bool) hideBottomNavigationBar;
MessagesScreen({@required this.hideBottomNavigationBar});
@override
_MessagesScreenState createState() => _MessagesScreenState();
}
class _MessagesScreenState extends State<MessagesScreen> {
RoseUser roseUser;
Database database = Database();
List<Chat> chats = [];
Chat
selectedChat; // whichever chat the user clicks on, to provide to chat view screen
int messagesIndex = 0; // 0 is messages list, 1 is specific chat
@override
Widget build(BuildContext context) {
DocumentSnapshot roseUserSnap = Provider.of<DocumentSnapshot>(context);
if (roseUserSnap != null) roseUser = RoseUser.fromDatabase(roseUserSnap);
if (messagesIndex == 1)
return ChatViewScreen(selectedChat, updateMessagesIndex: (index) {
setState(() {
messagesIndex = 0;
selectedChat = null;
widget.hideBottomNavigationBar(false);
});
});
return StreamBuilder<QuerySnapshot>(
stream: database.streamChatPreviews(roseUser),
builder: (context, snapshot) {
if (snapshot.hasError) {
return Center(
child: Text('Error streaming chat previews'),
);
}
if (snapshot.hasData) {
List<DocumentSnapshot> chatSnaps = snapshot.data.docs;
chats = chatSnaps.map((e) => Chat.fromDatabase(e)).toList();
chats.sort(
(a, b) => b.lastMessage.sentAt.compareTo(a.lastMessage.sentAt));
return ListView.builder(
itemCount: chats.length,
itemBuilder: (context, i) {
Chat chat = chats[i];
return GestureDetector(
onTap: () {
setState(() {
selectedChat = chat;
messagesIndex = 1;
widget.hideBottomNavigationBar(true);
});
},
child: ChatListCard(
chat: chat,
roseUser: roseUser,
));
});
}
return Center(
child: CircularProgressIndicator(),
);
});
}
}
Экран просмотра чата конкретного чата:
class ChatViewScreen extends StatefulWidget {
final Chat chat;
final Function(int) updateMessagesIndex;
ChatViewScreen(this.chat, {@required this.updateMessagesIndex});
@override
_ChatViewScreenState createState() => _ChatViewScreenState();
}
class _ChatViewScreenState extends State<ChatViewScreen> {
Chat chat;
RoseUser roseUser;
Database database = Database();
bool sentMessage = false;
@override
void initState() {
chat = widget.chat;
super.initState();
}
@override
Widget build(BuildContext context) {
DocumentSnapshot roseUserSnap = Provider.of<DocumentSnapshot>(context);
if (roseUserSnap != null) roseUser = RoseUser.fromDatabase(roseUserSnap);
if (roseUser == null)
return Center(
child: CircularProgressIndicator(),
);
return WillPopScope(
onWillPop: () {
widget.updateMessagesIndex(0);
return Future.value(false);
},
child: StreamBuilder<QuerySnapshot>(
stream: database.streamChatMessages(chat.chatId),
builder: (context, snapshot) {
if (snapshot.hasError) {
print(snapshot.error.toString());
return Center();
}
if (snapshot.hasData) {
QuerySnapshot messageDocs = snapshot.data;
chat.messages =
messageDocs.docs.map((e) => Message.fromDatabase(e)).toList();
}
return Column(
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(chat.title,
style: TextStyle(
fontSize: 18,
)),
IconButton(
icon: Icon(Icons.info),
onPressed: () {
print('go to chat info screen');
})
],
),
),
Expanded(
child: ListView.separated(
reverse: true,
itemCount: chat.messages.length,
itemBuilder: (context, index) {
Message message = chat.messages.toList()[index];
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0),
child: message.senderID == roseUser.uid
? MyMessageBubble(message: message)
: MessageBubble(message: message),
);
},
separatorBuilder: (context, index) {
if (index >= chat.messages.length - 1) return Container();
DateTime nextSent = chat.messages[index].sentAt;
DateTime firstSent = chat.messages[index 1].sentAt;
if (nextSent.difference(firstSent).inHours.abs() >= 24)
return Padding(
padding: const EdgeInsets.symmetric(
horizontal: 18.0, vertical: 8),
child: Column(
children: [
Divider(),
Text(DateFormat('MMMM d, yyyy').format(nextSent))
],
),
);
return Container();
},
),
),
Padding(
padding:
const EdgeInsets.only(left: 8.0, right: 8.0, bottom: 8.0),
child: MessageComposer(
onMessageSent: (text) async {
Message message = new Message(
text: text,
sender: roseUser.firstName ' ' roseUser.lastName,
sentAt: DateTime.now(),
containsMedia: false,
senderID: roseUser.uid);
setState(() {
chat.messages.add(message);
});
bool sendSuccess =
await database.sendMessage(chat, message);
if (!sendSuccess) {
setState(() {
chat.messages.remove(message);
});
} else {
sentMessage = true;
}
},
),
),
],
);
}),
);
}
}
The system works normally at first glance, and within the chat view screen I use a WillPopScope to catch the android system back gesture in order to «pop» back to the messages list, which in practicality is just changing the messagesIndex back to 0 via callback function. However, when I send a message (which invokes a firebase firestore call to a chat document and its subcollection of messages), the OnWillPop method becomes totally nonresponsive, and the system back gesture ceases to trigger anything at all.
I know I’m operating in jank territory with this custom navigation system, but I would love to be able to figure out the problem here rather than deal with Navigator 2.0. If anyone has any ideas, I’m all ears.
Thanks.