fluttersetstateflutter-appbar

how do i instantly update a value in appbar in flutter


I have a widget called 'HomePage' in my application. I gave a pageview to its body and put 2 pages in it. In the Appbar, there is a title that shows the user's money. on one of the pages there is a button that reduces money. but when I press this button the money is not updated immediately, if I change the page then it is updated. How do I immediately update the money value in the appbar without the page changing?

/////////////////// HOMEPAGE WİDGET ///////////////////

import 'package:flutter/material.dart';
import 'package:flutterapp/demo/user_infos.dart';

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(UserInfos.money.toString())),
      body: PageView(
        children: [
          Page1(),
          Page2(),
        ],
      ),
    );
  }
}
////////////////////////////////////////////////////////

///// UserInfo class//////

class UserInfos {
  static int money = 25000;
  ...
  ...
  ...
  ...
}

////////////////////////////////////////////////////////////////


//////////// Page1()/////////////////////////////////


class Page1 extends StatefulWidget {
  const Page1({super.key});

  @override
  State<Page1> createState() => _Page1State();
}

class _Page1State extends State<Page1> {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: ElevatedButton(
          onPressed: () {
            setState(() {
              UserInfos.money -= 1;
            });
          },
          child: const Text('decrease money')),
    );
  }
}


I also got help from chatgpt or something, but it didn't work. I need to change the page or do a hot reolad by doing 'ctrl+s'. only then the money value is updated


Solution

  • Calling [setState] notifies the framework that the internal state of this object has changed in a way that might impact the user interface in this subtree, which causes the framework to schedule a [build] for this [State] object.

    The key is rebuild your widgets tree everytime you change your widget' states

    When you do like this

    class Page1 extends StatefulWidget {
      UserInfos userInfo;
    
      const Page1({super.key, required this.userInfo});
    
      @override
      State<Page1> createState() => _Page1State();
    }
    
    class _Page1State extends State<Page1> {
      @override
      Widget build(BuildContext context) {
        return Center(
          child: ElevatedButton(
              onPressed: () {
                setState(() { // Page1 state changed, not homepage state
                  widget.userInfo.money -= 1;
                });
              },
              child: const Text('decrease money')),
        );
      }
    }
    

    only page1 widget will be scheduled to rebuild. If you want to update changes to your homepage widget, you need to call setState inside your homepage widget

    class HomePage extends StatefulWidget {
      const HomePage({super.key});
    
      @override
      State<HomePage> createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
    
      UserInfos _userInfo = new UserInfos();
    
      // setState here when userInfos changed
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(_userInfo.money.toString())),
          body: PageView(
            children: [
              Page1(userInfo: _userInfo),
              Page2(userInfo: _userInfo),
            ],
          ),
        );
      }
    }
    

    There are many way to do this, the simplest is callback.

    Your HomePage widget:

    class HomePage extends StatefulWidget {
      const HomePage({super.key});
    
      @override
      State<HomePage> createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      void _updateState() {
        setState(() {
          UserInfo.money += 100;
        });
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(UserInfo.money.toString())),
          body: PageView(
            children: List.generate(
              3,
              (index) => Page(
                action: 'Action $index',
                onPressed: _updateState, // Pass your actions to child widget
              ),
            ),
          ),
        );
      }
    }
    

    Your Page widget:

    class Page extends StatefulWidget {
      const Page({
        super.key,
        required this.action,
        this.onPressed,
      });
    
      final String action;
      final Function()? onPressed;
    
      @override
      State<Page> createState() => _PageState();
    }
    
    class _PageState extends State<Page> {
      @override
      Widget build(BuildContext context) {
        return Center(
          child: ElevatedButton(
            onPressed: widget.onPressed, // Callback to change state
            child: Text(widget.action),
          ),
        );
      }
    }
    

    Your _updateState() function is called inside HomePage widget, so it will rebuild your widget after state changed.

    A better way is make your state observable

    class UserInfo {
      // when you change money value, every listeners of this will be updated.
      static ValueNotifier<int> money = ValueNotifier(25000);
    }
    

    Update money value by

    UserInfo.money.value = 100; // new value
    

    And make your appbar listen for money value changes

    AppBar(
      title: ValueListenableBuilder( // Your appbar will listen any UserInfo.money change
        valueListenable: UserInfo.money,
        builder: (context, value, child) => Text(value.toString()),
      ),
    )
    

    Full solution

    import 'package:flutter/material.dart';
    
    class HomePage extends StatefulWidget {
      const HomePage({super.key});
    
      @override
      State<HomePage> createState() => _HomePageState();
    }
    
    class _HomePageState extends State<HomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: ValueListenableBuilder( // Your appbar will listen any UserInfo.money change
              valueListenable: UserInfo.money,
              builder: (context, value, child) => Text(value.toString()),
            ),
          ),
          body: PageView(
            children: List.generate(
              3,
              (index) => Page(
                action: 'Action $index',
              ),
            ),
          ),
        );
      }
    }
    
    class UserInfo {
      static ValueNotifier<int> money = ValueNotifier(25000);
    }
    
    class Page extends StatefulWidget {
      const Page({
        super.key,
        required this.action,
      });
    
      final String action;
    
      @override
      State<Page> createState() => _PageState();
    }
    
    class _PageState extends State<Page> {
      @override
      Widget build(BuildContext context) {
        return Center(
          child: ElevatedButton(
            onPressed: () {
              UserInfo.money.value += 100;
            },
            child: Text(widget.action),
          ),
        );
      }
    }