Before sending a message (i.e. calling setValue on a Firebase object), is there a recommended way to determine if the user is online or offline?
For example:
[firebase setValue:someValue withCompletionBlock:^(NSError *error, Firebase *ref) {
// This block is ONLY executed if the write actually completes. But what if the user was offline when this was attempted?
// It would be nicer if the block is *always* executed, and error tells us if the write failed due to network issues.
}];
We need this in our iOS app because the user could lose connectivity if they went into a tunnel for instance. If Firebase doesn’t offer a built-in way to do this, we’ll just resort to monitoring iOS's Reachability API.
They have a section of their docs devoted to this here.
Basically observe the .info/connected ref
Firebase* connectedRef = [[Firebase alloc] initWithUrl:#"https://SampleChat.firebaseIO-demo.com/.info/connected"];
[connectedRef observeEventType:FEventTypeValue withBlock:^(FDataSnapshot *snapshot, NSString *prevName) {
if([snapshot.value boolValue]) {
// connection established (or I've reconnected after a loss of connection)
}
else {
// disconnected
}
}];
You can do something like this. Setup the observer and post notification on status change. Basically the same as accepted answer but adapted to new version of firebase framework.
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
...
FIRDatabaseReference *ref = [[FIRDatabase database] referenceWithPath:#".info/connected"];
[ref observeEventType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot * _Nonnull snapshot) {
NSString *value = snapshot.value;
NSLog(#"Firebase connectivity status: %#", value);
self.firebaseConnected = value.boolValue;
[[NSNotificationCenter defaultCenter] postNotificationName:#".fireBaseConnectionStatus" object:nil];
}];
}
Then in any view controller of your app you can do this. Observe notifications and do something based on that (update your ui, etc).
- (void) fireBaseConnectionStatus:(NSNotification *)note
{
AppDelegate *app = (AppDelegate *)[[UIApplication sharedApplication] delegate];
[self updateButtons:app.firebaseConnected];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(fireBaseConnectionStatus:) name:#".fireBaseConnectionStatus" object:nil];
}
Hope this will help.
PS. Perhaps you will find it interesting idea to also monitor basic reachability with well known reachability.[mh] framework. Then you also could decide how do you act in case that firebase is connected on wifi or 3g.
Swift 3
let connectedRef = FIRDatabase.database().reference(withPath: ".info/connected")
connectedRef.observe(.value, with: { snapshot in
if let connected = snapshot.value as? Bool, connected {
print("Connected")
} else {
print("Not connected")
}
})
More info - https://firebase.google.com/docs/database/ios/offline-capabilities
Related
I read up on all the posts I could find and am still getting a nil URL when launching the app for the first time after tapping a dynamic link pasted into the Notes app on iOS. Actually I followed the video:
https://www.youtube.com/watch?v=sFPo296OQqk
Universal Links work fine and as expected through application(_:continue:restorationHandler:).
When coming through application(_:open:options:) (formerly application:openURL:options:) however, the URL comes in as <my-scheme-name>://google/link/?is_weak_match=1. No matter how I configure my project/app, the URL is always nil. Also, application(_:open:options:) is called on every first launch of the app regardless of whether a dynamic link was tapped before the app was installed or not. Is that to be expected?
Configuration:
apple-app-site-association file is set up and looks good for Universal Links.
Custom URL scheme set up in Info.plist.
Using latest GoogleService-Info.plist
Not in Safari 'Private' Mode
Calling in didFinishLaunchingWithOptions
[FIROptions defaultOptions].deepLinkURLScheme = CUSTOM_URL_SCHEME;
[FIRApp configure];
- (BOOL)application:(UIApplication *)application
openURL:(NSURL *)url
sourceApplication:(NSString *)sourceApplication
annotation:(id)annotation {
FIRDynamicLink *dynamicLink = [[FIRDynamicLinks dynamicLinks dynamicLinkFromCustomSchemeURL:url];
if (dynamicLink) {
// Handle the deep link. For example, show the deep-linked content or
// apply a promotional offer to the user's account.
// [START_EXCLUDE]
// In this sample, we just open an alert.
NSString *message = [self generateDynamicLinkMessage:dynamicLink];
[self showDeepLinkAlertViewWithMessage:message];
// [END_EXCLUDE]
return YES;
}
// [START_EXCLUDE silent]
// Show the deep link that the app was called with.
[self showDeepLinkAlertViewWithMessage:[NSString stringWithFormat:#"openURL:\n%#", url]];
// [END_EXCLUDE]
return NO;
}
// [END openurl]
// [START continueuseractivity]
- (BOOL)application:(UIApplication *)application
continueUserActivity:(NSUserActivity *)userActivity
restorationHandler:(void (^)(NSArray *))restorationHandler
{
// [START_EXCLUDE silent]
NSLog(#"%#", userActivity.webpageURL);
__weak AppDelegate *weakSelf = self;
// [END_EXCLUDE]
BOOL handled = [[FIRDynamicLinks dynamicLinks]
handleUniversalLink:userActivity.webpageURL
completion:^(FIRDynamicLink * _Nullable dynamicLink,
NSError * _Nullable error) {
// [START_EXCLUDE]
AppDelegate *strongSelf = weakSelf;
NSString *message = [strongSelf generateDynamicLinkMessage:dynamicLink];
[strongSelf showDeepLinkAlertViewWithMessage:message];
// [END_EXCLUDE]
}];
// [START_EXCLUDE silent]
if (!handled) {
// Show the deep link URL from userActivity.
NSString *message =
[NSString stringWithFormat:#"continueUserActivity webPageURL:\n%#", userActivity.webpageURL];
[self showDeepLinkAlertViewWithMessage:message];
}
// [END_EXCLUDE]
return handled;
}
// [END continueuseractivity]
- (NSString *)generateDynamicLinkMessage:(FIRDynamicLink *)dynamicLink {
NSString *matchConfidence;
if (dynamicLink.matchConfidence == FIRDynamicLinkMatchConfidenceStrong) {
matchConfidence = #"strong";
} else {
matchConfidence = #"weak";
}
NSString *msg = [NSString stringWithFormat:#"App URL: %#\n"
#"Match Confidence: %#\n",
dynamicLink.url, matchConfidence];
return msg;
}
- (void)showDeepLinkAlertViewWithMessage:(NSString *)message {
UIAlertAction *okAction = [UIAlertAction actionWithTitle:#"OK"
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *action) {
NSLog(#"OK"); }];
UIAlertController *alertController =
[UIAlertController alertControllerWithTitle:#"Deep-link Data"
message:message
preferredStyle:UIAlertControllerStyleAlert];
[alertController addAction:okAction];
[self.window.rootViewController presentViewController:alertController animated:YES completion:nil];
}
Setup:
Xcode 8.3
Deployment target: iOS 10.3.3
Objective-C
Please try to connect to Cellular and WiFi and repeat the experiment. Also connecting to different WiFi networks may help to figure out the issue. If your WiFi has small number of public facing IP addresses than retrieving pending dynamic link may fail. Also see suggestions in this answer Firebase Dynamic Links not survive installation
If that does not work, I suggest opening support ticket with Firebase.
About link <my-scheme-name>://google/link/?is_weak_match=1 you are receiving: this link being sent by FDL iOS SDK to communicate the event that pending dynamic link is not available. You will receive this link only after first launch of the application. If we found pending dynamic link, we will pass that dynamic link instead.
I am using Sinch SDK version is: 3.7.1-0313526 - with Xcode 6.3 - IOS SDK 8.3 and I would like to know what is the best way to let the app know that there was a missed call when it was in background or not running so I can update UI and badges for missed calls.
The behaviour which I am looking for it's pretty standard. The app is in background or not running, a call arrives. If it is a missed call, the app badge will be update and the notification shown will change to 'Missed call'. Like all the other calling apps.
So far I have tried the following to try to get the missed call from sinch:
- (void)handleRemoteNotification:(NSDictionary *)userInfo {
// Extract the Sinch-specific payload from the Apple Remote Push Notification
NSString* SIN = [userInfo valueForKey:#"sin"];
// Get previously initiated Sinch client
id<SINClient> client = _client;
id<SINNotificationResult> result = [client relayRemotePushNotificationPayload: SIN];
if ([result isCall] && [[result callResult] isTimedOut]) {
//Let set the badge number
[self setTheCallBadgeValue];
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:#"Missed call"
message:[NSString stringWithFormat:#"Missed call from %#", callerName]
delegate:nil
cancelButtonTitle:nil
otherButtonTitles:#"OK", nil];
[alert show];
}
}
I call the above method from:
#pragma mark - SINManagedPushDelegate
- (void)managedPush:(id<SINManagedPush>)unused
didReceiveIncomingPushWithPayload:(NSDictionary *)payload
forType:(NSString *)pushType {
// NSLog(#"Incoming push - This is the payload: %#", payload);
[self handleRemoteNotification:payload];
}
Unfortunately, that only seems to work on the following cases:
User needs to tap on the notification alert after a few second (around 10s)
The app will open, then it will show the alert for missed call
If user doesn't tap on the notification alert after waiting a few seconds or just ignore the notification alert and just open the app by tapping on the app icon, the app will never know that there was a missed call as "[[result callResult] isTimeout] will not become true", therefore the app will never update UI and the user won't never know that there was a missed call.
For full disclosure, I am using Parse SDK version is 1.7.4 and only added Push notification as a mean to inform the client of new calls:
#pragma Mark - Let Instantiate Sinch!
- (void)initSinchClientWithUserId:(NSString *)userId {
// NSLog(#"Sinch client has started with user id: %#", userId);
if (!_client) {
_client = [Sinch clientWithApplicationKey: SINCH_APPLICATION_KEY
applicationSecret: SINCH_APPLICATION_SECRET
environmentHost: SINCH_ENVIRONMENT_HOST
userId:userId];
_client.delegate = self;
_client.callClient.delegate = self;
[_client setSupportCalling:YES];
[_client enableManagedPushNotifications];
[_client setSupportActiveConnectionInBackground:NO];
[_client start];
// [_client startListeningOnActiveConnection];
NSString *currUsrName = [userDefaults objectForKey:#"currentUserFullName"];
[_client setPushNotificationDisplayName:currUsrName];
}
}
Thank you very much in advance for any help.
You could send a push via parse when you recieve calldid end with reason noanswer.
When a request comes from watch kit extension for data, can we call any function from iphone app into handleWatchKitExtensionRequest method and pass the data in reply(#{});?like this If we can call, please let me know the procedure for doing the same.
Please help me in achieving this.
Thanks in advance!!!
I have made this stuff in my app. (But not passed reply)
In Watch Kit Extention
On One button Tap event i have write below code
NSMutableDictionary *callDict = [[NSMutableDictionary alloc] init];
[callDict setObject:#"SendPushLocation" forKey:#"type"];
[callDict setObject:contactDetail forKey:#"data"];
[WKInterfaceController openParentApplication:callDict reply:^(NSDictionary *replyInfo, NSError *error) {
}];
And After that in appDelegate.m
Added this code to achieve what you want.
- (void)application:(UIApplication *)application handleWatchKitExtensionRequest:(NSDictionary *)userInfo reply:(void(^)(NSDictionary *replyInfo))reply {
storyboard = [UIStoryboard storyboardWithName:#"Main" bundle:nil];
ContactListTableViewController *contactTableVC = [storyboard instantiateViewControllerWithIdentifier:#"ContactListTableVC"];
[contactTableVC viewDidLoadCopy];
if ([userInfo[#"type"] isEqualToString:#"SendPush"]) {
[contactTableVC sendPushFromWatchKit:userInfo[#"data"] withSendLocation:NO];
} else if([userInfo[#"type"] isEqualToString:#"SendPushLocation"]) {
[contactTableVC sendPushFromWatchKit:userInfo[#"data"] withSendLocation:YES];
} else if([userInfo[#"type"] isEqualToString:#"LocalNotification"]) {
[contactTableVC scheduleLocalNotification:userInfo[#"data"]];
} else if([userInfo[#"type"] isEqualToString:#"ReScheduleLocalNotification"]) {
[contactTableVC reScheduleLocalNotification:userInfo[#"data"]];
}
}
I don't need reply so i have not tried for that.
I have seen a lot of different posts about how to solve this problem, but I have had no luck. I have tried the heartbeat solution, and it does nothing. I know that my keychain is storing my refresh token, but it is not serving any use.
Steps:
Start app
Go to load directory (root in this case)
Get this error:
EDIT: First I get a 20000 error. It seems my authentication tokens are not refreshing.
Error Domain=com.box.sdk.errordomain Code=20002 "The operation
couldn’t be completed. (com.box.sdk.errordomain error 20002.)"
Go through Box login process again.
Reload tableview
Works.
I am using this code to refresh my access tokens (I think it is supposed to)
if (storedRefreshToken)
{
[BoxSDK sharedSDK].OAuth2Session.refreshToken = storedRefreshToken;
}
I feel like I am missing something here also.
I need my user to stay logged in for the allowed 14 days. How can I get the app login state to survive app restarts?
I am using the latest V2 SDK.
EDIT:
I have tried everything, from refreshing the refreshtoken in the keychain on each ViewController to referencing the AppDelegate. I can't get it to stay logged in and just keep getting the 20002 error when I start the app again (not resume, but cold start). I don't want to use the Box filepicker, but I want to make my own tableview. Any other ideas out there?
AppDelegate:
in didFinishLaunching:
[BoxSDK sharedSDK].OAuth2Session.clientID = #"XXXXXXXXXX";
[BoxSDK sharedSDK].OAuth2Session.clientSecret = #"XXXXXXX";
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(boxAPITokensDidRefresh:) name:BoxOAuth2SessionDidBecomeAuthenticatedNotification object:[BoxSDK sharedSDK].OAuth2Session];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(setRefreshTokenInKeychain:) name:BoxOAuth2SessionDidRefreshTokensNotification object:[BoxSDK sharedSDK].OAuth2Session];
// set up stored OAuth2 refresh token
_keychain = [[KeychainItemWrapper alloc] initWithIdentifier:REFRESH_TOKEN_KEY accessGroup:nil];
id storedRefreshToken = [_keychain objectForKey:(__bridge id)kSecValueData];
if (storedRefreshToken)
{
[BoxSDK sharedSDK].OAuth2Session.refreshToken = storedRefreshToken;
}
listener methods
- (void)boxAPITokensDidRefresh:(NSNotification *)notification
{
BoxOAuth2Session *OAuth2Session = (BoxOAuth2Session *) notification.object;
[self setRefreshTokenInKeychain:OAuth2Session.refreshToken];
_isBox = YES;
[self removeBoxLoginViewController];
}
- (void)setRefreshTokenInKeychain:(NSString *)refreshToken
{
[_keychain setObject:#"MyApp" forKey: (__bridge id)kSecAttrService];
[_keychain setObject:refreshToken forKey:(__bridge id)kSecValueData];
NSLog(#"refreshToken: %#", refreshToken);
}
Main ViewController:
ViewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(boxAPIAuthenticationDidSucceed:) name:BoxOAuth2SessionDidBecomeAuthenticatedNotification object:[BoxSDK sharedSDK].OAuth2Session];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(boxAPIAuthenticationDidFail:) name:BoxOAuth2SessionDidReceiveAuthenticationErrorNotification object:[BoxSDK sharedSDK].OAuth2Session];
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(boxAPIAuthenticationRefreshToken:) name:BoxOAuth2SessionDidReceiveRefreshErrorNotification object:[BoxSDK sharedSDK].OAuth2Session];
[self boxAPIHeartbeat];
Heartbeat:
- (void)boxAPIHeartbeat
{
[[BoxSDK sharedSDK].foldersManager folderInfoWithID:#"0" requestBuilder:nil success:nil failure:nil];
}
ListenerMethods after hearbeat:
- (void)boxAPIAuthenticationDidSucceed:(NSNotification *)notification
{
NSLog(#"Received OAuth2 successfully authenticated notification");
BoxOAuth2Session *session = (BoxOAuth2Session *) [notification object];
NSLog(#"Access token (%#) expires at %#", session.accessToken, session.accessTokenExpiration);
NSLog(#"Refresh token (%#)", session.refreshToken);
//[self.tableView reloadData];
}
- (void)boxAPIAuthenticationDidFail:(NSNotification *)notification
{
NSLog(#"Received OAuth2 failed authenticated notification");
NSString *oauth2Error = [[notification userInfo] valueForKey:BoxOAuth2AuthenticationErrorKey];
NSLog(#"Authentication error (%#)", oauth2Error);
//[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)boxAPIAuthenticationRefreshToken:(NSNotification *)notification
{
BoxOAuth2Session *OAuth2Session = (BoxOAuth2Session *) notification.object;
[self setRefreshTokenInKeychain:OAuth2Session.refreshToken];
NSLog(#"REFRESH TOKEN: %#", OAuth2Session.refreshToken);
}
//trying this out????
- (void)setRefreshTokenInKeychain:(NSString *)refreshToken
{
[_keychain setObject:#"MyApp" forKey: (__bridge id)kSecAttrService];
[_keychain setObject:refreshToken forKey:(__bridge id)kSecValueData];
NSLog(#"refreshToken: %#", refreshToken);
}
I can't use the Box SDK if I can't get this figured out this weekend. I would think Box would want their SDK to be used by developers, but the documentation is so poor. What am I missing? I just want the app to stay logged in through cold starts!
It turns out, that the issue was with the ARC version of Keychain. I noticed this when I started placing NSLogs all over the place and noticed that the refreshToken getting returned at app launch, was not the refreshToken that was getting encoded into the Keychain. I replaced the ARC Keychain files with the ones from the sample app and put the ARC flag in, and it is working perfectly.
The box.com SDK for iOS has an object called sharedSDK that holds another object called OAuth2Session. OAuth2Session has a property called isAuthorized. On each application launch this property is set to NO. Even if I keep the refreshToken inside the system Keychain, and assign it at launch like so:
//...applicationDidFinisLaunching...
NSString *token = [controllerObject fetchFromKeychainForKey:#"com.box.token"];
[BoxSDK sharedSDK].OAuth2Session.refreshToken = token;
if ([BoxSDK sharedSDK].OAuth2Session.isAuthorized) {
//Not until signing in
NSLog(#"Authorized.)";
} else {
NSLog(#"Not Authorized.");
}
What should I be doing differently to check auth status? The Dropbox SDK has a method to determine if the session is linked, persists through launches.
I'm the author of the iOS SDK. The isAuthorized method is only a best guess of whether or not the current OAuth2 tokens are valid. From the documentation:
Compares accessTokenExpiration to the current time to determine if an access token may be valid. This is not a guarantee that an access token is valid as it may have been revoked or already refreshed.
Because accessTokenExpiration is not stored anywhere by the Box iOS SDK, this field will be nil following initialization, even if the refresh token is loaded.
The Box iOS SDK takes the stance that the Box API is the source of truth about state and does not attempt to perform client side checks that can be handled more reliably by the server.
The recommended way of reloading the OAuth2 session is to set the refresh token from the keychain as you have done and then issue a "heartbeat" API call to trigger an autorefresh or fail if the refresh token is invalid.
An example of this can be found in the Box iOS SDK sample app
- (void)viewDidLoad
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(boxAPIAuthenticationDidSucceed:)
name:BoxOAuth2SessionDidBecomeAuthenticatedNotification
object:[BoxSDK sharedSDK].OAuth2Session];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(boxAPIAuthenticationDidFail:)
name:BoxOAuth2SessionDidReceiveAuthenticationErrorNotification
object:[BoxSDK sharedSDK].OAuth2Session];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(boxAPIInitiateLogin:)
name:BoxOAuth2SessionDidReceiveRefreshErrorNotification
object:[BoxSDK sharedSDK].OAuth2Session];
// attempt to heartbeat. This will succeed if we successfully refresh
// on failure, the BoxOAuth2SessionDidReceiveRefreshErrorNotification notification will be triggered
[self boxAPIHeartbeat];
}
- (void)dealloc
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (void)boxAPIHeartbeat
{
[[BoxSDK sharedSDK].foldersManager folderInfoWithID:BoxAPIFolderIDRoot requestBuilder:nil success:nil failure:nil];
}
#pragma mark - Handle OAuth2 session notifications
- (void)boxAPIAuthenticationDidSucceed:(NSNotification *)notification
{
NSLog(#"Received OAuth2 successfully authenticated notification");
BoxOAuth2Session *session = (BoxOAuth2Session *) [notification object];
NSLog(#"Access token (%#) expires at %#", session.accessToken, session.accessTokenExpiration);
NSLog(#"Refresh token (%#)", session.refreshToken);
[self dismissViewControllerAnimated:YES completion:nil];
BOXAssert(self.viewControllers.count == 1, #"There should only be one folder in the hierarchy when authentication succeeds");
BoxFolderViewController *rootVC = (BoxFolderViewController *)self.topViewController;
[rootVC fetchFolderItemsWithFolderID:BoxAPIFolderIDRoot name:#"All Files"];
}
- (void)boxAPIAuthenticationDidFail:(NSNotification *)notification
{
NSLog(#"Received OAuth2 failed authenticated notification");
NSString *oauth2Error = [[notification userInfo] valueForKey:BoxOAuth2AuthenticationErrorKey];
NSLog(#"Authentication error (%#)", oauth2Error);
[self dismissViewControllerAnimated:YES completion:nil];
}
- (void)boxAPIInitiateLogin:(NSNotification *)notification
{
NSLog(#"Refresh failed. User is logged out. Initiate login flow");
dispatch_sync(dispatch_get_main_queue(), ^{
[self popToRootViewControllerAnimated:YES];
NSURL *authorizationURL = [BoxSDK sharedSDK].OAuth2Session.authorizeURL;
NSString *redirectURI = [BoxSDK sharedSDK].OAuth2Session.redirectURIString;
BoxAuthorizationViewController *authorizationViewController = [[BoxAuthorizationViewController alloc] initWithAuthorizationURL:authorizationURL redirectURI:redirectURI];
BoxAuthorizationNavigationController *loginNavigation = [[BoxAuthorizationNavigationController alloc] initWithRootViewController:authorizationViewController];
authorizationViewController.delegate = loginNavigation;
loginNavigation.modalPresentationStyle = UIModalPresentationFormSheet;
[self presentViewController:loginNavigation animated:YES completion:nil];
});
}
This view controller registers for OAuth2 notifications which are triggered in the event of a successful refresh or a logout. In the selectors you register for these callbacks, you can load a view controller in your app or load the BoxAuthorizationViewController to log a user in.