skip to Main Content

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


  1. do not use BlocProvider(create: …) twice

    In the AddTodoScreen class you can remove the BlocProvider(create…

    BlocProvider(
          create: (context) => TodoBloc(),
    

    or you can use BlocProvider.value like below

    BlocProvider.value(
      value: TodoBloc(),
      child: ...
    )
    
    Login or Signup to reply.
  2. 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 :-

      ElevatedButton(
                onPressed: () {
                  print(1);
                  String title = titleController.text;
                  print(2);
                  String description = descriptionController.text;
                  print(3);
                  if (title.isNotEmpty && description.isNotEmpty) {
                      context.read<TodoBloc>().add(
                        AddTodo(title,description),
                      );
                     print(4);
                    Navigator.pop(context); // Close the screen after adding 
                    todo
                    print(5);
                  }
                },
                child: Text('Add Todo'),
              ),
          },);
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search