I have simplified my real app into the following code below to show the problem. I am basically trying to show the scroll offset of a list view on another widget (the real app is a lot more complex where the widget does other stuff with that offset).
I am returning the ViewController()
as the body of my Scaffold in the main.dart
. In my ViewController
below, I have a stack which contains a ListView
and a Text
. I want the controller for the ListView
to update the Text
using the ChangeNotifierProvider
. It doesn't work for some reason.
My ChangeNotifier
is the scrollProvider
which has been declared as a variable of the entire ViewController
and I am passing the same scrollProvider
into the ChangeNotifierProvider
and also printing from the same.
What am I doing wrong?
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ViewController extends StatefulWidget {
const ViewController({super.key});
@override
State<ViewController> createState() => _ViewControllerState();
}
class _ViewControllerState extends State<ViewController> {
final scrollController = ScrollController();
final scrollProvider = ScrollProvider();
@override
void initState() {
scrollController.addListener(scrollHandler);
super.initState();
}
@override
void dispose() {
scrollController.removeListener(scrollHandler);
super.dispose();
}
void scrollHandler() {
scrollProvider.setCurrentOffset(scrollController.offset);
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
ListView.builder(
controller: scrollController,
itemBuilder: ((context, index) {
return Text(
"Index: $index",
style: const TextStyle(fontSize: 40),
);
}),
itemCount: 100,
),
Positioned(
left: 0,
bottom: 0,
right: 0,
child: CupertinoButton(
onPressed: (() {}),
child: ChangeNotifierProvider(
create: ((_) => scrollProvider),
child: Text(
"Offset: ${scrollProvider.currentOffset}",
// "Offset: ${Provider.of<ScrollProvider>(context, listen: false).currentOffset}",
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
backgroundColor: Colors.black,
),
),
),
),
),
],
);
}
}
class ScrollProvider extends ChangeNotifier {
double? currentOffset;
void setCurrentOffset(valueToSet) {
if (currentOffset != valueToSet) {
currentOffset = valueToSet;
print("currentOffset: $currentOffset");
notifyListeners();
}
}
}
CodePudding user response:
You need to take care of two things:
If you want to create a provider from an already existing value, use the ChangeNotifierProvider.value named constructor.
Wrap your
Text
widget into aConsumer<ScrollProvider>
so that it gets notified whencurrentOffset
is changed.
(And always call super.initState
in initState
in the first line and do your own logic afterwards.)
With some modifications in your code this is an example of how to get it working:
import 'package:flutter/material.dart';
import 'package:flutter/cupertino.dart';
import 'package:provider/provider.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const MaterialApp(
home: Scaffold(
body: Center(
child: SafeArea(child: ViewController()),
),
),
);
}
}
class ViewController extends StatefulWidget {
const ViewController({super.key});
@override
State<ViewController> createState() => _ViewControllerState();
}
class _ViewControllerState extends State<ViewController> {
final scrollController = ScrollController();
final scrollProvider = ScrollProvider();
@override
void initState() {
super.initState();
scrollController.addListener(scrollHandler);
}
@override
void dispose() {
scrollController.removeListener(scrollHandler);
super.dispose();
}
void scrollHandler() {
scrollProvider.setCurrentOffset(scrollController.offset);
}
@override
Widget build(BuildContext context) {
return Stack(
children: [
ListView.builder(
controller: scrollController,
itemBuilder: ((context, index) => Text(
"Index: $index",
style: const TextStyle(fontSize: 40),
)),
itemCount: 100,
),
Positioned(
left: 0,
bottom: 0,
right: 0,
child: CupertinoButton(
onPressed: (() {}),
child: ChangeNotifierProvider.value(
value: scrollProvider,
child: Consumer<ScrollProvider>(
builder: (context, value, child) => Text(
"Offset: ${value.currentOffset}",
style: const TextStyle(
fontSize: 32,
fontWeight: FontWeight.bold,
backgroundColor: Colors.black,
),
),
),
),
),
),
],
);
}
}
class ScrollProvider extends ChangeNotifier {
double? currentOffset;
void setCurrentOffset(valueToSet) {
if (currentOffset != valueToSet) {
currentOffset = valueToSet;
print("currentOffset: $currentOffset");
notifyListeners();
}
}
}