@tduncan / Cortana / commits / adf140f

fix(chat): guard cancelStream and _replaceMessageInAll against disposed Ref #87

Add ref.mounted checks across async gaps in cancelStream, _abortStreamImmediately, and upfront in _replaceMessageInAll and _clearPendingApproval to prevent 'Cannot use the Ref of NotifierProvider... after it has been disposed' crash when stopping a response. Fixes #87.

abdulmominsakib committed Sep 21, 2026 at 09:28 UTC adf140fe24fe9be0406b348f8709c0d663eed23f
1 file changed +11 -1
lib/features/chat/providers/chat_notifier.dart
+11 -1
@@ -294,7 +294,9 @@ class ChatNotifier extends Notifier<ChatState> {
294 if (!pending.completer.isCompleted) {
295 pending.completer.complete(false);
296 }
297 - state = state.copyWith(clearPendingApproval: true);
297 + if (ref.mounted) {
298 + state = state.copyWith(clearPendingApproval: true);
299 + }
300 }
301 }
302
@@ -536,6 +538,8 @@ class ChatNotifier extends Notifier<ChatState> {
538 _uiUpdateTimer = null;
539 _clearPendingApproval();
540
541 + if (!ref.mounted) return;
542 +
543 ref.read(chatServiceProvider)?.cancelStream();
544 ref.read(chatBackgroundServiceProvider).stop();
545 ref.read(isStreamingProvider.notifier).setStreaming(false);
@@ -1821,6 +1825,8 @@ class ChatNotifier extends Notifier<ChatState> {
1825 _uiUpdateTimer?.cancel();
1826 _uiUpdateTimer = null;
1827 _clearPendingApproval();
1828 + if (!ref.mounted) return;
1829 +
1830 ref.read(chatServiceProvider)?.cancelStream();
1831 ref.read(chatBackgroundServiceProvider).stop();
1832
@@ -1830,6 +1836,7 @@ class ChatNotifier extends Notifier<ChatState> {
1836 _lastSavedReasoningLength = 0;
1837
1838 await _saveService?.flush();
1839 + if (!ref.mounted) return;
1840
1841 final streamingMessage = _latestStreamingMessage ?? state.streamingMessage;
1842 if (streamingMessage != null) {
@@ -1841,9 +1848,11 @@ class ChatNotifier extends Notifier<ChatState> {
1848 stopReason: 'cancelled',
1849 );
1850 await _saveMessage(finalMessage);
1851 + if (!ref.mounted) return;
1852 _replaceMessageInState(finalMessage, clearStreaming: true);
1853 }
1854
1855 + if (!ref.mounted) return;
1856 state = state.copyWith(isStreaming: false, clearStreaming: true);
1857 ref.read(isStreamingProvider.notifier).setStreaming(false);
1858 _latestStreamingMessage = null;
@@ -2475,6 +2484,7 @@ class ChatNotifier extends Notifier<ChatState> {
2484 }
2485
2486 void _replaceMessageInAll(Message message, {bool clearStreaming = false}) {
2487 + if (!ref.mounted) return;
2488 final updatedAll = state.allMessages.map((m) {
2489 return m.id == message.id ? message : m;
2490 }).toList();