I am trying to calculate a percentage and set it as text into a Text Widget while building a page. In my app I have a setting page with a ListTile which brings the user to a new page by tapping on it. Inside the new page (percentage_settings_page.dart) I read the data from my SQLITE database which contains some users using a FutureBuilder<List<User>>
and ListView.builder()
.
While building the list view containing the name and the percentage of each user I need to calculate the remaining percentage, which is given by substracting the sum of the user's percentage from 100, and set it in the circular indicator. I am trying to achive this using the ValueNotifier
. This is how the pages look like:
percentage_settings_page.dart
:
class _PercentageSettingsPageState extends State<PercentageSettingsPage> {
/* other variables */
final ValueNotifier<double> _myPercentage = ValueNotifier<double>(100);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Divide percentage'),
leading: BackButton(
onPressed: () async => Navigator.pop(context),
),
),
body: SafeArea(
child: Column(
children: [
Padding(
padding: EdgeInsets.only(top: 20),
child: Align(
alignment: Alignment.topCenter,
child: CircularPercentIndicator(
radius: 120.0,
lineWidth: 10.0,
animation: true,
center: ValueListenableBuilder(
valueListenable: _myPercentage,
builder:
(BuildContext context, double value, Widget? child) {
return Text(
'$value', // <- this text should be updated
style: const TextStyle(
fontWeight: FontWeight.bold, fontSize: 20.0),
);
}),
footer: const Text(
'Remaining percentage',
style:
TextStyle(fontWeight: FontWeight.bold, fontSize: 17.0),
),
circularStrokeCap: CircularStrokeCap.round,
progressColor: Colors.orange,
),
),
),
SafeArea(
child: _buildUsersFromDB(),
),
],
),
),
);
}
Widget _buildUsersFromDB() {
return FutureBuilder<List<User>>(
future: _userDAO.readData(),
builder: (BuildContext context, AsyncSnapshot<List<User>> snapshot) {
return snapshot.hasData
? ListView.builder(
itemCount: snapshot.data!.length,
shrinkWrap: true,
itemBuilder: (BuildContext context, int i) {
return Card(
elevation: 2,
margin: const EdgeInsets.all(5),
child: _buildRow(snapshot.data![i]),
);
},
)
: const Center(
child: CircularProgressIndicator(),
);
});
}
Widget _buildRow(User user) {
_myPercentage.value -= user.percentage; // <- This line gives me problems
return ListTile(
title: Text(
user.name,
style: _biggerFont,
),
trailing: Text('${user.percentage} %'),
onTap: () => _showDoubleDialog(user).then((value) {
/* some code */
}),
);
}
}
But when I run the app I get the following errors:
════════ Exception caught by foundation library ════════════════════════════════
The following assertion was thrown while dispatching notifications for ValueNotifier<double>:
setState() or markNeedsBuild() called during build.
This ValueListenableBuilder<double> widget cannot be marked as needing to build because the framework is already in the process of building widgets. A widget can be marked as needing to be built during the build phase only if one of its ancestors is currently building. This exception is allowed because the framework builds parent widgets before children, which means a dirty descendant will always be built. Otherwise, the framework might not visit this widget during this build phase.
The widget on which setState() or markNeedsBuild() was called was: ValueListenableBuilder<double>
state: _ValueListenableBuilderState<double>#5709e
The widget which was currently being built when the offending call was made was: SliverList
delegate: SliverChildBuilderDelegate#e6e08(estimated child count: 3)
renderObject: RenderSliverList#aacf6 relayoutBoundary=up15 NEEDS-LAYOUT NEEDS-PAINT
When the exception was thrown, this was the stack
#0 Element.markNeedsBuild.<anonymous closure>
package:flutter/…/widgets/framework.dart:4217
#1 Element.markNeedsBuild
package:flutter/…/widgets/framework.dart:4232
#2 State.setState
package:flutter/…/widgets/framework.dart:1108
#3 _ValueListenableBuilderState._valueChanged
package:flutter/…/widgets/value_listenable_builder.dart:182
#4 ChangeNotifier.notifyListeners
package:flutter/…/foundation/change_notifier.dart:243
...
The ValueNotifier<double> sending notification was: ValueNotifier<double>#5ee2d(89.2)
Does someone have any idea how I can achive this or what the best practices are when trying to set a Widget during/right after the building?
Edit: I need the ValueNotifier since I need to update the remaining percentage whenever the percentage of a single user is updated (which is done by tapping on a user and selecting the new percentage)
I would just move the call to _userDAO.readData()
up and add the header text and list items based on that. This would remove the need for the _myPercentage
ValueNotifier
.