1

I built custom app for training. I created buttons with gesture detector and i assigned number to them and i created global variable "score". I want buttons to add their numbers to "score" variable and i want to show the variable in a container but Somehow it does not work.Can it be about states?Does anyone help me?


void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        appBarTheme: const AppBarTheme(
          backgroundColor: Colors.deepPurple,
          centerTitle: true,
          elevation: 3,
        ),
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatelessWidget {
  const HomeScreen({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text("My Application"),
      ),
      body: const Body(),
    );
  }
}

int score = 5;

class Body extends StatefulWidget {
  const Body({Key? key}) : super(key: key);

  @override
  State<Body> createState() => _BodyState();
}

class _BodyState extends State<Body> {
  @override
  Widget build(BuildContext context) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Container(
          height: 50,
          width: 200,
          decoration: const BoxDecoration(color: Colors.grey),
          child: Center(child: Text(score.toString())),
        ),
        const SizedBox(
          height: 70,
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: const [
            Numb(
              color: Colors.pink,
              numb: 1,
            ),
            Numb(
              color: Colors.pink,
              numb: 2,
            ),
            Numb(
              color: Colors.pink,
              numb: 3,
            ),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: const [
            Numb(
              color: Colors.pink,
              numb: 4,
            ),
            Numb(
              color: Colors.pink,
              numb: 5,
            ),
            Numb(
              color: Colors.pink,
              numb: 6,
            ),
          ],
        ),
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: const [
            Numb(
              color: Colors.pink,
              numb: 7,
            ),
            Numb(
              color: Colors.pink,
              numb: 8,
            ),
            Numb(
              color: Colors.pink,
              numb: 9,
            ),
          ],
        ),
      ],
    );
  }
}

class Numb extends StatefulWidget {
  final int? numb;
  final Color? color;

  const Numb({
    Key? key,
    required this.numb,
    required this.color,
  }) : super(key: key);

  @override
  State<Numb> createState() => _NumbState();
}

class _NumbState extends State<Numb> {
  @override
  Widget build(BuildContext context) {
    return GestureDetector(
      onTap: () {
        setState(() {
          score += widget.numb!;
        });
      },
      child: Container(
        margin: projectPadding.allPad * 0.5,
        decoration: BoxDecoration(color: widget.color),
        height: MediaQuery.of(context).size.height * 0.06,
        width: MediaQuery.of(context).size.width * 0.1,
        child: Center(
          child: Text(widget.numb.toString()),
        ),
      ),
    );
  }
}

class projectPadding {
  static const EdgeInsets horizantalPad = EdgeInsets.symmetric(horizontal: 20);
  static const EdgeInsets verticalPad = EdgeInsets.symmetric(vertical: 20);
  static const EdgeInsets allPad = EdgeInsets.all(20);
}
Md. Yeasin Sheikh
  • 54,221
  • 7
  • 29
  • 56
hasannn2666
  • 49
  • 1
  • 5

2 Answers2

1

Numb setState only update the _NumbState ui. in order to update parent widget, you can use callback method that will trigger setState on parent UI.

class Numb extends StatefulWidget {
  final int? numb;
  final Color? color;

  final Function(int) callBack;

  const Numb({
    Key? key,
    required this.numb,
    required this.color,
    required this.callBack,
  }) : super(key: key);

  @override
  State<Numb> createState() => _NumbState();
}


///....

onTap: () {
    setState(() {
      score += widget.numb!;
    });
    widget.callBack(score);
  },

And you can add logic and others operation like

Numb(
  color: Colors.pink,
  numb: 1,
  callBack: (p0) {
    setState(() {});
  },
),

I will also recommend starting state-management like riverpod / bloc

Md. Yeasin Sheikh
  • 54,221
  • 7
  • 29
  • 56
  • First of all, i want to thank you again. Actually i'm studying computer science at 2th grade. I want to learn flutter more. I know basic topics but i can't manage my app. Should my second level be state management as you recommended? – hasannn2666 Aug 04 '22 at 18:11
  • 1
    Yes, You can start with provider, or just shift to riverpod/bloc. Also, try not use Getx, – Md. Yeasin Sheikh Aug 04 '22 at 18:13
  • i also want to ask that i dont want to create global variable as i did "score" variable. I want to create that "score" variable in my parent widget and i want to access and change/update it from child widget. How can i do it? – hasannn2666 Aug 04 '22 at 18:28
  • No, you don't have to create global variable , you can pass `widget.numb(yourData)` and do operation on parent widget. you will get data like here `p0` `callBack: (p0) {` – Md. Yeasin Sheikh Aug 04 '22 at 18:32
  • sorry i did not understand how i will access widget.numb on my parent widget – hasannn2666 Aug 04 '22 at 18:47
  • Try print state on `callBack: (p0) {print(p0); setState(() {});},` hope you will get how callBack is working – Md. Yeasin Sheikh Aug 04 '22 at 18:49
  • i got it when i 'print' as you told me and i did it without global widget :) Thank you so much :) – hasannn2666 Aug 04 '22 at 19:03
  • yap. Also try using state management for project, it makes things simpler – Md. Yeasin Sheikh Aug 04 '22 at 19:08
  • 1
    okay thanks i will start with provider that i heard before then i will learn bloc. – hasannn2666 Aug 04 '22 at 19:25
1

Why doesn't it work?

  • When you use the setState method, it triggers a rebuild of the widget where it is called.
  • In your code, you call the setState method in your Numb widget, which will therefore be rebuilt.
  • The problem is that the score value that you display on screen is located in your Body widget, and you want this widget to be rebuilt whenever the score changes.
  • So how do we do that?

How to make it work?

This is a State Management issue and there are multiple ways to solve it. You can find in the official documentation a good example to understand how this works.

  • Lifting the State Up and Callbacks
    • Following the previous logic described above, you would have to call the setState method in the Body widget to trigger a rebuild, and this whenever the score changes, which means whenever a Numb widget is pressed.
    • For that you can take advantage of callbacks which are basically functions that you can pass as parameters, and that will run the code they contain when they are called (you can see the official documentation's example about accessing a state using callbacks).
      class Numb extends StatelessWidget { //<-- you can turn Numb into a StatelessWidget which is much simpler since you don't have to call the 'setState' method in it
        final int? numb;
        final Color? color;
        final Function(int) callback; //<-- add your callback as a field...
    
        const Numb({
          Key? key,
          required this.numb,
          required this.color,
          required this.callback, //<-- ... and in the constructor...
        }) : super(key: key);
    
        @override
        Widget build(BuildContext context) {
          return GestureDetector(
            onTap: () => callback(numb!), //<-- ... and simply call it this way in the onTap parameter, giving it the numb value
            child: Container(
              margin: projectPadding.allPad * 0.5,
              decoration: BoxDecoration(color: color),
              height: MediaQuery.of(context).size.height * 0.06,
              width: MediaQuery.of(context).size.width * 0.1,
              child: Center(
                child: Text(numb.toString()),
              ),
            ),
          );
        }
      }
    
      class _BodyState extends State<Body> {
        @override
        Widget build(BuildContext context) {
    
          var updateScoreCallback = (int number) => setState(() => score += number); //<-- your callback takes an int as parameter and call setState adding the input number to the score
    
          return Column(
            children: [
              ...,
              Numb(
                color: Colors.pink,
                numb: 1,
                callback: updateScoreCallback, //<-- give your callback to your Numb widgets
              ),
              Numb(
                color: Colors.pink,
                numb: 2,
                callback: updateScoreCallback,
              ),
              ...
        }
      }
    
    • Like that, pressing any of your Numb widget will call the callback, that will call the setState method in the appropriate Body widget.
  • State Management with Packages
    • The previous method to handle state management with callbacks is good when your case is simple, but if you need for example to access a state from multiple places in your code, it can be quickly too difficult to manage and inefficient. For that, there are multiple packages that are available to make things easier. The official recommandation to start with is the Provider package as it is simple to use, but depending on your need you may want to look for other options like BLoC, Redux, GetX, etc. (full list of state management approaches.
    • Using the Provider approach, start by adding the package in your project (flutter pub add provider), and add the following changes to your code:
      import 'package:provider/provider.dart';
    
      class ScoreData with ChangeNotifier { //<-- create a structure to hold your data, and use the mixin 'ChangeNotifier' to make ScoreData able to notify its listeners for any changes
        int _score = 5;
    
        int get score => _score;
    
        void addToScore(int number) {
          _score += number;
          notifyListeners(); //<-- this method comes from ChangeNotifier (which comes from the Flutter SDK and not from the Provider package), and notify all listeners
        }
      }
    
      class HomeScreen extends StatelessWidget {
        const HomeScreen({Key? key}) : super(key: key);
    
        @override
        Widget build(BuildContext context) {
          return Scaffold(
            appBar: AppBar(
              title: const Text("My Application"),
            ),
            body: ChangeNotifierProvider<ScoreData>( //<-- this says that at this level of the widget tree, you provide a ScoreData that can notify its listeners for any changes...
              create: (context) => ScoreData(), //<-- ... and you provide the ScoreData here by creating a new one (you can provide an existing one using the "value" parameter)
              child: const Body(),
            ),
          );
        }
      }
    
      class Body extends StatefulWidget {
        const Body({Key? key}) : super(key: key);
    
        @override
        State<Body> createState() => _BodyState();
      }
    
      class _BodyState extends State<Body> {
        @override
        Widget build(BuildContext context) {
          return Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              Container(
                height: 50,
                width: 200,
                decoration: const BoxDecoration(color: Colors.grey),
                child: Center(
                    child: Text(Provider.of<ScoreData>(context).score.toString())), //<-- this enables you to retrieve the provided ScoreData higher in the widget tree, and to listen to its value
              ),
              ...
            ],
          );
        }
      }
    
      class Numb extends StatelessWidget {
        final int? numb;
        final Color? color;
    
        const Numb({
          Key? key,
          required this.numb,
          required this.color,
        }) : super(key: key);
    
        @override
        Widget build(BuildContext context) {
          return GestureDetector(
            onTap: () =>
                Provider.of<ScoreData>(context, listen: false).addToScore(numb!), //<-- here as well you retrieve the provided ScoreData, but you only want to call its method "addToScore" without needing to listen to its changes, so add the "listen: false" parameter to make it work
            child: Container(
              ...
            ),
          );
        }
      }