Skip to content

Custom Objects: Problems with Queries, non-primitive fields (Pointers, Relation, File, GeoPoint) #128

New issue

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

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

Already on GitHub? Sign in to your account

Closed
danibjor opened this issue Mar 18, 2019 · 33 comments

Comments

@danibjor
Copy link

danibjor commented Mar 18, 2019

Tested on v1.0.16 branch.

Seems to be issues with non primitive fields.

ParseFile is missing values on properties file, name and url (value is null). But if you inspect the object, both name and url are in the _objectData Map.

Sub classing ParseObject gives Type Conversion Exceptions on non primitive fields.

ParseFile
Skjermbilde 2019-03-18 kl  11 51 03

This is result from QueryBuilder<Store> on Store Table.
Result is List<ParseObject>, runtime shows List<Store>
List<Store> stores = response.result; // throws exception: _TypeError (type List<ParseObject> is not a subtype of type List<Store>)
List<Store> stores = (response.result as List<ParseObject>).cast<Store>(); // works - but hacky
Store.city is Pointer to City class/table: City get city => get<City>('city')
Skjermbilde 2019-03-18 kl  11 48 41

Store table:
Skjermbilde 2019-03-18 kl  11 21 14

myParseObject.get<ParseObject>('customers') returns single object - should return List<>

Complete test example (file new flutter project, add dependency to sdk 1.0.16):

config.dart: (pm me to get access to the test site - don't want spambots to fill the database)

class Config {
  static const String keyApplicationId = '';
  static const String keyParseServerUrl = '';
}

main.dart:

import 'package:flutter/material.dart';
import 'package:parse_server_sdk/parse_server_sdk.dart';

import 'config.dart';

void main() {
  Parse().initialize(Config.keyApplicationId, Config.keyParseServerUrl, debug: true);
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Parse SDK',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(title: 'Flutter Parse SDK'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  int _counter = 0;
  bool _loading = false;

  List<Store> _stores;
  // List<ParseObject> _stores; 

  void _fetchData() async {
    setState(() {
      _loading = true;
    });

    var queryBuilder = QueryBuilder<Store>(Store());
    // queryBuilder.whereGreaterThan('openedAt', DateTime.now().add(Duration(days: -30)));
    // queryBuilder.whereGreaterThan('createdAt', DateTime.now().add(Duration(hours: -2, minutes: -50)));
    queryBuilder.includeObject(['city', 'manager', 'customers']);

    var response = await queryBuilder.query();

    setState(() {
      _loading = false;      
      _stores = null;

      // Hack: without cast, we get an exception, even tough runtime type of .result is correct.
      // Exception has occurred.
      // _TypeError (type 'List<ParseObject>' is not a subtype of type 'List<Store>')
      if (response.success && response.result != null) {
        _stores = (response.result as List<ParseObject>).cast<Store>();
      }
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _mainContent(),
      floatingActionButton: FloatingActionButton(
        onPressed: _fetchData,
        tooltip: 'Refresh',
        child: Icon(Icons.refresh),
      ),
    );
  }

  Widget _mainContent() {
    if (_loading == true) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Padding(padding: EdgeInsets.only(bottom: 20.0), child: Text('Loading data')),
            CircularProgressIndicator(),
          ],
        ),
      );
    }

    // no items
    if (_stores == null || _stores.length == 0) {
      return Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('There is no data'),
          ],
        ),
      );
    }

    // render data
    return ListView.builder(
      itemCount: _stores?.length,
      itemBuilder: (BuildContext context, int index) {
        Store store = _stores[index];
        
        // The following assertion was thrown building:
        // type 'ParseUser' is not a subtype of type 'List<ParseUser>'
        // if you inspect store._objectData, customers is of type ParseUser, should be List<ParseUser> (in this example where relation is to User table)
        // var allCustomers = store.customers.toString();
        // var allCustomers = store.customers;

        // photo is type ParseFile
        // photo._objectData => Map(name, url)
        // photo.file photo.name photo.url is null
        // var photo = store.photo;

        // city is Pointer to City table
        // The following assertion was thrown building:
        // type 'ParseObject' is not a subtype of type 'City'
        // var city = store.city;

        return ListTile(
          title: Text('${store.name}'),
          // manager is of type ParseUser - seems to work with built-in types
          // name is an extra field on that table
          subtitle: Text(store.manager.get<String>('name')), 
        );
      },
    );
  }
}

class Store extends ParseObject implements ParseCloneable {

  Store() : super(_keyTableName);
  Store.clone(): this();

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

  static const String _keyTableName = 'Store';
  static const String keyName = 'name';
  static const String keyManager = 'manager';
  static const String keyCustomers = 'customers';
  static const String keyOpenedAt = 'openedAt';
  static const String keyPhoto = 'photo';
  static const String keyCity = 'city';
  static const String keyLocation = 'location';

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

  ParseUser get manager => get<ParseUser>(keyManager);
  set manager(ParseUser manager) => set<ParseUser>(keyManager, manager);

  List<ParseUser> get customers => get<List<ParseUser>>(keyCustomers);
  set customers(List<ParseUser> customers) => set<List<ParseUser>>(keyCustomers, customers);

  DateTime get openedAt => get<DateTime>(keyOpenedAt);
  set openedAt(DateTime openedAt) => set<DateTime>(keyOpenedAt, openedAt);

  ParseFile get photo => get<ParseFile>(keyPhoto);
  set photo(ParseFile photo) => set<ParseFile>(keyPhoto, photo);

  // The following assertion was thrown building:
  // type 'ParseObject' is not a subtype of type 'City'
  // customers field in database is Relation<User>, this Store has one customer in relation table - this should return a List<User> not User
  City get city => get<City>(keyCity);
  set city(City city) => set<City>(keyCity, city);

  ParseGeoPoint get location => get<ParseGeoPoint>(keyLocation);
  set location(ParseGeoPoint location) => set<ParseGeoPoint>(keyLocation, location);
}

class City extends ParseObject implements ParseCloneable {

  City() : super(_keyTableName);
  City.clone(): this();

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

  static const String _keyTableName = 'City';
  static const String keyName = 'Name';
  static const String keyPhoto = 'photo';
  static const String keyLocation = 'location';
  
  String get name => get<String>(keyName);
  set name(String name) => set<String>(keyName, name);

  ParseFile get photo => get<ParseFile>(keyPhoto);
  set photo(ParseFile photo) => set<ParseFile>(keyPhoto, photo);

  ParseGeoPoint get location => get<ParseGeoPoint>(keyLocation);
  set location(ParseGeoPoint location) => set<ParseGeoPoint>(keyLocation, location);
}
@phillwiggins
Copy link
Member

Sorry, just reading slowly through this.

Subtyping ParseObjects and referencing them in another subtype of ParseObject requires something along the lines of this to be correctly deserialised.

@override
  UserFood fromJson(Map objectData) {
    super.fromJson(objectData);
    user = User.clone().fromJson(objectData[keyOwner]);
    food = Food.clone().fromJson(objectData[keyFoodItem]);
    meal = Meal.clone().fromJson(objectData[keyMeal]);
    return this;
  }

@RodrigoSMarques
Copy link
Contributor

Hi @danibjor
This latest version really "broke" some things that were working.

Try use my Fork:

  parse_server_sdk:
    git: 
      url: https://github.com/rodrigosmarques/flutter_parse_sdk.git
      ref: bugfix-parsequery-parsefile-parseuser-parseencoder

@RodrigoSMarques
Copy link
Contributor

My code that was working and work in my Fork

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:http/http.dart' as http;
import 'package:path_provider/path_provider.dart';

import 'package:flutter/material.dart';
import 'package:parse_server_sdk/parse_server_sdk.dart';

Future<File> _downloadFile(String url, String filename) async {
  http.Client _client = new http.Client();
  var req = await _client.get(Uri.parse(url));
  var bytes = req.bodyBytes;
  String dir = (await getApplicationDocumentsDirectory()).path;
  File file = new File('$dir/$filename');
  await file.writeAsBytes(bytes);
  return file;
}

void main() async {
  Parse().initialize("HIn4bvCmAYvwpDmPTmRjfoSwpTPPmGsPUMKYU36Q",
      "xxxxxxx",
      clientKey: "ggD2i3GejX3SIxpDgSbKHHV8uHUUP3QGiPPTlmPK",
      autoSendSessionId: true,
      debug: true,
      liveQueryUrl: "xxxxxxx");

  final localUser = await ParseUser.currentUser();

  final user = ParseUser(
      "user", "password", "[email protected]");
  var response = await user.login();

  if (response.success) {
    print("Login sucess");
  } else {
    print("Login error");
  }

  var point =
      ParseGeoPoint(debug: true, latitude: -20.2523818, longitude: -40.2665611);
  var point2 =
      ParseGeoPoint(debug: true, latitude: -19.2523818, longitude: -41.2665611);

  QueryBuilder<ParseUser> queryBuilder = QueryBuilder<ParseUser>(
      ParseUser.forQuery())
    ..orderByAscending("runCount")
    ..setLimit(1000)
    ..whereGreaterThan("runCount", 1)
    ..whereLessThan("runCount", 1000)
    ..whereEqualTo("tipo", "D")
    ..whereNear("localizacao", point)
    //..whereEqualTo("usuario", user)
    ..whereGreaterThan("createdAt", DateTime.now().subtract(Duration(days: 60)))
    ..whereLessThan("createdAt", DateTime.now());
  //..whereEqualTo("createdAt", DateTime.now());
  //..whereStartsWith("cidade", "Vit");
  //..whereContains("keywords", "leite");
  //..whereGreaterThan("tipo", "A")
  //..whereGreaterThan("runCount", 0);
  //..whereWithinMiles("localizacao", point, 5);
  //..whereWithinKilometers("localizacao", point, 50);
  //..whereWithinRadians("localizacao", point, 1);
  //..whereWithinGeoBox("localizacao", point, point2);
  var apiResponse = await queryBuilder.query();

  if (apiResponse.success && apiResponse.result != null) {
    print(
        "Result: ${((apiResponse.result as List<dynamic>).first as ParseObject).toString()}");
  } else {
    print("Result: ${apiResponse.error.message}");
  }
  final listUsers = await apiResponse.result;

  if (listUsers != null) {
    print(listUsers.length); //print(listUsers[0].toString());
  }

  var file = await _downloadFile(
      "https://i2.wp.com/blog.openshift.com/wp-content/uploads/parse-server-logo-1.png",
      "image.png");
  var parseFile = ParseFile(file, name: "image.png", debug: true);
  //var parseFile = ParseFile(null, url: "https://i2.wp.com/blog.openshift.com/wp-content/uploads/parse-server-logo-1.png", name: "image.png", debug: true);
  var fileResponse = await parseFile.save();

  if (fileResponse.success) {}

  await parseFile.download();

  var parseObject = ParseObject("TestAPI", debug: true);
  parseObject.set<String>("stringName", "Name");
  parseObject.set<double>("doubleNumber", 1.5);
  parseObject.set<int>("intNumber", 0);
  parseObject.set<bool>("boolFound", true);
  parseObject.set<List<String>>("listString", ["a", "b", "c", "d"]);
  parseObject.set<List<int>>("listNumber", [0, 1]);
  parseObject.set<DateTime>("dateTest", DateTime.now());
  parseObject.set<Map<String, dynamic>>(
      "jsonTest", {"field1": "value1", "field2": "value2"});
  parseObject.setIncrement("intNumber2", 2);
  parseObject.setDecrement("intNumber3", 2);
  parseObject.set<ParseGeoPoint>(
      "location", ParseGeoPoint(latitude: -20.2523818, longitude: -40.2665611));
  parseObject.set<ParseUser>("user", user);
  parseObject.set<List<ParseUser>>("users", [user, user]);
  parseObject.set<ParseFile>("fileImage", parseFile);
  parseObject.set<List<ParseFile>>("fileImages", [parseFile, parseFile]);

  apiResponse = await parseObject.save();

  if (apiResponse.success) {
    print("Objeto JSON: " + (apiResponse.result as ParseObject).toString());
  } else {
    print("Error: " + apiResponse.error.toString());
    return;
  }

  parseObject.setRemove("listString", ["a"]);
  apiResponse = await parseObject.save();

  if (apiResponse.success) {
    print("Objeto JSON: " + (apiResponse.result as ParseObject).toString());

    parseObject.objectId = (apiResponse.result as ParseObject).objectId;
  } else {
    print("Error: " + apiResponse.error.toString());
    return;
  }

  parseObject.setRemoveAll("listString", ["b", "c"]);
  apiResponse = await parseObject.save();

  if (apiResponse.success) {
    print("Objeto JSON: " + (apiResponse.result as ParseObject).toString());

    parseObject.objectId = (apiResponse.result as ParseObject).objectId;
  } else {
    print("Error: " + apiResponse.error.toString());
    return;
  }

  parseObject.setAdd("listString", ["a"]);
  apiResponse = await parseObject.save();

  if (apiResponse.success) {
    print("Objeto JSON: " + (apiResponse.result as ParseObject).toString());

    parseObject.objectId = (apiResponse.result as ParseObject).objectId;
  } else {
    print("Error: " + apiResponse.error.toString());
    return;
  }

  parseObject.setAddAll("listString", ["b", "c"]);
  apiResponse = await parseObject.save();

  if (apiResponse.success) {
    print("Objeto JSON: " + (apiResponse.result as ParseObject).toString());

    parseObject.objectId = (apiResponse.result as ParseObject).objectId;
  } else {
    print("Error: " + apiResponse.error.toString());
    return;
  }

  parseObject.setAddUnique("listString", ["a", "b", "c", "d", "e"]);
  apiResponse = await parseObject.save();

  if (apiResponse.success) {
    print("Objeto JSON: " + (apiResponse.result as ParseObject).toString());

    parseObject.objectId = (apiResponse.result as ParseObject).objectId;
  } else {
    print("Error: " + apiResponse.error.toString());
    return;
  }

  var instalattion = await ParseInstallation.currentInstallation();
  instalattion.deviceToken = "xyz";
  instalattion.set<ParseUser>("usuario", user);
  instalattion.subscribeToChannel("C");
  instalattion.subscribeToChannel("D");
  instalattion.subscribeToChannel("E");
  apiResponse = await instalattion.save();

  if (apiResponse.success && apiResponse.result != null) {
    print("Result: ${((apiResponse.result) as ParseObject).toString()}");
  } else {
    print("Result: ${apiResponse.error.message}");
  }
  instalattion.unsubscribeFromChannel("D");
  apiResponse = await instalattion.save();

  var listChannels = await instalattion.getSubscribedChannels();

  var apiResponseUser = await ParseObject('_User').getObject('b72MScHoSj');

  if (apiResponse.success) {
    var usuario = apiResponseUser.result as ParseObject;

    if (usuario != null) {
      var queryBuilder2 = QueryBuilder<ParseObject>(ParseObject('Produto'))
        ..whereEqualTo("owner", usuario.toPointer());

      var apiResponse2 = await queryBuilder2.query();

      if (apiResponse2.success && apiResponse2.result != null) {
        print(
            "Result: ${((apiResponse2.result as List<dynamic>).first as ParseObject).toString()}");
      } else {
        print("Result: ${apiResponse2.error.message}");
      }

      await usuario.pin();
      var usuarioPion = await usuario.fromPin(usuario.objectId);
      print(usuarioPion.toString());
    }
  }

  if (user != null) {
    await user.logout(deleteLocalUserData: false);
  }
  print("Fim");
  runApp(MyApp());
}

@phillwiggins
Copy link
Member

So to confirm, ParseFile and subtyping ParseObjects seems to not work?

@RodrigoSMarques
Copy link
Contributor

Yes, not working. Stopped.

On my Fork you do not have your latest release and I have everything working.

@phillwiggins
Copy link
Member

Well pull the latest commits first.

I have unit tests my end with everything working. Nothing major has changed in ParseFile.

@danibjor
Copy link
Author

@phillwiggins your example of overriding fromJson seems to do the trick on pointers. Thanks.
@RodrigoSMarques your repo / branch seems to fix most of the issues.

Still to go: Database columns of type Relation<OtherTable> is now null - was first object in list on 1.0.16 branch. Should be List<SomeType>.

@phillwiggins
Copy link
Member

@RodrigoSMarques correct your merge requests and create a new pull request. It doesn't look like a lot of the merge you created was actually added.

@danibjor In all honesty I've not had chance to look at relations, although look at the line:-
myParseObject.get<ParseObject>('customers')

, this wouldn't work as you are referencing a ParseObject rather than a List. Something like:-
myParseObject.get<List<Customer>>('customers')
might work better.

@danibjor
Copy link
Author

@phillwiggins myObject._objectData['relationColumn'] is null in the "working" repo/branch @RodrigoSMarques asked me to test. Pointers works on that branch.

Might be relational stuff in general. Have to dig deeper.

@phillwiggins
Copy link
Member

Okay cool.

Looks like the merge request has caused a few issues. @RodrigoSMarques update your current project and create a null PR. Should all be working then.

@phillwiggins
Copy link
Member

phillwiggins commented Mar 18, 2019 via email

@RodrigoSMarques
Copy link
Contributor

@phillwiggins ParseFile its working for me. Upload, Download e get in my Fork

@phillwiggins
Copy link
Member

phillwiggins commented Mar 18, 2019 via email

@phillwiggins
Copy link
Member

I've made some corrections on ParseFile. Does this now fix the problem?

@RodrigoSMarques
Copy link
Contributor

Let's do this. I will update my fork with your changes and I make bugfix than find error.

As my example app, it performs tests of all operations I can identify the problem.

@phillwiggins
Copy link
Member

phillwiggins commented Mar 18, 2019 via email

@RodrigoSMarques
Copy link
Contributor

@phillwiggins

I created the PR #131 with code changes that went back to working on my test project.

ParseFie = upload, download, retrieve
ParseGeopoint = save, retrieve
ParseQuery = Pointer, DateTime, String
ParseInstallation = Save, retrieve

Favor avaliar.

@phillwiggins
Copy link
Member

phillwiggins commented Mar 19, 2019 via email

@RodrigoSMarques
Copy link
Contributor

@phillwiggins
Yesterday, I deleted the fork that existed and created another one from its code that was available. This was yesterday at 19:55 (GMT).

I do not know what I might have done wrong.

@phillwiggins
Copy link
Member

phillwiggins commented Mar 19, 2019 via email

@RodrigoSMarques
Copy link
Contributor

OK. I'll make a PR of your code for the fork and handle the changes.

Please do not close my PR for now. As it has a history of the changes made and you may have to redo after the merge of the code.

@phillwiggins
Copy link
Member

phillwiggins commented Mar 19, 2019 via email

@RodrigoSMarques
Copy link
Contributor

Created new PR #132

@RodrigoSMarques
Copy link
Contributor

@danibjor

Can you do a test using my Fork to make sure it's working?

The master is updated with the changes.

pubspec.yaml

dependencies:
  flutter:
    sdk: flutter
   parse_server_sdk:
    git: git://github.com/rodrigosmarques/flutter_parse_sdk.git

@danibjor
Copy link
Author

danibjor commented Mar 19, 2019

@RodrigoSMarques have done some tests that failed previously:

  • basic queries: ok
  • queries with GeoPoint/within distance: ok
  • queries with .include (to also fetch pointer objects): ok

Stuff on objects that broke, tested:

  • primitive properties: ok
  • pointers to ParseUser: ok
  • pointer to other classes: ok
  • object.file: ok
  • object.geopoint: ok
  • object.someUser (pointer to ParseUser object): ok

object.relation don't work - but was expected as it's not (fully?) implemented.

@phillwiggins @RodrigoSMarques: I think we should have a go at this one.

Edit:
Also ok
object.pointerObject.geoPoint
object.pointerObject.file
object.pointerObject.someProp

@RodrigoSMarques
Copy link
Contributor

RodrigoSMarques commented Mar 19, 2019

@danibjor
Thank you for your feedback.

Can you tell me more about object.relation.

This would be: https://docs.parseplatform.org/rest/guide/#relational-queries

InQuery and notInQuery operators?

I'm starting to study the implementation because I also need them in my application.

@phillwiggins
How do I join Slack?

Do you intend to implement new features? I ask you not to duplicate things.

I intend to simplify the use of ACLs, complete other queries with Geopoint e Relational Queries

@danibjor
Copy link
Author

@RodrigoSMarques my bad - not constraining queries on column of type Relation<SomeClass> but rather accessing such a property on an object returned from the server. Right now, you get a single object, but the property should be of type List<SomeClass>

@phillwiggins ditto on Slack.

@phillwiggins
Copy link
Member

phillwiggins commented Mar 19, 2019 via email

@RodrigoSMarques
Copy link
Contributor

@danibjor
I did not understand.

Can you give me an example with native code (ios / Android) or indicate this in the documentation?

I have not worked with ParseRelation and I do not know how it works

@danibjor
Copy link
Author

danibjor commented Mar 19, 2019 via email

@RodrigoSMarques
Copy link
Contributor

@danibjor,
Okay, I got it, thank you. I'm going to study the implementation

@RodrigoSMarques
Copy link
Contributor

@phillwiggins
when releasing the new version, many of the open issues could be closed.

@RodrigoSMarques
Copy link
Contributor

@phillwiggins
this issue can be closed.

Resolved in the last release.

@danibjor
I am working on the Relational Query code. The In Query and Not In Query operators are ready. I'm studying the implementation you indicated. http://docs.parseplatform.org/android/guide/#relations

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

No branches or pull requests

3 participants