Angelina Gromova
Angelina Gromova

Reputation: 201

Riverpod: StateProvider is not updated

I have 2 providers. The one that deals with selected filters:

final filtersProvider = StateProvider.autoDispose<List<String>>(
  (ref) => [],
);

And the other that receives data. It's observes the filters value.

final priceResponseProvider =
    FutureProvider.autoDispose.family<PriceResponse, GetPriceParams>(
  (ref, params) {

    // This should update [priceResponseProvider] 
    // every time when [filtersProvider] changes.
    // But it does not 
    final filters = ref.watch(filtersProvider);
    return ref
        .read(priceFetcherProvider)
        .search(params.copyWith(filterBrands: filters));
  },
);

Besides I have 2 screens.

  1. PricePage is responsible for presenting results.
class PricePage extends ConsumerWidget {
  const PricePage({required this.params, super.key});

  static void navigate(
    BuildContext context, {
    required GetPriceParams params,
  }) =>
      context.pushNamed(name, extra: params);

  final GetPriceParams params;
  static const name = 'price_page';
  static const path = '/price_page';


  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final searchResult = ref.watch(priceResponseProvider(params));

    return Scaffold(
      body: searchResult.when(
        loading: () => const LoadingWidget(),
        error: (e, __) => Text((e as CustomException).message ?? ''),
        data: (PriceResponse resp) {

          final titles = resp.result.map((e) => e.caption).toList();
          final items = ref.watch(priceGroupsProvider(resp));

          return Scaffold(
            appBar: AppBarWidget(
              title: _title,
              image: AppImages.filter,
              onTap: () async {
                // Navigates to PriceFiltersPage and returns selected filters
                final filters = await PriceFiltersPage.navigate(
                  context,
                  model: PriceFiltersPageModel(
                    brands: resp.brands.map((e) => e.brand).toList(),
                    initialSelected: ref.read(filtersProvider),
                  ),
                );

                if (filters == null) return;

                // Updates provider with selected filters
                ref.read(filtersProvider.notifier).state = filters;
              },
            ),
            body: PriceListView(items: items)
          );
        },
      ),
    );
  }
}
  1. PriceFiltersPage is responsible for selecting results.

class PriceFiltersPageModel {
  PriceFiltersPageModel({
    required this.brands,
    required this.initialSelected,
  });

  final List<String> brands;
  final List<String> initialSelected;
}

class PriceFiltersPage extends StatefulWidget {
  const PriceFiltersPage({required this.model, super.key});

  static const String name = 'price_filters';
  static const String path = '/price_filters';
  final PriceFiltersPageModel model;

  static Future<List<String>?> navigate(
    BuildContext context, {
    required PriceFiltersPageModel model,
  }) async =>
      context.pushNamed(name, extra: model);

  @override
  State<PriceFiltersPage> createState() => _PriceFiltersPageState();
}

class _PriceFiltersPageState extends State<PriceFiltersPage> {
  late List<String> selected = widget.model.initialSelected;
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBarWidget(
        title: 'Фильтры',
        stageText: 'Сбросить',
        onLeadingButtonTap: () {},
      ),
      body: Padding(
        padding: AppDecoration.padding,
        child: SafeArea(
          child: Stack(
            children: [
              SingleChildScrollView(
                child: Padding(
                  padding: const EdgeInsets.only(top: 24, bottom: 90),
                  child: Column(
                    children: [
                      _TitleWidget(
                        title: 'Бренд',
                        onResetTap: () {},
                      ),
                      const SizedBox(height: 12),
                      FiltersChoiceChipWidget(
                        titles: widget.model.brands,
                        initialSelectedValues: widget.model.initialSelected,
                        onSelectedValues: (values) {
                          selected = values;
                        },
                      ),
                    ],
                  ),
                ),
              ),
              // КНОПКА ПРИМЕНИТЬ
              Positioned(
                left: 16,
                right: 16,
                bottom: 20,
                child: MainButton(
                  title: 'Применить',
                  onTap: () {

                    // returns selected filters

                    context.pop(selected);
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

I want to update priceResponseProvider every time when filtersProvider changes. However for some reason it does not executes when I change value filtersProvider.

I tried to to assign filtersProvider inside PriceFiltersPage, the same result. I also tries to listen to filtersProvider updates inside the PricePage but the listener does not executes.

Appreciate any help

Upvotes: 0

Views: 66

Answers (1)

Dhafin Rayhan
Dhafin Rayhan

Reputation: 8529

You passed the result of ref.read(filtersProvider) to PriceFiltersPage, which is a List<String>, and the page return the same List even though the elements might have been modified. Riverpod doesn't know those changes since the List refers to the same List that the provider previously has. Although this might not be the best practice, but as a quick fix you can pass your list to the page like this:

final filters = await PriceFiltersPage.navigate(
  context,
  model: PriceFiltersPageModel(
    brands: resp.brands.map((e) => e.brand).toList(),
    initialSelected: [...ref.read(filtersProvider)],
  ),
);

Please see the StateNotifierProvider documentation for the good practice on how to handle list in a provider.

Upvotes: 1

Related Questions