Skip to main content

Command Palette

Search for a command to run...

Cómo pasé de no saber Dart a construir una app completa con flutter

Updated
5 min read
Cómo pasé de no saber Dart a construir una app completa con flutter

Objetivo del blog: compartir mi proceso, dificultades y aprendizajes.

📚 ¿Por qué decidí aprender Flutter y Dart?

  • La capacidad de flutter para crear aplicaciones con alto desempeño para sistemas operativos iOS y Android a partir de un mismo código base.

  • Por su creciente adopción en aplicaciones de nivel empresarial señala su potencial futuro.

  • Curva de aprendizaje es bastante rápida, si se tiene conococimiento de POO (Programación Orientada a Objetos).

  • Dart es mencionado por el Tech Radar de Thoughtworks.

💡Mi punto de partida

Conocimientos en varios lenguajes de programación entre ellos: python, java, javascript. Experiencia con herramientas de CI/CD.

Contexto del dominio: Se requiere una aplicación que ayude a reportar la cantidad de banano recolectado por día, así como también el personal que ha laborado.

🛠️ Paso 1: Primer Encuentro con Dart y Flutter

Advertencia. Si bien esta estructura podría ser tomada como una referencia en aplicaciones sencillas. Si la aplicación sigue creciendo podría causar confusiones. Te dejo esta referencia en donde pueden guiarse de una estructura más limpia y mantenible,

➜  crechazo-app   
lib
├── data
├── exceptions
├── models
│   ├── builder
│   └── types
├── screens
│   ├── farms
│   ├── plate_detail
│   ├── reports
│   ├── staff
│   └── widgets
├── services
└── utils
  • data. Definición de la base de datos (BD), y carga de datos iniciales. Además se definen clases data para realizar consultas a la BD.

  • exceptions. Clases de exepciones personalizadas.

  • models. Definición de entidades

  • screeens: Pantallas de interfaz de usuario.

  • services: Lógica de negocio y conexión con las clases data.

  • utils: Agrupa funciones útiles en diferentes partes de la aplicación. No relacionados con la lógica de negocio específica.


📌 Paso 2: Mi Primer Widget y la Magia de Flutter

MaterialApp - la base de tu app

Piénsalo como el contenedor principal de tu aplicación en Flutter. Es como el "esqueleto" que define el estilo y la estructura general.

Scaffold - la estructura visual

Ahora que tenemos el "esqueleto" con MaterialApp, necesitamos una estructura con barras de herramientas, botones flotantes, etc. Ahí entra Scaffold.

StatelessWidget Widgets que no cambian

Un StatelessWidget es un elemento de la pantalla que no cambia con el tiempo.

🧠 Ejemplo: Piensa en un letrero en la calle. Siempre muestra el mismo mensaje, no importa lo que pase.

StatefulWidget - widgets que sí cambian

Un StatefulWidget puede cambiar su estado cuando el usuario interactúa con él.

🧠 Ejemplo: Imagina un interruptor de luz. Al presionarlo, cambia de encendido a apagado.


🚀 Paso 3: Entendiendo el Manejo de Estado

setState() es una función en Flutter que le dice a la app: "Hey, algo cambió, actualiza la pantalla".

Se usa en StatefulWidgets cuando necesitamos que la interfaz de usuario (UI) cambie en respuesta a una acción del usuario, como presionar un botón o ingresar texto.

🛠️ Reglas importantes sobre setState()

✅ Siempre úsalo dentro de un StatefulWidget.
✅ Solo actualiza lo necesario, no toda la app.
✅ No cambies el estado fuera de setState(), o Flutter no lo reconocerá.

🧠 Ejemplo: Cargando datos de un modelo a controladores.

Uno de los desafíos que enfrenté es entender cómo funciona el cambio de estado. En este ejemplo, se tiene a staffModel, si lo usamos fuera de setState() a pesar de setear los valores en los controladores, estos no se verán reflejados en la pantalla.

...
class _StaffEditScreenState extends State<StaffEditScreen> {
  final TextEditingController staffNameController = TextEditingController();
  final _formKey = GlobalKey<FormState>();

  @override
  void initState() {
    super.initState();
    SchedulerBinding.instance.addPostFrameCallback((_) {
      _loadFarmData();
    });
  }

  void _loadFarmData() {
    setState(() {
      staffNameController.text = widget.staffModel.name;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          ...
        ),
        body: Form(
            key: _formKey,
            child: Column(
              children: <Widget>[
                Padding(
                  child: TextFormField(
                      controller: staffNameController,
                      decoration: const InputDecoration(
                        border: UnderlineInputBorder(),
                        labelText: 'Nombre:',
                      )),
                )
              ],
            )));
  }
}

💾 Paso 4: Integrando una Base de Datos

Usando SQLite como base de datos

SQLite es una base de datos ligera y embebida que no necesita un servidor. Se usa en Flutter para almacenar datos localmente en el dispositivo, como configuraciones, listas de tareas o historiales de usuarios.

🔹 ¿Por qué usar SQLite en Flutter?
✅ No necesita internet.
✅ Almacena datos de manera estructurada (tablas y consultas SQL).
✅ Es rápido y eficiente para apps móviles.

Insertando datos

Future<void> insertarUsuario(Database db, String nombre) async {
  await db.insert("usuarios", {"nombre": nombre});
}

Lectura de datos

Future<List<Map<String, dynamic>>> obtenerUsuarios(Database db) async {
  return await db.query("usuarios");
}

📉 Paso 5: Lógica de negocio

Para evitar sobrecargar las clases de datos con la lógica negocio, se crean clases de servicios. Estas almacenarán cálculos y referencias a las clases datos.

import 'package:crechazo_app/data/farm_data.dart';
import 'package:crechazo_app/models/builder/farm_builder.dart';
import 'package:crechazo_app/models/farm_model.dart';

class FarmService {
  static final FarmService _instance = FarmService._(FarmData());
  FarmService._(this.farmData);

  static FarmService get instance => _instance;

  final FarmData farmData;

  FarmService(this.farmData);

  Future<List<FarmModel>> all() async {
    List<dynamic> list = await farmData.all();

    return List.generate(list.length, (i) {
      return FarmBuilder.fromMap(list[i]).build();
    });
  }

  Future<void> insert(FarmModel farmModel) async {
    await farmData.insert(farmModel);
  }

  Future<void> update(FarmModel farmModel) async {
    await farmData.update(farmModel);
  }
}

Para hacer referencia a las clases de servicios desde cualquier pantalla e inicializar una única vez, se usó el patrón de diseño Singleton.

⚙️ Pensamientos finales

Funcionalmente la aplicación estará lista usando estos conceptos. Sin embargo, es necesario optimizar el código para construir una aplicación escalable, mantenible usando principios arquitéctonicos eficaes.

En la próxima entrega mostraré cómo se desarrollan nuevas funciones usando como base el libro Flutter Design Patterns and Best Practices: Build scalable, maintainable, and production-ready apps using effective architectural principles de Daria Orlova, Esra Kadah y Jaime Blasco.