Search code examples
flutterriverpod

Not able to generate and display subtasks using StateNotifier and StateNotifierProvider


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());
      },
    );
  }
}

Solution

  • 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();
      },