Learning state management with a real example to-do app is a great choice for it. I will show you how to create a very simple to-do app with Flutter provider. Here we gonna use the Provider package to manage the state of the app.

Features:

  • Todo List

todo list

  • Add Todo

add todo

  • Edit Todo

Edit todo

  • Delete Todo

delete todo

Steps create a simple todo app:

Create a blank flutter project:

flutter create flutter_todo_app_with_provider

Add provider package: 

flutter pub add provider

Create todo model:

model/todo.dart:
class TodoFiled {
static const createdTime = 'createdTime';
}

class Todo {
DateTime createdTime;
String title;
String id;
String description;
bool isDone;

Todo({
required this.title,
required this.createdTime,
this.description = '',
required this.id,
this.isDone = false,
});
}

Create todo model provider:

provider/todos.dart:
import 'package:flutter/cupertino.dart';
import '../model/todo.dart';

class TodosProvider extends ChangeNotifier{
List _todos = [
Todo(
id: "1",
title: 'Buy Food πŸ•',
createdTime: DateTime.now(),
description: '''
- Eggs
- Milk
- Bread
- Water
'''
),
Todo(
id: "2",
title: 'Plan family trip to Norway ✈️',
createdTime: DateTime.now(),
description: '''
- Rent some hotels
- Rent a car
- Pack suitcase

'''
),
Todo(
id: "3",
title: 'Walk the Dog πŸ•',
createdTime: DateTime.now(),
),
Todo(
id: "4",
title: 'plan Jacobs birthday party πŸ₯³ πŸŽ‰',
createdTime: DateTime.now(),
),
];

List get todos=> _todos.where((todo) =>todo.isDone== false).toList();
List get todosCompleted=> _todos.where((todo) =>todo.isDone== true).toList();

void addTodo(Todo todo){
_todos.add(todo);
notifyListeners();
}
void removeTodo(Todo todo){
_todos.remove(todo);
notifyListeners();
}
bool toggleTodoStatus(Todo todo){
todo.isDone = !todo.isDone;
notifyListeners();

return todo.isDone;
}
void updateTodo(Todo todo, String title, String description){
todo.title = title;
todo.description= description;
notifyListeners();
}
}

Create widgets:

widgets/add_todo_dialog_widget.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../model/todo.dart';
import '../provider/todos.dart';
import 'todo_form_widget.dart';

class AddTodoDialogWidget extends StatefulWidget {
@override
_AddTodoDialogWidgetState createState() => _AddTodoDialogWidgetState();
}

class _AddTodoDialogWidgetState extends State {
final _formKey= GlobalKey();
String title = '';
String description = '';
@override
Widget build(BuildContext context) {
return AlertDialog(
content: Form(
key: _formKey,
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'add Todo',
style: TextStyle(
fontSize: 22,
fontWeight: FontWeight.bold,
),
),
SizedBox(
height: 8,
),
TodoFormWidget(
onChangedTitle: (title) {
setState(() {
this.title = title;
});
},
onChangedDescription: (description) {
setState(() {
this.description = description;
});
},
onSavedTodo: addTodo,
)
],
),
),
);
}

void addTodo() {
final isValid = _formKey.currentState!.validate();

if(!isValid) {
return;
} else{
final todo = Todo(
id: DateTime.now().toString(),
title: title,
description: description,
createdTime: DateTime.now(),
);
final provider = Provider.of(context,listen: false);
provider.addTodo(todo);

Navigator.of(context).pop();
}
}
}
widgets/completed_list_widget.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../provider/todos.dart';
import 'todo_widget.dart';

class CompletedListWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final provider = Provider.of(context);
final todos = provider.todosCompleted;

return todos.isEmpty
? Center(
child: Text(
'No completed tasks',
style: TextStyle(fontSize: 20),
),
)
: ListView.separated(
separatorBuilder: (context, index) => Container(
height: 20,
),
physics: BouncingScrollPhysics(),
padding: EdgeInsets.all(16),
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];

return TodoWidget(
todo: todo,
);
},
);
}
}
widgets/todo_form_widget.dart:
import 'package:flutter/material.dart';

class TodoFormWidget extends StatelessWidget {
final String? title;
final String? description;
final ValueChanged? onChangedTitle;
final ValueChanged? onChangedDescription;
final VoidCallback? onSavedTodo;

const TodoFormWidget(
{Key? key,
this.title,
this.description,
this.onChangedTitle,
this.onChangedDescription,
this.onSavedTodo})
: super(key: key);

@override
Widget build(BuildContext context) {
return Container(
child: SingleChildScrollView(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
buildTitle(),
SizedBox(height: 8,),
buildDescription(),
SizedBox(height: 32),
buildButton(),
],
),
),
);
}

Widget buildTitle() => TextFormField(
initialValue: title,
onChanged: onChangedTitle,
validator: (title) {
if (title!.isEmpty) {
return 'the title can not be empty';
}
return null;
},
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelText: 'Title',
),
);
Widget buildDescription() => TextFormField(
maxLines: 3,
initialValue: description,
onChanged: onChangedDescription,
decoration: InputDecoration(
border: UnderlineInputBorder(),
labelText: 'Description'
),
);
Widget buildButton()=> SizedBox(
width: double.infinity,
child: ElevatedButton(
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
),
onPressed: onSavedTodo,
child: Text('Save'),
),
);
}
widgets/todo_list_widget.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../provider/todos.dart';
import 'todo_widget.dart';

class TodoListWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final provider = Provider.of(context);
final todos = provider.todos;

return todos.isEmpty
? Center(
child: Text(
'No todos',
style: TextStyle(fontSize: 20),
),
)
: ListView.separated(
separatorBuilder: (context, index) => Container(
height: 20,
),
physics: BouncingScrollPhysics(),
padding: EdgeInsets.all(16),
itemCount: todos.length,
itemBuilder: (context, index) {
final todo = todos[index];

return TodoWidget(
todo: todo,
);
},
);
}
}
widgets/todo_widget.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../page/edit_todo_page.dart';
import '../model/todo.dart';
import '../provider/todos.dart';
import '../util/utils.dart';

class TodoWidget extends StatelessWidget {
final Todo todo;

const TodoWidget({Key? key, required this.todo}) : super(key: key);
@override
Widget build(BuildContext context) {
return ClipRRect(
borderRadius: BorderRadius.circular(16),
child: buildTodo(context),
);
}

Widget buildTodo(BuildContext context)=> GestureDetector(
onTap: ()=> editTodo(context, todo),

child: Container(
color: Colors.white,
padding: EdgeInsets.all(20),
child: Row(
children: [
Checkbox(
activeColor: Colors.blue,
checkColor: Colors.white,
value: todo.isDone,
onChanged: (_){
final provider = Provider.of(context, listen: false);
final isDone= provider.toggleTodoStatus(todo);
Utils.showSnackBar(context,
isDone ? 'Task completed' :
'Task marked incomplete'

);

},
),
SizedBox(width: 20,),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
todo.title,
style: TextStyle(
fontWeight: FontWeight.bold,
color: Colors.blue,
fontSize: 22,
),
),
if(todo.description.isNotEmpty)
Container(
margin: EdgeInsets.only(top: 4),
child: Text(
todo.description,
style: TextStyle(fontSize: 20, height: 1.5),
),
),
],
)
),
IconButton(onPressed: (){
editTodo(context,todo);
}, icon: Icon(Icons.edit),color: Colors.green,),
IconButton(onPressed: (){
showAlertDialog(context);
}, icon: Icon(Icons.delete),color: Colors.red,)
],
),
),
);

void deleteTodo(BuildContext context, Todo todo) {
final provider = Provider.of(context, listen: false);
provider.removeTodo(todo);

Utils.showSnackBar(context, 'Deleted the task');
}

void editTodo(BuildContext context, Todo todo) {
Navigator.of(context).push(MaterialPageRoute(builder: (context)=>EditTodoPage(todo: todo))) ;
}
showAlertDialog(BuildContext context) {

// set up the button
Widget okButton = TextButton(
child: Text("OK"),
onPressed: () {
deleteTodo(context, todo);
Navigator.of(context).pop();
},
);
// set up the buttons
Widget cancelButton = TextButton(
child: Text("Cancel"),
onPressed: () {
Navigator.of(context).pop();
},
);

// set up the AlertDialog
AlertDialog alert = AlertDialog(
title: Text("Delete Todo"),
content: Text("Are you sure."),
actions: [
okButton,
cancelButton
],
);

// show the dialog
showDialog(
context: context,
builder: (BuildContext context) {
return alert;
},
);
}
}
void doNothing(BuildContext context) {}

Create util:

util/utils.dart:
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';

class Utils{
static void showSnackBar(BuildContext context, String text)=>
Scaffold.of(context)
..removeCurrentSnackBar()
..showSnackBar(SnackBar(content: Text(text)));
}

Create page:

page/edit_todo_page.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import '../model/todo.dart';
import '../provider/todos.dart';
import '../widgets/todo_form_widget.dart';

class EditTodoPage extends StatefulWidget {
final Todo todo;

const EditTodoPage({Key? key, required this.todo}) : super(key: key);
@override
_EditTodoPageState createState() => _EditTodoPageState();
}

class _EditTodoPageState extends State {
final _formKey = GlobalKey();
late String title;
late String description;

@override
void initState() {
// TODO: implement initState
super.initState();
title= widget.todo.title;
description= widget.todo.description;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Edit Todo'),
actions: [
IconButton(
icon: Icon(Icons.delete),
onPressed: (){
final provider = Provider.of(context, listen: false);
provider.removeTodo(widget.todo);
Navigator.of(context).pop();
}
)
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Form(
key: _formKey,
child: TodoFormWidget(
title: title,
description: description,
onChangedTitle: (title){
setState(() {
this.title= title;
});
},
onChangedDescription: (description){
setState(() {
this.description= description;
});
},
onSavedTodo: saveTodo,
),
),
),
);
}

void saveTodo() {
final isValid= _formKey.currentState!.validate();
if(!isValid){
return;
} else{
final provider = Provider.of(context, listen: false);
provider.updateTodo(widget.todo, title, description);
Navigator.of(context).pop();
}
}
}

Complete your main.dart file:

main.dart:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'provider/todos.dart';
import 'widgets/add_todo_dialog_widget.dart';
import 'widgets/completed_list_widget.dart';
import 'widgets/todo_list_widget.dart';

void main() {
runApp(ChangeNotifierProvider(
create: (context)=> TodosProvider(),
child: MaterialApp
(
debugShowCheckedModeBanner: false,
home : MyApp())));
}

class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}

class _MyAppState extends State {
int selectedIndex = 0;
@override
Widget build(BuildContext context) {
final tabs = [
TodoListWidget(),
CompletedListWidget(),
];

return Scaffold(
appBar: AppBar(
backgroundColor: Colors.blue,
title: Text("Todo App"),
),
bottomNavigationBar: BottomNavigationBar(
backgroundColor: Colors.blue,
unselectedItemColor: Colors.white.withOpacity(0.7),
selectedItemColor: Colors.white,
currentIndex: selectedIndex,
onTap: (index) {
setState(() {
selectedIndex = index;
});
},
items: [
BottomNavigationBarItem(
icon: Icon(Icons.fact_check_outlined),
label: 'Todos',
),
BottomNavigationBarItem(
icon: Icon(
Icons.done,
size: 28,
),
label: 'Completed',
),
],
),
body: tabs[selectedIndex],
floatingActionButton: FloatingActionButton(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
backgroundColor: Colors.blue,
onPressed: () => showDialog(
builder: (context) => AddTodoDialogWidget(), context: context,
barrierDismissible: true,
),
child: Icon(Icons.add),
),
);
}
}

Video Demo and Source Code:

 

Source Code