Cretezy / redux_persist

Persist Redux State
https://pub.dartlang.org/packages/redux_persist
MIT License
130 stars 41 forks source link

Unhandled Exception: Converting object to an encodable object failed: Instance of 'AppState' #53

Closed tuckerjt07 closed 5 years ago

tuckerjt07 commented 5 years ago

I am getting the above error message when trying to use Flutter. I believe the issue is due to the fact that AppState has a runtimeType property being added somewhere that is not serializable. Has anyone else faced this issue?

image

My files:

main.dart

import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_redux/flutter_redux.dart';
import 'package:redux_persist/redux_persist.dart';
import 'package:redux_persist_flutter/redux_persist_flutter.dart';
import 'package:marketplace/actions/counter_actions.dart';
import 'package:marketplace/middleware/counter_middleware.dart';
import 'package:marketplace/reducers/app_reducer.dart';
import 'package:marketplace/store/state.dart';
import 'package:marketplace/widgets/counter.dart';
import 'package:redux/redux.dart';

void main() async {

  final persistor = Persistor<AppState>(
    storage: FlutterStorage(),
    serializer: JsonSerializer<AppState>(AppState.fromJson),
  );

  final initialState = await persistor.load();

  final Store<AppState> store = Store<AppState>(
    appReducer,
    initialState: initialState ?? AppState.initial(),
    middleware: [
      persistor.createMiddleware(),
      CounterMiddleware()
    ]
  );
  runApp(App(store));
}

class App extends StatelessWidget {

  final Store<AppState> store; 
  App(this.store);

  @override
  Widget build(BuildContext context) {
    store.dispatch(new IntializeNumberAction());
    return StoreProvider(
          store: store, 
          child: MaterialApp(
       title: 'Flutter Redux', 
       home: Scaffold(
         appBar: AppBar(
           title: Text('Redux in Flutter'),
         ),
         body: Counter()
       )
       ),
    );

  }
}

counter_model.dart

class Counter {
  final int count;
  final bool isCalculating;

  Counter({this.count, this.isCalculating});

  static Counter fromJson(dynamic json) {
    return json != null ?? new Counter(count: json["count"], isCalculating: json["isCalculating"]) ?? new Counter().toJson();
  }

  dynamic toJson() {
    return {
      "count": count,
      "isCalculating": isCalculating
    };
  }
}

state.dart

import 'package:marketplace/models/counter_model.dart';

class AppState {
  final Counter counter;

  AppState({this.counter});

  factory AppState.initial() => AppState(
    counter: new Counter(count: 0, isCalculating: false)
  );

  AppState copyWith({
    counter
  }) {
    return AppState(
      counter: counter ?? this.counter
    );
  }

  static AppState fromJson(dynamic json) {

    if (json != null) {
      return new AppState(counter: json["counter"] as Counter);
    } else {
      return AppState.initial();
    }
  }

  dynamic toJson() => {'counter', counter};
}
Cretezy commented 5 years ago

In your state.dart, you made 2 small typos:

  dynamic toJson() => {'counter', counter};

to

  dynamic toJson() => {'counter': counter.toJson()};

Also, your AppState.fromJson won't work:

  static AppState fromJson(dynamic json) {
    if (json != null) {
      return new AppState(counter: Counter.fromJson(json["counter"]));
    } else {
      return AppState.initial();
    }
  }

And your Counter.fromJson:

    return json != null
      ? Counter(count: json["count"], isCalculating: json["isCalculating"])
      : Counter();
tuckerjt07 commented 5 years ago

Thanks that did the trick. I feel like a moron but it works.

Cretezy commented 5 years ago

You're welcome :)

tuckerjt07 commented 5 years ago

Actually, it's not throwing a fatal error anymore but I'm still getting a serialization error.

I'm still learning the ins and outs of Dart and Flutter but it seems to me it has to be the runtimeType property. If I stop the execution on a breakpoint and run json.encode(state.counter) it completes with no issues. It's only with runtimeType that it errors out.

E/flutter (18904): #2      _JsonStringStringifier.stringify  (dart:convert/json.dart:819:5)
E/flutter (18904): #3      JsonEncoder.convert  (dart:convert/json.dart:255:30)
E/flutter (18904): #4      JsonCodec.encode  (dart:convert/json.dart:166:45)
E/flutter (18904): #5      JsonSerializer.encode 
package:redux_persist/src/serialization.dart:28
E/flutter (18904): #6      Persistor.save 
package:redux_persist/src/persistor.dart:155
E/flutter (18904): <asynchronous suspension>
E/flutter (18904): #7      Persistor.createMiddleware.<anonymous closure>.<anonymous closure> 
package:redux_persist/src/persistor.dart:67
E/flutter (18904): #8      _rootRun  (dart:async/zone.dart:1120:38)
E/flutter (18904): #9      _CustomZone.run  (dart:async/zone.dart:1021:19)
E/flutter (18904): #10     _CustomZone.runGuarded  (dart:async/zone.dart:923:7)
E/flutter (18904): #11     _CustomZone.bindCallbackGuarded.<anonymous closure>  (dart:async/zone.dart:963:23)
E/flutter (18904): #12     _rootRun  (dart:async/zone.dart:1124:13)
E/flutter (18904): #13     _CustomZone.run  (dart:async/zone.dart:1021:19)
E/flutter (18904): #14     _CustomZone.bindCallback.<anonymous closure>  (dart:async/zone.dart:947:23)
E/flutter (18904): #15     Timer._createTimer.<anonymous closure>  (dart:async-patch/timer_patch.dart:21:15)
E/flutter (18904): #16     _Timer._runTimers  (dart:isolate-patch/timer_impl.dart:382:19)
E/flutter (18904): #17     _Timer._handleMessage  (dart:isolate-patch/timer_impl.dart:416:5)
E/flutter (18904): #18     _RawReceivePortImpl._handleMessage  (dart:isolate-patch/isolate_patch.dart:172:12)
E/flutter (18904):
Cretezy commented 5 years ago

What's the first line of the error?

tuckerjt07 commented 5 years ago
E/flutter (19419): [ERROR:flutter/lib/ui/ui_dart_state.cc(148)] Unhandled Exception: Converting object to an encodable object failed: Instance of 'AppState'

Sorry didn't see that I missed it.

Cretezy commented 5 years ago

Can you give me the full update code for app state and counter?

tuckerjt07 commented 5 years ago

state.dart

import 'package:marketplace/models/counter_model.dart';

class AppState {
  final Counter counter;

  AppState({this.counter});

  factory AppState.initial() => new AppState(
    counter: new Counter(count: 0, isCalculating: false)
  );

  AppState copyWith({
    counter
  }) {
    return AppState(
      counter: counter ?? this.counter
    );
  }

  static AppState fromJson(dynamic json) {
    return json != null ? new AppState(counter: Counter.fromJson(json["counter"])) : AppState.initial();
  }

  dynamic toJson() => {'counter', counter.toJson()};
}

counter_model.json

class Counter {
  final int count;
  final bool isCalculating;

  Counter({this.count, this.isCalculating});

  static Counter fromJson(dynamic json) {
    return json != null
      ? Counter(count: json["count"], isCalculating: json["isCalculating"])
      : Counter();
  }

  dynamic toJson() {
    return {
      "count": count,
      "isCalculating": isCalculating
    };
  }
}

Breakpoint at serialization.dart line 28 with manual execution in debug console

stringToUint8List(json.encode(state.counter))
[123, 34, 99, 111, 117, 110, 116, 34, 58, 49, 48, 48, 44, 34, 105, 115, 67, 97, 108, 99, 117, 108, 97, 116, 105, 110, 103, 34, 58, 116, 114, 117, 101, 125]
stringToUint8List(json.encode(state.hashCode))
[52, 56, 54, 53, 55, 50, 48, 49, 56]
stringToUint8List(json.encode(state.runtimeType))
Unhandled exception:
Converting object to an encodable object failed: AppState
#0      _JsonStringifier.writeObject (dart:convert/json.dart:647:7)
#1      _JsonStringStringifier.printOn (dart:convert/json.dart:834:17)
#2      _JsonStringStringifier.stringify (dart:convert/json.dart:819:5)
#3      JsonEncoder.convert (dart:convert/json.dart:255:30)
Cretezy commented 5 years ago

stringToUint8List(json.encode(state.runtimeType)) doesn't really make sense, are you calling that yourself?

Also might need a null-check:

  dynamic toJson() => {'counter', counter != null ? counter.toJson() : null};
tuckerjt07 commented 5 years ago

Yeah, I'm calling that myself. With everything else in AppState serializing out individually I was trying it to see if it would. I'll try the null check.

No luck, it's still throwing the exception with the null check.

tuckerjt07 commented 5 years ago

I started over from scratch and set up persist first. Had a couple of false starts but it's working now. Don't really see a difference. Thanks for the patience and help!

Cretezy commented 5 years ago

Sorry for late reply. Glad it's working for you!