I am trying to generate and display subtasks in Flutter using the Riverpod package. I have a SubtaskNotifier which extends StateNotifier with an AsyncValue<List> state. The SubtaskNotifier has a generateSubtasks method which updates its state with a FutureProvider that generates the subtasks.But its not working properly i am able to get the data but i am not seeing the loading state.What i want to achieve is i want to show a button which triggers a generate subtasks and show loading if there is data hide the button and display the data
final generateGoalTaskSubtasksControllerProvider =
FutureProvider.family<List<String>, GoalTask>((ref, task) async {
final chatAPIController = ref.read(chatAPIControllerProvider.notifier);
final subtasks =
await chatAPIController.generateGoalTaskSubtasksController(task);
ref.read(goalTaskSubtasksListProvider.notifier).updateTasks(subtasks);
return subtasks;
});
Here's the SubtaskNotifier:
class SubtaskNotifier extends StateNotifier<AsyncValue<List<String>>> {
final GoalTask goalTask;
final ProviderContainer container;
SubtaskNotifier(this.container, this.goalTask) : super(AsyncValue.data([]));
void generateSubtasks() {
state =
container.read(generateGoalTaskSubtasksControllerProvider(goalTask));
}
}
I am trying to create a SubtaskNotifier instance using StateNotifierProvider.family:
final subtaskNotifierProvider = StateNotifierProvider.family<SubtaskNotifier,
AsyncValue<List<String>>, GoalTask>((ref, task) {
return SubtaskNotifier(ref.container, task);
});
I want to display these subtasks in a widget (SubtaskGenerator) and I want to trigger the generation of subtasks when a button ("Generate Subtasks") is pressed.
Here's the SubtaskGenerator:
class SubtaskGenerator extends ConsumerWidget {
final GoalTask goalTask;
const SubtaskGenerator({Key? key, required this.goalTask}) : super(key: key);
@override
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.read(themeProvider);
final subtaskNotifier =
ref.watch(subtaskNotifierProvider(goalTask).notifier);
return subtaskNotifier.state.when(
data: (subtasks) {
return Column(
children: [
if (subtasks.isEmpty)
Center(
child: TextButton(
child: Text('Generate Subtasks'),
onPressed: () {
subtaskNotifier.generateSubtasks();
},
),
),
for (String subtask in subtasks)
ListTile(
title: Text(subtask),
trailing: IconButton(
icon: Icon(
Icons.delete,
color: theme.colorScheme.onError,
),
onPressed: () {},
),
),
if (subtasks.isEmpty)
Text(
"No subtasks Available",
style: GoogleFonts.poppins(color: theme.colorScheme.onTertiary),
),
],
);
},
error: (error, stacktrace) {
return const Center(child: Text("Error occurred"));
},
loading: () {
return Center(child: AnimatedEmojiLoadingIndicator());
},
);
}
}
You are probably missing this:
void generateSubtasks() {
state = const AsyncValue.loading();
final tasksFuture = container.read(generateGoalTaskSubtasksControllerProvider(goalTask).future);
state = await AsyncValue.guard(() => tasksFuture);
}
Also, additionally use ref.watch(provider)
in the build
method, otherwise your widget will not be rebuilt:
Widget build(BuildContext context, WidgetRef ref) {
final theme = ref.watch(themeProvider);
final subtaskNotifier = ref.watch(subtaskNotifierProvider(goalTask));
...
onPressed: () {
ref.read(subtaskNotifierProvider(goalTask).notifier)
.generateSubtasks();
},
For convenience, define a common field:
var subtaskProvider = subtaskNotifierProvider(goalTask);
final subtaskNotifier = ref.watch(subtaskProvider);
...
onPressed: () {
ref.read(subtaskProvider.notifier).generateSubtasks();
},