I am testing FireBase in my iOS app and it is pretty amazing so far but it seems if I use [fb removeValue]; when I am not connected the change is not always reflected any where else.
Here is my code:
-(void) deleteFromFirebase {
Firebase *fb =[[Firebase alloc] initWithUrl:[NSString stringWithFormat:#"https://repzio.firebaseio.com/orders/%#/%#",self.purchaseOrder.ManufacturerID, self.purchaseOrder.OrderGUID]];
[fb removeValue];
}
Obviously this causes issues when the app need to remove data and have it persisted. Has anyone else run into this issue? Am I handling this wrong?
I would use blocks!
[fb removeValueWithCompletionBlock:^(NSError *error, Firebase *ref) {
if (!error) {
// Save worked
}
else {
// cache for later, or notify user that there was an error and they should try again.
}
}];
Related
i have followed every step described in the docs of facebook-iso-sdk 4.8.0 for iOS 9, but still couldn't preform app switch on "login-with-facebook" in my app, even if facebook app is already installed.
As you can see in screen shot below i have modified info.plist, but still can't get native app switch to work.
I have also double checked for typo-mistakes in info.plist value. and i can assure you they are correct.
Here is my code :-
if (![AppDelegate SharedInstance].login) {
[AppDelegate SharedInstance].login = [[FBSDKLoginManager alloc] init];
}
[AppDelegate SharedInstance].login.loginBehavior = FBSDKLoginBehaviorNative;
[[AppDelegate SharedInstance].login logInWithReadPermissions:#[#"public_profile",#"email",#"user_friends"] fromViewController:self handler:^(FBSDKLoginManagerLoginResult *result, NSError *error) {
if (error) {
}
else if (result.isCancelled)
{
// Handle cancellations
}
else
{
NSLog(#"result.grantedPermissions == %#",result.grantedPermissions);
if (result.token)
{
[[[FBSDKGraphRequest alloc] initWithGraphPath:#"me" parameters:#{#"fields": #"id, name, email, first_name, last_name"}]
startWithCompletionHandler:^(FBSDKGraphRequestConnection *connection, id result, NSError *error) {
if (!error) {
NSLog(#"fetched user:%#", result);
NSString *userImageURL = [NSString stringWithFormat:#"https://graph.facebook.com/%#/picture?type=large", [result objectForKey:#"id"]];
[dictFacebookDetail addEntriesFromDictionary:result];
[dictFacebookDetail setObject:userImageURL forKey:#"profilepic"];
NSLog(#"facebook login result --- %#",dictFacebookDetail);
[self performSelectorInBackground:#selector(CheckFacebookUser:) withObject:dictFacebookDetail];
}
}];
}
}
}];
What am i missing ?
I have found a solution, but you should change something in FBSDKLogin pod. I was debugging the Pod and I realized that Facebook ask in the class FBSDKServerConfiguration for the server configuration for the app. It returns a JSON with some information to configure the Pod for our app. I realized that by default the JSON returns this dictionary:
"ios_sdk_dialog_flows" = {
default = {
"use_native_flow" = 0;
"use_safari_vc" = 1;
};
message = {
"use_native_flow" = 1;
};
};
By default the use_native_flow is 0, so when it saves the information in userDefaults for the next app launches.
So, when the app calls FBSDKLoginMananger login method and checks for the loginBehaviour in this method, the variable useNativeDialog returns NO. So the switch uses the next case. case FBSDKLoginBehaviorBrowser:
- (void)logInWithBehavior:(FBSDKLoginBehavior)loginBehavior
{
.
.
...
switch (loginBehavior) {
case FBSDKLoginBehaviorNative: {
if ([FBSDKInternalUtility isFacebookAppInstalled]) {
[FBSDKServerConfigurationManager loadServerConfigurationWithCompletionBlock:^(FBSDKServerConfiguration *serverConfiguration, NSError *loadError) {
BOOL useNativeDialog = [serverConfiguration useNativeDialogForDialogName:FBSDKDialogConfigurationNameLogin];
if (useNativeDialog && loadError == nil) {
[self performNativeLogInWithParameters:loginParams handler:^(BOOL openedURL, NSError *openedURLError) {
if (openedURLError) {
[FBSDKLogger singleShotLogEntry:FBSDKLoggingBehaviorDeveloperErrors
formatString:#"FBSDKLoginBehaviorNative failed : %#\nTrying FBSDKLoginBehaviorBrowser", openedURLError];
}
if (openedURL) {
completion(YES, FBSDKLoginManagerLoggerAuthMethod_Native, openedURLError);
} else {
[self logInWithBehavior:FBSDKLoginBehaviorBrowser];
}
}];
} else {
[self logInWithBehavior:FBSDKLoginBehaviorBrowser];
}
}];
break;
}
// intentional fall through.
}
case FBSDKLoginBehaviorBrowser: {
.
.
.
}
As we see in the code, we know if the app is installed in this if,
if ([FBSDKInternalUtility isFacebookAppInstalled]).
To solve the problem, I have changed this line
BOOL useNativeDialog = [serverConfiguration useNativeDialogForDialogName:FBSDKDialogConfigurationNameLogin];
to
BOOL useNativeDialog = YES;
I know this is not a good practice and it will change if I update this Pod, but at least is working and I needed it now.
I guess we can change that configuration in facebook developers admin site, but I haven't found anything.
Facebook has changed Facebook login behavior for iOS9.
Here is the quote from Facebook blog post:
We've been monitoring data and CTRs for over 250 apps over the last 6 weeks since iOS 9 launched. The click-through rate (CTR) of SVC Login outperforms the CTR of app-switch Login and is improving at 3x the rate of the app-switch experience. This indicates that the SVC experience is better for people and developers today, and will likely be the best solution in the long run. For this reason, the latest Facebook SDK for iOS uses SVC as the default experience for Login.
typedef NS_ENUM(NSUInteger, FBSDKLoginBehavior)
{
/*!
#abstract This is the default behavior, and indicates logging in through the native
Facebook app may be used. The SDK may still use Safari instead.
*/
FBSDKLoginBehaviorNative = 0,
/*!
#abstract Attempts log in through the Safari or SFSafariViewController, if available.
*/
FBSDKLoginBehaviorBrowser,
/*!
#abstract Attempts log in through the Facebook account currently signed in through
the device Settings.
#note If the account is not available to the app (either not configured by user or
as determined by the SDK) this behavior falls back to \c FBSDKLoginBehaviorNative.
*/
FBSDKLoginBehaviorSystemAccount,
/*!
#abstract Attemps log in through a modal \c UIWebView pop up
#note This behavior is only available to certain types of apps. Please check the Facebook
Platform Policy to verify your app meets the restrictions.
*/
FBSDKLoginBehaviorWeb,
};
there is lot of behaviour available to access the fb login.try using alternate what you prefer from this.
FBSDKLoginManager *loginmanager = [[FBSDKLoginManager alloc] init];
loginmanager.loginBehavior=FBSDKLoginBehaviorNative;
Like this ...hope this will help you :)
(FBSDKServerConfiguration *)_defaultServerConfigurationForAppID:(NSString *)appID
{
// Use a default configuration while we do not have a configuration back from the server. This allows us to set
// the default values for any of the dialog sets or anything else in a centralized location while we are waiting for
// the server to respond.
static FBSDKServerConfiguration *_defaultServerConfiguration = nil;
if (![_defaultServerConfiguration.appID isEqualToString:appID]) {
// Bypass the native dialog flow for iOS 9+, as it produces a series of additional confirmation dialogs that lead to
// extra friction that is not desirable.
NSOperatingSystemVersion iOS9Version = { .majorVersion = 9, .minorVersion = 0, .patchVersion = 0 };
I am using Azure Mobile Service as a backend for an iOS app. I have set up everything to work with offline sync which allows me to view, add, or modify data even when there is no network connection. I am now into testing and I run into an error: "The item provided was not valid" when I try to synchronize data.
Here's what I am doing:
I add a new athlete to the syncTableWithName:#"Athlete" with this:
NSDictionary *newItem = #{#"firstname": #"Charles", #"lastname": #"Lambert", #"laterality" : #"Orthodox"};
[self.athletesService addItem:newItem completion:^{
NSLog(#"New athlete added");
}];
Here's the addItem function:
-(void)addItem:(NSDictionary *)item completion:(CompletionBlock)completion
{
// Insert the item into the Athlete table
[self.syncTable insert:item completion:^(NSDictionary *result, NSError *error)
{
[self logErrorIfNotNil:error];
// Let the caller know that we finished
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}];
}
For now everything is fine and the item is in the syncTable. The problem is when I try to synchronize with the Azure Mobile Service. Here's the syncData function I am calling:
-(void)syncData:(CompletionBlock)completion
{
// push all changes in the sync context, then pull new data
[self.client.syncContext pushWithCompletion:^(NSError *error) {
[self logErrorIfNotNil:error];
[self pullData:completion];
}];
}
The pushWithCompletion gets me the error: "The item provided was not valid." and same for the pullData function that gets called after:
-(void)pullData:(CompletionBlock)completion
{
MSQuery *query = [self.syncTable query];
// Pulls data from the remote server into the local table.
// We're pulling all items and filtering in the view
// query ID is used for incremental sync
[self.syncTable pullWithQuery:query queryId:#"allAthletes" completion:^(NSError *error) {
[self logErrorIfNotNil:error];
// Let the caller know that we finished
dispatch_async(dispatch_get_main_queue(), ^{
completion();
});
}];
}
I have tried inserting directly in the MSTable and that works fine. It's really when I am using the MSSyncTable that I run into this error. Although when I insert data manually in my database and that I synchronize my context I can fetch data and display within my UITableView.
Lookin forward to see what you guys think about this. Thanks a lot!
I just edited my question thanks to #phillipv.
When I add an item using NSDictionary just like I did I run into the error "The item provided was not valid". So I tried adding an item by first inserting it to my managedObjectContext and then calling:
NSDictionary *dict = [MSCoreDataStore tableItemFromManagedObject:newAthlete];
I then I get the error when I try to sync: "The item provided did not have a valid id."
I feel like I am experiencing a circle.. :S
#Charley14, you can work around the bug by adding the following handler.
- (void)tableOperation:(nonnull MSTableOperation *)operation onComplete:(nonnull MSSyncItemBlock)completion
{
NSMutableDictionary *rwItem = [NSMutableDictionary dictionaryWithDictionary:operation.item];
// Temporary workaround
[rwItem removeObjectsForKeys:#[ #"relationship1", #"relationship2"]];
operation.item = rwItem;
[operation executeWithCompletion:completion];
}
The tableOperation:onComplete: handler is simply removing keys that correspond to the relationships. You will have to replace 'relationship1', 'relationship2' in the code snippet with names of actual relationships in your application.
Once the bug (https://github.com/Azure/azure-mobile-services/issues/779) is fixed, this workaround can be removed.
This appears to be a bug in the iOS SDK, as the Many to One relationship is not supposed to be exposed in the object given to the operation during a Push call.
Created the following bug with more details on GitHub: https://github.com/Azure/azure-mobile-services/issues/779
The cause of the error message is due to the fact that the relationship is a NSSet on the object, and the NSJSONSerializer throws as it does not know how to convert that to JSON.
I'm trying to retrieve the current user's current geopoint, but nothing happens when I call the geoPointForCurrentLocationInBackground:. I can't log the NSLog's from the block, however the other NSLog appears in the console.
- (IBAction)whereCurrentUser:(id)sender {
[PFGeoPoint geoPointForCurrentLocationInBackground:^(PFGeoPoint *geoPoint, NSError *error) {
if (geoPoint) {
NSLog(#"GEOPOINT %#", geoPoint);
}
else
{
NSLog(#"ERROR");
}
}];
NSLog(#"BUTTON TAPPED");
}
Do I need to implement something else? The documentation is quite clear therefore I can't figure out what could be the problem.
There's a solution posted on Parse.com PFGeoPoint.geoPointForCurrentLocationInBackground not doing anything. Basically update Parse to the latest version and add the NSLocationWhenInUseUsageDescription key to your Info.plist, this is a change made in iOS 8 and is needed whenever you want to use an users location within your app.
I’m testing code based on the firechat-ios example. I’ve added the FirebaseSimpleLogin call loginToFacebookAppWithId and have it set up so that one view controller performs the login and then transitions to a different view controller that holds the chat logic:
self.firebase = [[Firebase alloc] initWithUrl:#"https://xxxxx.firebaseio.com/"];
[self observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
NSLog(#"%#", snapshot.value);
// Add the chat message to the array.
[self.chat addObject:snapshot.value];
// Reload the table view so the new message will show up.
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}];
FirebaseSimpleLogin *authClient = [[FirebaseSimpleLogin alloc] initWithRef:self.firebase];
[authClient loginToFacebookAppWithId:kFacebookAppID permissions:#[#"email"]
audience:ACFacebookAudienceOnlyMe
withCompletionBlock:^(NSError *error, FAUser *user) {
if (error != nil) {
// There was an error logging in
NSLog(#"facebook error");
} else {
// We have a logged in facebook user
NSLog(#"facebook logged in");
[authClient checkAuthStatusWithBlock:^(NSError* error, FAUser* user) {
if (error != nil) {
// Oh no! There was an error performing the check
NSLog(#"auth error");
} else if (user == nil) {
// No user is logged in
NSLog(#"auth not logged in");
} else {
// There is a logged in user
NSLog(#"auth logged in");
// segue to the chat view controller
[self performSegueWithIdentifier:#"segueToViewController" sender:self];
}
}];
}
}];
Here are the firebase rules:
{
"rules": {
".read": "auth != null",
".write": "auth != null"
}
}
The problem is, about 10% of the time, the UITableView of the chat messages is blank, and I don’t see any chat message entries in the log. I’ve tried playing around with the order of observeEventType, putting it before and after the loginToFacebookAppWithId call.
I’m wondering if there is a race condition where maybe the messages are arriving before I call observeEventType. I’ve checked the return value of observeEventType and I get a FirebaseHandle of 1 even when no messages arrive. I’ve also upgraded the firebase framework that comes with firechat ios to https://cdn.firebase.com/ObjC/Firebase.framework-LATEST.zip and it still fails.
I thought that maybe the connection dropped, but I’m able to post messages with childByAutoId after I’ve authenticated and see them appear on the firebase server. I just never receive any messages.
I wonder if it’s trying to send me the messages in the brief moment before I’m authenticated, and failing because I don’t have read permission. Is there a way to delay event observations until after I’m in?
I’ve tried everything I can think of but I can’t make it work reliably.
---------- UPDATE ----------
I seem to be able to log in every time if I type my credentials manually. I'm currently checking for a previous successful login with:
[FBSession openActiveSessionWithAllowLoginUI:false]
To determine if I successfully logged in on the last launch of the app. If it fails, I go to a view controller for FirebaseSimpleLogin. But if it works, I call FirebaseSimpleLogin in the current view controller and wait till it succeeds in the background.
I'm running in the simulator, so I tried deleting the preferences plist at:
~/Library/Application Support/iPhone Simulator/7.0.3/Applications/XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX/Library/Preferences/com.xxxxxxxxxx.plist
and relaunching, which forces me to re-authenticate. Then I tried typing in my credentials and logging in 25 times without a problem.
So I think the problem is either somehow related to trying to login with Facebook before I use FirebaseSimpleLogin, or logging in with credentials from the previous launch (without bringing up the login dialog). I'm still trying to narrow down the culprit.
---------- UPDATE 2 ----------
I just wanted to add a note that after further testing, I found that the call to:
[FBSession openActiveSessionWithAllowLoginUI:false]
has no effect on FirebaseSimpleLogin. If I skip that call altogether and simply substitute true or false there, I can reproduce the issue. The problem turned out to be a race condition, see my answer below.
I finally figured out what was happening, it was due a wrong assumption on my part about UIViewController message callbacks and CFRunLoop.
The code sample in my question was distilled down from my real code to remove extraneous calls, but it turns out the part I removed was actually the culprit. I had written a function to log in and wait until success or failure on the spot (rather than receiving the response in a block later) by using a run loop:
-(bool)loginUsingFacebookReturningError:(NSError**)error andUser:(FAUser**)user
{
__block NSError *errorTemp;
__block FAUser *userTemp;
[self loginUsingFacebookWithCompletionBlock:^(NSError *error, FAUser *user) {
errorTemp = error;
userTemp = user;
CFRunLoopStop(CFRunLoopGetCurrent());
}];
CFRunLoopRun(); // needs a timeout or way for the user to cancel but I haven't implemented it yet
if(error) *error = errorTemp;
if(user) *user = userTemp;
return !errorTemp && userTemp;
}
-(void)loginUsingFacebookWithCompletionBlock:(void (^)(NSError* error, FAUser* user))block
{
FirebaseSimpleLogin *authClient = [[FirebaseSimpleLogin alloc] initWithRef:self.firebase];
[authClient loginToFacebookAppWithId:kFacebookAppID permissions:#[#"email"]
audience:ACFacebookAudienceOnlyMe
withCompletionBlock:^(NSError *error, FAUser *user) {
if (error != nil) {
// There was an error logging in
NSLog(#"facebook error");
block(error, nil);
} else {
// We have a logged in facebook user
NSLog(#"facebook logged in");
[authClient checkAuthStatusWithBlock:block];
}
}];
}
This was called with:
NSError *error;
FAUser *user;
bool success = [self loginUsingFacebookReturningError:&error andUser:&user];
The way loginUsingFacebookReturningError works is, it calls loginUsingFacebookWithCompletionBlock which fires off the loginToFacebookAppWithId and checkAuthStatusWithBlock messages like usual, but then I start a run loop. The run loop allows processing to happen in the background, even though the main thread pauses on CFRunLoopRun() until the completion block calls CFRunLoopStop().
What I hadn't realized is that run loops continue to process the application's messages in the background. So while I thought program flow had stopped in viewDidLoad, it had actually continued and called viewWillAppear, which is where I had placed my call to observeEventType (because I assumed that authentication would be complete by the time the program got there).
This created a race condition where the program attached the observeEventType callback during the time that Facebook and Firebase were authenticating. 90% of the time, authentication had completed before observeEventType was called, but 10% of the time there was lag or other network delays and observeEventType was called prematurely.
I fixed the problem by moving the FirebaseSimpleLogin code to its own view controller in the storyboard, and using the completion block to initiate the segue to the next view controller, which installed the observeEventType callback.
So to summarize: the solution is to call FirebaseSimpleLogin's authentication, and then AFTER it has finished and the completion block is done, call observeEventType. Otherwise Firebase's rules will deny your request to see data that's only visible to authenticated users (which is correct).
Here is the final code, untested but the method works:
// only global for illustration purposes, should really go in a singleton or AppDelegate, or be passed through the segue to the next view controller
Firebase *gFirebase;
// LoginViewController (root view controller in storyboard)
- (void)viewDidLoad
{
[super viewDidLoad];
gFirebase = [[Firebase alloc] initWithUrl:#"https://xxxxx.firebaseio.com/"];
FirebaseSimpleLogin *authClient = [[FirebaseSimpleLogin alloc] initWithRef:gFirebase];
[authClient loginToFacebookAppWithId:kFacebookAppID permissions:#[#"email"]
audience:ACFacebookAudienceOnlyMe
withCompletionBlock:^(NSError *error, FAUser *user) {
if (error != nil) {
// There was an error logging in
NSLog(#"facebook error");
} else {
// We have a logged in facebook user
NSLog(#"facebook logged in");
[authClient checkAuthStatusWithBlock:^(NSError* error, FAUser* user) {
if (error != nil) {
// Oh no! There was an error performing the check
NSLog(#"auth error");
} else if (user == nil) {
// No user is logged in
NSLog(#"auth not logged in");
} else {
// There is a logged in user
NSLog(#"auth logged in");
// segue to the chat view controller
[self performSegueWithIdentifier:#"segueToViewController" sender:self];
}
}];
}
}];
}
// ViewController (destination of segueToViewController)
- (void)viewDidLoad
{
[super viewDidLoad];
[gFirebase observeEventType:FEventTypeChildAdded withBlock:^(FDataSnapshot *snapshot) {
NSLog(#"%#", snapshot.value);
// Add the chat message to the array.
[self.chat addObject:snapshot.value];
// Reload the table view so the new message will show up.
[self.tableView reloadData];
[self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:([self.tableView numberOfRowsInSection:0] - 1) inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:YES];
}];
}
I am trying my hand at some very basic implementation of MagicalRecord to get the hang of it and run into the following.
When I save an entry and then fetch entries of that type it will come up with the entry I just saved. However, when I save the entry, close the app, start it again, and then fetch, it comes up empty.
Code for saving:
- (void)createTestTask{
NSManagedObjectContext *localContext = [NSManagedObjectContext contextForCurrentThread];
Task *task = [Task createInContext:localContext];
task.tName = #"First Task";
task.tDescription = #"First Task created with MagicalRecord. Huzzah!";
NSError *error;
[localContext save:&error];
if (error != Nil) {
NSLog(#"%#", error.description);
}
}
Code for fetching: (all I want to know here if anything is actually saved)
- (void) fetchTasks{
NSArray *tasks = [Task findAll];
NSLog(#"Found %d tasks", [tasks count]);
}
I am sure I am missing something here, but not anything I can seem to find on stackoverflow or in the Tutorials I looked at.
Any help is welcome.
I have to ask the obvious "Is it plugged in" question: Did you initialize the Core Data Stack with one of the +[MagicalRecord setupCoreDataStack] methods?
Did your stack initialize properly? That is, is your store and model compatible? When they aren't, MagicalRecord (more appropriately, Core Data) will set up the whole stack without the Persistent Store. This is annoying because it looks like everything is fine until it cannot save to the store...because there is no store. MagicalRecord has a +[MagicalRecord currentStack] method that you can use to examine the current state of the stack. Try that in the debugger after you've set up your stack.
Assuming you did that, the other thing to check is the error log. If you use
[localContext MR_saveToPersistentStoreAndWait];
Any errors should be logged to the console. Generally when you don't see data on a subsequent run of your app, it's because data was not saved when you thought you called save. And the save, in turn, does not happen because your data did not validate correctly. A common example is if you have a required property, and it's still nil at the time you call save. "Normal" core data does not log these problems at all, so you might think it worked, when, in fact, the save operation failed. MagicalRecord, on the other hand, will capture all those errors and log them to the console at least telling you what's going on with your data.
When i have started with magical record I was also facing this problem, problem is context which you are using to save data. here is my code which might help you
[MagicalRecord saveWithBlock:^(NSManagedObjectContext *localContext) {
NSArray *userInfoArray = [UserBasicInfo findByAttribute:#"userId" withValue:[NSNumber numberWithInt:loggedInUserId] inContext:localContext];
UserBasicInfo* userInfo;
if ([userInfoArray count]) {
userInfo = [userInfoArray objectAtIndex:0];
} else {
userInfo = [UserBasicInfo createInContext:localContext];
}
userInfo.activeUser = [NSNumber numberWithBool:YES];
userInfo.firstName = self.graphUser[#"first_name"];
userInfo.lastName = self.graphUser[#"last_name"];
userInfo.userId = #([jsonObject[#"UserId"] intValue]);
userInfo.networkUserId = #([jsonObject[#"NetworkUserId"] longLongValue]);
userInfo.userPoint = #([jsonObject[#"PointsEarned"] floatValue]);
userInfo.imageUrl = jsonObject[#"Picturelist"][0][#"PictureUrL"];
userInfo.imageUrlArray = [NSKeyedArchiver archivedDataWithRootObject:jsonObject[#"Picturelist"]];
} completion:^(BOOL success, NSError *error) {
}];
Use this when your done
[[NSManagedObjectContext MR_defaultContext]saveToPersistentStoreAndWait];