Skip to content
New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Unable to cast nested custom object #237

Closed
manhhavu opened this issue Aug 2, 2019 · 4 comments
Closed

Unable to cast nested custom object #237

manhhavu opened this issue Aug 2, 2019 · 4 comments

Comments

@manhhavu
Copy link
Contributor

manhhavu commented Aug 2, 2019

Hi,

It seems that when a class' field is custom object, the SDK is currently unable to cast the correct type to the nested object with an error 'type 'ParseObject' is not a subtype of type '''.

import 'package:parse_server_sdk/parse_server_sdk.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:test_api/test_api.dart';

class TestMother extends ParseObject implements ParseCloneable {
  static const String _keyTableName = 'TestMother';
  static const String _keyChild = 'child';

  TestMother() : super(_keyTableName);

  TestMother.clone() : this();

  @override
  clone(Map map) => TestMother.clone()..fromJson(map);

  TestChild get child => get<TestChild>(_keyChild);
  set child(TestChild value) => set<TestChild>(_keyChild, value);
}

class TestChild extends ParseObject implements ParseCloneable {
  static const String _keyTableName = 'TestChild';
  static const String _keyName = 'name';

  TestChild() : super(_keyTableName);

  TestChild.clone() : this();

  String get name => get<String>(_keyName);
  set name(String name) => set<String>(_keyName, name);

  @override
  clone(Map map) => TestMother.clone()..fromJson(map);
}

void main() {
  const endpoint = '<endpoint>';
  const TIMEOUT = const Timeout(const Duration(minutes: 2));

  setUp(() async {
    // Preferences needed by Parse SDK
    SharedPreferences.setMockInitialValues({});

    await Parse().initialize(
      '<client_id>',
      '$endpoint/parse/',
      autoSendSessionId: true,
      debug: true,
    );
  });

  test('Save and delete object with Parse', () async {
    final child = TestChild()..name = "Baby";

    final mother = TestMother()..child = child;

    await mother.save();

    final response = await TestMother().getObject(mother.objectId);
    final saved = response.result as TestMother;

    // This line throw error "type 'ParseObject' is not a subtype of type 'TestChild'"
    expect(saved.child, isNotNull);
  }, timeout: TIMEOUT);
}

Version: 1.0.22 (same problem with older versions : 1.0.21, 1.0.19)

I don't know if it is expected and is there any workaround for that? I have a lot of nested custom objects to update so using the raw method set(key, value) is very cumbersome.

Thanks for your help,

@gorillatapstudio
Copy link

gorillatapstudio commented Aug 7, 2019

I msged before and it seems it is expected. My work around is have my MotherObject mixed the Cache and use a utility for get. (not sure whey github automatically strikes some texts).

MotherClass implements HasCache {
TestChild get child => Utils.getParseObject(this, "child", () => TestChild());
}
abstract class HasCache {
var cache = Map();
}

typedef S Constructor();

static T getParseObject(
ParseObject object, String key, Constructor constructor) {

var cache = (object as HasCache)?.cache;
if (cache != null && cache[key] != null) {
  return cache[key];
}

var value = object.get<dynamic>(key);
if (value == null) return null;
final dynamic json = const JsonDecoder().convert(value.toString());
if (json == null) return null;

T obj = constructor().fromJson(json);

cache[key] = obj;
return obj;

}

@manhhavu
Copy link
Contributor Author

manhhavu commented Aug 8, 2019

Thank @gorillatapstudio for the workaround.

@phillwiggins : What do you think about of making this workaround as default in the lib? Or you have another idea to handle this case more cleanly?

@gorillatapstudio
Copy link

the string conversion in my workaround is not efficient. Hope @phillwiggins can support get(key) directly in the sdk and convert directly.

@phillwiggins
Copy link
Member

Hey @manhhavu & @gorillatapstudio

Unfortunately, I'm doing something similar. In my classes, I do something like this in my Parse class.

@override Day fromJson(Map<String, dynamic> objectData) { super.fromJson(objectData); if (objectData.containsKey(keyOwner)) { owner = User.clone().fromJson(objectData[keyOwner]); } return this; }

The issue is that Flutter doesn't support reflection, and based on best practices, when we actually convert our JSON to a returnable object, we define that the return type is ParseObject. Even using generics we haven't found a better approach to this.

The approach above works well too, but my approach is the reason you will see that ParseObjects implement the cloneable method.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants