Curso de Flutter con Firebase

Creación de Listados Dinámicos en Aplicaciones Flutter

Curso de Flutter con Firebase

Contenido del curso

Creación de Listados Dinámicos en Aplicaciones Flutter

Resumen

Construir una pantalla que separe ingresos y gastos en Flutter requiere combinar BlocBuilder, condicionales de estado y un ListView.builder que muestre cada categoría con su total. Aquí verás cómo armar esa lógica paso a paso, partiendo del transcript, para que un listado dinámico responda a cada estado de tu aplicación.

Cómo reemplazar un ListBuilder por un BlocBuilder en Flutter

El primer movimiento es eliminar el list builder existente dentro de category list y convertirlo en un BlocBuilder que reciba las instancias del income expense block y el state que ya venías manejando [00:14].

Este cambio te permite reaccionar a cada estado del bloc dentro del mismo widget, pasando context y state como parámetros del builder. Desde ahí, toda la pantalla se construye en función de condicionales.

¿Qué hace un BlocBuilder en Flutter? Es un widget que escucha los cambios de estado de un Bloc y reconstruye la interfaz cada vez que el estado cambia, recibiendo context y state en su builder.

Qué condicionales de estado debes manejar

La lógica se apoya en cuatro estados que cubren el ciclo completo de la pantalla:

  • TransactionLoading: retorna un CircularProgressIndicator mientras llegan los datos.
  • TransactionLoaded: ejecuta la separación de categorías y construye el listado.
  • TransactionError: muestra un Text con el mensaje recibido desde state.message.
  • Estado vacío: retorna un Center con el texto No transaction found.

Un detalle clave: al comparar estados no uses ==, usa el operador is, porque estás verificando el tipo de la instancia, no una igualdad de valor [10:30].

Cómo separar income y expense con una función privada

Dentro del estado TransactionLoaded necesitas una función que recorra las transacciones y devuelva totales por tipo. Esta función se llama _calculateCategoryTotals y es privada, por eso lleva guion bajo al inicio [02:18].

Recibe un List<IncomeExpenseModel> de transacciones y devuelve un Map<String, double> con dos llaves inicializadas en cero: income y expense. Inicializarlas evita errores cuando aún no hay datos para mostrar.

dart Map<String, double> _calculateCategoryTotals(List<IncomeExpenseModel> transactions) { Map<String, double> categoryTotals = { 'income': 0, 'expense': 0, }; for (var transaction in transactions) { var category = transaction.type; categoryTotals[category] = (categoryTotals[category] ?? 0) + transaction.amount; } return categoryTotals; }

El for recorre cada transacción, toma su type como categoría y suma el amount al acumulado. Si la llave no existe, el operador ?? la inicializa en cero antes de sumar.

Por qué retornar fuera del for

Un error común es colocar el return categoryTotals dentro del bucle. Si lo haces, la función retorna después de la primera iteración y pierdes el resto de las sumas. El return debe ir fuera del for, al final de la función [09:45].

Cómo construir el ListView.builder con tarjetas dinámicas

Con los totales calculados, el siguiente paso es renderizar un ListView.builder que itere sobre categoryData.length y construya una tarjeta por cada categoría [05:42].

Dentro del itemBuilder defines dos variables locales:

  • category: obtenida con categoryData.keys.elementAt(index).
  • total: obtenido con categoryData[category].

Luego retornas un Card con color theme.primaryColorDark, un margin constante usando EdgeInsets.symmetric con valor vertical, y un ListTile como hijo.

¿Para qué sirve ListView.builder en lugar de ListView normal? Construye los elementos de forma perezosa, solo renderiza los visibles en pantalla, lo que mejora el rendimiento cuando tienes listas largas o dinámicas.

Cómo dar formato al título y al trailing

El ListTile recibe tres propiedades clave:

  • title: un Text con el nombre traducido. Si category == 'income' muestras Income, si no, Expense.
  • trailing: un Text con el total formateado como '\$${total.toString()}', usando backslash para escapar el signo pesos.
  • textStyle: fontSize de 16, fontWeight.bold en el título y peso normal en el trailing, con color theme.bar.foreground.

Un detalle de depuración: si ves expense repetido en pantalla, revisa que las llaves del mapa estén en minúscula (income, expense) y que coincidan exactamente con el valor de transaction.type [12:50].

Cómo manejar errores y estados vacíos en la UI

Los dos últimos condicionales completan la experiencia. Para TransactionError retornas un Center con un Text que recibe state.message, sin marcarlo como const porque contiene una variable.

Para el estado vacío, retornas un Center con un Text constante que diga No transaction found. Aquí sí puedes usar const porque el texto es estático.

Con estos cuatro caminos cubiertos (cargando, cargado, error y vacío), tu pantalla de spending responde a cualquier escenario que entregue el bloc.

Te queda pendiente añadir la visualización del income total y ajustar la barra de progreso para que la pantalla quede completa. Tómale un screenshot a tu avance y compártelo en los comentarios.