Retrieve Records from Quickblox API - quickblox

I am using Quickblox SDK in my Application and designed Custom Objects in the Quickblox Admin panel.Here i am having some complex relationship between Custom Objects and not able to figure out how to retrieve the records using the Quickblox API.
Problem:
There are two tables 1.User 2.Group, i am successfully fetching all the groups created by a specific using by using parent_id key as mention in Quickblox API for maintaining relationships between tables.
I want to retrieve all the groups created by the user and also the groups in which the user is added as a member which are created by other Users.How can i solve this? In my group table i am having a column that is an array of userIds to represent all the group members.
Can anyone tell me how to resolve this.How i can query and retrieve.I am really poor at DB knowledge and moreover Quickblox is a having different approach in the DB Design.

NSMutableDictionary *getRequest = [NSMutableDictionary dictionary];
[getRequest setObject:user_id forKey:#“user_ids_column_name[in]"];
[getRequest setObject:user_id forKey:#"ParentID"];
QBRequestErrorBlock errorBlock = ^(QBResponse *response) {
NSLog(#"error: %#", response.error);
};
[QBRequest objectsWithClassName:#“Group” extendedRequest:getRequest successBlock:^(QBResponse *response, NSArray *objects, QBResponsePage *page){
NSLog(#"Success");
} errorBlock:errorBlock];

Related

Firebase query not returning correct data

I have the following data in Firebase:
- Info/
-2018-09-23-13-38-00-WhyNewYo
-2018-09-23-14-04-57-PompeoWe
-2018-09-23-14-10-32-FocusonE
-2018-09-23-14-39-00-SenPatty
-2018-09-23-14-40-30-Rebelatt
-2018-09-23-14-57-33-Piratesa
Each entries are created at a different time but I named them based on a specific title so I know which is the latest and it would be in alphabetical order when I view them in the Firebase console automatically.
When I wanted to query the latest 2 data (the last 2 based on the order shown above), but Firebase seems to return incorrect data and I am using the following code:
FIRDatabaseReference *dataRef = [[FIRDatabase database] reference];
dataRef = [dataRef child:[NSString stringWithFormat:#"/info"]];
[[[dataRef queryOrderedByValue] queryLimitedToLast:2] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
NSDictionary *infoFromFirebase= snapshot.value;
When you query and observe FIRDataEventTypeValue, you get all matching nodes in a single snapshot. If you call snapshot.value the data is converted to a dictionary, and information on the order of the matching nodes is lost.
To maintain the order of the nodes, be sure to iterate over the children of the snapshot with for (FIRDataSnapshot* child in snapshot.children) { … }.

Deleting all table records from core data database using relationship

I'm using core data for my application i have 4-5 tables one of which is userProfile table. i have implemented logout in the app. if user logs out of the app im deleting user profile and will be inserting new if logged in with other user account. i want to delete all the records from database on userprofile delete. im using relationship for this but it is not deleting other records from the db even if user profile record has been deleted.
one thing i would like to mention is all the data is coming from service. and i am using cascade delete rule for relationship created between userprofile table and other tables.
You have two different ways to achieve this.
The first is to delete the store and create it again. This means access the store in the file system and delete the sql file, for example. For example, you can find how to achieve it in the following discussion: Delete/Reset all entries in Core Data?.
The second is solution is to create a cascade relationship in UserProfile entity that will link the other ones. In the latter you must set up an inverse relationship (nullify would be the correct approach). For further info see my answer at Setting up a parent-child relationship in Core Data.
Said this, and based on my experience, I would discourage to save user info (e.g. passwords) in Core Data. Instead, adopt the Keychain for this. There are libraries that wrap the Keychain access in an easy manner (e.g. SSKeychain).
I tried using relationship using cascade rule but didn't worked for me so I used Delete/Reset all entries in Core Data method. Used following code for this.
NSError * error;
NSURL * storeURL = [[AppDelegate.managedObjectContext persistentStoreCoordinator] URLForPersistentStore:[[[AppDelegate.managedObjectContext persistentStoreCoordinator] persistentStores] lastObject]];
[AppDelegate.managedObjectContext lock];
[AppDelegate.managedObjectContext reset];//to drop pending changes
//delete the store from the current managedObjectContext
if ([[AppDelegate.managedObjectContext persistentStoreCoordinator] removePersistentStore: [[[AppDelegate.managedObjectContext persistentStoreCoordinator] persistentStores] lastObject] error:&error])
{
// remove the file containing the data
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:&error];
//recreate the store like in the appDelegate method
[[[AppDelegate managedObjectContext] persistentStoreCoordinator] addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options: #{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES} error:&error];
}
[AppDelegate.managedObjectContext unlock];
//that's it !
This worked for me.

One To Many With Convenience Backpointer

I'd like to model a one-to-many relationship between users and checkIns on Parse.com. Additionally, I'd like a user to have a convenience pointer to it's lastCheckin, to avoid having to query checkIns to get a user's most recent checkIn. Using the following code, the user's lastCheckin column is always empty in the Data Browser:
PFUser *currentUser = [PFUser currentUser];
PFObject *checkIn = [PFObject objectWithClassName:#"CheckIn"];
checkIn[#"forUser"] = currentUser;
checkIn[#"foursquareID"] = selectedVenue[#"id"];
checkIn[#"name"] = selectedVenue[#"name"];
checkIn[#"location"] = [PFGeoPoint geoPointWithLatitude:[selectedVenue[#"location"][#"lat"] doubleValue]
longitude:[selectedVenue[#"location"][#"lng"] doubleValue]];
[checkIn saveInBackgroundWithBlock:^(BOOL succeeded, NSError *error) {
currentUser[#"lastCheckin"] = checkIn;
[currentUser saveInBackground];
}];
Removing the following line causes the User.lastCheckin to correctly point to the desired CheckIn in the Data Browser, but obviously this breaks the one-to-many linkage:
checkIn[#"forUser"] = currentUser;
Is there some way for me to get both the one-to-may relationship between Users and CheckIns, and a User.lastCheckIn pointer?
If you are setting the lastCheckin on the User each time you create a new CheckIn you can access that checkin easily by fetching the current user and including lastCheckin with includeKey.
http://blog.parse.com/2011/12/06/queries-for-relational-data/
To get all checkins for a user you can simply query the CheckIn class where the current user is equal to forUser. That is your 1 to many relationship. You do not need to maintain a relation of checkins on the User class since you have a pointer from each CheckIn instance back to the user as a pointer. When you set lastCheckin on the user instance you are not changing the property on older checkins.
Perhaps you are thinking this is like SQL with primary and foreign keys. I get confused by NoSQL since I have a SQL background. I am always learning to think like Parse and NoSQL. What I see above is all you need to query all checkins for a user and the last checkin for a user. You can also simply query all checkins for a user, limit result to 1 and sort it so the newest checkin is the first result. It should be quite fast on Parse.
You may also want to look into using Cloud Code. You could call a Cloud Code function and simply pass the parameters you need to create a new checkin and have all of this work done in Cloud Code. An After Save function could be used to update lastCheckin on the user.
http://www.parse.com/docs/js/symbols/Parse.Cloud.AfterSaveRequest.html
If you user on Cloud Code you can update how the data is managed without updating the iOS app.

Dealing with duplicate contacts due to linked cards in iOS' Address Book API

Some beta-users of my upcoming app are reporting that the list of contacts contain a lot of duplicate records. I'm using the result from ABAddressBookCopyArrayOfAllPeople as the data source for my customized table view of contacts, and it baffles me that the results are different from the iPhone's 'Contacts' app.
When looking more closely at the Contacts app, it seems that the duplicates originate from entries with "Linked Cards". The screenshots below have been obfuscated a bit, but as you see in my app on the far right, "Celine" shows up twice, while in the Contacts app on the left there's only one "Celine". If you click the row of that single contact, you get a "Unified Info" card with two "Linked Cards" (as shown in the center, I didn't use Celine's contact details because they didn't fit on one screenshot):
The issues around "Linked Cards" have quite a few topics on Apple's forums for end users, but apart from the fact that many point to a 404 support page, I can't realistically go around fixing all of my app's users' address books. I would much rather like to deal with it elegantly and without bothering the user. To make matters worse, it seems I'm not the only one with this issue, since WhatsApp is showing the same list containing duplicate contacts.
Just to be clear about the origins of the duplicate contacts, I'm not storing, caching or otherwise trying to be smart about the array ABAddressBookCopyArrayOfAllPeople returns. So the duplicate records come directly from the API call.
Does anyone know how to deal with or detect these linked cards, preventing duplicate records from showing up? Apple's Contacts app does it, how can the rest of us do so too?
UPDATE: I wrote a library and put it on Cocoapods to solve the issue at hand. See my answer below
One method would be to only retrieve the contacts from the default address book source:
ABAddressBookRef addressBook = ABAddressBookCreate();
NSArray *people = (__bridge NSArray *)ABAddressBookCopyArrayOfAllPeopleInSource(addressBook, ABAddressBookCopyDefaultSource(addressBook));
But that is lame, right? It targets the on-device address book, but not extra contacts that might be in Exchange or other fancy syncing address books.
So here's the solution you're looking for:
Iterate through the ABRecord references
Grab each respective "linked references" (using ABPersonCopyArrayOfAllLinkedPeople)
Bundle them in an NSSet (so that the grouping can be uniquely identified)
Add that NSSet to another NSSet
Profit?
You now have an NSSet containing NSSets of linked ABRecord objects. The overarching NSSet will have the same count as the number of contacts in your "Contacts" app.
Example code:
NSMutableSet *unifiedRecordsSet = [NSMutableSet set];
ABAddressBookRef addressBook = ABAddressBookCreate();
CFArrayRef records = ABAddressBookCopyArrayOfAllPeople(addressBook);
for (CFIndex i = 0; i < CFArrayGetCount(records); i++)
{
NSMutableSet *contactSet = [NSMutableSet set];
ABRecordRef record = CFArrayGetValueAtIndex(records, i);
[contactSet addObject:(__bridge id)record];
NSArray *linkedRecordsArray = (__bridge NSArray *)ABPersonCopyArrayOfAllLinkedPeople(record);
[contactSet addObjectsFromArray:linkedRecordsArray];
// Your own custom "unified record" class (or just an NSSet!)
DAUnifiedRecord *unifiedRecord = [[DAUnifiedRecord alloc] initWithRecords:contactSet];
[unifiedRecordsSet addObject:unifiedRecord];
CFRelease(record);
}
CFRelease(records);
CFRelease(addressBook);
_unifiedRecords = [unifiedRecordsSet allObjects];
I've been using ABPersonCopyArrayOfAllLinkedPeople() in my app for some time now. Unfortunately, I've just discovered that it doesn't always do the right thing. For example, if you have two contacts that have the same name but one has the "isPerson" flag set and the other does not, the above function won't consider them "linked". Why is this an issue? Because Gmail(exchange) sources don't support this boolean flag. If you try to save it as false, it will fail, and the contact you saved in it will come back on the next run of your app as unlinked from the contact you saved in iCload (CardDAV).
Similar situation with social services: Gmail doesn't support them and the function above will see two contacts with the same names as different if one has a facebook account and one does not.
I'm switching over to my own name-and-source-recordID-only algorithm for determining whether two contact records should be displayed as a single contact. More work but there's a silver lining: ABPersonCopyArrayOfAllLinkedPeople() is butt-slow.
The approach that #Daniel Amitay provided contained nuggets of great value, but unfortunately the code is not ready for use. Having a good search on the contacts is crucial to my and many apps, so I spent quite a bit of time getting this right, while on the side also addressing the issue of iOS 5 and 6 compatible address book access (handling user access via blocks). It solves both the many linked cards due to incorrectly synched sources and the cards from the newly added Facebook integration.
The library I wrote uses an in-memory (optionally on-disk) Core Data store to cache the address book record ID's, providing an easy background-threaded search algorithm that returns unified address book cards.
The source is available on a github repository of mine, which is a CocoaPods pod:
pod 'EEEUnifiedAddressBook'
With the new iOS 9 Contacts Framework you can finally have your unified contacts.
I show you two examples:
1) Using fast enumeration
//Initializing the contact store:
CNContactStore* contactStore = [CNContactStore new];
if (!contactStore) {
NSLog(#"Contact store is nil. Maybe you don't have the permission?");
return;
}
//Which contact keys (properties) do you want? I want them all!
NSArray* contactKeys = #[
CNContactNamePrefixKey, CNContactGivenNameKey, CNContactMiddleNameKey, CNContactFamilyNameKey, CNContactPreviousFamilyNameKey, CNContactNameSuffixKey, CNContactNicknameKey, CNContactPhoneticGivenNameKey, CNContactPhoneticMiddleNameKey, CNContactPhoneticFamilyNameKey, CNContactOrganizationNameKey, CNContactDepartmentNameKey, CNContactJobTitleKey, CNContactBirthdayKey, CNContactNonGregorianBirthdayKey, CNContactNoteKey, CNContactImageDataKey, CNContactThumbnailImageDataKey, CNContactImageDataAvailableKey, CNContactTypeKey, CNContactPhoneNumbersKey, CNContactEmailAddressesKey, CNContactPostalAddressesKey, CNContactDatesKey, CNContactUrlAddressesKey, CNContactRelationsKey, CNContactSocialProfilesKey, CNContactInstantMessageAddressesKey
];
CNContactFetchRequest* fetchRequest = [[CNContactFetchRequest alloc] initWithKeysToFetch:contactKeys];
[fetchRequest setUnifyResults:YES]; //It seems that YES is the default value
NSError* error = nil;
__block NSInteger counter = 0;
And here i loop through all unified contacts using fast enumeration:
BOOL success = [contactStore enumerateContactsWithFetchRequest:fetchRequest
error:&error
usingBlock:^(CNContact* __nonnull contact, BOOL* __nonnull stop) {
NSLog(#"Unified contact: %#", contact);
counter++;
}];
if (success) {
NSLog(#"Successfully fetched %ld contacts", counter);
}
else {
NSLog(#"Error while fetching contacts: %#", error);
}
2) Using unifiedContactsMatchingPredicate API:
// Contacts store initialized ...
NSArray * unifiedContacts = [contactStore unifiedContactsMatchingPredicate:nil keysToFetch:contactKeys error:&error]; // Replace the predicate with your filter.
P.S You maybe also be interested at this new API of CNContact.h:
/*! Returns YES if the receiver was fetched as a unified contact and includes the contact having contactIdentifier in its unification */
- (BOOL)isUnifiedWithContactWithIdentifier:(NSString*)contactIdentifier;
I'm getting all sources ABAddressBookCopyArrayOfAllSources, moving the default one ABAddressBookCopyDefaultSource to the first position, then iterate through them and getting all people from source ABAddressBookCopyArrayOfAllPeopleInSource skipping ones I've seen linked before, then getting linked people on each ABPersonCopyArrayOfAllLinkedPeople.

How to load multiple unrelated objects with RestKit?

I need to load mulitple objects that don't have relationships but located on the same remote API using RestKit into CoreData (in other words download remote objects to use them locally in iOS)
So I've setup my Client and objectManager:
client = [RKClient clientWithBaseURL:[NSURL URLWithString:baseUrl]];
[client setUsername:#"someUsername"];
[client setPassword:#"somePassword"];
objectManager = [RKObjectManager managerWithBaseURLString:baseUrl];
[objectManager setClient:client];
Then I setup some mappings for one Object#1:
RKManagedObjectMapping* companyMapping = [RKManagedObjectMapping mappingForEntityWithName:#"Company" inManagedObjectStore:objectManager.objectStore];
companyMapping.primaryKeyAttribute = #"backendID";
[companyMapping mapKeyPath:#"id" toAttribute:#"backendID"];
[companyMapping mapKeyPath:#"company_name" toAttribute:#"companyName"];
Then I setup some mappings for Object #2:
RKManagedObjectMapping* eventMapping = [RKManagedObjectMapping mappingForEntityWithName:#"Event" inManagedObjectStore:objectManager.objectStore];
eventMapping.primaryKeyAttribute = #"backendID";
[eventMapping mapKeyPath:#"id" toAttribute:#"backendID"];
[eventMapping mapKeyPath:#"description" toAttribute:#"eventDescription"];
After that I set objectMapping to mappingProvider:
[objectManager.mappingProvider setObjectMapping:companyMapping forResourcePathPattern:remoteObjectPath];
[objectManager.mappingProvider setObjectMapping:eventMapping forResourcePathPattern:remoteObjectPath];
And lastly I call "loadObjects..." for both of these objects one after another:
[objectManager loadObjectsAtResourcePath:remoteCompaniesObjectPath delegate:self];
[objectManager loadObjectsAtResourcePath:remoteEventsObjectPath delegate:self];
What happens after this that data from both Companies and Events is somehow loaded into the Events table, so table data is completely unusable.
If I run only loadObjectsAtResourcePath:remoteCompaniesObjectPath then everything works as expected remote Company object downloads into it's own table in the CoreData.
While it does make sense why it's happening (data For Companies starting to load and gets new mapping, so it loads to the wrong table) the QUESTION is:
How to call loadObjectsAtResourcePath:remoteObjectPath methods to load multiple unrelated objects so they would properly loaded into their respective tables in the CoreData???
Well I think there is nothing There for restkit to distinguish between two object types company and events.
There are two ways of going about this. Both are straightforward, upto you what to choose:
You provide a rootkeypath to restkit for both company and events. And then you modify your api to include a rootkeypath in the json. Something like below..
["company":{
"Backendid":1
"company name":"abc"
}
{"events":{
{Backendid":1
"description":"party"
}]
In this case restkit will know which is an event and what is a company and will automatically map to the right table.. Checkout rkgithub example for more tips.
You set your online api to have 2 different urls for both. So then the different URL will be make sure restkit doesn't get tables mixed up.
Also something to ask here, does your events column have a company name column as well? Because I would have thought restkit will give you an error as from your mapping there doesn't seem to be a column named company name in an events table. Are you trying to do some sort of to-many relationship here? Where company has-many events type of thing.
Your mapping provider needs a tweak -- set each mapping with its respective URL.
Change mapping from:
[objectManager.mappingProvider setObjectMapping:companyMapping forResourcePathPattern:remoteObjectPath];
[objectManager.mappingProvider setObjectMapping:eventMapping forResourcePathPattern:remoteObjectPath];
To:
[objectManager.mappingProvider setObjectMapping:companyMapping forResourcePathPattern: remoteCompaniesObjectPath];
[objectManager.mappingProvider setObjectMapping:eventMapping forResourcePathPattern:remoteEventsObjectPath];