Jenner Acosta Diaz
Curso profesional de Flutter

Counter App

En main.dart

import 'package:flutter/material.dart';
import 'package:hello_world_app/presentation/screens/counter/counter_screen.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: CounterScreen()
    );
  }
}

A nuestro Screen Home estamos indicando que va a ser CounterScreen(), asi que vamos a crearo

/lib/presentation/screens/counter/counter_screen.dart

import 'package:flutter/material.dart';

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('10', style:TextStyle( fontSize: 160, fontWeight: FontWeight.w100 )),
            Text('Clicks', style:TextStyle( fontSize: 25))
          ],
        ),
      ),
        floatingActionButton: FloatingActionButton(onPressed: () {

        },
        child: const Icon( Icons.plus_one ),
      ),
    );
  }
}

Material Design 3

Actualmente estamos teniendo los estilos de Material 2, vamos a modificarlo para tener material 3, ello es tan facil como agregar:

theme: ThemeData(
   useMaterial3: true
),

Lo agregamos dentro de nuestro Widget Build en main.dart, quedando de la siguiente manera:

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        useMaterial3: true,
        colorSchemeSeed: Colors.blue
      ),
      home: const CounterScreen()
    );
  }

Tambié podemos agregarle una paleta de colores con:

colorSchemeSeed: Colors.blue

Cambiar el estado de la aplicación

Si quiero cambiar el estado no puedo hacerlo directamente en el StatelessWidget, asi que lo vamos a cambiar a un Stateful Widget

Nuestro código quedaría asi:

import 'package:flutter/material.dart';

class CounterScreen extends StatefulWidget {

  const CounterScreen({super.key});

  @override
  State<CounterScreen> createState() => _CounterScreenState();
}

class _CounterScreenState extends State<CounterScreen> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Counter Screen'),
      ),
      body: const Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('10',
                style: TextStyle(fontSize: 160, fontWeight: FontWeight.w100)),
            Text('Clicks', style: TextStyle(fontSize: 25))
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {},
        child: const Icon(Icons.plus_one),
      ),
    );
  }
}

Ahora en _CounterScreenState podemos agregar nuestra variable

int clickCounter = 0;

la cual va a variar cuando pulsemos el boton más, eso lo hacemos agregandole la pariable al onPressed

onPressed: () {
   clickCounter++;
},

Quedando nuestro _counterScreenState de la siguiente manera:

class _CounterScreenState extends State<CounterScreen> {
  int clickCounter = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Counter Screen'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('$clickCounter',
                style: const TextStyle(
                    fontSize: 160, fontWeight: FontWeight.w100)),
            const Text('Clicks', style: TextStyle(fontSize: 25))
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          clickCounter++;
        },
        child: const Icon(Icons.plus_one),
      ),
    );
  }
}

Acá hay un problema, es que el state si esta aumentando y eso lo podemos ver si hacemos un hot reload, el problema es que tenemos que definir una función para que actualice la pantalla.

Lo solucionamos agregando un setState:

setState(() {
  clickCounter++;
});

Ahora ya podemos ver en tiempo real como va sumando los números.

Cuando está el número 1, lo correcto sería que esté en singular, para ello podemos usar el operador ternario:

Text( (clickCounter == 1) ? 'Click' : 'Clicks',
  style: const TextStyle(fontSize: 25))

Vamos a querer agregar más botones en la pare interior, para ello a nuestro floatingActionButton vamos agregarle un Column con un arreglo de children, vamos a poder agregar varios botones, alinearlos, darle espaciado y diferentes acciones

floatingActionButton: Column(
  mainAxisAlignment: MainAxisAlignment.end,
  children: [
    FloatingActionButton(
      shape: const StadiumBorder(),
      onPressed: () {
        setState(() {
          clickCounter++;
        });
      },
      child: const Icon(Icons.plus_one),
    ),

    const SizedBox(height: 10),

    FloatingActionButton(
      onPressed: () {
        setState(() {
          clickCounter--;
        });
      },
      child: const Icon(Icons.exposure_minus_1_outlined),
    ),
  ],
)

Widgets Personalizados

En este momento nos afrontamos a que si queremos hacer algun cambio, como cambiar el redondeado, forma, etc, se hace boton por boton, pero hacer eso no es correcto, lo ideal es que herede alguna funciones, para ello vamos a tener que crear nuestros Widgets personalizados.

En este ejemplo vamos a crear un custom Widget llamado CustomButton

class CustomButton extends StatelessWidget {
  final IconData icon;
  final VoidCallback? onPressed;

  const CustomButton({
    super.key,
    required this.icon,
    this.onPressed,
  });

  @override
  Widget build(BuildContext context) {
    return FloatingActionButton(
      shape: const StadiumBorder(),
      onPressed: onPressed,
      child: Icon(icon),
    );
  }
}

Esto lo podemos llamar y dar la propiedades respectivas:

children: [
  CustomButton(
    icon: Icons.refresh_outlined,
    onPressed: () {
      clickCounter = 0;
      setState(() {});
    },
  ),
  const SizedBox(height: 10),
  CustomButton(
    icon: Icons.plus_one,
    onPressed: () {
      clickCounter++;
      setState(() {});
    },
  ),
  const SizedBox(height: 10),
  CustomButton(
    icon: Icons.exposure_minus_1_outlined,
    onPressed: () {
      if (clickCounter == 0) return;
      clickCounter--;
      setState(() {});
    },
  ),
],