Send data created while the user offline - ios

I want to allow users to create some items while they are offline. then send created items to backend when the user reconnect to internet.I am confused, What's the proper way to achieve that?
Should I use waitsforconnectivity of URLSession and it will send the request even when the user close the app
Or should I schedule a background task? if so then how to trigger this task when user connect to the internet?
Notes: I am using Alamofire for networking

I think you're overcomplicating this.
If you're using Alamofire for networking then I wouldn't suggest the first approach as that would be mixing the usage of URLSession and Alamofire for networking and that's not a great idea.
In terms of your second approach. Why does it need to be a background task? Why can't you just check if the user is first connected to the internet, and if they are you can proceed normally. If not, then just create items and cache them somehow. Then when you reconnect to the internet you can send the cached items first as you would send normal items.
Alamofire has a built in NetworkReachabilityManager which will help you determine your network status. There's a nice example in this answer for using it.

You can use Alomafire itself do that:
NetworkManager
class NetworkManager {
//shared instance
static let shared = NetworkManager()
let reachabilityManager = Alamofire.NetworkReachabilityManager(host: "www.google.com")
func startNetworkReachabilityObserver() {
reachabilityManager?.listener = { status in
switch status {
case .notReachable:
print("The network is not reachable")
case .unknown :
print("It is unknown whether the network is reachable")
case .reachable(.ethernetOrWiFi):
print("The network is reachable over the WiFi connection")
case .reachable(.wwan):
print("The network is reachable over the WWAN connection")
}
}
// start listening
reachabilityManager?.startListening()
}
}
Reachability Observer
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
// add network reachability observer on app start
NetworkManager.shared.startNetworkReachabilityObserver()
return true
}
}

Related

Code working in AppDelegate but not in any other class

I am using socket.io client for swift. When I declare and connect the socket and use its commands in AppDelegate, things work fine. But as soon as I move this stuff from AppDelegate to another class (making socket a global variable) and call the function of that class from AppDelegate, socket errors start to appear (the description of errors is really irrelevant to this topic. I had a discussion regarding that in Socket.io issues)
The question I wanna ask is this happening? I have used
deinit {
NSLog("CCNOTIFICATIONS RESOURCES GOT DE INITIALIZED")
}
in that file but this never gets called.
According to my knowledge, AppDelegate is a delegate which gets called when there is some change in app's state i-e its launches or goes to background/foreground. And also that it is not a good practice to mess huge amount of code in AppDelegate.
Apparently there is no difference in scope of both files then why is this happening?
Please correct me if I am wrong in my understanding.
How this error problem can be solved? Or least what other approach I can try to get to root of this problem?
Any help would be appreciated.
UPDATE:
Here is the code:
When I write it in AppDelegate:
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
public static var socket = SocketIOClient(socketURL: URL(string: “URL”)!, config: [.log(true), .compress])
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
AppDelegate.socket.connect()
AppDelegate.socket.on("connect") {data, ack in
print("Connected successfully")
}
AppDelegate.socket.on("book-adventure-provider") {data, ack in
print("BOOKING CAME")
print(data)
}
return true
}
When I write it in other file:
CCNotifications:
public class CCNotifications : INotifications
{
public var socket = SocketIOClient(socketURL: URL(string: “URL”)!, config: [.log(true), .compress, .forcePolling(true), .forceNew(true)])
public func sendBookingRequest(adventureId: String, success: #escaping () -> Void, error: #escaping CErrorCallback) {
if (ConnectionStatus.isConnected() == false)
{
let errorObj = CError()
errorObj.message = "No internet connection"
errorObj.code = ClientConstants.connectionErrorCode
error(errorObj)
}
else
{
let jwtToken = KeychainWrapper.standard.string(forKey: "jwtToken")
let data = ["adventure_id": adventureId, "jwt": jwtToken]
socket.emit("book-adventure", data)
socket.on("book-adventure-seeker") { data, ack in
print(data)
}
}
}
}
CuufyClient:
public class CuufyClient : IClient {
public var notifications: INotifications? = nil
public static var Notifications: INotifications? { get { return CuufyClient.client?.notifications}}
//region Actions
public static func Initialize(completion: ((_ error: CError?) -> Void)?) {
CuufyClient.client?.notifications = CCNotifications ()
completion? (nil)
}
//endregion
}
Finally calling it as:
CuufyClient.Notifications?.funcWhichIWannaCall.
Note:
Both ways work. But when I write code in CCNotifications and run it, at sometimes it starts giving socket error: Session id unknown and then connects itself again and again.
UPDATE 2:
A line before the socket error in the logs I have observed this error:
2017-12-15 15:45:34.133480+0500 TestTarget[5332:230404] TIC Read Status [1:0x60000017f440]: 1:57
Upon searching this error I got to know that in Xcode 9, this error shows that TCP connection has closed. When I opened this issue on socket.io branch the specialist said:
This library operates on a fairly high level above the underlying
networking. It relies on either URLSession or Starscream to tell it
when the connection is lost.
Can anyone help me regarding URLSession that why TCP is getting closed as I am fairly new to iOS.
First to say, your approach of keeping the AppDelegate thin is a good idea.
Regarding the deinit: This function will be called when the object ceases to exist. Since global variables will life "forever" - as long as they are assigned to a different object - they will be cleaned up only at program termination (not: background mode etc.) Therefore, you will never see the deinit call.
Your thoughts about the life cycle are correct, there shouldn't be a difference if you have done everything correctly - which I cannot judge here because you didn't show any code of how and when that global socket is created/initialized etc.
Regarding the updated question: Unfortunatly, I don't know the socket.io library. But having just a short look, your code differs slightly between the AppDelegate and the other version. It might be a timeing issue - just check (and maybe fix) a few things:
When do you call connect()?
You might want to first set up all the ...on()-Handlers before connecting or calling emit; otherwise the server might respond before everything has set up correctly
Check if you might set up the ...on()-Handlers only one, centrally, and not with every sendBookingRequest-call
What is jwtToken? Is it an API token for you application, or some kind of seesion token? When do you store the value, and why is it stored in the keychain? Might its value change during requests/responses?
You should not place socket event listener in sendBookingRequest method.
You just need to bind a socket event listener once.
socket.on("book-adventure-seeker") { data, ack in
print(data)
}
so, you can place it in on connect event.
AppDelegate.socket.on("connect") {data, ack in
print("Connected successfully")
}

Will my app be rejected if I use CallKit framework to check status of ongoing call even if my app does not have any VOIP functionality?

I am working on an app that has one section from where user can tap on a mobile number to make cellular calls. To make call, I am using following code.
if let phoneCallURL = URL(string: "tel://\(9999999999)") {
let application:UIApplication = UIApplication.shared
if (application.canOpenURL(phoneCallURL)) {
if #available(iOS 10.0, *) {
application.open(phoneCallURL, options: [:], completionHandler: nil)
} else {
// Fallback on earlier versions
application.openURL(phoneCallURL)
}
}
}
This shows an alert with option to Call or Cancel. Now when I select Call option, I need to check the state of the ongoing call.
To do this I am making use of CallKit framework.
var callObserver = CXCallObserver()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
callObserver.setDelegate(self, queue: nil)
}
func callObserver(_ callObserver: CXCallObserver, callChanged call: CXCall) {
if call.hasConnected {
}
if call.isOutgoing {
}
if call.hasEnded {
}
if call.isOnHold {
}
}
This delegate methods receives callback when an outgoing call starts or ends.
Now since my app does not have any functionality related to VOIP, I just want to know if it is OK to use CallKit framework like this to know the call status.
Will my app be rejected for Appstore upload if I use CallKit like this and not have a VOIP functionality in the app?
Any help on this will be much appreciated.
In general, CallKit.framework's CXCallObserver API may be used by any app, regardless of whether that app provides VoIP calling functionality itself or not.
Note however that it's not guaranteed that any particular call that your app observes are the result of the user deciding to call based on your app's opening of a tel: URL. It is possible that the user declined to open the tel: URL that your app offered to open, and instead dialed a call separately or received an incoming call around the same time period, and your app would receive similar-looking CXCallObserver delegate callbacks regardless. It's not possible to reliably distinguish between calls initiated outside your app from those started from your app.

How do I detect change in internet connection on iOS, delegate style?

I need a function to run only when the system detects there is no internet connection, then another function to run when the system detects an internet connection.
I'm thinking of something like this:
func onInternetConnection() {
//Enable actions
}
func onInternetDisconnection() {
//Disable actions, alert user
}
I will also need a way to detect when the system is reconnecting, so I can let the user know it's reconnecting, like in Facebook's Messenger.
How can I do this?
I'm using Moya/Alamofire for my network layer.
This works in case of Alamofire
import Alamofire
// In your view did load or in app delegate do like this
let reachabilityManager = NetworkReachabilityManager()
reachabilityManager.listener = { status in
switch status {
case .notReachable:
print("The network is not reachable")
self.onInternetDisconnection()
case .unknown :
print("It is unknown whether the network is reachable")
self.onInternetDisconnection() // not sure what to do for this case
case .reachable(.ethernetOrWiFi):
print("The network is reachable over the WiFi connection")
self.onInternetConnection()
case .reachable(.wwan):
print("The network is reachable over the WWAN connection")
self.onInternetConnection()
}
}
Alamofire and Rechability are library and that have some features to check internet connection. You can use one from that.

How to get incoming/outgoing call event in background state

In one of my app it has a feature of playing sound that I achieved successfully. Even though when app is running (foreground state) and we received the incoming call, app music gets stopped and resume again when call gets disconnected.
Now real problem is here. When app enters in the background state, we are not receiving any event for incoming/outgoing call. In the background mode If music is playing inside my app and we get any incoming call, then app music is stopped automatically but not resume again when call disconnected unlike iPhone Music app.
Is it a limitation of the iOS or can we achieve that ?
Note: I'm not looking for any solution for Jailbreak devices or Enterprise apps
Have you tried to create call center and assign handler block in AppDelegate class? The following has to work.
import UIKit
import CoreLocation
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
let callCenter: CTCallCenter = CTCallCenter()
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
window = UIWindow(frame: UIScreen.mainScreen().bounds)
window?.rootViewController = ViewController()
window?.makeKeyAndVisible()
callCenter.callEventHandler = {
(call: CTCall!) in
switch call.callState {
case CTCallStateConnected:
print("CTCallStateConnected")
case CTCallStateDisconnected:
print("CTCallStateDisconnected")
case CTCallStateIncoming:
print("CTCallStateIncoming")
default:
print("default")
}
}
return true
}
}
Do not forget to switch on Background Modes for this. And perform something in the background as well, smth like receiving location.

How can i check from watchOS 2 if application on iPhone is opened or not and be able to send NSUserDefaults no matter the app status?

How can I check from watchOS 2 if application on iPhone is opened or not?
I want to send a message with NSUserDefaults from watch to iPhone via sendMessage (to be able to update interface on phone when message received) when both applications are running and I want to send NSUserDefaults even if only watchOS 2 app is running.
From what I read I found this:
/** The counterpart app must be reachable for a send message to succeed. */
#property (nonatomic, readonly, getter=isReachable) BOOL reachable;
It's always reachable from what I check.
Reachable means the apple watch and iPhone are connected via bluetooth or wifi. It doesn't necessarily mean the iPhone app is running. If reachable is true, when you try to sendMessage from the apple watch it will launch the iPhone app in the background. You need to assign the WKSession delegate as soon as possible because the delegates methods (sendMessage) will fire soon. I think what you are saying you want to do is call sendMessage if you can, and it not use the transferUserInfo method instead. To do this, first on your apple watch:
func applicationDidFinishLaunching() {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
// NOTE: This should be your custom message dictionary
// You don't necessarily call the following code in
// applicationDidFinishLaunching, but it is here for
// the simplicity of the example. Call this when you want to send a message.
let message = [String:AnyObject]()
// To send your message.
// You could check reachable here, but it could change between reading the
// value and sending the message. Instead just try to send the data and if it
// fails queue it to be sent when the connection is re-established.
session.sendMessage(message, replyHandler: { (response) -> Void in
// iOS app got the message successfully
}, errorHandler: { (error) -> Void in
// iOS app failed to get message. Send it in the background
session.transferUserInfo(message)
})
}
Then, in your iOS app:
// Do this here so it is setup as early as possible so
// we don't miss any delegate method calls
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
self.watchKitSetup()
return true
}
func watchKitSetup() {
// Stop if watch connectivity is not supported (such as on iPad)
if (WCSession.isSupported()) {
let session = WCSession.defaultSession()
session.delegate = self
session.activateSession()
}
}
func session(session: WCSession, didReceiveMessage message: [String : AnyObject], replyHandler: ([String : AnyObject]) -> Void) {
// Handle the message from the apple watch...
dispatch_async(dispatch_get_main_queue()) {
// Update UI on the main thread if necessary
}
}
func session(session: WCSession, didReceiveUserInfo userInfo: [String : AnyObject]) {
// Handle the message from the apple watch...
dispatch_async(dispatch_get_main_queue()) {
// Update UI on the main thread if necessary
}
}
You probably want to use the application context of WatchConnectivity:
have a look at WCSession.updateApplicationContext( )
It sends the most important configuration info to the counterpart as soon as the counterpart is reachable, even if the counterpart is not reachable at the time of sending. If you call updateApplicationContext multiple times, only the latest is sent.
For much deeper info watch the WWDC 2015 session about WatchConnectivity: https://developer.apple.com/videos/wwdc/2015/?id=713
It describes more means to send data, but I think the application context fits best for you.
The session also details how to find out if the counterpart is reachable, but I think you don't need that for your use case.

Resources