Counter app in Flutter
Tags: flutter, beginners, learning
Published: 2024-04-25 10:55:05

Hi there, 

I have the journey of [learning flutter](https://github.com/vasanth9/learning_flutter). I did learn Flutter almost 3 years back for some time but didn't work on any significant flutter project. I want to use the Github repo with a different app for each branch other than main branch.

The basic idea of counter app is to learn about stateful widget.

> lib/main.dart

```dart
import 'package:flutter/material.dart';
import 'package:learning_flutter/pages/counter.dart';

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

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Counter(),
    ) ;
  }
}

```
calling the counter widget in home of MaterialApp of MyApp. Create a pages folder under lib and create a file in pages with counter.dart

> lib/pages/counter.dart

```dart
import 'package:flutter/material.dart';

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

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {

  int _counter = 0;

  void _incrementCounter(){
    setState(() {
      _counter++;
    });
  }


  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
          const Text("You have pressed the button this many times"),
          Text(_counter.toString(),
          style: const TextStyle(fontSize: 38),
          ),
          ElevatedButton(onPressed: _incrementCounter, child: const Text("Increment"))
        ],),
      ),
    );
  }
}

```

create Counter class as a stateful widget. declare a variable `_counter` and initialise with the value zero.

```dart
int _counter = 0;
```

write a function to update the value of _counter by 1 when ever the function is invoked. We should wrap the update of _counter variable inside the setState function.
```dart
void _incrementCounter(){
    setState(() {
      _counter++;
    });
  }
```
updating `_counter++` directly without `setState` in Flutter application will not automatically update the UI with the new value of `_counter`. This is because Flutter's UI is declarative, meaning the Ui is a function of the current state. When the state changes, the UI needs to be updated to reflect these changes. However, without calling `setState`, Flutter's framework is not notified of the state changes, and thus UI is not updated.

Finally, `setState` notifies the Flutter Framework that the state of the widget has changed. This triggers to schedule a call to `build` method which updates the UI to reflect the new state.

Add an ElevatedButton to trigger the _incrementCounter when clciked.
```dart
 ElevatedButton(
onPressed: _incrementCounter,
child: const Text("Increment")
)
```

To see the changes value convert the _counter to string and display using Text widget

```dart
Text(_counter.toString())
```

Result: 

![basic counter flutter app](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1vmx35m5i3sn8g4vqkh0.png)

I am following the `Mitch Koko` youtube video [FULL Flutter Masterclass: Beginner to Pro](https://youtu.be/TclK5gNM_PM?si=p9iC542SRLPPkjrw) for learning Flutter as of now.

Whenever I follow along the youtube video for coding I want to go one step further to learn better. So, from this Counter I want to transform it as 25 min timer.

```dart
import 'dart:async';
```
The idea for timer is that assign a variable with 25*60 => 25 minutes and decrement it every second after clicking play, we can pause while timer decrementing and playing again will start from where timer paused. Reset timer when ever needed.

```dart
 final _oneSecond = const Duration(seconds: 1); // one second Duration
  Timer? _timer; // timer object 
  bool _isPause = true; // toggle between pause and play.
  int _counter = 25 * 60; // 25 minutes.
```

start Timer function => toggle the pause option,
start the timer and decrement the `_counter` variable every second.
clear the time and toggle the pause option when `_counter == 0`

```dart 
void _startTimer() {
    // toggle the pause option
    setState(() {
      _isPause = false;
    });

    _timer = Timer.periodic(
             _oneSecond, // duration
            (timer) {
      if (_counter == 0) {
        // cancel the timer and toggle the pause
        setState(() {
          _timer?.cancel();
          _isPause = true;
        });

      } else {
      // decrement the counter
        setState(() {
          _counter--;
        });
      }
    });
  }
/*The dispose method is then used to cancel the timer when the widget is removed from the tree, ensuring that the timer does not continue to run and use resources unnecessarily. */
 @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }
```
start Timer function => toggle the pause option and cancel the timer.

```dart
void _pauseTimer() {
    setState(() {
      _timer?.cancel();
      _isPause = true;
    });
  }
```

reset Timer function => set the pause option to initial state, cancel the timer and set the counter to initial state.

```dart
  void _resetTimer() {
    setState(() {
      _timer?.cancel();
      _isPause = true;
      _counter = 25 * 60;
    });
  }
```

converting the integer `_counter` to minutes and seconds

```dart 
"${(_counter ~/ 60).toString().padLeft(2, "0")}:${(_counter % 60).toString().padLeft(2, "0")}",
```
~/60 is integer division with 60 gives minutes, 
%60 is modulo with 60 gives seconds.
toString() converts integer to string
padLeft(2,"0") gives consistent 2 digits => padded the string "0" until the length of the string is 2.


> lib/pages/counter.dart
```dart
import 'package:flutter/material.dart';
import 'dart:async';

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

  @override
  State<Counter> createState() => _CounterState();
}

class _CounterState extends State<Counter> {
  final _oneSecond = const Duration(seconds: 1);
  Timer? _timer;
  bool _isPause = true;

  int _counter = 25 * 60; // 25 minutes.

  void _startTimer() {
    setState(() {
      _isPause = false;
    });
    _timer = Timer.periodic(_oneSecond, (timer) {
      if (_counter == 0) {
        setState(() {
          _timer?.cancel();
          _isPause = true;
        });
      } else {
        setState(() {
          _counter--;
        });
      }
    });
  }

  void _pauseTimer() {
    setState(() {
      _timer?.cancel();
      _isPause = true;
    });
  }

  void _resetTimer() {
    setState(() {
      _timer?.cancel();
      _isPause = true;
      _counter = 25 * 60;
    });
  }

  @override
  void dispose() {
    _timer?.cancel();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              "${(_counter ~/ 60).toString().padLeft(2, "0")}:${(_counter % 60).toString().padLeft(2, "0")}",
              style: const TextStyle(fontSize: 38),
            ),
            ElevatedButton(
              onPressed: _isPause ? _startTimer : _pauseTimer,
              child: _isPause
                  ? const Icon(Icons.play_arrow)
                  : const Icon(Icons.pause),
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: _resetTimer,
        child: const Icon(Icons.restart_alt_sharp),
      ),
    );
  }
}
```


![25 minutes timer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lre86jruacncf2tif7d7.png)

Thats it,
Thank you reading.
See you in the next article.

Lets Level Up.


References:
[github counter/timer](https://github.com/vasanth9/learning_flutter/tree/counter)
[FULL Flutter Masterclass: Beginner to Pro](https://youtu.be/TclK5gNM_PM?si=p9iC542SRLPPkjrw)
[Working with timers in flutter](https://www.dhiwise.com/post/working-with-flutter-timer-creating-timers-in-your-app)