rrousselGit / riverpod

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

Using the ref in the provider doesn't get the nearest ProviderContainer, it takes the original ProviderContainer instead. #2731

Closed tbm98 closed 11 months ago

tbm98 commented 11 months ago

I have put a widget in a ProviderScope, that widget uses ConsumerStatefulWidget and it gets the state when overridden, however the provider associated with that widget can't get the overridden state, instead it has get the original state.

rrousselGit commented 11 months ago

I don't get what this is about.

Could you make a complete example?

tbm98 commented 11 months ago

Hello @rrousselGit. It is my complete example

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

part 'main.g.dart';

final stateProvider = StateProvider<int>((ref) {
  return 1;
});

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: ElevatedButton(
          onPressed: () {
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) {
                  return ProviderScope(
                    overrides: [
                      stateProvider.overrideWith((ref) => 3),
                    ],
                    child: const PageTwow(),
                  );
                },
              ),
            );
          },
          child: const Text('open new page')),
    );
  }
}

@riverpod
class PageTwo extends _$PageTwo {
  @override
  int build() {
    return 0;
  }

  void readState() {
    // expect to print 3, but it print 1
    print(ref.read(stateProvider));
  }
}

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

  @override
  ConsumerState createState() => _PageTwoState();
}

class _PageTwoState extends ConsumerState<PageTwow> {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        ElevatedButton(
            onPressed: () {
              // print 3 (correct)
              print(ref.read(stateProvider));
            },
            child: const Text('Read state from widget')),
        ElevatedButton(
            onPressed: () {
              ref.read(pageTwoProvider.notifier).readState();
            },
            child: const Text('Read state from provider')),
      ],
    );
  }
}

Just run -> open page two -> click to both of two button and see the console log Read state from widget print 3 Read state from provider print 1

tbm98 commented 11 months ago

my deps:

cupertino_icons: ^1.0.5 flutter_riverpod: ^2.3.6 riverpod_annotation: ^2.1.1

flutter_lints: ^2.0.2 build_runner: ^2.4.6 riverpod_lint: ^1.3.2 riverpod_generator: ^2.2.3

doctor: [!] Flutter (Channel beta, 3.13.0-0.1.pre, on Microsoft Windows [Version 10.0.19045.3208], locale en-US) ! Warning: flutter on your path resolves to C:\Users\tbm98\fvm\versions\beta\bin\flutter, which is not inside your current Flutter SDK checkout at C:\Users\tbm98\fvm\default. Consider adding C:\Users\tbm98\fvm\default\bin to the front of your path. ! Warning: dart on your path resolves to C:\Users\tbm98\fvm\versions\beta\bin\dart, which is not inside your current Flutter SDK checkout
at C:\Users\tbm98\fvm\default. Consider adding C:\Users\tbm98\fvm\default\bin to the front of your path. [√] Windows Version (Installed version of Windows is version 10 or higher) [√] Android toolchain - develop for Android devices (Android SDK version 33.0.2) [√] Chrome - develop for the web [√] Visual Studio - develop Windows apps (Visual Studio Community 2022 17.6.2) [√] Android Studio (version 2022.2) [√] IntelliJ IDEA Community Edition (version 2023.1) [√] VS Code (version 1.80.0) [√] Connected device (3 available) [√] Network resources

run on desktop window

rrousselGit commented 11 months ago

That's expected. You're missing dependencies on the second provider. Though you'll have to migrate the Stateprovider to codegen first.

Then you can write:

@Riverpod(dependencies: [firstProvider])
class Second extends _$Second {
  ...

  // Correctly points to the deepest override
  Object? read() => ref.read(firstProvider);
}

What's unexpected though is that you should see an assert error telling you precisely this. I'll make a separate issue.