flutterblocflutter-bloc

BlocProvider.of() called with a context that does not contain a TodoBloc


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,
    );
  }
}

Solution

  • 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.