Skip to content

Commit baa7eee

Browse files
committed
fix: expose user_cache and make it a changenotifier
1 parent 0261ce1 commit baa7eee

File tree

3 files changed

+34
-6
lines changed

3 files changed

+34
-6
lines changed

packages/flutter_chat_core/lib/src/utils/user_cache.dart

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import 'package:flutter/foundation.dart';
12
import '../models/user.dart';
23
import 'typedefs.dart';
34

@@ -6,7 +7,9 @@ import 'typedefs.dart';
67
/// Uses a Least Recently Used (LRU) eviction strategy to limit memory usage.
78
/// When the cache reaches the maximum size, the least recently accessed user
89
/// will be removed to make space for new entries.
9-
class UserCache {
10+
///
11+
/// Extends [ChangeNotifier] to allow widgets to listen for cache modifications.
12+
class UserCache extends ChangeNotifier {
1013
final Map<UserID, User?> _cache = {};
1114
final List<UserID> _accessOrder = []; // For LRU eviction
1215

@@ -19,7 +22,9 @@ class UserCache {
1922
/// ```dart
2023
/// final cache = UserCache(maxSize: 100);
2124
/// ```
22-
UserCache({this.maxSize = 100});
25+
UserCache({this.maxSize = 100}) {
26+
assert(maxSize > 0, 'UserCache maxSize must be a positive integer.');
27+
}
2328

2429
/// Gets a user synchronously from cache, or returns null if not cached.
2530
///
@@ -68,26 +73,37 @@ class UserCache {
6873
void clear() {
6974
_cache.clear();
7075
_accessOrder.clear();
76+
notifyListeners();
7177
}
7278

7379
/// Removes a specific user from the cache.
7480
void remove(UserID userId) {
75-
_cache.remove(userId);
76-
_accessOrder.remove(userId);
81+
if (_cache.containsKey(userId)) {
82+
_cache.remove(userId);
83+
_accessOrder.remove(userId);
84+
notifyListeners();
85+
}
7786
}
7887

7988
/// Internal method to add or update a user in the cache.
8089
/// Handles LRU eviction if the cache is full.
8190
void _cacheUser(UserID userId, User? user) {
91+
var needsNotify = true;
8292
// Handle eviction if needed
8393
if (_accessOrder.length >= maxSize && !_cache.containsKey(userId)) {
8494
final oldestId = _accessOrder.removeAt(0);
8595
_cache.remove(oldestId);
96+
} else if (_cache.containsKey(userId) && _cache[userId] == user) {
97+
needsNotify = false;
8698
}
8799

88100
// Update cache
89101
_cache[userId] = user;
90102
_accessOrder.remove(userId);
91103
_accessOrder.add(userId);
104+
105+
if (needsNotify) {
106+
notifyListeners();
107+
}
92108
}
93109
}

packages/flutter_chat_ui/lib/src/avatar.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class Avatar extends StatelessWidget {
4343
Widget build(BuildContext context) {
4444
final theme = context.watch<ChatTheme>();
4545
final resolveUser = context.read<ResolveUserCallback>();
46-
final userCache = context.read<UserCache>();
46+
final userCache = context.watch<UserCache>();
4747

4848
// Try to get from cache synchronously first
4949
final cachedUser = userCache.getSync(userId);

packages/flutter_chat_ui/lib/src/chat.dart

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,12 @@ class Chat extends StatefulWidget {
3131
/// If not provided, a default instance is created.
3232
final CrossCache? crossCache;
3333

34+
/// Optional user-provided cache for resolved `User` objects.
35+
/// Ideally, you would not provide this and rely on the default internal LRU cache.
36+
/// However, you can supply your own instance if you need direct control to clear
37+
/// the cache for a specific user (e.g., when an avatar URL changes and requires a refresh).
38+
final UserCache? userCache;
39+
3440
/// The visual theme for the chat UI.
3541
/// If not provided, defaults to [ChatTheme.light].
3642
final ChatTheme? theme;
@@ -68,6 +74,7 @@ class Chat extends StatefulWidget {
6874
required this.chatController,
6975
this.builders,
7076
this.crossCache,
77+
this.userCache,
7178
this.theme,
7279
this.onMessageSend,
7380
this.onMessageTap,
@@ -86,6 +93,7 @@ class _ChatState extends State<Chat> with WidgetsBindingObserver {
8693
late ChatTheme _theme;
8794
late Builders _builders;
8895
late final CrossCache _crossCache;
96+
late final UserCache _userCache;
8997
late DateFormat _timeFormat;
9098

9199
@override
@@ -95,6 +103,7 @@ class _ChatState extends State<Chat> with WidgetsBindingObserver {
95103
_updateTheme();
96104
_updateBuilders();
97105
_crossCache = widget.crossCache ?? CrossCache();
106+
_userCache = widget.userCache ?? UserCache(maxSize: 100);
98107
_timeFormat = widget.timeFormat ?? DateFormat('HH:mm');
99108
}
100109

@@ -133,14 +142,17 @@ class _ChatState extends State<Chat> with WidgetsBindingObserver {
133142
Provider.value(value: _theme),
134143
Provider.value(value: _builders),
135144
Provider.value(value: _crossCache),
145+
if (widget.userCache != null)
146+
ChangeNotifierProvider.value(value: _userCache)
147+
else
148+
ChangeNotifierProvider(create: (_) => _userCache),
136149
Provider.value(value: _timeFormat),
137150
Provider.value(value: widget.onMessageSend),
138151
Provider.value(value: widget.onMessageTap),
139152
Provider.value(value: widget.onMessageLongPress),
140153
Provider.value(value: widget.onAttachmentTap),
141154
ChangeNotifierProvider(create: (_) => ComposerHeightNotifier()),
142155
ChangeNotifierProvider(create: (_) => LoadMoreNotifier()),
143-
Provider(create: (_) => UserCache(maxSize: 100)),
144156
],
145157
child: Container(
146158
color:

0 commit comments

Comments
 (0)