Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ProxyProvider triggers an assertion failure when depending on a provided value of the same type it is providing #796

Open
jjoelson opened this issue Dec 28, 2022 · 1 comment
Assignees
Labels
bug Something isn't working needs triage

Comments

@jjoelson
Copy link

jjoelson commented Dec 28, 2022

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;
      });
    }
  }
}
@jjoelson jjoelson added bug Something isn't working needs triage labels Dec 28, 2022
@jjoelson
Copy link
Author

jjoelson commented Dec 29, 2022

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,
);

@rrousselGit rrousselGit self-assigned this May 10, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working needs triage
Projects
None yet
Development

No branches or pull requests

2 participants