felangel / bloc

A predictable state management library that helps implement the BLoC design pattern
https://bloclibrary.dev
MIT License
11.79k stars 3.39k forks source link

mapEventToState not called #2048

Closed OnyemaAnthony closed 3 years ago

OnyemaAnthony commented 3 years ago

here is my bloc class, when i trigger the SaveTaskEvent the event is called but the bloc and the repository is not called,am using Sqflite db as my repository

import 'dart:async'; import 'package:bloc/bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/cupertino.dart'; import 'package:todo_list/models/todo_list_model.dart'; import 'package:todo_list/repository/database_repository.dart';

part 'task_event.dart';

part 'task_state.dart';

class TaskBloc extends Bloc<TaskEvent, TaskState> { DatabaseRepository _repository;

TaskBloc({@required DatabaseRepository repository}) : assert (repository != null), _repository = repository, super(TaskInitial());

@override TaskState get initialState => TaskInitial();

@override Stream mapEventToState(TaskEvent event,) async { if (event is FetchAllTaskEvent) { yield _mapFetchAllTAskEventToState(event); } else if (event is SaveTaskEvent) { _mapSaveTaskEventToState(event); } }

Stream _mapFetchAllTAskEventToState( FetchAllTaskEvent event) async* { yield TaskLoadingState(); TodoListModel task;

try {
  List tasks = await _repository.geAllTask();
  for (int i = 0; i < tasks.length; i++) {
    task = TodoListModel.map(tasks[i]);
  }
  yield TaskLoadedState(task);
} catch (e) {
  yield TaskErrorState(e.toString());
}

} Stream _mapSaveTaskEventToState(SaveTaskEvent event) async* { yield TaskLoadingState();

try {
  int result = await _repository.saveTask(event.task);

  yield TaskAddedState(result);
} catch (e) {
  yield TaskErrorState(e.toString());
}

} }

mikededo commented 3 years ago

Hello @OnyemaAnthony πŸ˜ƒ

Here's the code properly formatted - more readable:

import 'dart:async';
import 'package:bloc/bloc.dart';
import 'package:equatable/equatable.dart';
import 'package:flutter/cupertino.dart';
import 'package:todo_list/models/todo_list_model.dart';
import 'package:todo_list/repository/database_repository.dart';

part 'task_event.dart';
part 'task_state.dart';

class TaskBloc extends Bloc<TaskEvent, TaskState> {
  DatabaseRepository _repository;

  TaskBloc({@required DatabaseRepository repository})
      : assert(repository != null),
        _repository = repository,
        super(TaskInitial());

  @override
  TaskState get initialState => TaskInitial();

  @override
  Stream mapEventToState(
    TaskEvent event,
  ) async* {
    if (event is FetchAllTaskEvent) {
      yield* _mapFetchAllTAskEventToState(event);
    } else if (event is SaveTaskEvent) {
      _mapSaveTaskEventToState(event);
    }
  }

  Stream _mapFetchAllTAskEventToState(FetchAllTaskEvent event) async* {
    yield TaskLoadingState();
    TodoListModel task;

    try {
      List tasks = await _repository.geAllTask();
      for (int i = 0; i < tasks.length; i++) {
        task = TodoListModel.map(tasks[i]);
      }
      yield TaskLoadedState(task);
    } catch (e) {
      yield TaskErrorState(e.toString());
    }
  }

  Stream _mapSaveTaskEventToState(SaveTaskEvent event) async* {
    yield TaskLoadingState();

    try {
      int result = await _repository.saveTask(event.task);

      yield TaskAddedState(result);
    } catch (e) {
      yield TaskErrorState(e.toString());
    }
  }
}

Do you have Equatable implemented in the TaskEvent class and subclasses? Could you debug the following, and see whether the app stops or not?

@override
Stream mapEventToState(
  TaskEvent event,
) async* {
  assert(event is SaveTaskEvent);

  if (event is FetchAllTaskEvent) {
    yield* _mapFetchAllTAskEventToState(event);
  } else if (event is SaveTaskEvent) {
    _mapSaveTaskEventToState(event);
  }
}

We would need a bit more of information in order to guess where the error may be πŸ˜„

OnyemaAnthony commented 3 years ago

@mikededo thanks for your reply, what more information do you need,I created a class where I performed CRUD with sqflite DB,I used the class as the repository of the bloc then I tried adding an item to the DB from the UI and it's working well but when I migrate it to bloc and trigger the SaveTaskEvent it's shows on the log that the event was trigger but the Bloc is not being called

mikededo commented 3 years ago

Are you using a BlocObserver which prints when onEvent is called? (Check this link from the docs).

OnyemaAnthony commented 3 years ago

Yea i used Bloc observer,am comfortable using Bloc with firebase Firestore as the repository, this is my first time of using sqflite and that is the first time am experiencing this error

mikededo commented 3 years ago

With this:

I tried adding an item to the DB from the UI and it's working well

Do you mean that your databse reflectes the changes? Or no new item is added? πŸ€”

OnyemaAnthony commented 3 years ago

No new item was added, the function that adds the item is in the repository and the function was never called

OnyemaAnthony commented 3 years ago

The function that adds the item is called from the mapSaveTaskToState function,due to the Bloc was not called the item was not saved

mikededo commented 3 years ago

The function that adds the item is called from the mapSaveTaskToState function,due to the Bloc was not called the item was not saved.

Yes, I expected that. It kind of strange, ngl. Could you attach some code, for instance, where do you call add the event? πŸ‘€

OnyemaAnthony commented 3 years ago

here is the code i use as my repository that is the sqflite file

import 'dart:io'; import 'package:flutter/material.dart'; import 'package:path_provider/path_provider.dart'; import 'package:sqflite/sqflite.dart'; import 'package:path/path.dart'; import 'package:todo_list/models/todo_list_model.dart'; import 'package:todo_list/utility/utilities.dart';

class DatabaseRepository { static final DatabaseRepository _instance = DatabaseRepository.internal();

factory DatabaseRepository() => _instance;

static Database _db;

DatabaseRepository.internal();

Future get db async { if (_db != null) { return _db; } _db = await initDb(); }

initDb() async { Directory documentDirectory = await getApplicationDocumentsDirectory();

String path = join(documentDirectory.path, 'tododb.db');

var dataBase = await openDatabase(path, version: 1, onCreate: _onCreate);

}

void _onCreate(Database db, int newVersion) async { await db.execute( "CREATE TABLE ${Utility.todoTable}(${Utility.id} INTEGER PRIMARY KEY, ${Utility.task} TEXT,${Utility.deadline}TEXT})"); }

Future saveTask(TodoListModel todoListModel) async { var dbClient = await db;

int result =
    await dbClient.insert(Utility.todoTable, todoListModel.toMap());
return result;

}

Future geAllTask() async { var dbClient = await db;

var result = await dbClient.rawQuery("SELECT * FROM ${Utility.todoTable}");

return result.toList();

}

Future getCount() async { var dbClient = await db;

return Sqflite.firstIntValue(
    await dbClient.rawQuery("SELECT COUNT(*) FROM ${Utility.todoTable}"));

}

Future getTask(int id) async { var dbClient = await db;

var result = await dbClient.rawQuery(
    "SELECT * FROM ${Utility.todoTable} WHERE ${Utility.id} = $id");

if (result.length == 0) {
  return null;
} else {
  return TodoListModel.fromMap(result.first);
}

}

Future deleteTask(int id) async { var dbClient = await db;

return await dbClient
    .delete(Utility.todoTable, where: "${Utility.id} = ?", whereArgs: [id]);

}

Future updateTask(TodoListModel todoListModel) async { var dbClient = await db; return await dbClient.update(Utility.todoTable, todoListModel.toMap(), where: "${Utility.id} = ?", whereArgs: [todoListModel.id]); }

Future close()async{ var dbClient = await db; return dbClient.close(); } }

OnyemaAnthony commented 3 years ago

here is the ui part where am calling the event, am adding the event in the saveTask function

import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:intl/intl.dart'; import 'package:todo_list/bloc/task/task_bloc.dart'; import 'package:todo_list/models/todo_list_model.dart'; import 'package:todo_list/repository/database_repository.dart'; import 'package:todo_list/utility/utilities.dart';

class AddTaskScreen extends StatefulWidget { @override _AddTaskScreenState createState() => _AddTaskScreenState(); }

class _AddTaskScreenState extends State<AddTaskScreen> { TaskBloc taskBloc;

TextEditingController taskController = TextEditingController(); TextEditingController dateController = TextEditingController(); TextEditingController timeController = TextEditingController(); final DateFormat formatter = DateFormat('dd, MMMM, yyyy');

// TodoListModel task = TodoListModel(deadLine: '', task: '');

@override Widget build(BuildContext context) { return BlocProvider( create: (BuildContext context) => TaskBloc(repository: DatabaseRepository()), child: Builder(builder: (BuildContext ctx) { return Scaffold( appBar: AppBar( centerTitle: true, title: Text('Add a new Task'), actions: [ GestureDetector( onTap:** () { saveTask(ctx); }, child: Container( margin: EdgeInsets.only(right: 20), child: Icon( Icons.check, size: 30, )), ) ], ), body: buildTask(ctx)); } ), ); }

Widget buildTaskForm(BuildContext ctx) { return Container( margin: EdgeInsets.all(20), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text("What need to be done?"), Row( children: [ Expanded( child: Container( width: 400, child: TextFormField( controller: taskController, textInputAction: TextInputAction.newline, keyboardType: TextInputType.multiline, maxLines: null, decoration: InputDecoration( hintText: 'Enter Task Here', //border: OutlineInputBorder(), ), ), ), ), SizedBox( width: 10, ), Icon( Icons.keyboard_voice, color: Theme.of(context).primaryColor, size: 28, ), ], ), SizedBox( height: 40, ), Center( child: Text( "Dead line", style: TextStyle(fontSize: 19), )), SizedBox( height: 10, ), Row( children: [ Expanded( child: Container( width: 400, child: TextFormField( onTap: () { _showDatePicker(context); }, controller: dateController, textInputAction: TextInputAction.newline, keyboardType: TextInputType.multiline, maxLines: null, decoration: InputDecoration( hintText: 'Enter Date', //border: OutlineInputBorder(), ), ), ), ), SizedBox( width: 10, ),

          Icon(
            Icons.date_range,
            color: Theme.of(context).primaryColor,
            size: 28,
          ),
          //  SizedBox(height: 10,),

          dateController.text.isEmpty
              ? Container()
              : Expanded(child: buildTime()),
        ],
      ),
    ],
  ),
);

}

Row buildTime() { return Row( children: [ Expanded( child: Container( width: 400, child: TextFormField( onTap: () { _selectTime(context); }, controller: timeController, textInputAction: TextInputAction.newline, keyboardType: TextInputType.multiline, decoration: InputDecoration( hintText: 'Enter Time', //border: OutlineInputBorder(), ), ), ), ), SizedBox( width: 10, ), Icon( Icons.timer, color: Theme.of(context).primaryColor, size: 28, ), ], ); }

Future _selectTime(BuildContext context) async { final TimeOfDay picked = await showTimePicker( context: context, initialTime: TimeOfDay.now(), ); if (picked != null) setState(() { timeController.text = picked.toString().substring(10, 15); }); }

void _showDatePicker(BuildContext context) { showDatePicker( context: context, initialDate: DateTime.now(), firstDate: DateTime(2020), lastDate: DateTime(3000)) .then((pickedDate) { if (pickedDate == null) { return; } setState(() { dateController.text = formatter.format(pickedDate).toString(); //task.deadLine = dateController.text; }); }); }

saveTask(BuildContext ctx) async { taskBloc = BlocProvider.of(ctx); taskBloc.add( SaveTaskEvent(TodoListModel( deadLine: dateController.text, task: taskController.text)), ); }

Widget buildTask(BuildContext context) { return BlocBuilder<TaskBloc, TaskState>( builder: (ctx, state) { if (state is TaskInitial) { return buildTaskForm(ctx); } else if (state is TaskLoadingState) { return Utility.showCirclarLoader(); } else if (state is TaskAddedState) { return buildTaskForm(ctx); } else if (state is TaskErrorState) { //return Utility.showLongErrorToast(state.message); } return Container(); }, ); } }```

OnyemaAnthony commented 3 years ago

@mikededo sir please i dont know how to format code this is my first time of reporting an issue here

OnyemaAnthony commented 3 years ago

i think the problem is coming from the repository because i did everything right in the bloc file and also the ui the same way i use to do when am using firebase the only thing different is the data base please if there is any other thing i need to do to connect bloc and sqflite db that i did not do you can tell me

mikededo commented 3 years ago

Here you have an explanation from the GitHub docs on how to write codeblocks in Markdown. Try editing your comments! πŸ˜„

In the function saveTask, you are calling BlocProvider.of(ctx) without specifying the BLoC type. As you can see in the documentation, you should be using BlocProvider.of<BlocA>(context). However, it is strange that your app does not show any error and that, as you say, the BlocObserver is called.

Could you try adding what I propsed before here? Aside from this, do you have a public repository of the project? Therefore I could fork it, try it and help you finding the issue (it's easier for me to debug if I can compile and run the app myself :wink:).

OnyemaAnthony commented 3 years ago

i have added the type of bloc but still the same thing is happening https://github.com/OnyemaAnthony/todo_list.git is the link to the repository you can clone it and switch to develop branch

mikededo commented 3 years ago

I have found the issue. I will provide a pull request explaining you the why! πŸ’―

OnyemaAnthony commented 3 years ago

okay thank you sir i really appreciate

mikededo commented 3 years ago

It is a pleasure πŸ˜ƒ

If everything works as expected, merge the pr and close both the issue and the pr πŸ‘‹

OnyemaAnthony commented 3 years ago

Okay I will do just that but I haven't seen the pull request

mikededo commented 3 years ago

Go to the repository and open the Pull Requests tab. Here's the link

OnyemaAnthony commented 3 years ago

its working thank you sir God bless you,but i did not know the cause of the error

mikededo commented 3 years ago

As I have stated in the pr, the issue was not yielding the function inside mapEventToState.

Instead of:

  @override
  Stream<TaskState> mapEventToState(
    TaskEvent event,
  ) async* {
    if (event is FetchAllTaskEvent) {
      yield* _mapFetchAllTAskEventToState(event);
    } else if (event is SaveTaskEvent) {
      yield* _mapSaveTaskEventToState(event);
    }
  }

You were doing:

  @override
  Stream<TaskState> mapEventToState(
    TaskEvent event,
  ) async* {
    if (event is FetchAllTaskEvent) {
      yield* _mapFetchAllTAskEventToState(event);
    } else if (event is SaveTaskEvent) {
      _mapSaveTaskEventToState(event);  // <- Here's the issue
    }
  }

You were not yielding _mapSaveTaskToState, which would not call the function.

OnyemaAnthony commented 3 years ago

ohhhh now i get that is a very stupid mistake and i was not able to figure it out thanks anyways