skip to Main Content

I’m using Riverpod in my Flutter application, and I’ve run into an issue where my ConsumerWidget isn’t rebuilding when changes occur in a nested provider.

I have a BankrollListScreen which watches the bankrollListControllerProvider. Inside the build method, I loop through a list of Bankroll objects and for each Bankroll, I want to display its total which I calculate using the bankrollTotalProvider.

// this is simplified version of my code
class BankrollListScreen extends ConsumerWidget {
  const BankrollListScreen({super.key});

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    AsyncValue<List<Bankroll>> bankrollListValue =
        ref.watch(bankrollListControllerProvider);

    return bankrollListValue.when(
      data: (bankrollList) => ListView(
        children: [
          for (var bankroll in bankrollList)
            GameSessionListTile(
              bankrollTotal: Text(
                ref.watch(
                  bankrollTotalProvider(bankroll.id),
                ).toString(),
              ),
              onTap: () {
                context.pushNamed(
                  AppRoute.gameSessionList.name,
                  pathParameters: {
                    'id': bankroll.id,
                    'bankrollName': bankroll.name!
                  },
                );
              },
            ),
        ],
      ),
      // ... [Consider handling other cases for the AsyncValue]
    );
  }
}

Here’s how I calculate the bankrollTotal:

@riverpod
Future<double> bankrollTotal(
  BankrollTotalRef ref,
  String bankrollId,
) async {
  final gameSessionList = await ref
      .watch(gameSessionRepositoryProvider)
      .getGameSessionListForBankroll(bankrollId);

  final depositList = await ref
      .watch(depositRepositoryProvider)
      .getDepositListForBankroll(bankrollId);

  final gameSessionTotal = gameSessionList.fold(
      0.0, (sum, session) => sum + (session.cashout ?? 0.0));

  final depositTotal =
      depositList.fold(0.0, (sum, deposit) => sum + (deposit.amount));

  final totalBankroll = gameSessionTotal + depositTotal;
  return totalBankroll;
}

The problem I’m facing is when bankrollTotalProvider changes, it doesn’t invoke the build method of the BankrollListScreen as it’s only being watched by bankrollListControllerProvider.

How can I make my BankrollListScreen rebuild when there are any changes in bankrollTotalProvider?


Possible solution?

I believe I’ve found a solution.

The ref.watch will invoke the build method when the state of a provider changes. The issue with my code is that the state of bankrollTotalProvider doesn’t alter unless I intervene manually.

Therefore, in this scenario, instead of monitoring the provider, we should create another widget that triggers the method manually.

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:today_poker/features/statistics/service/bankroll_stat_service.dart';

class BankrollTotalTextWidget extends ConsumerWidget {
  final String bankrollId;

  const BankrollTotalTextWidget({super.key, required this.bankrollId});

  Future<String> fetchBankrollTotal(WidgetRef ref) async {
    final value = await ref
        .watch(bankrollStatServiceProvider(bankrollId).notifier)
        .getBankrollTotal(bankrollId);
    return value.toString();
  }

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    Future<String> bankrollTotalFuture = fetchBankrollTotal(ref);
    return FutureBuilder<String>(
      future: bankrollTotalFuture,
      builder: (context, snapshot) {
        if (snapshot.connectionState == ConnectionState.done) {
          if (snapshot.hasData) {
            return Text(snapshot.data!);
          } else if (snapshot.hasError) {
            print(snapshot.error);
            return Text("Error: ${snapshot.error}");
          }
        }
        // While data is loading:
        return const CircularProgressIndicator();
      },
    );
  }
}

Then, I pass BankrollTotalTextWidget to bankrollTotal.

bankrollTotal: BankrollTotalTextWidget(bankroll.id),

2

Answers


  1. First, by reading your business logic (to which I’m basically unaware of) it’s weird to see that bankrollListControllerProvider and bankrollTotalProvider are unrelated. I would expected a reactive dependency, such that bankrollTotalProvider watches bankrollListControllerProvider to compute its thing. But I see that’s not the case.

    Anyways…

    The problem I’m facing is when bankrollTotalProvider changes, it doesn’t invoke the build method of the BankrollListScreen as it’s only being watched by bankrollListControllerProvider.

    Something’s wrong here. I’d suggest giving your list elements a key (e.g. based on bankroll.id) so that you can better debug why and how those elements are replaced by the framework.

    You’ve correctly registered bankrollTotalProvider(id) as a dependency for BankrollListScreen, therefore this should definitely work.

    A note on your "possible solution"

    Have you tried to just use final total = ref.watch(bankrollTotalProvider(id)) in that BankrollTotalTextWidget, instead? It basically does the same thing, except you won’t use a FutureProvider, but the usual AsyncValue instead.

    If that works, it means you’ve encountered a classic Flutter-ish "builder" problem. See the docs.

    Login or Signup to reply.
  2. You seem to be updating the state of your bankrollListControllerProvider provider incorrectly. Try using List.of on update, or use the spread operator like this:

    newState = ...;
    
    state = List.of(newState);
    // or
    
    state = [...newState];
    

    This will indicate to your bankrollListControllerProvider that its state has indeed been updated. All collections should use this state update approach. With simple types, you can do a simple assignment state = newState;

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search