riverpod icon indicating copy to clipboard operation
riverpod copied to clipboard

Cannot use ref functions after the dependency of a provider changed but before the provider rebuilt, when chaining providers

Open saibotma opened this issue 4 months ago • 16 comments

Describe the bug This example has got two providers, one state provider, which persists the entered text, and a view model provider, which forwards the entered text from the state provider & also forwards the input event to the state provider. The example is very stripped down, and obviously you could do the same thing with only one provider, without any issue, however the issue appeared in a more complex real world situation where two providers were necessary.

To Reproduce

  • Run the example
  • Spam the keyboard while focusing the text field
  • Notice the exceptions

The exception disappears when you move the read the notifier inside the onChanged callback or stop watching the view model provider, as indicated in the comments.

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

part 'main.g.dart';

@riverpod
class MyState extends _$MyState {
  @override
  String build() => "";

  void setText(String text) {
    state = text;
  }
}

@riverpod
class MyViewModel extends _$MyViewModel {
  @override
  String build() => ref.watch(myStateProvider);

  void enterText(String text) {
    ref.read(myStateProvider.notifier).setText(text);
  }
}

void main() {
  runApp(const ProviderScope(child: MyApp()));
}

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

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      home: MyScreen(),
    );
  }
}

class MyScreen extends ConsumerStatefulWidget {
  const MyScreen({super.key});

  @override
  ConsumerState<MyScreen> createState() => _MyScreenState();
}

class _MyScreenState extends ConsumerState<MyScreen> {
  @override
  Widget build(BuildContext context) {
    // Using this notifier causes the exception.
    final notifier = ref.read(myViewModelProvider.notifier);
    // Commenting this line out makes the exception go away.
    ref.watch(myViewModelProvider);
    return Scaffold(
      body: Center(
        child: TextField(
          onChanged: (it) {
            // Using this notifier does not cause the exception.
            //final notifier = ref.read(myViewModelProvider.notifier);
            notifier.enterText(it);
          },
        ),
      ),
    );
  }
}
======== Exception caught by widgets ===============================================================
The following assertion was thrown while calling onChanged:
Cannot use ref functions after the dependency of a provider changed but before the provider rebuilt
'package:riverpod/src/framework/element.dart':
Failed assertion: line 674 pos 7: '!_didChangeDependency'

When the exception was thrown, this was the stack: 
#2      ProviderElementBase._assertNotOutdated (package:riverpod/src/framework/element.dart:674:7)
#3      ProviderElementBase.read (package:riverpod/src/framework/element.dart:688:5)
#4      MyViewModel.enterText (package:riverpod_bug_1902/main.dart:23:9)
#5      _MyScreenState.build.<anonymous closure> (package:riverpod_bug_1902/main.dart:62:22)
#6      EditableTextState._formatAndSetValue (package:flutter/src/widgets/editable_text.dart:3834:27)
#7      EditableTextState.updateEditingValue (package:flutter/src/widgets/editable_text.dart:3036:7)
#8      TextInput._updateEditingValue (package:flutter/src/services/text_input.dart:2025:43)
#9      TextInput._handleTextInputInvocation (package:flutter/src/services/text_input.dart:1858:29)
#10     TextInput._loudlyHandleTextInputInvocation (package:flutter/src/services/text_input.dart:1759:20)
#11     MethodChannel._handleAsMethodCall (package:flutter/src/services/platform_channel.dart:559:55)
#12     MethodChannel.setMethodCallHandler.<anonymous closure> (package:flutter/src/services/platform_channel.dart:552:34)
#13     _DefaultBinaryMessenger.setMessageHandler.<anonymous closure> (package:flutter/src/services/binding.dart:567:35)
#14     _invoke2 (dart:ui/hooks.dart:344:13)
#15     _ChannelCallbackRecord.invoke (dart:ui/channel_buffers.dart:45:5)
#16     _Channel.push (dart:ui/channel_buffers.dart:135:31)
#17     ChannelBuffers.push (dart:ui/channel_buffers.dart:343:17)
#18     PlatformDispatcher._dispatchPlatformMessage (dart:ui/platform_dispatcher.dart:722:22)
#19     _dispatchPlatformMessage (dart:ui/hooks.dart:257:31)
(elided 2 frames from class _AssertionError)
====================================================================================================

Expected behavior No exception should be thrown.

saibotma avatar Feb 20 '24 06:02 saibotma