flutter Riverpod删除的小部件仍在调试模式下执行族提供程序选择器

tzxcd3kk  于 2023-03-13  发布在  Flutter
关注(0)|答案(1)|浏览(130)

我的状态包含一个用户可以修改的列表,向列表添加新项是可以的,但是当你从列表中删除项时,小部件仍然在执行FamilyProvider选择器,这会失败,因为它要查找的项已经被删除了。
然而,这似乎只发生在Windows和Chrome的调试版本中。在发布模式下,我无法重现它。在dartpad.dev上,我也无法重现它。
要重现此问题,请单击任意按钮将其删除,并注意Error: Bad state: No element
完整示例:

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

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

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

  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

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

  final String title;

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final buttons = ref.watch(appStateProvider).buttons;

    print('Rebuild HomePage with $buttons');

    return Scaffold(
      appBar: AppBar(
        title: Text(title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            const Text('Click on any of the buttons to remove the widget'),
            for (final btn in buttons) _ButtonWidget(id: btn.id),
          ],
        ),
      ),
    );
  }
}

class _ButtonWidget extends ConsumerWidget {
  final int id;

  _ButtonWidget({
    required this.id,
  });

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    print('Rebuild ButtonWidget $id');

    // this one is still being executed, even if the widget is removed.
    final buttonState = ref.watch(buttonStateFamilyProvider(id));

    return ElevatedButton(
      onPressed: () {
        final appStateController = ref.read(appStateProvider.notifier);
        final newList = appStateController.state.buttons.toList()
          ..removeWhere((btn) => btn.id == id);

        appStateController.state = AppState(newList);
      },
      style: ElevatedButton.styleFrom(backgroundColor: Colors.blueGrey),
      child: Text(buttonState.name),
    );
  }
}

final appStateProvider = StateProvider<AppState>((ref) {
  return const AppState([
    ButtonState(1, 'Button 1'),
    ButtonState(2, 'Button 2'),
  ]);
});

final buttonStateFamilyProvider = Provider.family.autoDispose<ButtonState, int>(
  (ref, id) {
    ref.onCancel(() {
      print('cancelled buttonStateFamilyProvider($id)');
    });

    print('Executing buttonStateFamilyProvider($id)');

    final result = ref.watch(
      appStateProvider.select(
        (state) => state.buttons.firstWhere(
          (btn) => btn.id == id,
        ),
      ),
    );

    print('buttonStateFamilyProvider($id) = $result');

    return result;
  },
);

class AppState with EquatableMixin {
  final List<ButtonState> buttons;

  const AppState(this.buttons);

  @override
  List<Object?> get props => [buttons];
}

class ButtonState with EquatableMixin {
  final String name;
  final int id;

  const ButtonState(this.id, this.name);

  @override
  List<Object?> get props => [name];
}

pubspec.yaml

name: riverpod_widget_rebuild
description: A new Flutter project.

publish_to: 'none' 

version: 1.0.0+1

environment:
  sdk: '>=2.18.5 <3.0.0'

dependencies:
  flutter:
    sdk: flutter

  flutter_riverpod: ^2.3.0
  cupertino_icons: ^1.0.2
  equatable: ^2.0.5

dev_dependencies:
  flutter_test:
    sdk: flutter

  flutter_lints: ^2.0.0

flutter:
  uses-material-design: true
rxztt3cl

rxztt3cl1#

正如雷米所建议的:你可以作弊:

state.buttons.firstWhere(..., orElse: () => ref.state)

如果在删除ID时重新生成提供程序,则不会引发此错误。
或者,您可以使提供程序可为空。

final buttonStateFamilyProvider =
    Provider.family.autoDispose<ButtonState?, int>(
  (ref, id) {
    print('Executing buttonStateFamilyProvider($id)');

    final result = ref.watch(
      appStateProvider.select(
        (state) => state.buttons.firstWhereOrNull(
          (btn) => btn.id == id,
        ),
      ),
    );

    print('buttonStateFamilyProvider($id) = $result');

    return result;
  },
);

在小部件中添加'!',因为它不能为空。

@override
  Widget build(BuildContext context, WidgetRef ref) {
    print('Rebuild ButtonWidget $id');

    // Added '!' since this cannot return null if the widget exists. 
    final buttonState = ref.watch(buttonStateFamilyProvider(id))!;

    return ElevatedButton(
      onPressed: () {
        final appStateController = ref.read(appStateProvider.notifier);
        final newList = appStateController.state.buttons.toList()
          ..removeWhere((btn) => btn.id == id);

        appStateController.state = AppState(newList);
      },
      style: ElevatedButton.styleFrom(backgroundColor: Colors.blueGrey),
      child: Text(buttonState.name),
    );
  }

相关问题