Cengiz Gumus
Cengiz Gumus

Reputation: 63

Unexpected dispose behavior in riverpod Providers with nested watch methods

I have a problem with Riverpod providers. They can be automatically disposed of when we watch the state and notifier of another provider within a provider, even if it is a non-disposable provider.

More detail: https://github.com/rrousselGit/riverpod/issues/3592

The test code, you can test on dartpad.dev please focus only on the console output and the home controller ID on the UI. The counter not working is normal.

import 'dart:math';

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

void main() {
  runApp(
    ProviderScope(
      observers: [Logger()],
      child: const MyApp(),
    ),
  );
}

class Logger extends ProviderObserver {
  @override
  void didAddProvider(
    ProviderBase<dynamic> provider,
    Object? value,
    ProviderContainer container,
  ) {
    print('Added [${provider.name ?? provider.runtimeType}] value: $value');
  }

  @override
  void didDisposeProvider(
    ProviderBase<dynamic> provider,
    ProviderContainer container,
  ) {
    print('Disposed [${provider.name ?? provider.runtimeType}]');
  }
}

class HomeControllerState {
  HomeControllerState({required this.id});
  final int id;
}

class HomeController extends StateNotifier<HomeControllerState> {
  HomeController(this.ref)
      : super(HomeControllerState(id: Random().nextInt(1000)));
  late final StateNotifierProviderRef<HomeController, HomeControllerState> ref;

  @override
  HomeControllerState build() {
    return HomeControllerState(id: Random().nextInt(1000));
  }

  void watch() {
    ref.watch(testProvider);
  }

  void increment() {
    ref.watch(testProvider.notifier).increment();
  }
}

final homeControllerProvider =
    StateNotifierProvider<HomeController, HomeControllerState>((ref) {
  return HomeController(ref);
});

class TestState {
  TestState({this.count = 0});
  final int count;

  TestState copyWith({required int count}) {
    return TestState(count: count);
  }
}

class TestController extends Notifier<TestState> {
  @override
  TestState build() {
    return TestState();
  }

  void increment() {
    state = state.copyWith(count: state.count + 1);
  }
}

final testProvider =
    NotifierProvider<TestController, TestState>(TestController.new);

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData(
        colorSchemeSeed: Colors.blue,
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends ConsumerWidget {
  const MyHomePage({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final homeControllerState = ref.watch(homeControllerProvider);
    final homeController = ref.watch(homeControllerProvider.notifier);
    //final testState = ref.watch(testProvider);

    return Scaffold(
      appBar: AppBar(
        title: const Text('Riverpod Example with Nested Providers'),
      ),
      body: Center(
        child: Column(
          mainAxisSize: MainAxisSize.min,
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Text('HomeController ID: ${homeControllerState.id}'),
            const SizedBox(height: 20),
            const Text('TestController State:'),
            Text(
              '000',
              style: Theme.of(context).textTheme.headlineMedium,
            ),
            ElevatedButton(
                onPressed: () {
                  homeController.watch();
                },
                child: Text('watch')),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () => homeController.increment(),
        tooltip: 'Increment',
        child: const Icon(Icons.add),
      ),
    );
  }
}

Upvotes: 0

Views: 98

Answers (1)

Cengiz Gumus
Cengiz Gumus

Reputation: 63

Now I fixed and understood This is not a bug; it's Riverpod's intended behavior.

It's very important to understand that if you call watch in a provider and the watched provider changes, the provider will also be recreated.

In this situation, we should use listen.

Am I right?

Upvotes: 0

Related Questions