admin管理员组

文章数量:1122832

I have a simple app that consists of checkboxes and a progress indicator. The checkbox widget is stateful as is the progress indicator.

Is there a way to allow my progress indicator to update based on a button click without resorting to something like Riverpod?

I have a number of features (2 in this reduced sample) in a magazine that are check boxes. I have a progress indicator that moves based on the number of checkboxes.

I have tried changing the key on the progress indicator.

Here is the code attempting to do it with globals. I would prefer not to use globals either.

This code is from a Flutter example and they solved the problem with Riverpod. Smart-alack that I am, I thought for sure there had to be an easier way using just Flutter/Dart standard stuff.

import 'package:flutter/material.dart';

int numberOfClicks = 0;
List<Widget> tasks = [
  const TaskItem(label: 'Embedded in Thin Slices'),
  const TasbkItem(label: 'Technology Features'),
  const TaskItem(label: 'Datasheets'),
];
double percentage = 0.0;

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Circuit Cellar!',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Circuit Cellar Reading Planner!'),
      ),
      body: const Column(
        children: [
//          Progress(percent: percentage),
          Progress(),
          TaskList(),
        ],
      ),
    );
  }
}

class Progress extends StatefulWidget {
  const Progress({super.key});
  @override
  State<Progress> createState() => _ProgressState();
}

class _ProgressState extends State<Progress> {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('Reading Progress:'),
        LinearProgressIndicator(value: percentage),
      ],
    );
  }
}

class TaskList extends StatelessWidget {
  const TaskList({super.key});
  @override
  Widget build(BuildContext context) {
    return Column(children: tasks);
  }
}

class TaskItem extends StatefulWidget {
  final String label;

  const TaskItem({super.key, required this.label});

  @override
  TaskItemState createState() => TaskItemState();
}

class TaskItemState extends State<TaskItem> {
  bool? _value = false;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Checkbox(
          onChanged: (newValue) {
            setState(() {
              _value = newValue;
              // ! checks for null
              if (_value!) {
                numberOfClicks++;
              } else {
                numberOfClicks--;
              }
              percentage = numberOfClicks / tasks.length;
            });
          },
          value: _value,
        ),
        Text(widget.label),
      ],
    );
  }
}

I have a simple app that consists of checkboxes and a progress indicator. The checkbox widget is stateful as is the progress indicator.

Is there a way to allow my progress indicator to update based on a button click without resorting to something like Riverpod?

I have a number of features (2 in this reduced sample) in a magazine that are check boxes. I have a progress indicator that moves based on the number of checkboxes.

I have tried changing the key on the progress indicator.

Here is the code attempting to do it with globals. I would prefer not to use globals either.

This code is from a Flutter example and they solved the problem with Riverpod. Smart-alack that I am, I thought for sure there had to be an easier way using just Flutter/Dart standard stuff.

import 'package:flutter/material.dart';

int numberOfClicks = 0;
List<Widget> tasks = [
  const TaskItem(label: 'Embedded in Thin Slices'),
  const TasbkItem(label: 'Technology Features'),
  const TaskItem(label: 'Datasheets'),
];
double percentage = 0.0;

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Circuit Cellar!',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Circuit Cellar Reading Planner!'),
      ),
      body: const Column(
        children: [
//          Progress(percent: percentage),
          Progress(),
          TaskList(),
        ],
      ),
    );
  }
}

class Progress extends StatefulWidget {
  const Progress({super.key});
  @override
  State<Progress> createState() => _ProgressState();
}

class _ProgressState extends State<Progress> {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const Text('Reading Progress:'),
        LinearProgressIndicator(value: percentage),
      ],
    );
  }
}

class TaskList extends StatelessWidget {
  const TaskList({super.key});
  @override
  Widget build(BuildContext context) {
    return Column(children: tasks);
  }
}

class TaskItem extends StatefulWidget {
  final String label;

  const TaskItem({super.key, required this.label});

  @override
  TaskItemState createState() => TaskItemState();
}

class TaskItemState extends State<TaskItem> {
  bool? _value = false;

  @override
  Widget build(BuildContext context) {
    return Row(
      children: [
        Checkbox(
          onChanged: (newValue) {
            setState(() {
              _value = newValue;
              // ! checks for null
              if (_value!) {
                numberOfClicks++;
              } else {
                numberOfClicks--;
              }
              percentage = numberOfClicks / tasks.length;
            });
          },
          value: _value,
        ),
        Text(widget.label),
      ],
    );
  }
}
Share Improve this question asked Nov 23, 2024 at 6:21 rjapengarjapenga 10712 bronze badges 1
  • ValueNotifier, callback, possible duplicate of How to pass data from a child Stateful widget to Parent Widget in Flutter – Md. Yeasin Sheikh Commented Nov 23, 2024 at 7:40
Add a comment  | 

1 Answer 1

Reset to default 2

To solve your problem without Riverpod or global variables, you can use InheritedWidget, which is built into Flutter. It helps share state between widgets cleanly.

Get rid of globals, No numberOfClicks or percentage. Add InheritedWidget, It’ll track completed tasks and let both Progress and TaskItem widgets update state dynamically. Keep everything local, The Progress bar updates automatically when a checkbox is toggled.

Here’s the updated code:

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Circuit Cellar!',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const MyHomePage(),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key});

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int completedTasks = 0;

  void updateProgress(bool isChecked) {
    setState(() {
      completedTasks += isChecked ? 1 : -1;
    });
  }

  @override
  Widget build(BuildContext context) {
    return TaskInherited(
      completedTasks: completedTasks,
      totalTasks: 3,
      updateProgress: updateProgress,
      child: Scaffold(
        appBar: AppBar(
          title: const Text('Circuit Cellar Reading Planner!'),
        ),
        body: const Column(
          children: [
            Progress(),
            TaskList(),
          ],
        ),
      ),
    );
  }
}

class TaskInherited extends InheritedWidget {
  final int completedTasks;
  final int totalTasks;
  final void Function(bool) updateProgress;

  const TaskInherited({
    super.key,
    required this.completedTasks,
    required this.totalTasks,
    required this.updateProgress,
    required Widget child,
  }) : super(child: child);

  static TaskInherited of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<TaskInherited>()!;
  }

  @override
  bool updateShouldNotify(TaskInherited oldWidget) {
    return completedTasks != oldWidget.completedTasks;
  }
}

class Progress extends StatelessWidget {
  const Progress({super.key});

  @override
  Widget build(BuildContext context) {
    final inheritedData = TaskInherited.of(context);

    double progress = inheritedData.completedTasks / inheritedData.totalTasks;

    return Column(
      children: [
        const Text('Reading Progress:'),
        LinearProgressIndicator(value: progress),
        Text('${(progress * 100).toStringAsFixed(0)}% completed'),
      ],
    );
  }
}

class TaskList extends StatelessWidget {
  const TaskList({super.key});

  @override
  Widget build(BuildContext context) {
    final tasks = [
      const TaskItem(label: 'Embedded in Thin Slices'),
      const TaskItem(label: 'Technology Features'),
      const TaskItem(label: 'Datasheets'),
    ];

    return Column(children: tasks);
  }
}

class TaskItem extends StatefulWidget {
  final String label;

  const TaskItem({super.key, required this.label});

  @override
  TaskItemState createState() => TaskItemState();
}

class TaskItemState extends State<TaskItem> {
  bool _isChecked = false;

  @override
  Widget build(BuildContext context) {
    final inheritedData = TaskInherited.of(context);

    return Row(
      children: [
        Checkbox(
          onChanged: (newValue) {
            setState(() {
              _isChecked = newValue!;
              inheritedData.updateProgress(_isChecked);
            });
          },
          value: _isChecked,
        ),
        Text(widget.label),
      ],
    );
  }
}

本文标签: flutterHow do you update one Stateful Widget from another stateful widgetStack Overflow