Google+ Sign-In is now deprecated and Google is advising developers to use Google Sign-In instead. Using GIDSignIn class I was able to make users login via their Google account. Now I want to get the contact list including names and profile pictures from Google+. What is the ideal solution to this problem?
I found the following link helpful.
http://www.appcoda.com/google-sign-in-how-to/
Also this StackOverflow post gave an incomplete solution to the problem.
Get Google contacts using API on iOS
Please share your solutions.
With the help of the links I stated before, I was able to somewhat solve the problem.
First of all, I included AFNetworking into my Xcode project.
https://github.com/AFNetworking/AFNetworking
Secondly, in the AppDelegate.m I used the following code in the application:didFinishLaunchingWithOptions: method.
[[GIDSignIn sharedInstance] setClientID:
#"XXXXXXXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX.apps.googleusercontent.com"];
[[GIDSignIn sharedInstance] setShouldFetchBasicProfile:YES]; // default value
[[GIDSignIn sharedInstance] setScopes:#[#"https://www.googleapis.com/auth/plus.login",
#"https://www.googleapis.com/auth/plus.me"]];
Then in my FriendsViewController.m file where I show the list of people from Google+ including names and profile pictures in a collection view, I used the following code.
if ([[GIDSignIn sharedInstance] hasAuthInKeychain])
{
NSString * urlString = [NSString stringWithFormat:
#"https://www.googleapis.com/plus/v1/people/me/people/visible?access_token=%#",
[GIDSignIn sharedInstance].currentUser.authentication.accessToken];
// use connected in place of visible if you want only the people who use the app
AFJSONResponseSerializer * responseSerializer =
[AFJSONResponseSerializer serializerWithReadingOptions:NSJSONReadingAllowFragments];
AFHTTPSessionManager * sessionManager = [AFHTTPSessionManager manager];
[sessionManager setResponseSerializer:responseSerializer];
[sessionManager GET:urlString parameters:nil progress:nil
success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject)
{
if (responseObject != nil)
{
NSArray * arrayFriends = [responseObject valueForKey:#"items"];
for (id object in arrayFriends)
{
NSString * stringName = [object valueForKey:#"displayName"];
NSString * stringUrlProfilePicture =
[[object valueForKey:#"image"] valueForKey:#"url"];
NSURL * urlProfilePicture = [NSURL URLWithString:stringUrlProfilePicture];
NSData * dataProfilePicture = [NSData dataWithContentsOfURL:urlProfilePicture];
UIImage * imageProfilePicture = [UIImage imageWithData:dataProfilePicture];
NSMutableDictionary * dictionary = [[NSMutableDictionary alloc] init];
[dictionary setObject:stringName forKey:#"name"];
[dictionary setObject:imageProfilePicture forKey:#"profilePicture"];
[self.arrayDictionaryFriends addObject:dictionary];
// self.arrayDictionaryFriends is used as the data source
}
[self.collectionViewFriends reloadData];
}
}
failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error)
{
}];
}
Don't forget to implement the other points mentioned in the following links.
https://developers.google.com/identity/sign-in/ios/start-integrating
https://developers.google.com/identity/sign-in/ios/sign-in
The solution is not perfect as there is a delay when all the images are loaded from the urls. I would like to get feedback on the solution and idea of possible improvements.
i have been trying to implement the follow code , but I am having a hard time understanding the following code:
- (void)getRoutesWithStopName:(NSString *) stopName
success:(void (^)(NSArray *routes))success
error:(void (^)(NSString *errorMsg)) error
{
[[self AFManagerObject] POST:GET_ROUTES
parameters:#{#"params" : #{ #"stopName": [NSString stringWithFormat:#"%%%#%%",[stopName lowercaseString]]} }
success:^(AFHTTPRequestOperation *operation, id responseObject) {
NSArray *routesRows = responseObject[#"rows"];
NSMutableArray *routes = [[NSMutableArray alloc] initWithCapacity:routesRows.count];
for(NSDictionary *dicRoute in routesRows)
{
FLBRoute *route = [[FLBRoute alloc] initWithAttrs:dicRoute];
[routes addObject:route];
}
success(routes);
}
failure:^(AFHTTPRequestOperation *operation, NSError *err) {
error(err.description);
}
];
}
I tried learning about blocks but I still can not understand what is going on here. Can you provide me a step by step explanation of the code ?
actually here used for webserviceCall
step-1
- (void)getRoutesWithStopName:(NSString *) stopName
success:(void (^)(NSArray *routes))success
error:(void (^)(NSString *errorMsg)) error
// here pass the one NSString and get the response using NSArray and failure using NSString
step-2
// here used AFNEtworking for call web service
//request block
[self AFManagerObject] -- NSObject class for AFNetworking method place.
POST:GET_ROUTES --> post is default function of request Type, GET_ROUTES --> your Macro class for Request URL
parameters --> send the parameter to server
[[self AFManagerObject] POST:GET_ROUTES
parameters:#{#"params" : #{ #"stopName": [NSString stringWithFormat:#"%%%#%%",[stopName lowercaseString]]} }
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
/*********** success response serlize and store into Array**********/
NSArray *routesRows = responseObject[#"rows"];
NSMutableArray *routes = [[NSMutableArray alloc] initWithCapacity:routesRows.count];
for(NSDictionary *dicRoute in routesRows)
{
FLBRoute *route = [[FLBRoute alloc] initWithAttrs:dicRoute];
[routes addObject:route];
// this is your NSObject class for save the details ,
}
success(routes);
/************** success stop **********/
}
/*********** error if request is fail ************/
failure:^(AFHTTPRequestOperation *operation, NSError *err) {
error(err.description);
}
];
/*********** error if request is stop ************/
I think you need to read a little more about callbacks https://en.m.wikipedia.org/wiki/Callback_(computer_programming) and blocks https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/WorkingwithBlocks/WorkingwithBlocks.html and https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Blocks/Articles/00_Introduction.html
Basically the method send a POST request and as you know it needs some time for the request to be sent to the server and for the server to respond. You don't want in this time your application to be freezed, so 2 callbacks are used, 1 for success case and 1 for failure case. A block callback is just a block of code that you want to be executed later, when the server will respond back, being a success or failure.
Creating first app with webservices, I am using AFNetworking for webservices. Everything is working fine but i have no idea , that how to fetch data out from block which i am getting in response. This is what i have done so far
+(WebServices *)sharedManager{
static WebServices *managerServices = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
managerServices = [[self alloc] init];
});
return managerServices;
}
-(NSArray *)firstPostService{
//1
NSURL *url = [NSURL URLWithString:BaseURLString];
//2
AFHTTPSessionManager *manager = [[AFHTTPSessionManager alloc] initWithBaseURL:url];
manager.responseSerializer = [AFHTTPResponseSerializer serializer];
NSDictionary *param = #{#"request" : #"get_pull_down_menu" , #"data" : #"0,0,3,1"};
[manager POST:#"person.php" parameters:param success:^(NSURLSessionDataTask *task, id responseObject) {
[self methodUsingJsonFromSuccessBlock:responseObject];
} failure:^(NSURLSessionDataTask *task, NSError *error) {
UIAlertView *av = [[UIAlertView alloc] initWithTitle:#"Error retrieving data" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil];
[av show];
}];
if (list.count == 0) {
NSLog(#"Nothing in array yet!!");
}
else{
NSLog(#"Object 1 is : %#", [list objectAtIndex:1]);
}
return list;
}
- (void)methodUsingJsonFromSuccessBlock:(id)json {
// use the json
NSString *string = [NSString stringWithUTF8String:[json bytes]];
NSLog(#"This is data : %#", string);
list = [string componentsSeparatedByString:#"\n"];
NSLog(#"After sepration first object: %#", [list objectAtIndex:1]);
//NSLog(#"json from the block : %#", json);
}
What i understand reading from different blogs and tuts, that block is a separate thread and what every i do finishes with it. I read some where that this is normally use for it
dispatch_async(dispatch_get_main_queue(), ^{
data = [string componentsSeparatedByString:#"\n"];
//WHERE DATA IS __block NSArray * data = [[NSArray alloc] init];
});
and i was returning it in the of the function(firstPostService) but nothing happen. i still get an empty array outside the block. Kindly help me , suggest me some good reading stuff. Thanking you all in advance.
You say:
I need this data to my view controller i am trying to return in dispatch part but it is not allowing. Is it possible to get data into my viewcontroller class ?
Yes, it's possible. But, no, firstPostService should not return the results. It can't because it returns immediately, but the POST completion blocks won't be called until much later. There's nothing to return by the time firstPostService returns.
At the end of your original question, you said:
What i understand reading from different blogs and tuts, that block is a separate thread and what every i do finishes with it. I read some where that this is normally use for it
dispatch_async(dispatch_get_main_queue(), ^{
data = [string componentsSeparatedByString:#"\n"];
//WHERE DATA IS __block NSArray * data = [[NSArray alloc] init];
});
This is not the appropriate pattern of __block local variable. You generally use that __block pattern when dealing with some block that runs synchronously (for example the block of an enumeration method). But while you can use __block variable with asynchronous block, you almost never do (and it doesn't quite make sense to even try to do it). When you use appropriate completion block patterns, there's no need for any __block variable.
So, let's go back to your original code sample: So, you should take a page from AFNetworking and employ completion blocks yourself. When the AFNetworking POST method wanted to return data to your code asynchonously, it used a completion block pattern, instead. Thus, if your own firstPostService wants to pass back data asynchronously, it should do the same.
For example:
#interface WebServices ()
#property (nonatomic, strong) AFHTTPSessionManager *manager;
#end
#implementation WebServices
// note, use `instancetype` rather than actually referring to WebServices
// in the `sharedManager` method
+ (instancetype)sharedManager
{
static id sharedMyManager = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
sharedMyManager = [[self alloc] init];
});
return sharedMyManager;
}
// I'd also suggest that you init the `AFHTTPSessionManager` only once when this
// object is first instantiated, rather than doing it when `firstPostService` is
// called
- (instancetype)init
{
self = [super init];
if (self) {
NSURL *url = [NSURL URLWithString:BaseURLString];
self.manager = [[AFHTTPSessionManager alloc] initWithBaseURL:url];
self.manager.responseSerializer = [AFHTTPResponseSerializer serializer];
}
return self;
}
// Notice:
//
// 1. This now has a return type of `void`, because when it instantly returns,
// there is no data to return.
//
// 2. In order to pass the data back, we use the "completion handler" pattern.
- (void)firstPostServiceWithCompletionHandler:(void (^)(NSArray *list, NSError *error))completionHandler {
NSDictionary *param = #{#"request" : #"get_pull_down_menu" , #"data" : #"0,0,3,1"};
[self.manager POST:#"person.php" parameters:param success:^(NSURLSessionDataTask *task, id responseObject) {
NSArray *list = [self methodUsingJsonFromSuccessBlock:responseObject];
if (completionHandler) {
completionHandler(list, nil);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
[[[UIAlertView alloc] initWithTitle:#"Error retrieving data" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil] show];
if (completionHandler) {
completionHandler(nil, error);
}
}];
// // none of this code belongs here!!! You are dealing with asynchronous methods.
// // the `list` has not been returned by the time you get here!!! You shouldn't even
// // be using instance variable anyway!
//
// if (list.count == 0) {
//
// NSLog(#"Nothing in array yet!!");
// }
// else{
// NSLog(#"Object 1 is : %#", [list objectAtIndex:1]);
//
// }
// return list;
}
- (NSArray *)methodUsingJsonFromSuccessBlock:(NSData *)data {
// note, do not use `stringWithUTF8String` with the `bytes` of the `NSData`
// this is the right way to convert `NSData` to `NSString`:
NSString *string = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"This is string representation of the data : %#", string);
// Note, retire the `list` instance variable, and instead use a local variable
NSArray *list = [string componentsSeparatedByString:#"\n"];
NSLog(#"After sepration first object: %#", [list objectAtIndex:1]);
return list;
}
#end
Then, you could invoke that like so:
[[WebServices sharedManager] firstPostServiceWithCompletionHandler:^(NSArray *list, NSError *error) {
if (error) {
// handle the error here
} else {
// use the `list` results here
}
}];
// NOTE, DO NOT USE `list` HERE. By the time you get here, `list` has not been
// returned. Only use it in the above block.
//
// In fact, you can see that if you put a `NSLog` or breakpoint here, and again, above
// where it says "use the `list` results` here", you'll see that it's running the code
// inside that block _after_ this code down here!
I'd suggest you tackle the above first, to first make sure you completely understand the proper asynchronous technique of the completion block pattern. We don't want to complicate things quite yet. Make sure you're getting the sort of data you wanted before you proceed to what I will describe below.
But, once you've grokked the above, it's time to look at your JSON parsing. You make several reference to JSON, but if that's what it really is, then using componentsSeparatedByString is not the right way to parse it. You should use NSJSONSerialization. Or even better, you can let AFNetworking do that for you (right now, you're making it more complicated than it needs to be and your results will not be formatted correctly).
Above, I kept your methodUsingJsonFromSuccessBlock in the process, but if you're really dealing with JSON, you should eliminate that method entirely. Let AFNetworking do this for you.
You should eliminate the line that says:
responseSerializer = [AFHTTPResponseSerializer serializer];
The default serializer is AFJSONResponseSerializer which is what you want to use if handling JSON requests.
The methodUsingJsonFromSuccessBlock is then no longer needed because AFNetworking will do the JSON conversion for you. So firstPostServiceWithCompletionHandler should look like:
- (void)firstPostServiceWithCompletionHandler:(void (^)(NSArray *list, NSError *error))completionHandler {
NSDictionary *param = #{#"request" : #"get_pull_down_menu" , #"data" : #"0,0,3,1"};
[self.manager POST:#"person.php" parameters:param success:^(NSURLSessionDataTask *task, id responseObject) {
if (completionHandler) {
completionHandler(responseObject, nil);
}
} failure:^(NSURLSessionDataTask *task, NSError *error) {
[[[UIAlertView alloc] initWithTitle:#"Error retrieving data" message:[error localizedDescription] delegate:nil cancelButtonTitle:#"Ok" otherButtonTitles:nil, nil] show];
if (completionHandler) {
completionHandler(nil, error);
}
}];
}
I am struggling to come up with an elegant solution to a problem involving multiple network requests. If you need further information than what has been provided then please don't hesitate to ask.
I need to download data from a server, create core data objects from it and then use information from those objects to download the next set of data. So I am transversing a hierarchy.
So for example:
I make my first request to the server and pull down the Regions which is made up of 4 objects (North, South, East, West). I am saving these to core data.
Once that is done (not sure best way to track this) I then need to do a fetch request on the region entity to get back those 4 objects. Each region contains a number of counties which I need to request from the server. So I loop through the regions and make a network request for each region.
I loop through each returned dictionary (one for each region) to create each county.
Here is my code to download the regions and county:
+ (void)downloadRegions
{
NSString *search = #"organisation";
NetworkHandler *networkHandler = [[NetworkHandler alloc] init];
[networkHandler downloadData:search];
}
+ (void)downloadCounty
{
NSManagedObjectContext *context = [DatabaseHandler sharedHandler].managedObjectContext;
NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:#"Region" inManagedObjectContext:context];
[request setEntity:entity];
//NSPredicate *searchFilter = [NSPredicate predicateWithFormat:#"attribute = %#", searchingFor];
NSError *error = nil;
NSArray *results = [context executeFetchRequest:request error:&error];
NSLog(#"%#", results);
NSString *search = #"organisation?code=";
for (Region *region in results) {
NSString *s = [search stringByAppendingString:[NSString stringWithFormat:#"%#", region.code]];
NetworkHandler *networkHandler = [[NetworkHandler alloc] init];
[networkHandler downloadData:s];
}
}
Both of the above methods call:
- (void)downloadData:(NSString *)searchUrl
{
NSString *apiURL = [kBaseURL stringByAppendingPathComponent:#"api"];
NSString *finalURL = [apiURL stringByAppendingPathComponent:searchUrl];
self.dataTask = [self.session dataTaskWithURL:[NSURL URLWithString:finalURL]
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
//self.jsonData[searchUrl] = json;
[self createObjectInDatabase:json andSearchURL:searchUrl];
NSLog(#"This has been reached");
}];
[self.dataTask resume];
}
- (void)createObjectInDatabase:(id)data andSearchURL:(NSString *)searchURL
{
if ([searchURL isEqual:#"organisation"]) {
[Region createNewRegionWithData:data inManagedObjectContext:[DatabaseHandler sharedHandler].managedObjectContext];
}
else {
[LAT createNewLATWithData:data inManagedObjectContext:[DatabaseHandler sharedHandler].managedObjectContext];
}
}
I am not sure if I am doing this the best way. In regards to making the request, creating the object and then making the next request.
My biggest issue is knowing when to make the next request. i.e - knowing when the download has completed and all the core data objects have been created successfully before making a request for those objects and using them in the next request. I am currently making the second request manually but need it to be done automatically.
I hope that is clear. I am finding it hard to explain :-). Thanks in advance.
What if you passed a string into downloadData: that the completion handler then used to post a notification as it was finishing? When you receive each notification, you know which step of the process to go to next.
I have a big array of dictionaries with the data of trips. I want to send it in all one request but I want to get responses after each dictionary send. I using afnetworking 2.0 and use AFHTTPRequestSerializer serializer.
Update. I use this method now, but if user get a lot of data there is a risk that it can be interrupted in case of bad internet. And i have to know what trips was been sent
- (void)sendTripData:(NSArray*)trips
withSuccessBlock:(RequestSuccessBlockWithDict)successBlock
failureBlock:(FailureBlock)failureBlock
{
NSString* path = #"add_trips";
NSDictionary *params = #{#"trips":trips,
#"mobile_id":[[UIDevice currentDevice].identifierForVendor UUIDString]};
[self POST:path
parameters:params
success:^(AFHTTPRequestOperation *operation, id responseObject)
{
NSLogLight(#"Full data trips send success");
if ([responseObject isKindOfClass:[NSDictionary class]])
{
NSDictionary* dict = (NSDictionary*) responseObject;
if ([dict[#"status"] isEqualToString:#"ok"])
{
successBlock(responseObject);
}
else
{
failureBlock(nil);
}
}
else
{
failureBlock(nil);
}
}
failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLogLight(#"Full data trips send failed");
NSString* errorReason = nil;
if (operation.response)
{
errorReason = error.localizedRecoverySuggestion;
}
failureBlock(errorReason);
}];
}
You should look into HTTP Pipelining. This isn't something that AFNetworking is necessarily going to help you with. The 'standard' model for HTTP requests is 1 request in, 1 response out. Optimizations like Keep-Alive and Pipelining exist, but will take more work to really take advantage of.