英文:
Flutter : StreamBuilder doesn't automatically update ListView when new data comes
问题
在我的聊天应用程序中,我试图在后端更新/添加新数据时自动更新列表。我正在使用StreamBuilder来实现这一点,但当后端数据发生更改时,ListView没有更新。我在后端服务器上使用了一个使用Spring Boot和MySQL作为数据库的服务器。我找到的大多数聊天应用程序示例都基于Firebase作为服务器,这并没有帮助我解决问题。我在这里做错了什么,请帮助我纠正这个问题,因为我是Flutter的初学者。
class ChatScreen extends ConsumerStatefulWidget {
final dynamic data;
final dynamic category;
const ChatScreen({
Key? key, required this.data, required this.category,
}) : super(key: key);
@override
ConsumerState<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends ConsumerState<ChatScreen> with SingleTickerProviderStateMixin {
dynamic dataItem;
List<dynamic>? chatList = [];
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
child: WillPopScope(
child: showChat(),
),
);
}
showChat() {
return StreamBuilder<dynamic>(
stream: ref.watch(chatControllerProvider).getChatsList(widget.data['id'], widget.category['id']),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Loader();
} else if(snapshot.data!.isEmpty) {
return NoChat();
} else {
dataItem = snapshot.data['chatRoom'];
chatList = snapshot.data['chatList'];
return showChatView();
}
},
);
}
showChatView() {
int dataLength = chatList!.length;
return Column(
children: [
Expanded(
child: ListView(
reverse: true,
padding: EdgeInsets.all(20),
children: List.generate(chatList!.length, (index) {
return MessageBox(
data: chatList![dataLength - 1 - index],
);
}),
),
),
],
);
}
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:.../chat_repository.dart';
final chatControllerProvider = Provider<ChatController>(
(ref) {
final chatApi = ref.watch(chatRepositoryProvider);
return ChatController(chatApi: chatApi, ref: ref);
},
);
class ChatController {
ChatController({
required ChatApi chatApi,
required ProviderRef ref,
}) : _chatApi = chatApi,
_ref = ref;
final ChatApi _chatApi;
final ProviderRef _ref;
Stream<dynamic> getChatsList(int roomId, int categoryId) async* {
var result = await _chatApi.getRoomChatList(roomId, categoryId);
yield result;
}
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:.../chat_api.dart';
final chatRepositoryProvider = Provider<ChatApi>((ref) {
return ChatApi();
});
class ChatApi {
late Dio dioRest;
ChatApi() {
dioRest = Dio(
BaseOptions(
headers: {
HttpHeaders.contentTypeHeader: ContentType.json.value,
},
),
);
}
Future getRoomChatList(int roomId, int categoryId) async {
try {
token = getToken();
var result = await dioRest.post(
token.baseUrl! + '.../getRoomChatList',
options: Options(...),
queryParameters: {'roomId': roomId, 'categoryId': categoryId},
);
if (result.statusCode == 200) {
return jsonDecode(result.data);
}
throw DioError(requestOptions: result.requestOptions);
} on DioError catch (error) {
if (error.response!.statusCode! >= 400) {
throw TokenException(message: "Token invalid or expired");
}
throw BusinessException(
BusinessError.fromJson(error.response?.data),
statusCode: error.response?.statusCode,
);
} catch (error) {
throw Error();
}
}
}
英文:
In my chat application I am trying to update the list automatically when there is an update/new data in the backend.I am using StreamBuilder to do that, but it's not updating the ListView when the backend data changes. I am using a spring boot server with mysql as database on the backend server side.Most of the chat application example I found are based on a firebase as server, which didn't help me to solve the issue.What I am doing wrong here, please help to correct the problem since i am a beginner in flutter.
class ChatScreen extends ConsumerStatefulWidget {
final dynamic data;
final dynamic category;
const ChatScreen({
Key? key, required this.data, required this.category,
}) : super(key: key);
@override
ConsumerState<ChatScreen> createState() => _ChatScreenState();
}
class _ChatScreenState extends ConsumerState<ChatScreen> with SingleTickerProviderStateMixin {
dynamic dataItem;
List<dynamic>? chatList = [];
@override
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return Container(
child: WillPopScope(
child: showChat(),
),);
}
showChat() {
return StreamBuilder<dynamic>(
stream: ref.watch(chatControllerProvider).getChatsList(widget.data['id'],widget.category['id']),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const Loader();
} else if(snapshot.data!.isEmpty) {
return NoChat();
} else {
dataItem = snapshot.data!['chatRoom'];
chatList = snapshot.data!['chatList'];
return showChatView();
}
},);
}
showChatView() {
int dataLength = chatList!.length;
return Column(
children: [
Expanded(child: ListView(
reverse: true,
padding: EdgeInsets.all(20),
children: List.generate(chatList!.length, (index) {
return MessageBox(
data: chatList![dataLength - 1 - index],
);
},),
),),
],
);
}
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../chat_repository.dart';
final chatControllerProvider = Provider<ChatController>(
(ref) {
final chatApi = ref.watch(chatRepositoryProvider);
return ChatController(chatApi: chatApi, ref: ref);
},
);
class ChatController {
ChatController({
required ChatApi chatApi,
required ProviderRef ref,
}) : _chatApi = chatApi,
_ref = ref;
final ChatApi _chatApi;
final ProviderRef _ref;
Stream<dynamic> getChatsList(int roomId,int categoryId) async* {
var result = await _chatApi.getRoomChatList(roomId,categoryId);
yield result;
}
}
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '...../chat_api.dart';
final chatRepositoryProvider = Provider<ChatApi>((ref) {
return ChatApi();
});
class ChatApi{
late Dio dioRest;
ChatApi() {
dioRest = Dio(
BaseOptions(
headers: {
HttpHeaders.contentTypeHeader: ContentType.json.value,
},
),
);
}
Future getRoomChatList(int roomId,int categoryId) async {
try {
token = getToken();
var result = await dioRest.post(token.baseUrl!+'..../getRoomChatList',options: Options(.....),
queryParameters: {'roomId' : roomId,'categoryId' : categoryId},
);
if (result.statusCode == 200) {
return jsonDecode(result.data);
}
throw DioError(requestOptions: result.requestOptions);
} on DioError catch (error) {
if (error.response!.statusCode! >= 400) {
throw TokenException(message: "Token invalid or expired");
}
throw BusinessException(BusinessError.fromJson(error.response?.data), statusCode: error.response?.statusCode);
} catch (error) {
throw Error();
}
}
}
答案1
得分: 1
从你的代码片段看,似乎你只在 ChatController
的 getChatsList()
函数中获取了一次聊天列表。然而,在实时聊天应用中,你需要监听来自后端的更新,以便在有新消息到达时进行更新。
ChatController
中的 getChatsList()
方法返回一个 Stream
,但你使用了 async*
和 yield
包装的单个 API 调用结果,这不会在有新数据时自动更新。
举个实际的例子,假设你正在使用 Firebase,一个常见的保持聊天实时更新的方式是使用 onValue
事件监听器。使用 onValue
时,Firebase 会自动调用回调函数,每当数据更改时,你的聊天就会实时更新。
以下是如何使用它的示例:
Stream<dynamic> getChatsList(int roomId, int categoryId) {
return _chatApi.getRoomChatList(roomId, categoryId).asStream();
}
Future getRoomChatList(int roomId, int categoryId) async {
try {
token = getToken();
DatabaseReference chatRef = FirebaseDatabase.instance
.reference()
.child('rooms')
.child(roomId.toString())
.child(categoryId.toString());
chatRef.onValue.listen((event) {
var data = event.snapshot.value;
// 根据需要处理数据
return data;
});
} catch (error) {
throw Error();
}
}
在上面的示例中,为特定聊天室的 Firebase 数据库引用添加了一个监听器。数据库中该位置的任何更改都将触发 onValue
事件,并在实时更新你的数据。
这只是一个通用示例。你需要根据你的特定 Firebase 数据库结构调整数据库引用。
如果你不太可能使用 Firebase 或类似的实时数据库,而是使用 REST API,那么实时更新更加复杂,可能涉及其他技术,如 WebSocket、Server-Sent Events (SSE) 或推送通知。你需要检查你的后端是否支持其中的任何一种。
英文:
From your code snippet, it seems like you're only fetching the chat list once in your getChatsList()
function in ChatController
. However, in a real-time chat application, you'll want to listen for updates from your backend as new messages arrive.
The method getChatsList()
in the ChatController
returns a Stream
but you're yielding the result of a single API call wrapped with async*
and yield
, which won't automatically update when there's new data.
To use a practical exemple I'll assume you're using Firebase, a common way to keep your chat updated in real-time is by using the onValue
event listener. When using onValue
, Firebase will automatically call your callback function every time the data changes, allowing your chat to update in real-time.
Here is an example of how to use it:
Stream<dynamic> getChatsList(int roomId,int categoryId) {
return _chatApi.getRoomChatList(roomId, categoryId).asStream();
}
Future getRoomChatList(int roomId,int categoryId) async {
try {
token = getToken();
DatabaseReference chatRef = FirebaseDatabase.instance
.reference()
.child('rooms')
.child(roomId.toString())
.child(categoryId.toString());
chatRef.onValue.listen((event) {
var data = event.snapshot.value;
// process data as needed
return data;
});
} catch (error) {
throw Error();
}
}
In the above example, a listener is added to the Firebase database reference for the specific chat room. Any changes to that location in the database will trigger the onValue
event and update your data in real-time.
This is just a generic example. You'll have to adjust the database reference according to your specific Firebase database structure.
Now since I doubt you're using Firebase or a similar real-time database, but a REST API instead, then real-time updates are more complex and may involve other technologies like WebSocket, Server-Sent Events (SSE), or push notifications. You'll need to check if your backend supports any of these or not.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论