rrousselGit / riverpod

A reactive caching and data-binding framework. Riverpod makes working with asynchronous code a breeze.
https://riverpod.dev
MIT License
6.17k stars 943 forks source link

Error: setState() or markNeedsBuild() called during build. #636

Closed hisaichi5518 closed 3 years ago

hisaichi5518 commented 3 years ago

Describe the bug After the build, I edited the code and did a hot-restart, and when I tap the FAB, an error occurs.

======== Exception caught by widgets library =======================================================
The following assertion was thrown building Consumer(dirty, dependencies: [UncontrolledProviderScope], state: _ConsumerState#65420):
setState() or markNeedsBuild() called during build.

This UncontrolledProviderScope widget cannot be marked as needing to build because the framework is already in the process of building widgets.  A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: UncontrolledProviderScope
The widget which was currently being built when the offending call was made was: Consumer
  dirty
  dependencies: [UncontrolledProviderScope]
  state: _ConsumerState#65420
The relevant error-causing widget was: 
  Consumer file:///Users/hisaichi5518/src/github.com/rrousselGit/river_pod/examples/counter/lib/main.dart:40:16
When the exception was thrown, this was the stack: 
dart-sdk/lib/_internal/js_dev_runtime/private/ddc_runtime/errors.dart 236:49  throw_
packages/flutter/src/widgets/framework.dart 4217:11                           <fn>
packages/flutter/src/widgets/framework.dart 4231:14                           markNeedsBuild
packages/flutter_riverpod/src/framework.dart 283:5                            [_debugCanModifyProviders]
packages/riverpod/src/framework/base_provider.dart 708:42                     <fn>
...

To Reproduce

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// A Counter example implemented with riverpod

void main() {
  runApp(
    // Adding ProviderScope enables Riverpod for the entire project
    const ProviderScope(child: MyApp()),
  );
}

class MyApp extends ConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      home: ProviderScope( // ADDED!
        overrides: [
          counterProvider.overrideWithValue(StateController(10)),
        ],
        child: Home(),
      ),
    );
  }
}

/// Providers are declared globally and specifies how to create a state
final counterProvider = StateProvider((ref) => 0);

class Home extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter example')),
      body: Center(
        // Consumer is a widget that allows you reading providers.
        // You could also use the hook "ref.watch(" if you uses flutter_hooks
        child: Consumer(builder: (context, ref, _) {
          final count = ref.watch(counterProvider).state;
          return Text('$count');
        }),
      ),
      floatingActionButton: FloatingActionButton(
        // The read method is an utility to read a provider without listening to it
        onPressed: () => ref.read(counterProvider).state++,
        child: const Icon(Icons.add),
      ),
    );
  }
}

Expected behavior I'm hoping there will be no errors.

Sadmansamee commented 3 years ago

Getting same error as well

nikitadol commented 3 years ago

This can happen if the widget is rebuilt and a new value StateController(10) is created

Try to store this value instead of creating it in the build method

rrousselGit commented 3 years ago

I am unable to reproduce the problem with the example you gave.

What Flutter and Riverpod version are you using?

hisaichi5518 commented 3 years ago

riverpod version

I have reproduced it with da2f6e5bc23e3a6f264e74803c70e8d9f4f38a7c.

Flutter version

[✓] Flutter (Channel stable, 2.2.3, on macOS 11.5 20G71 darwin-x64, locale ja)
    • Flutter version 2.2.3 at /Users/hisaichi5518/flutter
    • Framework revision f4abaa0735 (7 weeks ago), 2021-07-01 12:46:11 -0700
    • Engine revision 241c87ad80
    • Dart version 2.13.4

The following code did not reproduce the problem. thanks, nikitadol.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

// A Counter example implemented with riverpod

void main() {
  runApp(
    // Adding ProviderScope enables Riverpod for the entire project
    const ProviderScope(child: MyApp()),
  );
}

// ADDED!
final stateController = StateController(10);

class MyApp extends ConsumerWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return MaterialApp(
      home: ProviderScope(
        // ADDED!
        overrides: [
          counterProvider.overrideWithValue(stateController),
        ],
        child: Home(),
      ),
    );
  }
}

/// Providers are declared globally and specifies how to create a state
final counterProvider = StateProvider((ref) => 0);

class Home extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    return Scaffold(
      appBar: AppBar(title: const Text('Counter example')),
      body: Center(
        // Consumer is a widget that allows you reading providers.
        // You could also use the hook "ref.watch(" if you uses flutter_hooks
        child: Consumer(builder: (context, ref, _) {
          final count = ref.watch(counterProvider).state;
          return Text('$count');
        }),
      ),
      floatingActionButton: FloatingActionButton(
        // The read method is an utility to read a provider without listening to it
        onPressed: () => ref.read(counterProvider).state++,
        child: const Icon(Icons.add),
      ),
    );
  }
}
nikitadol commented 3 years ago

@hisaichi5518 Can you take this out of the widget or use State?

 final stateController = StateController(10);
hisaichi5518 commented 3 years ago

@nikitadol Yes, I can. I've updated the code. Should I avoid create and use StateController(10) in the build method? (as well as StateProvider etc)

nikitadol commented 3 years ago

@hisaichi5518 What is the final code? Did this fix the problem?

hisaichi5518 commented 3 years ago

Here's the final code! ( https://github.com/rrousselGit/river_pod/issues/636#issuecomment-902616825 ). Thanks to your advice, the error no longer occurs 🙂 However, I am not sure if it is a spec or a bug that creating a StateController in the build method causes an error.

nikitadol commented 3 years ago

@hisaichi5518 Since the problem has been resolved, I will explain what happened 😃

Since Hot Reload causes the widget to be rebuilt, a new StateController is created. And since it is new, it triggers a rebuild of the provider. And since this happens during the build - you see this error

hisaichi5518 commented 3 years ago

I see, I understand! The problem is completely solved and I will close it. Thank you very much!

joranmulderij commented 1 year ago

Seems like .overrideWithValue is the problem here. When I changed it to .overrideWith and passed a function the problem was solved.

I'm not sure if this is expected behavior. This simply means that .overrideWithValue can not really be used.

fisforfaheem commented 11 months ago

happening if I show a laert dialog: import 'package:ai_mockinterview/core/color/color_manager.dart'; import 'package:ai_mockinterview/core/di/injection_container.dart'; import 'package:ai_mockinterview/core/extensions/widget_ext.dart'; import 'package:ai_mockinterview/core/route/routes.dart'; import 'package:ai_mockinterview/core/utils/constants.dart'; import 'package:ai_mockinterview/core/utils/size_utils.dart'; import 'package:ai_mockinterview/core/widgets/common_alert_dialog.dart'; import 'package:ai_mockinterview/core/widgets/common_app_bar.dart'; import 'package:ai_mockinterview/features/result/data/model/result_model.dart'; import 'package:ai_mockinterview/features/result/presentation/cubit/result_cubit.dart'; import 'package:ai_mockinterview/features/result/presentation/cubit/result_state.dart'; import 'package:chewie/chewie.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart';

class ScoreProcessingPage extends StatelessWidget { const ScoreProcessingPage({super.key});

@override Widget build(BuildContext context) { final cubit = context.read(); return BlocProvider( create: (context) => ResultCubit( ResultModel as ResultModel, sl(), ), child: WillPopScope( onWillPop: () { cubit.videoPlayerController.dispose(); cubit.chewieController!.dispose(); return Future.value(true); }, child: Scaffold( backgroundColor: AppColors.backgroundNew, appBar: PreferredSize( preferredSize: Size.fromHeight(56.h), child: CommonAppBar( height: 54.h, leadingWidth: 20.w, actions: [ Text( 'Video Preview', style: TextStyle(color: AppColors.whiteA700), ).paddingCustom( left: 0.0, top: 24.0, right: 170.0.h, bottom: 0.0) ], leading: cubit.myAppBar.appbarImage( svgPath: ImageConstantNew.backArrow, onTap: () { cubit.videoPlayerController.dispose(); cubit.chewieController?.dispose(); AppRouter.pushAndRemoveUntil(AppRouter.homePage); }, ), title: cubit.myAppBar.appbarSubtitle( text: ' Back', onTap: () { cubit.videoPlayerController.dispose(); cubit.chewieController?.dispose(); AppRouter.pushAndRemoveUntil(AppRouter.homePage); }, ), ).paddingLeft(left: 20), ), body: BlocConsumer<ResultCubit, ResultState>( bloc: cubit, listener: (context, state) { if (state is VideoPreviewStatePlaying) { cubit.isSpeaking = true; } else { cubit.isSpeaking = false; } }, // listener: (context, state) {}, builder: (context, state) { return Center( child: SingleChildScrollView( child: Column( children: [ Stack( children: [ state is VideoPreviewStateError ? Center( child: cubit.videoPath == '' ? CommonAlertDialog.showCustomDialog( context, title: 'ALERT', message: 'Video not found, Please record again', allowBtnText: 'OK', denyBtnText: '', allowBtnFunction: () { cubit.videoPlayerController .dispose(); cubit.chewieController?.dispose(); AppRouter.pushAndRemoveUntil( AppRouter.homePage); }, ) : SizedBox( height: 50, child: AspectRatio( aspectRatio: 1, child: Center( child: CircularProgressIndicator( color: AppColors.background, ), ), ), ), ) : AspectRatio( aspectRatio: cubit .videoPlayerController.value.aspectRatio, child: Padding( padding: const EdgeInsets.symmetric( vertical: 20), child: Chewie( controller: cubit.chewieController!, ), ), ) ], ), ], ), ), ); }, ), ), ), ); } }