provider
provider copied to clipboard
`ProxyProvider` triggers an assertion failure when depending on a provided value of the same type it is providing
Hi and thank you for this great package. Please let me know if there's any additional detail I can add to this issue!
Describe the bug
When using ProxyProvider
(or ProxyProvider0
, ProxyProvider2
, etc.) to provide a value of a given type, while also depending an an ancestor value of that same type, a widget rebuild triggers an assertion failure within the Flutter framework:
The following assertion was thrown building _InheritedProviderScope<AppTheme?>(dirty, dependencies: [_InheritedProviderScope<AppTheme?>], value: Instance of 'PartialAppTheme'):
'package:flutter/src/widgets/framework.dart': Failed assertion: line 5472 pos 14: '() {
package:flutter/…/widgets/framework.dart:5472
// check that it really is our descendant
Element? ancestor = dependent._parent;
while (ancestor != this && ancestor != null) {
ancestor = ancestor._parent;
}
return ancestor == this;
}()': is not true.
The relevant error-causing widget was
ProxyProvider0<AppTheme>
To Reproduce
A full reproduction is in the collapsed section below, but the PartialAppThemeProvider
here is the main part:
class AppTheme {
final String property1 = 'AppThemeProperty1';
final String property2 = 'AppThemeProperty2';
}
class PartialAppTheme implements AppTheme {
final AppTheme baseTheme;
@override
final String property1;
@override
String get property2 => baseTheme.property2;
PartialAppTheme(this.baseTheme, {required this.property1});
}
class PartialAppThemeProvider extends SingleChildStatelessWidget {
const PartialAppThemeProvider({
super.key,
super.child,
required this.property1,
});
final String property1;
@override
Widget buildWithChild(BuildContext context, Widget? child) {
return ProxyProvider0<AppTheme>(
update: (context, _) {
final baseTheme = Provider.of<AppTheme>(context);
return PartialAppTheme(
baseTheme,
property1: property1,
);
},
child: child,
);
}
}
PartialAppThemeProvider
attempts to "override" the AppTheme
provided by an ancestor based on that ancestor value and a widget parameter.
Full reproduction code and assertion failure stack trace
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:provider/single_child_widget.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return Provider<AppTheme>(
create: (context) => AppTheme(),
child: MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(),
),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
late bool _themeChanged = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Column(
children: [
const ThemedWidget(),
const Divider(),
PartialAppThemeProvider(
property1:
_themeChanged ? 'PartialProperty1' : 'ChangedPartialProperty1',
child: const ThemedWidget(),
),
const Divider(),
TextButton(
onPressed: () {
setState(() {
_themeChanged = !_themeChanged;
});
},
child: Text('Change Theme'),
),
],
),
);
}
}
class ThemedWidget extends StatelessWidget {
const ThemedWidget({super.key});
@override
Widget build(BuildContext context) {
final theme = context.watch<AppTheme>();
return Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
children: [
Text('property1: ${theme.property1}'),
Text('property2: ${theme.property2}'),
],
),
);
}
}
class PartialAppThemeProvider extends SingleChildStatelessWidget {
const PartialAppThemeProvider({
super.key,
super.child,
required this.property1,
});
final String property1;
@override
Widget buildWithChild(BuildContext context, Widget? child) {
return ProxyProvider0<AppTheme>(
update: (context, _) {
final baseTheme = Provider.of<AppTheme>(context);
return PartialAppTheme(
baseTheme,
property1: property1,
);
},
child: child,
);
}
}
class AppTheme {
final String property1 = 'AppThemeProperty1';
final String property2 = 'AppThemeProperty2';
}
class PartialAppTheme implements AppTheme {
final AppTheme baseTheme;
@override
final String property1;
@override
String get property2 => baseTheme.property2;
PartialAppTheme(this.baseTheme, {required this.property1});
}
Assertion failure stack trace:
═══════ Exception caught by widgets library ═══════════════════════════════════
The following assertion was thrown building _InheritedProviderScope<AppTheme?>(dirty, dependencies: [_InheritedProviderScope<AppTheme?>], value: Instance of 'PartialAppTheme'):
'package:flutter/src/widgets/framework.dart': Failed assertion: line 5472 pos 14: '() {
package:flutter/…/widgets/framework.dart:5472
// check that it really is our descendant
Element? ancestor = dependent._parent;
while (ancestor != this && ancestor != null) {
ancestor = ancestor._parent;
}
return ancestor == this;
}()': is not true.
The relevant error-causing widget was
ProxyProvider0<AppTheme>
lib/main.dart:95
When the exception was thrown, this was the stack
#2 InheritedElement.notifyClients
package:flutter/…/widgets/framework.dart:5472
#3 _InheritedProviderScopeElement.build
package:provider/src/inherited_provider.dart:552
#4 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4878
#5 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#6 ProxyElement.update
package:flutter/…/widgets/framework.dart:5228
#7 _InheritedProviderScopeElement.update
package:provider/src/inherited_provider.dart:523
#8 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#9 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#10 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#11 StatelessElement.update
package:flutter/…/widgets/framework.dart:4956
#12 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#13 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#14 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#15 StatelessElement.update
package:flutter/…/widgets/framework.dart:4956
#16 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#17 RenderObjectElement.updateChildren
package:flutter/…/widgets/framework.dart:5904
#18 MultiChildRenderObjectElement.update
package:flutter/…/widgets/framework.dart:6460
#19 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#20 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#21 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#22 StatelessElement.update
package:flutter/…/widgets/framework.dart:4956
#23 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#24 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#25 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#26 StatelessElement.update
package:flutter/…/widgets/framework.dart:4956
#27 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#28 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#29 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#30 ProxyElement.update
package:flutter/…/widgets/framework.dart:5228
#31 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#32 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#33 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#34 ProxyElement.update
package:flutter/…/widgets/framework.dart:5228
#35 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#36 RenderObjectElement.updateChildren
package:flutter/…/widgets/framework.dart:5904
#37 MultiChildRenderObjectElement.update
package:flutter/…/widgets/framework.dart:6460
#38 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#39 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#40 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#41 ProxyElement.update
package:flutter/…/widgets/framework.dart:5228
#42 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#43 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#44 StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:5050
#45 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#46 StatefulElement.update
package:flutter/…/widgets/framework.dart:5082
#47 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#48 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#49 StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:5050
#50 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#51 StatefulElement.update
package:flutter/…/widgets/framework.dart:5082
#52 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#53 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#54 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#55 ProxyElement.update
package:flutter/…/widgets/framework.dart:5228
#56 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#57 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#58 StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:5050
#59 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#60 StatefulElement.update
package:flutter/…/widgets/framework.dart:5082
#61 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#62 SingleChildRenderObjectElement.update
package:flutter/…/widgets/framework.dart:6307
#63 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#64 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#65 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#66 ProxyElement.update
package:flutter/…/widgets/framework.dart:5228
#67 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#68 SingleChildRenderObjectElement.update
package:flutter/…/widgets/framework.dart:6307
#69 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#70 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#71 StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:5050
#72 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#73 StatefulElement.update
package:flutter/…/widgets/framework.dart:5082
#74 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#75 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#76 StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:5050
#77 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#78 StatefulElement.update
package:flutter/…/widgets/framework.dart:5082
#79 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#80 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#81 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#82 ProxyElement.update
package:flutter/…/widgets/framework.dart:5228
#83 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#84 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#85 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#86 ProxyElement.update
package:flutter/…/widgets/framework.dart:5228
#87 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#88 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#89 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#90 ProxyElement.update
package:flutter/…/widgets/framework.dart:5228
#91 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#92 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#93 StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:5050
#94 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#95 StatefulElement.update
package:flutter/…/widgets/framework.dart:5082
#96 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#97 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#98 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#99 ProxyElement.update
package:flutter/…/widgets/framework.dart:5228
#100 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#101 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#102 StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:5050
#103 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#104 StatefulElement.update
package:flutter/…/widgets/framework.dart:5082
#105 Element.updateChild
package:flutter/…/widgets/framework.dart:3570
#106 ComponentElement.performRebuild
package:flutter/…/widgets/framework.dart:4904
#107 StatefulElement.performRebuild
package:flutter/…/widgets/framework.dart:5050
#108 Element.rebuild
package:flutter/…/widgets/framework.dart:4604
#109 BuildOwner.buildScope
package:flutter/…/widgets/framework.dart:2667
#110 WidgetsBinding.drawFrame
package:flutter/…/widgets/binding.dart:882
#111 RendererBinding._handlePersistentFrameCallback
package:flutter/…/rendering/binding.dart:378
#112 SchedulerBinding._invokeFrameCallback
package:flutter/…/scheduler/binding.dart:1175
#113 SchedulerBinding.handleDrawFrame
package:flutter/…/scheduler/binding.dart:1104
#114 SchedulerBinding._handleDrawFrame
package:flutter/…/scheduler/binding.dart:1015
#115 _invoke (dart:ui/hooks.dart:148:13)
#116 PlatformDispatcher._drawFrame (dart:ui/platform_dispatcher.dart:318:5)
#117 _drawFrame (dart:ui/hooks.dart:115:31)
(elided 2 frames from class _AssertionError)
════════════════════════════════════════════════════════════════════════════════
Expected behavior
My expectation is that I can use ProxyProvider
to both depend on and provide a new value for the same type.
I can work around the issue by avoiding ProxyProvider
and tracking the ancestor dependency manually by using Provider.of<AppTheme>(context)
in a stateful widget:
class PartialAppThemeProvider extends SingleChildStatefulWidget {
const PartialAppThemeProvider({
super.key,
super.child,
required this.property1,
});
final String? property1;
@override
State<PartialAppThemeProvider> createState() =>
_PartialAppThemeProviderState();
}
class _PartialAppThemeProviderState
extends SingleChildState<PartialAppThemeProvider> {
late AppTheme _theme;
@override
void didChangeDependencies() {
super.didChangeDependencies();
_updateTheme();
}
@override
void didUpdateWidget(covariant PartialAppThemeProvider oldWidget) {
super.didUpdateWidget(oldWidget);
if (widget.property1 != oldWidget.property1) {
_updateTheme();
}
}
@override
Widget buildWithChild(BuildContext context, Widget? child) {
return Provider<AppTheme>.value(
value: _theme,
child: child,
);
}
void _updateTheme() {
final baseTheme = Provider.of<AppTheme>(context);
if (widget.property1 != null) {
setState(() {
_theme = PartialAppTheme(
baseTheme,
property1: widget.property1!,
);
});
} else {
setState(() {
_theme = baseTheme;
});
}
}
}
After messing with it a bit more, I found a simpler work around by using the build
method's context instead of the update
callback's context to get the base theme:
return ProxyProvider0<AppTheme>(
update: (innerContext, _) {
// Use outer context instead of the `update` callback's context
final baseTheme = Provider.of<AppTheme>(context);
return PartialAppTheme(
baseTheme,
property1: property1,
);
},
child: child,
);
(Though perhaps this partially defeats the purpose of proxy provider by making my entire widget rebuild when the base theme changes instead of just re-running the update
closure)
Using ProxyProvider
to fetch the dependency automatically triggers the assertion failure:
// This triggers the assertion failure on rebuild
return ProxyProvider<AppTheme, AppTheme>(
update: (context, baseTheme, _) {
return PartialAppTheme(
baseTheme,
property1: property1,
);
},
child: child,
);