Как использовать провайдера Riverpod в initState
Я использую Riverpod для управления состоянием в своем веб-приложении. Я пытаюсь создать AppBar, который автоматически прокручивается до определенных частей ListView.
Для этой цели я создал ScrollController в качестве провайдера.
final scrollControllerProvider = StateProvider<ScrollController?>((ref) => ScrollController());
Для прокрутки я использую.animateTo
из действий AppBar.
ref.read(scrollControllerProvider)!.animateTo(
0,
duration: const Duration(milliseconds: 500),
curve: Curves.easeInOut,
);
Прокрутка работает, но выдает исключение
The provided ScrollController is currently attached to more than one ScrollPosition.
Я читал, что должен использовать StatefulWidget. Однако, используя ConsumerStatefulWidget, я не могу создать ScrollController с помощью Riverpod, потому что мне нужно инициировать его вinitState()
и я не могу получить доступ к провайдеру с него. Возможно ли совместить эти два элемента?
3 ответа
Я думаю, что решение состоит в том, чтобы использовать ref.read(...) в методе инициализации. Это не обновит представление и не примет правильное значение.
ScrollController
этоChangeNotifier
класс, вместоStateProvider
, использоватьChangeNotifierProvider
, имейте в виду, если вы не можете прикрепить его к нескольким прокручиваемым.
final myScrollControllerProvider =
ChangeNotifierProvider((ref) => ScrollController());
может быть, мы можем сделать это в новом классе, создать лайкforceDispose()
метод:
class MyScrollController extends ScrollController {
void forceDispose(){
this.dispose();
}
}
но поскольку я не уверен, как вы это используете и где вы будете располагать его в каком-то состоянии, либо мы не можем инициализировать его в каком-то initState(), давайте просто оставим провайдеру autoDispose самого себя:
final myScrollControllerProvider =
ChangeNotifierProvider.autoDispose((ref) => ScrollController());
я проверяю это, я создаю нового потребителя для имитации другого виджета, но вы должны убедиться, что на самом деле прокручиваемый элемент все еще смонтирован:
class MyWidget extends ConsumerWidget {
const MyWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context, ref) {
final sc = ref.watch(myScrollControllerProvider);
return Scaffold(
body: Column(
children: [
Row(
children: [
Consumer(
builder: (BuildContext context, WidgetRef ref, Widget? _) {
final scFromAnotherWidget = ref.watch(myScrollControllerProvider);
return TextButton(
onPressed: () {
scFromAnotherWidget.jumpTo(sc.offset + 30);
},
child: const Text("Animate to"));
},
),
TextButton(
onPressed: () {
Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => const NewScreen()));
},
child: const Text("Navigate to brand new Screen"))
],
),
Expanded(
child: ListView.builder(
controller: sc,
itemCount: 100,
itemBuilder: (context, index) => ListTile(
title: Text('$index'),
),
),
),
],
),
);
}
}
class NewScreen extends StatelessWidget {
const NewScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: TextButton(
onPressed: () {
Navigator.pushReplacement(context,
MaterialPageRoute(builder: (context) => const MyWidget()));
},
child: const Text('Back to My Widget')),
),
);
}
}
У вас есть доступ кref
внутри любой функции, которая находится вConsumerStatefulWidget
. Таким образом, вы можете просто позвонить своему провайдеру вinitState()
работать, и он должен вести себя нормально
...
class _MyClassState extends ConsumerState<MyClass> {
late ScrollController _scrollController;
@override
void initState() {
super.initState();
_scrollController = ref.watch(scrollControllerProvider); // Doesn't throw error
}
...