Frame Drop because of Charts in Home page
03:44 11 Aug 2026

I have a problem with the Home page, which is the main page the user is redirected to after authentication.

We need to make requests to five different routes. Three of these routes provide data for charts, which we then display on the Home page.

The charts are:

  • Cash Flow

  • Income and Expenses

  • Top Expenses

Here are the versions of the packages I am using:

flutter_riverpod: ^3.3.2
syncfusion_flutter_charts: ^34.1.33

Here are the charts and their corresponding providers:
home_page.dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hesaabdar_accountant_application/features/home/providers/home_api_provider.dart';
import 'package:hesaabdar_accountant_application/features/home/widgets/charts/cashFlow/cash_flow_chart.dart';
import 'package:hesaabdar_accountant_application/features/home/widgets/charts/cashFlow/cash_flow_chart_skeleton.dart';
import 'package:hesaabdar_accountant_application/features/home/widgets/charts/income/income_expense_chart_skeleton.dart';
import 'package:hesaabdar_accountant_application/features/home/widgets/charts/topExpense/top_expenses_chart_skeleton.dart';
import 'package:hesaabdar_accountant_application/features/home/widgets/error_card.dart';
import 'package:hesaabdar_accountant_application/features/home/widgets/charts/income/income_expense_chart.dart';
import 'package:hesaabdar_accountant_application/features/home/widgets/charts/topExpense/top_expenses_chart.dart';
import 'package:hesaabdar_accountant_application/features/notifications/providers/notifications_provider.dart';
import 'package:skeletonizer/skeletonizer.dart';
import 'package:hesaabdar_accountant_application/features/home/widgets/receivables_payables_card.dart';
import 'package:hesaabdar_accountant_application/l10n/app_localizations.dart';
import 'package:lucide_flutter/lucide_flutter.dart';

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

  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final l10n = AppLocalizations.of(context)!;
    final receivablesAsync = ref.watch(totalReceivablesProvider);
    final payablesAsync = ref.watch(totalPayablesProvider);
    final expensesAsync = ref.watch(topExpensesProvider);
    final incomeExpenseAsync = ref.watch(incomeExpenseProvider);
    final cashFlowAsync = ref.watch(cashFlowProvider);

    return RefreshIndicator(
      color: Theme.of(context).colorScheme.secondaryContainer,
      onRefresh: () async {
        await Future.wait([
          ref.read(totalReceivablesProvider.notifier).refresh(),
          ref.read(totalPayablesProvider.notifier).refresh(),
          ref.read(topExpensesProvider.notifier).refresh(),
          ref.read(incomeExpenseProvider.notifier).refresh(),
          ref.read(notificationsProvider.notifier).refresh(),
          ref.read(cashFlowProvider.notifier).refresh(),
        ]);
      },
      child: ListView(
        physics: const AlwaysScrollableScrollPhysics(),
        padding: const EdgeInsets.symmetric(vertical: 20),
        children: [
          receivablesAsync.when(
            data: (data) => ReceivablesPayablesCard(
              title: l10n.totalReceivables,
              indicatesTo: l10n.totalUnPaidInvoices,
              total: data.total,
              currentValue: data.current,
              currentOverDue: data.overdue,
              iconWidget: LucideIcons.arrowDownLeft,
            ),
            loading: () => Skeletonizer(
              enabled: true,
              child: ReceivablesPayablesCard(
                title: l10n.totalReceivables,
                indicatesTo: l10n.totalUnPaidInvoices,
                total: 1000000,
                currentValue: 400000,
                currentOverDue: 600000,
                iconWidget: LucideIcons.arrowDownLeft,
              ),
            ),
            error: (err, stack) => ErrorCard(
              message: err.toString(),
              onRetry: () =>
                  ref.read(totalReceivablesProvider.notifier).refresh(),
            ),
          ),
          const SizedBox(height: 20),
          payablesAsync.when(
            data: (data) => ReceivablesPayablesCard(
              title: l10n.totalPayables,
              indicatesTo: l10n.totalUnPaidBills,
              total: data.total,
              currentValue: data.current,
              currentOverDue: data.overdue,
              iconWidget: LucideIcons.arrowUpRight,
            ),
            loading: () => Skeletonizer(
              enabled: true,
              child: ReceivablesPayablesCard(
                title: l10n.totalPayables,
                indicatesTo: l10n.totalUnPaidBills,
                total: 1000000,
                currentValue: 700000,
                currentOverDue: 300000,
                iconWidget: LucideIcons.arrowUpRight,
              ),
            ),
            error: (err, stack) => ErrorCard(
              message: err.toString(),
              onRetry: () => ref.read(totalPayablesProvider.notifier).refresh(),
            ),
          ),
          const SizedBox(height: 20),
          cashFlowAsync.when(
            data: (data) => CashFlowChart(
              data: data,
              size: MediaQuery.of(context).size.height * 0.4,
            ),
            loading: () => CashFlowChartSkeleton(
              size: MediaQuery.of(context).size.height * 0.4,
            ),
            error: (err, stack) => ErrorCard(
              message: err.toString(),
              onRetry: () => ref.read(cashFlowProvider.notifier).refresh(),
            ),
          ),
          const SizedBox(height: 20),
          incomeExpenseAsync.when(
            data: (data) => IncomeExpenseChart(
              data: data,
              size: MediaQuery.of(context).size.height * 0.45,
            ),
            loading: () => IncomeExpenseChartSkeleton(
              size: MediaQuery.of(context).size.height * 0.45,
            ),
            error: (err, stack) => ErrorCard(
              message: err.toString(),
              onRetry: () => ref.read(incomeExpenseProvider.notifier).refresh(),
            ),
          ),
          const SizedBox(height: 20),
          expensesAsync.when(
            data: (data) => TopExpensesChart(
              data: data,
              title: l10n.topExpense,
              size: MediaQuery.of(context).size.height * 0.4,
            ),
            error: (err, stack) => ErrorCard(
              message: err.toString(),
              onRetry: () => ref.invalidate(topExpensesProvider),
            ),
            loading: () => TopExpensesChartSkeleton(
              size: MediaQuery.of(context).size.height * 0.4,
            ),
          ),    
        ],
      ),
    );
  }
}

home_api_provider.dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:hesaabdar_accountant_application/core/providers/provider.dart';
import 'package:hesaabdar_accountant_application/features/home/api/home_api.dart';
import 'package:hesaabdar_accountant_application/features/home/models/cash_flow_filter.dart';
import 'package:hesaabdar_accountant_application/features/home/models/cash_flow_model.dart';
import 'package:hesaabdar_accountant_application/features/home/models/expense_filter.dart';
import 'package:hesaabdar_accountant_application/features/home/models/income_expense_filter.dart';
import 'package:hesaabdar_accountant_application/features/home/models/income_expense_model.dart';
import 'package:hesaabdar_accountant_application/features/home/models/receivables_payables_model.dart';
import 'package:hesaabdar_accountant_application/features/home/models/top_expense_model.dart';

final homeApiProvider = Provider((ref) {
  return HomeApi(apiService: ref.watch(apiServiceProvider));
});

class TotalReceivablesNotifier extends AsyncNotifier {
  @override
  Future build() async {
    return ref.read(homeApiProvider).getTotalReceivables();
  }

  Future refresh() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(
      () => ref.read(homeApiProvider).getTotalReceivables(),
    );
  }
}

final totalReceivablesProvider =
    AsyncNotifierProvider(
      TotalReceivablesNotifier.new,
    );

class TotalPayablesNotifier extends AsyncNotifier {
  @override
  Future build() async {
    return ref.read(homeApiProvider).getTotalPayables();
  }

  Future refresh() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(
      () => ref.read(homeApiProvider).getTotalPayables(),
    );
  }
}

final totalPayablesProvider =
    AsyncNotifierProvider(
      TotalPayablesNotifier.new,
    );

class ExpenseFilterNotifier extends Notifier {
  @override
  ExpenseFilter build() => ExpenseFilter.thisFiscalYear;

  void setFilter(ExpenseFilter filter) {
    state = filter;
  }
}

final expenseFilterProvider =
    NotifierProvider(
      ExpenseFilterNotifier.new,
    );

class TopExpensesNotifier extends AsyncNotifier {
  @override
  Future build() async {
    final filter = ref.watch(expenseFilterProvider);
    return ref.read(homeApiProvider).getTopExpenses(filter: filter);
  }

  Future refresh() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(
      () => ref
          .read(homeApiProvider)
          .getTopExpenses(filter: ref.read(expenseFilterProvider)),
    );
  }
}

final topExpensesProvider =
    AsyncNotifierProvider(
      TopExpensesNotifier.new,
    );

class IncomeExpenseFilterNotifier extends Notifier {
  @override
  IncomeExpenseFilter build() => IncomeExpenseFilter.thisFiscalYear;

  void setFilter(IncomeExpenseFilter filter) {
    state = filter;
  }
}

final incomeExpenseFilterProvider =
    NotifierProvider(
      IncomeExpenseFilterNotifier.new,
    );

class AccountingBasisNotifier extends Notifier {
  @override
  AccountingBasis build() => AccountingBasis.accrual;

  void setBasis(AccountingBasis basis) {
    state = basis;
  }
}

final accountingBasisProvider =
    NotifierProvider(
      AccountingBasisNotifier.new,
    );

class IncomeExpenseNotifier extends AsyncNotifier {
  @override
  Future build() async {
    final filter = ref.watch(incomeExpenseFilterProvider);
    final basis = ref.watch(accountingBasisProvider);
    return ref
        .read(homeApiProvider)
        .getIncomeAndExpense(filter: filter, basis: basis);
  }

  Future refresh() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(
      () => ref
          .read(homeApiProvider)
          .getIncomeAndExpense(
            filter: ref.read(incomeExpenseFilterProvider),
            basis: ref.read(accountingBasisProvider),
          ),
    );
  }
}

final incomeExpenseProvider =
    AsyncNotifierProvider(
      IncomeExpenseNotifier.new,
    );

// Remembers which time-period filter is picked for cash flow.
class CashFlowFilterNotifier extends Notifier {
  @override
  CashFlowFilter build() => CashFlowFilter.thisFiscalYear;

  void setFilter(CashFlowFilter filter) {
    state = filter;
  }
}

final cashFlowFilterProvider =
    NotifierProvider(
      CashFlowFilterNotifier.new,
    );

// Watches the filter above — changing it automatically re-fetches.
class CashFlowNotifier extends AsyncNotifier {
  @override
  Future build() async {
    final filter = ref.watch(cashFlowFilterProvider);
    return ref.read(homeApiProvider).getCashFlow(filter: filter);
  }

  Future refresh() async {
    state = const AsyncLoading();
    state = await AsyncValue.guard(
      () => ref
          .read(homeApiProvider)
          .getCashFlow(filter: ref.read(cashFlowFilterProvider)),
    );
  }
}

final cashFlowProvider = AsyncNotifierProvider(
  CashFlowNotifier.new,
);

I cannot put the charts in here because of the limitations of message.

flutter dart