In provider 6, what does context.watch<SurveyAnswers?>() return when no matching provider is found, and when is a nullable lookup appropriate?
answer
- provider 6.0.0 change
- nullable type argument
- null instead of throwing
- ProviderNullException for a null value
- Model and Model? resolve together
basics
~20 sSince provider 6.0.0, a nullable type argument such as watch<SurveyAnswers?>() or read<SurveyAnswers?>() returns null when no provider is found, instead of throwing ProviderNotFoundException. Use it for reusable widgets that genuinely work with or without the provider.
solid answer
~40 sIn provider 6 the nullability of the type argument decides what a failed lookup does. `context.watch<SurveyAnswers>()` with no provider above throws `ProviderNotFoundException`; `context.watch<SurveyAnswers?>()` returns `null`, and `read` and `select` behave the same way. A related error is `ProviderNullException`: reading a non-nullable `T` from a provider whose value is `null`. 6.0.0 also made `Provider<Model>` and `Provider<Model?>` resolve to the same, deepest provider, so reading `Model?` can no longer miss a `Model` provider. Nullable lookups suit widgets that are optional consumers, like a progress bar that shows survey progress only inside a survey flow. They are the wrong fix for a misplaced provider: they turn a loud error into a silently empty UI.
code
dart · 16 linesimport 'package:flutter/material.dart';
import 'package:provider/provider.dart';
// SurveyAnswers is a ChangeNotifier with a double progress getter.
class SurveyProgressBar extends StatelessWidget {
const SurveyProgressBar({super.key});
@override
Widget build(BuildContext context) {
// null outside a survey flow instead of ProviderNotFoundException
final answers = context.watch<SurveyAnswers?>();
if (answers == null) return const SizedBox.shrink();
return LinearProgressIndicator(value: answers.progress);
}
}go deeper
Recall that adding ? to the type in watch or read returns null instead of throwing when no provider is found.
Explain the difference between ProviderNotFoundException and ProviderNullException, and the 6.0.0 change that unified Model and Model? lookups.
Show judgement about when a nullable lookup is a designed state versus a masked placement bug, and push back on blanket nullable reads in review.
Set a rule for shared widget libraries: optional provider dependencies must be documented and have a designed null state, keeping required dependencies loud.
## Two outcomes, chosen by the type argument When a provider read cannot find a match, provider 6 looks at whether the requested type is **nullable**: | Call | No provider above | Provider above with a null value | |---|---|---| | `context.watch<SurveyAnswers>()` | throws `ProviderNotFoundException` | throws `ProviderNullException` | | `context.watch<SurveyAnswers?>()` | returns `null` | returns `null` | | `context.read<SurveyAnswers?>()` | returns `null` | returns `null` | | `context.select<SurveyAnswers?, int?>((a) => a?.answered)` | selector receives `null` | selector receives `null` | The package describes this as 'optionally depending on a provider': reusable widgets that may be used in various locations, including outside of a provider. ## The two exceptions - **`ProviderNotFoundException`**: no provider of the type was found above the context. Its debug message starts 'Could not find the correct Provider<T> above this X Widget' and lists the usual causes: a hot reload after adding a provider in `main`, a provider in a different route, and a context that is an ancestor of the provider. - **`ProviderNullException`**: a provider was found, but its value is `null` and the requested type was non-nullable. The message reads 'The widget X tried to read Provider<T> but the matching provider returned null' and suggests asking for `T?` instead. ## What 6.0.0 changed Before provider 6, `Provider<Model>` and `Provider<Model?>` were different lookup keys, so defining a provider as `Model?` and reading it as `Model` threw `ProviderNotFoundException`. The 6.0.0 changelog made two breaking changes: 1. Providers that differ **only** by nullability are treated as the same; a lookup resolves to the **deepest** one. 2. A nullable type argument returns `null` when nothing is found, instead of throwing. Together they mean the type argument's `?` controls **failure behaviour**, not which provider is found. ## When a nullable lookup is right - A **reusable widget** that adds something when a provider is present: a `SurveyProgressBar` that renders progress inside a survey flow and nothing elsewhere. - A widget shared between an app and a **showcase or test harness** where the provider is deliberately absent. - **Optional features** behind a provider that some builds omit. In each case, the `null` branch is a real, designed state of the widget. ## When it is the wrong fix - Silencing `ProviderNotFoundException` on a pushed route or dialog. The real problem is placement; a nullable read turns a clear error into a screen that silently shows nothing. - Hiding a provider that is null because of a bug upstream. Prefer letting `ProviderNullException` point at it. - Everywhere 'just in case'. It pushes null checks into every reader and makes a missing provider indistinguishable from a legitimately absent one. ## Testing both branches A widget with an optional provider dependency has two behaviours, and a widget test should cover both: 1. pump the widget **without** a provider and expect the empty or fallback rendering; 2. pump it under `ChangeNotifierProvider<SurveyAnswers>.value(value: fakeAnswers, child: ...)` and expect the real rendering; 3. change the fake and call `notifyListeners()`, then pump, to confirm the `watch` still rebuilds. The first test is exactly what a non-nullable lookup would turn into a `ProviderNotFoundException`, which is a useful way to explain the difference: the nullable version makes 'no provider' a supported configuration rather than a crash. ## Reading the code A nullable lookup should make its null branch visible, for example with a pattern or an early return: ```dart final answers = context.watch<SurveyAnswers?>(); if (answers == null) return const SizedBox.shrink(); return LinearProgressIndicator(value: answers.progress); ``` If reviewers cannot say what the null branch means for the user, the lookup probably should not be nullable.
- In provider 6, a Provider<Model?> sits above a widget that calls context.watch<Model>(); what happens?The lookup finds that provider, because providers differing only by nullability resolve to the same, deepest one. If its value is non-null the call returns it; if the value is `null`, the non-nullable read throws `ProviderNullException`, whose message suggests reading `Model?` instead.
- Why is watch<T?>() a poor fix for ProviderNotFoundException on a pushed route?The provider really is missing on that route, so the widget would always get `null` there and render its empty branch. The user sees a blank or broken page with no error. Fix the placement: lift the provider or re-provide the instance with a `.value` constructor.
saying these in an interview costs you the question
- watch<T?>() still throws ProviderNotFoundException when nothing is found
- Provider<Model> and Provider<Model?> are separate lookups in provider 6
- Making every lookup nullable is a safe default
- ProviderNullException means no provider exists above the widget
- A nullable lookup is the standard fix for a provider missing on a new route