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?
postObjectaccepts single object, and there is nopostObjectsmethod available. I have tried creating an array of objects and provide it topostObject. 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