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!
Your Widget is watching dashboardRepositoryProvider
, which doesn't update after the ref.refresh
call.
There's two things to consider:
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;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)"),
);
}