2
votes

I am creating a task application which has offline mode. I have almost created online mode successfully using RestKit.

While in offline mode, I am setting isSync attribute to false for each object, so that when network is available I could fetch these unsynced objects with predicate and POST them to server.

Unfortunately, I couldn't find a way to POST an array of these objects to server in one go using RestKit.

Does RestKit support this? Or is there any better way to implement offline support using RestKit?

Update:

I have found a way here, of creating an intermediate Entity (with its own mapping) that has a property of NSArray which can be POSTed to server.

But I have many entities (notes, tasks, comments ... etc) that needs offline functionality, do I have to create an additional intermediate Entity for each of my original Entity? (so that response of each can be mapped correctly in my original Entity)

Update 2:

After visiting here, I have found that POSTing an array of multiple objects in supported now in RestKit 0.20.0. But whenever I have POST an array of objects, no parameters are sent to server. This is how I am doing:

[DBTasks createTask:task attachments:nil completionHandler:^(DBTasks *task, NSError *error) {
            isPosting = NO;
            [self setLoadingState:NO];
            if (!error) {
                KLog(@"task is %@", task); // works perfect
                [self.tasksArray insertObject:task atIndex:0];
                [self reloadTasks];

            } else {
                KLog(@"error %@", error);
            }
        }];

This is my method for actually sending POST request:

(Just consider else part)

+ (void)createTask:(DBTasks *)task attachments:(NSArray *)attachments completionHandler:(void (^)(DBTasks *, NSError *))completionHandler {

    if (attachments != nil && attachments.count > 0) {
        NSMutableURLRequest *request =[[RKObjectManager sharedManager] multipartFormRequestWithObject:task
                                                                                               method:RKRequestMethodPOST
                                                                                                 path:URL_TASKS
                                                                                           parameters:@{@"total_attachments": [NSNumber numberWithInt:attachments.count]}
                                                                            constructingBodyWithBlock:^(id<AFMultipartFormData> formData) {

                                                                                int counter = 0;
                                                                                for (NSDictionary *dic in attachments) {
                                                                                    [formData appendPartWithFileData:UIImageJPEGRepresentation([dic objectForKey:@"image"], 0.7)
                                                                                                                name:[NSString stringWithFormat:@"attachment[%i]", counter]
                                                                                                            fileName:[dic objectForKey:@"name"]
                                                                                                            mimeType:@"image/jpg"];

                                                                                    counter++;
                                                                                }
                                                                            }];

        RKObjectRequestOperation *operation = [[RKObjectManager sharedManager] managedObjectRequestOperationWithRequest:request
                                                                                                   managedObjectContext:[NSManagedObjectContext MR_defaultContext]
                                                                                                                success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
                                                                                                                    KLog(@"success");
                                                                                                                    completionHandler((DBTasks *)[mappingResult firstObject], nil);

                                                                                                                } failure:^(RKObjectRequestOperation *operation, NSError *error) {
                                                                                                                    KLog(@"fail");
                                                                                                                    completionHandler(nil, error);
                                                                                                                }];
        [operation start];

    }

    // without attachment
    else {
        [[RKObjectManager sharedManager] postObject:task path:URL_TASKS parameters:nil
                                            success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
                                                completionHandler((DBTasks *)[mappingResult firstObject], nil);
                                            }
                                            failure:^(RKObjectRequestOperation *operation, NSError *error) {
                                                completionHandler(nil, error);
                                            }];
    }
}

Following is the mapping:

- (RKEntityMapping *)tasksMapping {

    RKEntityMapping *tasksMapping = [RKEntityMapping mappingForEntityForName:@"DBTasks" inManagedObjectStore:objectManager.managedObjectStore];
    tasksMapping.setDefaultValueForMissingAttributes = NO;
    tasksMapping.deletionPredicate = [NSPredicate predicateWithFormat:@"shouldBeDeleted = 1"];
    [tasksMapping setModificationAttributeForName:@"updated_at"];
    tasksMapping.identificationAttributes = @[@"id"];

    [tasksMapping addAttributeMappingsFromArray:@[@"completed_at", @"created_at", @"due_date", @"id", @"note", @"private", @"send_email", @"status", @"title", @"type", @"updated_at", @"user_id", @"parent_id", @"total_attachments", @"url", @"from", @"total_comments"]];

    [tasksMapping addAttributeMappingsFromDictionary:@{@"deleted": @"shouldBeDeleted"}];


    [tasksMapping addRelationshipMappingWithSourceKeyPath:@"attachments" mapping:[self attachmentsMapping]];
    [tasksMapping addRelationshipMappingWithSourceKeyPath:@"owner" mapping:[self contactsMapping]];
    [tasksMapping addRelationshipMappingWithSourceKeyPath:@"additional_owners" mapping:[self contactsMapping]];
    [tasksMapping addRelationshipMappingWithSourceKeyPath:@"tags" mapping:[self tagsMapping]];

    return tasksMapping;
}

The same mapping is used for POST/PUT/DELETE but with calling inverseMapping method.

Now when I call postObject with an array:

[DBTasks createTasks:[NSArray arrayWithObjects:task, task, nil] attachments:nil completionHandler:^(NSArray *array, NSError *error) {
            isPosting = NO;
            [self setLoadingState:NO];
            if (!error) {
                KLog(@"Array of objects is %@", array);
                [self.tasksArray insertObject:task atIndex:0];
                [self reloadTasks];

            } else {
                KLog(@"error %@", error);
            }
        }];

with almost previous method:

+ (void)createTasks:(NSArray *)tasksArray attachments:(NSArray *)attachments completionHandler:(void (^)(NSArray *, NSError *))completionHandler {
    [[RKObjectManager sharedManager] postObject:tasksArray path:URL_TASKS parameters:nil
                                        success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) {
                                            completionHandler(mappingResult.array, nil);
                                        }
                                        failure:^(RKObjectRequestOperation *operation, NSError *error) {
                                            completionHandler(nil, error);
                                        }];
}

The application is terminated

2014-05-05 16:18:30.692 MyApp[8195:f03] D restkit.object_mapping:RKMapperOperation.m:377 Executing mapping operation for representation: (
        {
        attachments =         (
        );
        "completed_at" = "<null>";
        "created_at" = "<null>";
        deleted = 0;
        "due_date" = "<null>";
        from = "Stand-alone task";
        id = "<null>";
        note = "<null>";
        owner = "<null>";
        private = 0;
        "send_email" = "<null>";
        status = "<null>";
        tags =         (
        );
        title = "<null>";
        "total_attachments" = 0;
        "total_comments" = 0;
        type = Task;
        "updated_at" = "<null>";
        url = "http://10.28.79.98:3000/workspace/tasks?open=task_";
        "user_id" = 26894;
    }
)
 and targetObject: (null)
2014-05-05 16:18:30.693 MyApp[8195:f03] D restkit.object_mapping:RKMapperOperation.m:297 Found mappable collection at keyPath '': (
        {
        attachments =         (
        );
        "completed_at" = "<null>";
        "created_at" = "<null>";
        deleted = 0;
        "due_date" = "<null>";
        from = "Stand-alone task";
        id = "<null>";
        note = "<null>";
        owner = "<null>";
        private = 0;
        "send_email" = "<null>";
        status = "<null>";
        tags =         (
        );
        title = "<null>";
        "total_attachments" = 0;
        "total_comments" = 0;
        type = Task;
        "updated_at" = "<null>";
        url = "http://10.28.79.98:3000/workspace/tasks?open=task_";
        "user_id" = 26894;
    }
)
2014-05-05 16:18:30.694 MyApp[8195:f03] CoreData: error: Failed to call designated initializer on NSManagedObject class 'DBTasks' 
2014-05-05 16:18:30.694 MyApp[8195:f03] D restkit.object_mapping:RKMapperOperation.m:231 Asked to map source object {
    attachments =     (
    );
    "completed_at" = "<null>";
    "created_at" = "<null>";
    deleted = 0;
    "due_date" = "<null>";
    from = "Stand-alone task";
    id = "<null>";
    note = "<null>";
    owner = "<null>";
    private = 0;
    "send_email" = "<null>";
    status = "<null>";
    tags =     (
    );
    title = "<null>";
    "total_attachments" = 0;
    "total_comments" = 0;
    type = Task;
    "updated_at" = "<null>";
    url = "http://10.28.79.98:3000/workspace/tasks?open=task_";
    "user_id" = 26894;
} with mapping <RKEntityMapping:0x10598240 objectClass=DBTasks propertyMappings=(
    "<RKAttributeMapping: 0x10599660 completed_at => completed_at>",
    "<RKAttributeMapping: 0x10599670 created_at => created_at>",
    "<RKAttributeMapping: 0x10599680 due_date => due_date>",
    "<RKAttributeMapping: 0x10599690 id => id>",
    "<RKAttributeMapping: 0x105996a0 note => note>",
    "<RKAttributeMapping: 0x105996b0 private => private>",
    "<RKAttributeMapping: 0x105996c0 send_email => send_email>",
    "<RKAttributeMapping: 0x105996d0 status => status>",
    "<RKAttributeMapping: 0x105996e0 title => title>",
    "<RKAttributeMapping: 0x105996f0 type => type>",
    "<RKAttributeMapping: 0x10599700 updated_at => updated_at>",
    "<RKAttributeMapping: 0x10599710 user_id => user_id>",
    "<RKAttributeMapping: 0x10599720 parent_id => parent_id>",
    "<RKAttributeMapping: 0x10599730 total_attachments => total_attachments>",
    "<RKAttributeMapping: 0x10599740 url => url>",
    "<RKAttributeMapping: 0x10599750 from => from>",
    "<RKAttributeMapping: 0x10599760 total_comments => total_comments>",
    "<RKAttributeMapping: 0x10599870 deleted => shouldBeDeleted>",
    "<RKRelationshipMapping: 0x1059b1c0 attachments => attachments>",
    "<RKRelationshipMapping: 0x1059c5f0 owner => owner>",
    "<RKRelationshipMapping: 0x1059dca0 additional_owners => additional_owners>",
    "<RKRelationshipMapping: 0x105a1e00 tags => tags>"
)>
2014-05-05 16:18:30.695 MyApp[8195:f03] D restkit.object_mapping:RKMappingOperation.m:952 Starting mapping operation...
2014-05-05 16:18:30.696 MyApp[8195:340b] D restkit.object_mapping:RKPropertyInspector.m:130 Cached property inspection for Class 'DBTasks': {
    "additional_owners" =     {
        isPrimitive = 0;
        keyValueCodingClass = NSSet;
        name = "additional_owners";
    };
    attachments =     {
        isPrimitive = 0;
        keyValueCodingClass = NSSet;
        name = attachments;
    };
    "completed_at" =     {
        isPrimitive = 0;
        keyValueCodingClass = NSDate;
        name = "completed_at";
    };
    "created_at" =     {
        isPrimitive = 0;
        keyValueCodingClass = NSDate;
        name = "created_at";
    };
    "due_date" =     {
        isPrimitive = 0;
        keyValueCodingClass = NSDate;
        name = "due_date";
    };
    from =     {
        isPrimitive = 0;
        keyValueCodingClass = NSString;
        name = from;
    };
    id =     {
        isPrimitive = 0;
        keyValueCodingClass = NSNumber;
        name = id;
    };
    note =     {
        isPrimitive = 0;
        keyValueCodingClass = NSString;
        name = note;
    };
    owner =     {
        isPrimitive = 0;
        keyValueCodingClass = DBContacts;
        name = owner;
    };
    "parent_id" =     {
        isPrimitive = 0;
        keyValueCodingClass = NSNumber;
        name = "parent_id";
    };
    private =     {
        isPrimitive = 0;
        keyValueCodingClass = NSNumber;
        name = private;
    };
    "send_email" =     {
        isPrimitive = 0;
        keyValueCodingClass = NSNumber;
        name = "send_email";
    };
    shouldBeDeleted =     {
        isPrimitive = 0;
        keyValueCodingClass = NSNumber;
        name = shouldBeDeleted;
    };
    status =     {
        isPrimitive = 0;
        keyValueCodingClass = NSNumber;
        name = status;
    };
    tags =     {
        isPrimitive = 0;
        keyValueCodingClass = NSSet;
        name = tags;
    };
    title =     {
        isPrimitive = 0;
        keyValueCodingClass = NSString;
        name = title;
    };
    topic =     {
        isPrimitive = 0;
        keyValueCodingClass = DBTopics;
        name = topic;
    };
    "total_attachments" =     {
        isPrimitive = 0;
        keyValueCodingClass = NSNumber;
        name = "total_attachments";
    };
    "total_comments" =     {
        isPrimitive = 0;
        keyValueCodingClass = NSNumber;
        name = "total_comments";
    };
    type =     {
        isPrimitive = 0;
        keyValueCodingClass = NSString;
        name = type;
    };
    "updated_at" =     {
        isPrimitive = 0;
        keyValueCodingClass = NSDate;
        name = "updated_at";
    };
    url =     {
        isPrimitive = 0;
        keyValueCodingClass = NSString;
        name = url;
    };
    "user_id" =     {
        isPrimitive = 0;
        keyValueCodingClass = NSNumber;
        name = "user_id";
    };
}
2014-05-05 16:18:30.698 MyApp[8195:340b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** setObjectForKey: key cannot be nil'
*** First throw call stack:
(
    0   CoreFoundation                      0x034721e4 __exceptionPreprocess + 180
    1   libobjc.A.dylib                     0x02be48e5 objc_exception_throw + 44
    2   CoreFoundation                      0x034fbeb8 -[__NSDictionaryM setObject:forKey:] + 888
    3   MyApp                         0x002dad3a __61-[RKPropertyInspector(CoreData) propertyInspectionForEntity:]_block_invoke61 + 154
    4   libdispatch.dylib                   0x04ce17b8 _dispatch_call_block_and_release + 15
    5   libdispatch.dylib                   0x04cf64d0 _dispatch_client_callout + 14
    6   libdispatch.dylib                   0x04ce4047 _dispatch_queue_drain + 452
    7   libdispatch.dylib                   0x04ce3e42 _dispatch_queue_invoke + 128
    8   libdispatch.dylib                   0x04ce4de2 _dispatch_root_queue_drain + 78
    9   libdispatch.dylib                   0x04ce5127 _dispatch_worker_thread2 + 39
    10  libsystem_pthread.dylib             0x05025dab _pthread_wqthread + 336
    11  libsystem_pthread.dylib             0x05029cce start_wqthread + 30
)
libc++abi.dylib: terminating with uncaught exception of type NSException
(lldb)

Any suggestion what I am doing wrong?

1
No, you don't. Show your model structure, required JSON sample and the code you tried using an array of objects (and describe what it did wrong). - Wain
@Wain, thanks for reply. Actually I have not implemented it, because I don't know how to do that. The postObject accepts single object, and there is no postObjects method available. I have tried creating an array of objects and provide it to postObject. The server has not written to handle this request, so I am waiting for server guy to for that. But I am still wondering if POSTing an array like this would reverse map exactly my local objects and update them. Am I going wrong? - Khawar
Posting an array is correct. The mapping controls what is taken from each object in the array to the JSON. - Wain
@Wain, I am trying to POST an array of objects to server, but server side guy says he doesn't receive any parameters. When I POST a single object, it works perfect. Any idea what can be wrong? I have updated the question. - Khawar

1 Answers

1
votes

The problem is not related to sending an array, it is due to your core data stack setup. There is a big difference between your two cases. The one which works uses the default context from magical record. The one which doesn't uses the object manager and it's associated object store - which would seem to not be configured.

From the code and error, posting a single task without attachments should also fail.

Check your core data stack creation and the association with the object manager.