Dependency / version conflicts
version solving failed: source_gen conflict between riverpod_generator and freezed
version solving failed: source_gen conflict between riverpod_generator and freezed
riverpod_generator ^4 requires source_gen ^3, but freezed ^2 requires source_gen ^2. They cannot coexist.Fix: upgrade freezed and freezed_annotation to ^3.x at the same time as riverpod_generator ^4.x.version solving failed: analyzer conflict between riverpod_generator ^4 and drift_dev ^2.32+
version solving failed: analyzer conflict between riverpod_generator ^4 and drift_dev ^2.32+
riverpod_generator ^4 requires analyzer ^9, but drift_dev >=2.32.0 bumped to analyzer ^10.Fix: pin drift_dev (and drift) below 2.32.0 until riverpod_generator releases support for analyzer ^10:Riverpod v3
undefined_identifier errors for authNotifierProvider, gatewayNotifierProvider, etc.
undefined_identifier errors for authNotifierProvider, gatewayNotifierProvider, etc.
riverpod_generator ^4, class-based providers drop the Notifier suffix from their generated variable name.| Class | v2 provider name | v3 provider name |
|---|---|---|
AuthNotifier | authNotifierProvider | authProvider |
GatewayNotifier | gatewayNotifierProvider | gatewayProvider |
RouterNotifier | routerNotifierProvider | routerProvider |
MessageSendNotifier | messageSendNotifierProvider | messageSendProvider |
build_runner.'valueOrNull' isn't defined for the type 'AsyncValue'
'valueOrNull' isn't defined for the type 'AsyncValue'
AsyncValue.value (which threw on error/loading) was removed. valueOrNull was renamed to value..valueOrNull → .value for all Riverpod AsyncValue usages. Do not rename valueOrNull on custom non-Riverpod types (e.g. a Result<T> class with its own valueOrNull getter).Runtime: 'Cannot use the Ref after it has been disposed' mid-async operation
Runtime: 'Cannot use the Ref after it has been disposed' mid-async operation
@riverpod (auto-dispose) providers can be paused or disposed between await points. Any ref.read() call after an await will throw this error at runtime.Two rules to follow:-
Action/service notifiers that span async work must be
keepAlive: true.MessageSendNotifier,GatewayNotifier, andRouterNotifierare examples — they are not tied to a single widget’s lifecycle. -
Capture all
ref.read()results before the firstawait.
unnecessary_import warning for flutter_riverpod
unnecessary_import warning for flutter_riverpod
package:riverpod_annotation/riverpod_annotation.dart re-exports everything from flutter_riverpod. Files that import both will get an unnecessary_import lint warning.Fix: remove import 'package:flutter_riverpod/flutter_riverpod.dart' from any file that already imports riverpod_annotation.Auto-dispose provider silently disposes a service that a keepAlive notifier still needs
Auto-dispose provider silently disposes a service that a keepAlive notifier still needs
keepAlive: true notifier obtains a dependency via ref.read(someProvider) inside build(). The dependency is auto-dispose. Because ref.read does not create a subscription, Riverpod has no active listener on someProvider and disposes it immediately after build() returns — even though the notifier is still alive.Concrete example: RecordingNotifier (keepAlive) used ref.read(audioRecorderProvider) to obtain the AudioRecorder. audioRecorderProvider is auto-dispose. With no active listener, Riverpod called recorder.dispose() right away. The very next call to startRecording() threw PlatformException: Record has not yet been created or has already been disposed.Fix: replace ref.read with ref.watch inside build() to keep the dependency alive for the full lifetime of the notifier:keepAlive notifier needs a service for its entire lifetime, always ref.watch that service inside build(). Using ref.read inside build() is only safe for one-shot reads where you don’t need the value to stay alive.Freezed v3
non_abstract_class_inherits_abstract_member on @freezed data classes
non_abstract_class_inherits_abstract_member on @freezed data classes
_$Foo mixin declares abstract field getters. A plain class Foo with _$Foo is a concrete class with unimplemented abstract members — this is a compile error.Affected pattern: any @freezed class Foo with _$Foo that has a single factory constructor (= _Foo).AuthState with unauthenticated / pairing / authenticated / error) — the mixin for unions has no abstract field getters, so class continues to work.'when' / 'map' isn't defined for the type after upgrading
'when' / 'map' isn't defined for the type after upgrading
when, map, maybeWhen, etc.) were moved from instance methods on the class to an extension (e.g. extension AuthStatePatterns on AuthState).Extension methods require a direct import of the library that defines them. Importing a file that imports the freezed file is not sufficient.Audio recording
Web: browser rejects AudioEncoder.aacLc — recording never starts
Web: browser rejects AudioEncoder.aacLc — recording never starts
startRecording() throws silently or the record package falls back to a broken state on Firefox/older Chrome. The browser’s MediaRecorder API only supports a subset of MIME types per browser:| Encoder | Chrome | Firefox | Safari |
|---|---|---|---|
opus (webm/opus) | ✅ | ✅ | ❌ |
aacLc (mp4/aac) | ✅ | ❌ | ✅ |
wav | ✅ | ✅ | ✅ |
AudioEncoder.aacLc works on Chrome but breaks on Firefox.Fix: probe encoders in preference order using isEncoderSupported and pick the first supported one:_recorder.start on web — the file extension on web is cosmetic only; the actual content type comes from the encoder:Race condition in swipe-to-cancel: 'Bad state: Future already completed'
Race condition in swipe-to-cancel: 'Bad state: Future already completed'
DartError: Bad state: Future already completed.Root cause: _onLongPressMoveUpdate fires on every pointer-move event while the finger is moving. Each event called unawaited(cancelRecording()) concurrently. The first call set isRecording.value = true during the await _recorder.stop() gap, so the second call also passed the if (!isRecording.value) return guard and attempted to complete the same Completer a second time.Fix — two-part:- Synchronous state mutation before any
awaitin both the notifier and the recorder implementation:
- Safe Completer completion — complete exactly once, then null out:
stopRecording() so it is also re-entry safe.Swipe-to-cancel doesn't trigger reliably — manual dx tracking drifts
Swipe-to-cancel doesn't trigger reliably — manual dx tracking drifts
details.globalPosition.dx at onLongPressStart and computing the delta on each onLongPressMoveUpdate) gives inconsistent results. The delta calculation can drift if the recorded start position is stale.Fix: use LongPressMoveUpdateDetails.offsetFromOrigin.dx which Flutter computes directly from the gesture origin. It is always relative to the long-press start point regardless of when you read it:-50.0 logical pixels is a good starting point (vs. the initially tried -80.0 which required too large a swipe).