flutterdartriverpodflutter-stateflutter-riverpod

How to re-render a Widget based on another widget using riverpod in flutter?


I want to know how can I refresh a table data (which is fetched from an API using a future provider) and re-render the table widget based on dropdown value change.

Following is the Repo file with providers:

import 'package:ct_analyst_app/src/features/dashboard/domain/dashboard_data.dart';
import 'package:dio/dio.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';

import '../../authentication/application/auth_local_service.dart';

abstract class IDashboardRepository {
  Future<void> fetchDashboard(String name);
  Future<void> fetchNames();
}

final clientProvider = Provider.family((ref, token) => Dio(BaseOptions(
    baseUrl: "http://${dotenv.env['IP']}/excel/",
    headers: {"authorization": token})));

class DashboardRepository implements IDashboardRepository {
  DashboardRepository(this.read);

  final Reader read;

  DashboardData? _data;
  DashboardData? get dashboardData => _data;

  List<dynamic>? _names;
  List<dynamic>? get names => _names;

  @override
  Future<DashboardData?> fetchDashboard(String name) async {
    final token = await read(authServiceProvider).getToken();

    final response = await read(clientProvider(token))
        .get('/getData', queryParameters: {"name": name});

    _data = DashboardData.fromJson(response.data);
    print(name);
    return _data;
  }

  @override
  Future<void> fetchNames() async {
    final token = await read(authServiceProvider).getToken();
    final response = await read(clientProvider(token)).get('/analystNames');

    _names = response.data["names"];
  }
}

final dashboardRepositoryProvider =
    Provider((ref) => DashboardRepository(ref.read));

final fetchDashboardData = FutureProvider.family<void, String>((ref, name) {
  final repoProvider = ref.watch(dashboardRepositoryProvider);
  return repoProvider.fetchDashboard(name);
});

final fetchAnalystNames = FutureProvider((ref) {
  final repoProvider = ref.watch(dashboardRepositoryProvider);
  return repoProvider.fetchNames();
});

I have tried to refresh the future provider in the dropdown onChange and it does fetch the new table data from the API. However, the widget which renders the data in the table is not getting re-rendered when the refresh is called.

Done as following:

 onChanged: (String? newValue) {
        ref.read(dropItemProvider.notifier).state = newValue as String;
        ref.refresh(fetchDashboardData(newValue));
        setState(() {
          widget.value = newValue;
        });
      },

I am using ref.watch on the data, still it does not re-render the widget if the data is changed.

class TableGenerator extends ConsumerWidget {
  const TableGenerator({Key? key}) : super(key: key);

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

    return data.dashboardData != null
        ? SingleChildScrollView(
            scrollDirection: Axis.vertical,
            child: Row(
              children: [
                const FixedColumnWidget(data: [
                  "one",
                  "two",
                  "three",
                  "four",
                  "fifth",
                ]),
                ScrollableColumnWidget(
                    data: data.dashboardData as DashboardData)
              ],
            ))
        : const CircularProgressIndicator();
  }
}

Am I missing something or how should I approach this problem? like different providers or something else?

Thanks!


Solution

  • Your Widget is watching dashboardRepositoryProvider, which doesn't update after the ref.refresh call.

    There's two things to consider:

    1. dashboardRepository should just expose your repo / services, and instead it is used to observe actual data. This is not affecting your app directly, but it is part of the problem imho. I'd expect your Widget to observe a FutureProvider that exposes (and caches, etc.) the data by calling the methods inside your repository;
    2. Then, let's analyze why your Widget isn't updating: dashboardRepository isn't depending, i.e. performing a watch, on the Provider you're refreshing, which is fetchDashboardData, nor it is depending on dropItemProvider (I am specifying this since your onChanged callback updates / refreshes two different Providers).

    I think your should refactor your code so that it will expose actual data from a FutureProvider which exploits your repositories and can be simply refreshed similarly as what you already are doing.

    Quick FutureProvider example:

    // WARNING: PSEUDOCODE
    
    final myDataProvider = FutureProvider<MyClass>((ref) {
      final repo = ref.watch(myRepo);
    
      final response = repo.getSomeData(...);
      // TODO: add error handling, debouncing, cancel tokens, etc.
    
      return MyClass.fromJson(response.data);  // e.g.
    });
    

    Quick usage:

    // WARNING: PSEUDOCODE
    
    @override
    Widget build(BuildContext context, WidgetRef ref) {
      final myData = ref.watch(myDataProvider);
    
      return ElevatedButton(
        onTap: () {
          ref.invalidate(myDataProvider);
        },
        child: Text("Click me to refresh me (data: $myData)"),
      );
    }