-1

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?

asked Apr 22, 2024 at 16:26

2 Answers 2

0

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.

answered Apr 22, 2024 at 18:13
Sign up to request clarification or add additional context in comments.

Comments

0

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;

Comments

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.