Riverpod - How to wrap a PreferredSizeWidget inside a Consumer
Solution 1
The error comes from the appBar
's parameter of Scaffold
requiring a PreferredSizeWidget
. I can think of two solutions :
- You can wrap your
Consumer
with aPreferredSize
and useSize.fromHeight()
aspreferredSize
. That is if the height is constant amongst your appbars. - You can avoid using the
appBar
parameter altogether by wrapping yourScaffold
's body with anExpanded
inside aColumn
, and making theConsumer
its first child.
Solution 2
This is my implementation based on Mickael suggestion:
First I've createad a AppBarParams
class to save the AppBar state
@freezed
class AppBarParams with _$AppBarParams {
const factory AppBarParams({
required String title,
required List<Widget> actions,
}) = _AppBarParams;
}
Then I've created a StateProvider
in global providers file like this:
final appBarParamsProvider = StateProvider<AppBarParams>((ref) {
return AppBarParams(title: "Default title", actions: []);
});
and attached it in the main app with a Consumer
:
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: "App Title",
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: SafeArea(
child: Scaffold(
appBar: PreferredSize(
preferredSize: Size.fromHeight(kToolbarHeight),
child: Consumer(
builder: (context, watch, child) {
final appBarParams = watch(appBarParamsProvider).state;
return AppBar(
title: Text(appBarParams.title),
actions: appBarParams.actions
);
})
),
body: ... your body widget
)
)
)
}
}
Then you just need to edit the provider state to have the AppBar update accordingly, to update the AppBar when the user switches between pages, I've created this mixin:
mixin AppBarHandler {
void updateAppBarParams(
BuildContext context, {
required String title,
List<Widget> Function()? actions
}) {
WidgetsBinding.instance!.addPostFrameCallback((_) async {
context.read(appBarParamsProvider).state = context
.read(appBarParamsProvider).state
.copyWith(
title: title,
actions: actions != null ? actions() : []
);
});
}
}
And in every main screen view which has to change the title or the actions I did this:
class Page1 extends HookWidget with AppBarHandler {
const Page1({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
updateAppBarParams(context,
title: "Page 1 title",
actions: () => [
IconButton(icon: const Icon(Icons.refresh), onPressed: () {
//a custom action for Page1
context.read(provider.notifier).updateEntries();
})
]
);
... your screen widget
}
}
Comments
-
Burak Akyalçın over 1 year
I have a
DefaultTabController
and I have a method that returnsList<PreferredSizeWidget>
which are theAppBar
's of pages. I want them to watch a state in aChangeNotifier
and therefore I want to wrap them inside of aConsumer
. When I try to do so I get an error like this:"The argument type 'Widget' can't be assigned to the parameter type 'PreferredSizeWidget'."
How can I fix this?
Thanks and regards.