Reconstruir la UI cuando el State cambia
Instrucciones sobre cómo manejar el state con ChangeNotifiers.
Aprende a usar ListenableBuilder para reconstruir la UI automáticamente y manejar todos los estados posibles con expresiones switch.
Qué lograrás
Pasos
1
Introducción
Introducción
La capa View es tu interfaz de usuario (UI), y en Flutter,
eso se refiere a los widgets de tu app.
En lo que respecta a este tutorial, la parte importante es
conectar tu UI para responder a los cambios de datos desde el ViewModel.
ListenableBuilder
es un widget que puede "escuchar" a un
ChangeNotifier, y se reconstruye automáticamente cuando el
ChangeNotifier proporcionado llama a notifyListeners().
2
Crear el widget de la vista del artículo
Crear el widget de la vista del artículo
Crea el widget ArticleView que
administra el diseño de tu página y el ciclo de vida de tu ViewModel.
Dado que debe inicializar explícitamente la obtención de datos antes de renderizar,
impleméntalo como un StatefulWidget.
Comienza creando la estructura básica con estado (stateful):
import 'package:flutter/material.dart';
class ArticleView extends StatefulWidget {
const ArticleView({super.key});
@override
State<ArticleView> createState() => _ArticleViewState();
}
class _ArticleViewState extends State<ArticleView> {
// The view model will be instantiated here next.
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Wikipedia Flutter')),
body: const Center(child: Text('Loading...')),
);
}
}
3
Instanciar el ViewModel del artículo
Instanciar el ViewModel del artículo
A continuación, inicializa tu ArticleViewModel mapeándolo al ciclo de vida del State.
Proporciona el ViewModel y ejecuta fetchArticle() dentro de initState():
class ArticleView extends StatefulWidget {
const ArticleView({super.key});
@override
State<ArticleView> createState() => _ArticleViewState();
}
class _ArticleViewState extends State<ArticleView> {
final ArticleViewModel viewModel = ArticleViewModel(ArticleModel());
@override
void initState() {
super.initState();
viewModel.fetchArticle();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Wikipedia Flutter')),
body: const Center(child: Text('Loading...')),
);
}
}
4
Actualiza tu aplicación para incluir la vista del artículo
Actualiza tu aplicación para incluir la vista del artículo
Conecta todo actualizando tu MainApp para
incluir tu ArticleView completado.
Reemplaza tu MainApp existente con esta versión actualizada:
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const MaterialApp(home: ArticleView());
}
}
Este cambio pasa de la prueba basada en consola a la experiencia completa de UI con una administración del State adecuada.
5
Escuchar los cambios de State
Escuchar los cambios de State
Envuelve tu UI en un ListenableBuilder
para escuchar los cambios de State,
y pásale un objeto ChangeNotifier.
En este caso, ArticleViewModel extiende ChangeNotifier.
class ArticleView extends StatefulWidget {
const ArticleView({super.key});
@override
State<ArticleView> createState() => _ArticleViewState();
}
class _ArticleViewState extends State<ArticleView> {
final ArticleViewModel viewModel = ArticleViewModel(ArticleModel());
@override
void initState() {
super.initState();
viewModel.fetchArticle();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Wikipedia Flutter')),
body: ListenableBuilder(
listenable: viewModel,
builder: (context, child) {
return const Center(child: Text('Loading...'));
},
),
);
}
}
ListenableBuilder utiliza el patrón builder,
el cual requiere un callback en lugar de un widget child para
construir el árbol de widgets debajo de él.
Estos widgets son flexibles porque puedes
realizar operaciones dentro del callback,
construyendo diferentes widgets según el State.
6
Manejar los posibles estados del ViewModel
Manejar los posibles estados del ViewModel
Recuerda el ArticleViewModel, el cual tiene tres propiedades en las que
la UI está interesada:
Summary? summarybool isLoadingException? error
Dependiendo del estado combinado de estas propiedades, la UI puede mostrar diferentes widgets. Usa el soporte de Dart para expresiones switch para manejar todas las combinaciones posibles de una forma limpia y legible:
class ArticleView extends StatefulWidget {
const ArticleView({super.key});
@override
State<ArticleView> createState() => _ArticleViewState();
}
class _ArticleViewState extends State<ArticleView> {
final ArticleViewModel viewModel = ArticleViewModel(ArticleModel());
@override
void initState() {
super.initState();
viewModel.fetchArticle();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Wikipedia Flutter')),
body: Center(
child: ListenableBuilder(
listenable: viewModel,
builder: (context, _) {
return switch ((
viewModel.isLoading,
viewModel.summary,
viewModel.error,
)) {
(true, _, _) => const CircularProgressIndicator(),
(_, _, final Exception e) => Text('Error: $e'),
(_, final summary?, _) => ArticlePage(
summary: summary,
nextArticleCallback: viewModel.fetchArticle,
),
_ => const Text('Something went wrong!'),
};
},
),
),
);
}
}
Este es un excelente ejemplo de cómo un framework declarativo y reactivo como Flutter y un patrón como MVVM trabajan juntos: La UI se renderiza basándose en el State y se actualiza cuando un cambio de State lo demanda, pero no administra ningún State ni el proceso de actualizarse a sí misma. La lógica de negocio y el renderizado están completamente separados entre sí.
7
Completar la UI
Completar la UI
Lo único que queda es usar las propiedades y métodos proporcionados por el ViewModel para construir la UI.
Ahora crea un widget ArticlePage que muestre el contenido real del artículo.
Este widget reutilizable recibe datos de resumen (summary) y una función callback:
class ArticlePage extends StatelessWidget {
const ArticlePage({
super.key,
required this.summary,
required this.nextArticleCallback,
});
final Summary summary;
final VoidCallback nextArticleCallback;
@override
Widget build(BuildContext context) {
return const Center(
child: Text('Article content will be displayed here...'),
);
}
}
8
Añadir un diseño con scroll
Añadir un diseño con scroll
Reemplaza el marcador de posición con un diseño de columna con scroll:
class ArticlePage extends StatelessWidget {
const ArticlePage({
super.key,
required this.summary,
required this.nextArticleCallback,
});
final Summary summary;
final VoidCallback nextArticleCallback;
@override
Widget build(BuildContext context) {
return const SingleChildScrollView(
child: Column(
children: [Text('Article content will be displayed here...')],
),
);
}
}
9
Añadir contenido del artículo y botón
Añadir contenido del artículo y botón
Completa el diseño con un widget de artículo y un botón de navegación:
class ArticlePage extends StatelessWidget {
const ArticlePage({
super.key,
required this.summary,
required this.nextArticleCallback,
});
final Summary summary;
final VoidCallback nextArticleCallback;
@override
Widget build(BuildContext context) {
return SingleChildScrollView(
child: Column(
children: [
ArticleWidget(summary: summary),
ElevatedButton(
onPressed: nextArticleCallback,
child: const Text('Next random article'),
),
],
),
);
}
}
10
Crear el ArticleWidget
Crear el ArticleWidget
El ArticleWidget maneja la visualización del contenido real del artículo
con el estilo adecuado y renderizado condicional.
Configurar la estructura básica del artículo
#Comienza con el widget que acepta un parámetro summary:
class ArticleWidget extends StatelessWidget {
const ArticleWidget({super.key, required this.summary});
final Summary summary;
@override
Widget build(BuildContext context) {
return const Text('Article content will be displayed here...');
}
}
Añadir padding y diseño de columna
#Envuelve el contenido en el padding y diseño adecuados:
class ArticleWidget extends StatelessWidget {
const ArticleWidget({super.key, required this.summary});
final Summary summary;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: Column(
spacing: 10,
children: [const Text('Article content will be displayed here...')],
),
);
}
}
Añadir visualización condicional de imagen
#Añade la imagen del artículo que solo se muestra cuando está disponible:
class ArticleWidget extends StatelessWidget {
const ArticleWidget({super.key, required this.summary});
final Summary summary;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: Column(
spacing: 10,
children: [
if (summary.hasImage) Image.network(summary.originalImage!.source),
const Text('Article content will be displayed here...'),
],
),
);
}
}
Completar con contenido de texto estilizado
#Reemplaza el texto de marcador de posición con un título, descripción y extracto con el estilo adecuado:
class ArticleWidget extends StatelessWidget {
const ArticleWidget({super.key, required this.summary});
final Summary summary;
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8),
child: Column(
spacing: 10,
children: [
if (summary.hasImage) Image.network(summary.originalImage!.source),
Text(
summary.titles.normalized,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.displaySmall,
),
if (summary.description != null)
Text(
summary.description!,
overflow: TextOverflow.ellipsis,
style: Theme.of(context).textTheme.bodySmall,
),
Text(summary.extract),
],
),
);
}
}
Este widget demuestra algunos conceptos importantes de UI:
- Renderizado condicional:
Las sentencias
ifmuestran contenido solo cuando está disponible. - Estilo de texto: Diferentes estilos de texto crean una jerarquía visual utilizando el sistema de temas de Flutter.
- Espaciado adecuado:
El parámetro
spacingproporciona un espaciado vertical consistente. - Manejo de desbordamiento (overflow):
TextOverflow.ellipsisevita que el texto rompa el layout.
11
Ejecutar la aplicación completa
Ejecutar la aplicación completa
Haz Hot reload en tu aplicación por última vez. Ahora deberías ver:
- Un indicador de carga mientras se carga el artículo inicial.
- El título del artículo, la descripción y el extracto del resumen.
- Una imagen (si el artículo tiene una).
- Un botón para cargar otro artículo aleatorio.
Para ver la UI reactiva en acción, haz clic en el botón Next random article. La app muestra un estado de carga, obtiene nuevos datos y actualiza la pantalla automáticamente.
12
Revisión
Revisión
Qué lograste
Aquí tienes un resumen de lo que construiste y aprendiste en esta lección.Usé ListenableBuilder para reconstruir la UI automáticamente
ListenableBuilder escucha a tu ViewModel y reconstruye automáticamente sus hijos cada vez que se llama a
notifyListeners(). En el patrón MVVM, esta es la conexión clave entre tu ViewModel y la View.
Manejé todos los estados posibles con expresiones switch
Utilizando una expresión switch, tuviste en cuenta las posibles combinaciones de estados con una interfaz de usuario adecuada, mostrando condicionalmente un indicador de carga, un mensaje de error o el contenido real del artículo. Con este manejo, la UI es ahora más robusta y completa.
Construí la capa View completa con el estilo adecuado
Creaste ArticleView, ArticlePage y ArticleWidget con renderizado condicional, estilo de texto, espaciado adecuado y manejo de overflow. Estos son patrones de UI principales que usarás en cada aplicación Flutter.
Completé la arquitectura MVVM
Has construido una aplicación completa con las capas Model (operaciones de datos), ViewModel (administración del State) y View (UI reactiva). Esta separación de responsabilidades ayuda a que tu código sea más fácil de probar, mantener y escalar.
13
Ponte a prueba
Ponte a prueba
Quiz de ListenableBuilder
1 / 2Para crear animaciones basadas en un ChangeNotifier.
No exactamente.
ListenableBuilder reconstruye la UI ante cambios de State, no específicamente para animaciones.
Para controlar manualmente cuándo se deben reconstruir los widgets.
No exactamente.
La reconstrucción es automática cuando se llama a notifyListeners(); no la controlas manualmente.
Para escuchar a un ChangeNotifier y reconstruir automáticamente sus widgets hijos cuando se llama a
notifyListeners().¡Así es!
ListenableBuilder escucha a un Listenable y reconstruye su función builder cuando recibe una notificación.
Para almacenar en caché las construcciones de widgets y mejorar el rendimiento.
No exactamente.
ListenableBuilder se trata de actualizaciones reactivas, no de almacenamiento en caché.
Cuando el Listenable al que está escuchando llama a notifyListeners().
¡Así es!
ListenableBuilder se suscribe al Listenable y reconstruye su función builder cada vez que se llama a
notifyListeners().Solo cuando el widget se monta por primera vez.
No exactamente.
Se reconstruye cada vez que se llama a
notifyListeners(), no solo al montarse.Cuando el widget padre se reconstruye.
No exactamente.
ListenableBuilder se reconstruye basándose en el Listenable, no en reconstrucciones del padre.
Cada vez que se refresca el frame de la aplicación.
No exactamente.
ListenableBuilder solo se reconstruye cuando recibe una notificación, no en cada frame.
A menos que se indique lo contrario, la documentación en este sitio refleja Flutter 3.44.0. Página actualizada por última vez el 2026-05-06. Ver código fuente oreportar un problema.