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,
);
}
}
2
Answers
do not use BlocProvider(create: …) twice
In the AddTodoScreen class you can remove the BlocProvider(create…
or you can use BlocProvider.value like below
If You already define your Bloc into the main class you don’t need to provide bloc provider to another class
So Please remove Bloc Provider from your add_todo_screen.dart
and update your button with this code :-