How to specify what should and shouldn't be tracked as a session (not event) when we add Google Analytics to an iOS app - ios

If we instantiate session (not event, action or screen) tracking with Google Analytics for an iOS or Watch app, is there any way to specify what should and shouldn't be tracked as a session for example not to call the start lifecycle tracking on each launch -- e.g. I don't want background tasks or resuming the watch app or activating the glance to count as sessions. As a developer, do I have control over it to filer the session tracking data before sending to Google or all session tracking data are automatically dispatched to Google? currently I call this function in AppDelegate didFinishLaunchingWithOptions
(void) initializeGoogleAnalytics: (NSString * ) containerFilename {
if ([self googleTagManager] == nil) {
TAGManager * tagManager = [TAGManager instance];
[tagManager setLogger: [TWNGTMLogger logger]];
[tagManager setDispatchInterval: 20.0];
[tagManager setDispatchInterval: 1.0];
[self setGoogleTagManager: tagManager];
//Open GTM tag container (async)
[TAGContainerOpener openContainerWithId: containerFilename tagManager: [self googleTagManager] openType: kTAGOpenTypePreferFresh timeout: nil notifier: self];
DMLogInfo(DebugLogTypeTracking, # "Google Analytics initialized, container: %#, version: %#", containerFilename, kGAIVersion);
}
}

1) Is there a way to turn off or turn on Session Tracking in GTM code on iOS?
There's not a way to adjust session tracking itself using the GTM UI or the datalayer, other than preventing GA tags from firing (mentioned below).
2) Is there a way to AVOID session tracking if the app is launched in background?
The main way is to prevent the tag from firing when the app is opened in the background would be to set up a "blocking trigger" that looks for an event the indicates the app was launched in the background. Then, even if datalayer events were being fired telling the tag to fire, the blocking trigger would prevent the tag from firing, and therefore prevent a session from starting.

Related

Nativescript: Continue code execution when app goes to background

(edited to provide updated info)
I have a nativescript app that performs various tasks that I would like to continue going if the phone goes into background mode or is locked.
Focused on iOS, with Nativescript Angular. I am also new to using obj C code in Nativescript.
As an easy example, let's say I want to print to the console every 5 seconds after a user hits a button, so I have the following code in my component ts file:
coolComponent.ts:
#Component({...})
Export class coolComponent {
...
whenButtonClicked(){
setInterval(function(){
console.log('button has been clicked. show every 5 seconds!');
}, 5000);
}
Without further code, when the user hits the button, it will print to console every 5 seconds, but then stop when the app is in the background or phone is locked. How do I get the function to continue executing even when app is in the background or locked?
In seeing different sources, like here (NS docs on background execution) and here (docs on app delegate) , it looks like the first step is to create a custom-app-delegate, get that to work, and then identify the background task in info.plist.
I have gotten things generally to be functional, like this:
app/custom-app-delegate.ts:
import { ios, run as applicationRun } from "tns-core-modules/application";
export class CustomAppDelegate extends UIResponder implements
UIApplicationDelegate {
public static ObjCProtocols = [UIApplicationDelegate];
public applicationDidEnterBackground(application: UIApplication) {
console.log('in background mode!')
}
}
main.ts:
import { platformNativeScriptDynamic } from "nativescript-angular/platform";
import { AppModule } from "./app.module";
import * as application from "tns-core-modules/application";
import { CustomAppDelegate } from "./custom-app-delegate";
application.ios.delegate = CustomAppDelegate;
platformNativeScriptDynamic().bootstrapModule(AppModule);
app/app.module.ts:
import { CustomAppDelegate } from "./custom-app-delegate";
app/App_Resources/iOS/info.plist:
...
<key>UIBackgroundModes</key>
<array>
<string>fetch</string>
</array>
edit: create reference.d.ts:
/// <reference path="./node_modules/tns-platform-declarations/ios.d.ts" />
/// <reference path="./node_modules/tns-platform-declarations/android.d.ts" />
Edit: FYI, to get the custom-app-delegate to work, I also had to download "tns-platform-declerations", with the command:
$ npm i tns-platform-declarations --save-dev
With this, the app properly reads "in background mode!" when the app goes to the background. So the custom-app-delegate is functional.
However, the examples online assume that the code in the custom-app-delegate is independent of the rest of the app, so they assume there are new tasks to do when the app goes into background mode.
That is not the case here. I have a task that is being performed from the coolComponent function, and when the app goes into background or is locked I want that to continue.
This probably requires that coolComponent.ts communicate with custom-app-delegate, but I don't know how to do this.
Just repeating the code in both files--having the setInterval function appear in both coolComponent.ts and custom-app-delegate--does not work, because this would not result in the custom-app-delegate continuing on the same timing that began in coolComponent.ts after the user hit the button.
So how can I have the code start in coolComponent.ts and continue after the app is in background mode?
Technically you can't force your app to be active when user no longer wants it to be (by locking the phone / minimising the app). If you like to run anything in background, you will have to use background fetch in iOS.
iOS allows you to run code in the background only for certain situations. For instance (but not limited to):
Background location updates.
Audio and video playback (PiP in iPad)
Remote Push Notifications handling
among others...
If your app does not fit any of the available categories, the best you can do is to request to iOS more time to run in the background (by default is 10 seconds). This will allow you to run for 3 more minutes. Just keep running the task in an infinite loop and gracefully terminate your app before the granted 180 seconds.
Regarding background fetch, this mechanism allows apps to update its contents in the background. iOS will execute apps that declare background fetch at least once a day, so you, in your delegate can perform an update from the server. This mechanism is not suitable for what you are looking for.

Callkit incoming call ui

I am using call kit framework for calling and please help me
how to remove call kit ui when incoming call occurs during app in foreground and call in process, i am getting call kit ui in background.
You can check for app state before reporting a new incoming call to CXProvider. If you do not wish to use system incoming call screen when your app is in foreground, then give the if statement not to report new incoming call screen if the app is in foreground.
Example:
let state = UIApplication.shared.applicationState
if state == .background {
// background
provider.reportNewIncomingCall(with: UUID(uuidString: call.callUUID)!, update: callUpdate) { error in /* */ }
}

How to correctly implement transfer of settings from iOS app to watchOS2 complication

What I want to achieve is the following:
Complication(s) get updated in the background at intervals of 30
minutes
Complication(s) get updated whenever the watch app runs and
receives its own updated data
Complication(s) get updated whenever
the iOS app runs and the user changes a setting which affects the
watch data (such as changing location of weather observations, or
display units)
Items 1. and 2. seem to be straightforward, and nicely addressed here: What is the flow for updating complication data for Apple Watch?
However, for item 3, in the iOS app, I set up a WCSession instance and call transferCurrentComplicationUserInfo, sending the new settings as NSDictionary. In the watch extension, this invokes didReceiveUserInfo in WCSessionDelegate.
- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *,id> *)userInfo {
// Code here to apply the new settings
// ....
// Invoke a NSUSRLSession-based web query to get new data
[self queryForNewDataWithCompletionHandler:^(NCUpdateResult result) {
if (result == NCUpdateResultNewData) {
// Have new data from web to display
CLKComplicationServer *server = [CLKComplicationServer sharedInstance];
for (CLKComplication *complication in server.activeComplications) {
[server reloadTimelineForComplication:complication];
}
}
// Set date for next complication update to 30 mins from now
// ...
}];
}
The problem I am having is that watchOS is calling requestedUpdateDidBegin in a separate thread, shortly after it invoked didReceiveUserInfo and this starts executing BEFORE I have a chance to obtain updated data using the new settings in the newly received UserInfo dictionary from the app.
Consequently, the complications get updated twice in short succession - once by the WatchOS having called requestedUpdateDidBegin, which simply re-updates the complication with existing (stale) data, before I very soon after receive new data from the web and then have to update them again in my own code.
This seems unnecessary and a waste of resources, not to mention the limited budget of updates that Apple allows (supposedly 2 per hour).
Am I doing anything wrong here? How can I prevent watchOS2 from calling requestedUpdateDidBegin before I have had a chance to acquire new data from the web?
The purpose of transferCurrentComplicationUserInfo is to immediately pass current complication data to the extension. In your code, you are passing settings, however you are not including any weather data.
The issue you're seeing stems from trying to asynchronously fetch new data within the extension (which is returning before the data is available).
To handle this, you should fetch the current weather data on the phone based on the new settings, then pass (the new settings along with) the weather data in the current complication user info.
- (void)session:(WCSession *)session didReceiveUserInfo:(NSDictionary<NSString *,id> *)userInfo {
// Code here to apply the new settings for future updates
// ....
// Code here to update cache/backing store with current weather data just passed to us
// ....
CLKComplicationServer *server = [CLKComplicationServer sharedInstance];
for (CLKComplication *complication in server.activeComplications) {
[server reloadTimelineForComplication:complication];
}
}
This way, the complication server can immediately update the timeline using the current complication data you just transferred to the watch.
No stale data, no unnecessary second update.

watchOS 2.0 - Can't cancel WCSessionFileTransfer

Our app lets a user select records on iPhone that they want to be displayed in the watch app.
It works like this:
The user taps "Add to watch" on a record from their iPhone
A new version of the watch database is generated and sent to the watch
The watch app receives and saves the file and updates its interface
A new database file is sent to the watch and processed for each change. This is fine if the watch is awake since it will give the user live updates, but if the watch is asleep while the user makes 7 changes, it means the watch is accepting and processing 7 new files as soon as it wakes up.
We really only care about the most recent version of the watch database, so I'm trying to cancel all old outstanding file transfers.
Code:
On iPhone, each time a record is added/removed from watch database, we attempt (unsuccessfully) to cancel pending file transfers and then queue the latest database file:
// create watch database and store it at self.urlToDatabase
[self generateNewWatchDatabase];
if ([WCSession isSupported])
{
WCSession *session = [WCSession defaultSession];
session.delegate = self;
[session activateSession];
// this is the problem - cancel doesn't seem to do anything
for (WCSessionFileTransfer *fileTransfer in session.outstandingFileTransfers)
[fileTransfer cancel];
[session transferFile:self.urlToDatabase metadata:nil];
}
In the above code, calling [fileTransfer cancel] successfully removes the WCSessionFileTransfer object from session.outstandingFileTransfers, but didReceiveFile is still being called multiple times below.
Accepting the file on the watch:
- (void)session:(WCSession *)session didReceiveFile:(WCSessionFile *)file
{
// this method gets called once for every time -transferFile:metadata: is called above,
// even after cancelling outstanding file transfers
[self replaceDatabaseWithFile:file];
[self refreshItemsTable];
}
How do we cancel outstanding file transfers?
Edit
As per #ccjensen's recommendation, I tried the following in the method that fires when the user adds/removes a record to/from the watch:
// store a reference to the file transfer and immediately cancel it
WCSessionFileTransfer *transfer = [session transferFile:self.urlToDatabase metadata:nil];
[transfer cancel];
This still results in the file being sent to the watch, instead of cancelling it as one would expect.
I also tried the following:
Kill watch app (by holding the side button until 'Power Off' appears, and then holding it again)
Add/remove records from iPhone
Relaunch watch app
Even in this scenario the watch receives all 'cancelled' file transfers.
The documentation for the cancel method says:
Use this method to cancel a file transfer before it completes. If the file has already been transferred, calling this method has no effect.
So it sounds like the cancels are "best effort" and might not end up being able to cancel them in all cases, especially if the file has already been transferred.
Are you seeing it never work, even if you call cancel immediately (try testing without the watch app running as that seems to expedite the transfers)?
turned out the reason this worked was that the file url did not match the transfer's url I was checking 🙈
I recently found that keeping hold of the WCSessionFileTransfer in my own array and canceling them proved more reliable than using [WCSession defaultSession].outstandingFileTransfers.
NSMutableArray<WCSessionFileTransfer*>* inProgressTransfers = [NSMutableArray array];
So each time you call TransfeFile: metaData:
WCSessionFileTransfer* transfer = [[WCSession defaultSession] transferFile:url metadata:metadata];
if (transfer)
{
[self.inProgressTransfers addObject:transfer];
}
then at an appropriate time
for (WCSessionFileTransfer* ourTransfer in self.inProgressTransfers)
{
[ourTransfer cancel];
[self.inProgressTransfers removeObject:ourTransfer];
}
For some reason, keeping hold of the transfers ourself makes calling cancel work much more reliably.. hope that helps someone

Settings alarms while app is closed

How can I set local notifications with out forcing user to open app.
I need my app set a local notification for sunrise and sunset, but I don't want to ask people open app.
I know I can have up to 64 notifications via scheduleLocalNotification, but I need to set it for a year so I should be able to run app in background and set alarms for future sunrises and sunsets in background.
The simple answer is you can't. Your app can't run whenever it wants in the background; it can't schedule a timer to wake itself up to post more notifications when they are due.
The only way you could come close to something like this is by having a server which send a background push notification to your app as a wake-up call when a new batch of 64 notifications are coming close to needed to be posted.
However this would be relying on the fact the user doesn't terminate your app. If the user does then you'd have to send a non-background push notification to the user and hope they click on it to launch your app.
Android Awareness API has recently announced new features that provide a simple solution for your use-case (that avoids you having to explicitly manage location request or computing sunrise times). The way to achieve what you're trying to do is to create and register a TimeFence specified relative to sunrise/sunset.
For example:
// Create TimeFence
AwarenessFence sunriseFence =
TimeFence.aroundTimeInstant(TimeFence.TIME_INSTANT_SUNRISE,
0, 5 * ONE_MINUTE_MILLIS);
// Register fence with Awareness.
Awareness.FenceApi.updateFences(
mGoogleApiClient,
new FenceUpdateRequest.Builder()
.addFence("fenceKey", sunriseFence, myPendingIntent)
.build())
.setResultCallback(new ResultCallback<Status>() {
#Override
public void onResult(#NonNull Status status) {
if (status.isSuccess()) {
Log.i(TAG, "Fence was successfully registered.");
} else {
Log.e(TAG, "Fence could not be registered: " + status);
}
}
});
You will get callbacks when the fence evaluates to TRUE at sunrise, and when it evaluates back to FALSE at 5-min after sunrise.
Please check Fence API code snippets docs for how to add your custom app logic.

Resources