Search code examples
jsonflutterdartbloc

First: Converting object to an encodable object failed - then: HydratedCubit isn't hydrated


the following situation: I want to make a ToDo list with a Cubit. That the ToDo list does not disappear after leaving the app, I found the package hydrated Bloc. When I created the HydratedCubit with the 2 override methods for the FromJson and ToJson, creating them Inside the ToDoState and executed it, I got an exception, "Converting object to an encodable object failed: Instance of 'Todo'". After research I found out that JSON can't convert such a model and you have to overwrite these models with working From and To functions first. I tried this with the package "json serializable". Here I had to ask myself in which class I have to add the "@JsonSerializable()", inside the Todo model or the todo state, in which the error appears? I decided to add the @ for my todostate because adding it on the Todo model didn't change anything. After this package created me the "todo_list_state.g.dart" I don't get the error anymore, BUT: It also doesn't save the list, looks like HydratedCubit is being "ignored". Here a few code snippets:

First, I show you the Todo Model:

Uuid uuid = const Uuid();

class Todo extends Equatable {
  final String id;
  final String desc;
  final double menge;

  Todo( {
    String? id,
    required this.desc,
    required this.menge,

  }) : id = id ?? uuid.v4();

  
}

I left out the to string and props... As I mentioned before, I tried to add the JsonSerializableGenerator inside this class, but this didn't change something. Here is my TodoListState:

part of 'todo_list_cubit.dart';

@JsonSerializable()
class TodoListState extends Equatable {
  final List<Todo> todos;

  factory TodoListState.initial() {
    return TodoListState(todos: []);
  }

  Map<String, dynamic>toMap() => _$FoodListStateToMap(this, food);

  factory FoodListState.fromMap(Map<String, dynamic> map) => _$FoodListStateFromMap(map);

  }

Here are the generated functions inside 'todo_list_state.g.dart':

TodoListState _$TodoListStateFromMap(Map<String, dynamic> map) {
    return TodoListState (
        todo: map['todo'] as List<TodoModel>,
    );
}

Map<String, dynamic> _$TodoListStateToMap(TodoListState instance,  List<TodoModel> todo) {
    return {
        'todo': todo,
    };
}

At last here my Cubit functions where I override the From and To:

  @override
  TodoListState? fromJson(Map<String, dynamic> json) {
    return TodoListState.fromMap(json);
  }

  @override
  Map<String, dynamic>? toJson(TodoListStatestate) {
    return state.toMap();
  }

So does anybody know why my Hydrated Cubit doesn't save my list? Or do I have to convert the Model but just dont know how?... Appreciate your help. Thanks


Solution

  • Both model and states need to have toJson and fromJson methods. So the approach was right with using JsonSerializable. But converting objects to and from json is a very important thing to understand how to do without codegen tools. Those can be boilerplate and not always converting correctly complex objects. When doing it manually, it's also easier to debug. So in your case, additions to the model would be:

    class Todo extends Equatable {
      final String id;
      final String desc;
      final double menge;
    
      Todo( {
        String? id,
        required this.desc,
        required this.menge,
    
      }) : id = id ?? uuid.v4();
      factory Todo.fromJson(Map<String, dynamic> json){
        return Todo(id: json['id'],
                   desc: json['desc'],
                   menge: json['menge'],);
        }
      
      Map<String, dynamic> toJson() {
        return {
          'id': id,
          'desc': desc,
          'menge': menge,
        };
      }
    }
    

    Then, in your states (again, I am not going to use JsonSerializable):

    class TodoListState extends Equatable {
      final List<Todo> todos;
    
      TodoListState({this.todos = const <Todo>[]});
    
      factory TodoListState.fromJson(Map<String, dynamic> json) {
        var todos = <Todo>[];
        for (var item in json['todos']) {
          todos.add(Todo.fromJson(item));
        }
        return TodoListState(todos: todos);
      }
    
      Map<String, dynamic> toJson() {
        var jsonTodoList = <Map<String, dynamic>>[];
        for (var item in todos) {
          jsonTodoList.add(item.toJson());
        }
        return {
          'todos': jsonTodoList,
        };
      }
    }
    

    Then in your Cubit nothing changes

      @override
      TodoListState? fromJson(Map<String, dynamic> json) {
        return TodoListState.fromJson(json);
      }
    
      @override
      Map<String, dynamic>? toJson(TodoListState state) {
        return state.toJson();
      }
    

    Except for you may want to add all sorts of null safety checks around the conversions, because I omitted all aspects not related to the topic of the question.