Below is the code for a TODO application that I am developing using the BLoC state management pattern. However, I am encountering an error when navigating to the "Add Todo" screen and attempting to add a new todo item by pressing the button.
Full error message:
BlocProvider.of() called with a context that does not contain a TodoBloc.
No ancestor could be found starting from the context that was passed to BlocProvider.of<TodoBloc>(). This can happen if the context you used comes from a widget above the BlocProvider. The context used was: AddTodoScreen
pubspec.yaml file
name: todo_app_flutter
description: "A new Flutter project."
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
version: 1.0.0+1
environment:
sdk: '>=3.3.0 <4.0.0'
dependencies:
flutter:
sdk: flutter
shared_preferences: ^2.2.2
flutter_bloc: ^8.1.4
equatable: ^2.0.5
provider: ^6.1.2
cupertino_icons: ^1.0.6
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.0
flutter:
uses-material-design: true
main.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:todo_app_flutter/todo_screen.dart';
import 'bloc/todo_bloc.dart';
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Todo App',
home: BlocProvider(
create: (context) => TodoBloc(),
child: TodoScreen(),
),
);
}
}
todo_screen.dart
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'add_todo_screen.dart';
import 'bloc/todo_bloc.dart';
import 'main.dart';
class TodoScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Todo List'),
),
body:BlocBuilder<TodoBloc, TodoState>(
builder: (context, state) {
if (state is TodoInitial) {
// Initial state: show loading indicator or initial UI
return CircularProgressIndicator();
} else if (state is TodoLoaded) {
// Loaded state: display todo list
return ListView.builder(
itemCount: state.todos.length,
itemBuilder: (context, index) {
final todo = state.todos[index];
return ListTile(
title: Text(todo.title),
subtitle: Text(todo.description),
onTap: () {
// Handle tapping on a todo item (e.g., navigate to detail screen)
},
);
},
);
} else if (state is TodoError) {
// Error state: display error message
return Text('Error: ${state.message}');
} else {
// Other states: handle edge cases
return Container();
}
},
)
,
floatingActionButton: FloatingActionButton(
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => AddTodoScreen()),
);
},
child: Icon(Icons.add),
),
);
}
}
add_todo_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:todo_app_flutter/bloc/todo_bloc.dart';
class AddTodoScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
TextEditingController titleController = TextEditingController();
TextEditingController descriptionController = TextEditingController();
return BlocProvider(
create: (context) => TodoBloc(),
child: Scaffold(
appBar: AppBar(
title: const Text('Add Todo'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
TextFormField(
controller: titleController,
decoration: InputDecoration(labelText: 'Title'),
),
SizedBox(height: 16.0),
TextFormField(
controller: descriptionController,
decoration: InputDecoration(labelText: 'Description'),
maxLines: 3,
),
SizedBox(height: 16.0),
ElevatedButton(
onPressed: () {
print(1);
String title = titleController.text;
print(2);
String description = descriptionController.text;
print(3);
if (title.isNotEmpty && description.isNotEmpty) {
BlocProvider.of<TodoBloc>(context).add(
AddTodo(title, description),
);
print(4);
Navigator.pop(context); // Close the screen after adding todo
print(5);
}
},
child: Text('Add Todo'),
),
],
),
),
),
);
}
}
todo_bloc.dart
import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import '../models/todo.dart';
part 'todo_event.dart';
part 'todo_state.dart';
class TodoBloc extends Bloc<TodoEvent, TodoState> {
List<Todo> _todos = [];
TodoBloc() : super(TodoLoaded([
Todo(id: 1, title: 'Sample Todo 1', description: 'Description 1'),
Todo(id: 2, title: 'Sample Todo 2', description: 'Description 2'),
]));
@override
Stream<TodoState> mapEventToState(TodoEvent event,) async* {
print("TodoBloc");
if (event is AddTodo) {
final newTodo = Todo(
id: _todos.length + 1, // Generate unique id for the new todo
title: event.title,
description: event.description,
);
_todos.add(newTodo); // Add the new todo to the list
yield TodoLoaded(List.from(_todos)); // Emit the updated state
} else if (event is ToggleTodo) {
final todoIndex = _todos.indexWhere((todo) => todo.id == event.id); // Find todo by id
if (todoIndex != -1) {
_todos[todoIndex] = _todos[todoIndex].copyWith(
);
yield TodoLoaded(List.from(_todos)); // Emit the updated state
}
} else if (event is DeleteTodo) {
_todos.removeWhere((todo) => todo.id == event.id); // Remove todo by id
yield TodoLoaded(List.from(_todos)); // Emit the updated state
}
}
}
todo_state.dart
part of 'todo_bloc.dart';
abstract class TodoState extends Equatable {
const TodoState();
@override
List<Object?> get props => [];
}
class TodoInitial extends TodoState {
}
class TodoLoaded extends TodoState {
final List<Todo> todos;
TodoLoaded(this.todos);
@override
List<Object?> get props => [todos];
}
class TodoError extends TodoState {
final String message;
TodoError(this.message);
@override
List<Object?> get props => [message];
}
todo_event.dart
part of 'todo_bloc.dart';
abstract class TodoEvent extends Equatable {
const TodoEvent();
@override
List<Object?> get props => [];
}
class AddTodo extends TodoEvent {
final String title;
final String description;
AddTodo(this.title, this.description);
@override
List<Object?> get props => [title, description];
}
class ToggleTodo extends TodoEvent {
final int id;
ToggleTodo(this.id);
@override
List<Object?> get props => [id];
}
class DeleteTodo extends TodoEvent {
final int id;
DeleteTodo(this.id);
@override
List<Object?> get props => [id];
}
class LoadTodos extends TodoEvent {
LoadTodos(List<Todo> todos);
} // Define LoadTodos event
todo.dart
class Todo {
final int id;
final String title;
final String description;
Todo({
required this.id,
required this.title,
required this.description,
});
factory Todo.fromJson(Map<String, dynamic> json) {
return Todo(
id: json['id'] as int,
title: json['title'] as String,
description: json['description'] as String,
);
}
Map<String, dynamic> toJson() {
return {
'id': id,
'title': title,
'description': description,
};
}
Todo copyWith({
int? id,
String? title,
String? description,
}) {
return Todo(
id: id ?? this.id,
title: title ?? this.title,
description: description ?? this.description,
);
}
}
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:todo_app_flutter/todo_screen.dart';
import 'bloc/todo_bloc.dart';
void main() => runApp(App());
class App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MultiBlocProvider(
providers: [
BlocProvider(
create: (_) => TodoBloc()),
],
child: const AppView(),
);
}
}
class AppView extends StatelessWidget {
const AppView({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Todo App',
home: TodoScreen(),
);
}
}
I see you seem to be attempting to set TodoBloc
as a global state, but in reality, your current approach only sets the bloc state on the TodoScreen
page. You can modify the writing of main.dart as the code I sent above. This way, you'll have a global TodoBloc
, and you'll need to remove the BlocProvider( create: (context) => TodoBloc(),
in add_todo_screen.dart
.