skip to Main Content

I have a custom appbar class where I pass the title as its constructor.

class MyAppBar extends StatelessWidget implements PreferredSizeWidget {
  final String? title;
  @override
  Size get preferredSize => Size.fromHeight(AppBar().preferredSize.height);

  const MyAppBar({super.key, this.title});

  @override
  Widget build(BuildContext context) {
    return AppBar(title: Text(title ?? "CFlytics"));
  }
}

Now, I’m using MyAppBar in a ConsumerStatefulWidget with ‘appbartitle’ as a state variable passed to it.

When I update the appbartitle variable using setState, it doesn’t update the appbar title at all

class _MyHomePageState extends State<MyHomePage> {
  String? appbartitle;

  @override
  Widget build(BuildContext context) {
    final myData =ref.watch(someProvider);
    return Scaffold(
      appBar: MyAppBar(title: appbartitle),

      body: myData.when(
          data: (data) {
            setState(() {appbartitle = "123";});
            return ...;
          },

          loading: {return ...}
          error:  {return ...}

But when I wrap the setState line in Future.delayed even with 0 ms delay, it works as required

            Future.delayed(const Duration(milliseconds: 0), () {
              setState(() {appbartitle = "new title";});
            });

Why is it so and what would be the correct way to implement this?

2

Answers


  1. First of all, this syntax looks like old riverpod, and someprovider would be a FutureProvider in this sense. Nevertheless, I would expect to see the error ‘setstate or markNeedsBuild() called during build’ if you do this. I think it’s not working because, you are calling setstate in myData.when() which effectively calls setstate also, all within the same synchronous process.
    Placing it in a Future.delayed makes it an Asynchronous call which is scheduled after synchronous tasks, in the event loop. You can look up event queue, microtask queue and async processes in Flutter.
    By wrapping it in a Future, you are simply calling setstate at a later time after myData.when has processed it’s own setstate.
    Riverpod has ref.listen, which is a more appropriate method to use within the build to update data.
    Alternatively, I would write something like this to not change too much code.

    @override
    Widget build(BuildContext context) {
    final myData =ref.watch(someProvider);
    if(myData.hasValue){
     appBarTitle = '123';
    }
    return Scaffold(
      appBar: MyAppBar(title: appbartitle),
    
      body: myData.when(
          data: (data) {
           //don't call setstate here
            return ...;
          },
          loading: {return ...},
          error:  {return ...},),
      );
     }
    

    Nevertheless you will still need to handle loading and error states and myData.haveValue can be true whiile state is in loading/error.

    Login or Signup to reply.
  2. Rather than using class variable use : ValueNotifier<String?> appBarTitle = ValueNotifier<String?>(null);

    Wrap your AppBar with ValueListenableBuilder Like This :

    ValueListenableBuilder( valueListenable:appBarTitle,
    builder:(context,value,child){ return MyAppBar(title: value ?? "");
    } )

    assign new value like this :

    WidgetsBinding.instance.addPostFrameCallBack((){ appBarTitle.value = "newValue"; }); /// can update the listener value directly :)
    

    never use setState in builders;

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search