Lista de tareas: Estados vacíos
Parte de la sección Proyectos del Journey de Flutter de Coddy. Lección 9 de 36.
Toca «Listo» antes de terminar cualquier tarea y verás una pantalla en blanco. ¿Está rota o no hay nada? Un estado vacío responde a esa pregunta: un icono discreto y una línea que explica qué ocurre.
Cada filtro tiene su propio mensaje, así que una expresión switch sobre _filter lo elige:
String get _emptyMessage => switch (_filter) {
Filter.all => 'No tasks yet',
Filter.active => 'Nothing left to do',
Filter.done => 'Nothing done yet',
};Después, el espacio de la lista muestra una cosa u otra: shown.isEmpty ? Center(...) : ListView.builder(...). Comprueba la lista que se muestra, no _tasks: en «Listo», la lista completa no está vacía, pero la pantalla sí.
Dale al icono el color outline del tema, Theme.of(context).colorScheme.outline. Es el gris suave de tu color semilla, así que el estado vacío parece formar parte de la aplicación.
Esta es la última lección de la lista de tareas. Las comprobaciones prueban la aplicación completa: añadir, marcar, deslizar y filtrar.
Desafío
IntermedioMuestra un estado vacío cuando shown esté vacío:
- añade un getter
_emptyMessage:No tasks yetpara Todas,Nothing left to dopara Activas,Nothing done yetpara Hechas - en lugar de la lista, muestra una
Columncentrada con un iconoIcons.task_alt(tamaño 64, coloroutlinedel tema), un espacio de 12 píxeles y el mensaje - si no, muestra la lista como antes
Pruébalo tú mismo
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.teal),
),
home: const TodoScreen(),
);
}
}
class Task {
Task(this.title);
final String title;
bool done = false;
}
enum Filter { all, active, done }
class TodoScreen extends StatefulWidget {
const TodoScreen({super.key});
@override
State<TodoScreen> createState() => _TodoScreenState();
}
class _TodoScreenState extends State<TodoScreen> {
final List<Task> _tasks = [Task('Buy milk'), Task('Walk the dog'), Task('Read a book')];
final _controller = TextEditingController();
Filter _filter = Filter.all;
int get _left => _tasks.where((task) => !task.done).length;
List<Task> get _shown => switch (_filter) {
Filter.all => _tasks,
Filter.active => _tasks.where((task) => !task.done).toList(),
Filter.done => _tasks.where((task) => task.done).toList(),
};
void _addTask() {
final title = _controller.text.trim();
if (title.isEmpty) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Type a task first')),
);
return;
}
setState(() {
_tasks.add(Task(title));
});
_controller.clear();
}
void _deleteTask(Task task) {
final index = _tasks.indexOf(task);
setState(() {
_tasks.remove(task);
});
ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Deleted ${task.title}'),
action: SnackBarAction(
label: 'Undo',
onPressed: () {
setState(() {
_tasks.insert(index, task);
});
},
),
),
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
final shown = _shown;
return Scaffold(
appBar: AppBar(title: const Text('My Tasks')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.fromLTRB(16, 16, 16, 8),
child: Row(
children: [
Expanded(
child: TextField(
controller: _controller,
decoration: const InputDecoration(
labelText: 'New task',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 8),
IconButton.filled(
onPressed: _addTask,
icon: const Icon(Icons.add),
),
],
),
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Row(
children: [
Text(
_left == 1 ? '1 task left' : '$_left tasks left',
style: Theme.of(context).textTheme.titleSmall,
),
],
),
),
SegmentedButton<Filter>(
segments: const [
ButtonSegment(value: Filter.all, label: Text('All')),
ButtonSegment(value: Filter.active, label: Text('Active')),
ButtonSegment(value: Filter.done, label: Text('Done')),
],
selected: {_filter},
onSelectionChanged: (selection) {
setState(() {
_filter = selection.first;
});
},
),
const SizedBox(height: 8),
Expanded(
child: ListView.builder(
itemCount: shown.length,
itemBuilder: (context, index) {
final task = shown[index];
return Dismissible(
key: ObjectKey(task),
direction: DismissDirection.endToStart,
background: Container(
color: Colors.red,
alignment: Alignment.centerRight,
padding: const EdgeInsets.symmetric(horizontal: 24),
child: const Icon(Icons.delete, color: Colors.white),
),
onDismissed: (direction) => _deleteTask(task),
child: ListTile(
leading: Checkbox(
value: task.done,
onChanged: (value) {
setState(() {
task.done = value!;
});
},
),
title: Text(
task.title,
style: task.done
? const TextStyle(
decoration: TextDecoration.lineThrough,
color: Colors.grey,
)
: null,
),
),
);
},
),
),
],
),
);
}
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Proyectos
1Proyecto: Lista de tareas
Lista de tareas: La lista de tareasLista de tareas: Añadir una tarea4Proyecto: Rastreador de hábitos
Rastreador de hábitos: Tus hábitosRastreador de hábitos: La semana2Proyecto: Cuestionario
Cuestionario: La preguntaCuestionario: Correcto o incorrectoPractica por tu cuenta: Compilador de Flutter online