Riverpod - How to wrap a PreferredSizeWidget inside a Consumer

1,125

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 a PreferredSize and use Size.fromHeight() as preferredSize. That is if the height is constant amongst your appbars.
  • You can avoid using the appBar parameter altogether by wrapping your Scaffold's body with an Expanded inside a Column, and making the Consumer 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
  }
}
Share:
1,125
Burak Akyalçın
Author by

Burak Akyalçın

In love with mobile application development

Updated on December 26, 2022

Comments

  • Burak Akyalçın
    Burak Akyalçın over 1 year

    I have a DefaultTabController and I have a method that returns List<PreferredSizeWidget> which are the AppBar's of pages. I want them to watch a state in a ChangeNotifier and therefore I want to wrap them inside of a Consumer. 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.