Objective-c framework Version: 2.5.4

Change log

Pre requisites

Finotes framework supports iOS projects with minimum deployment target version 8 or above.

Integration

You need to add cocoa pods to your project. You can find more information here.
After integrating cocoa pods, add FinotesCore to your Podfile.

Incase, your development language is swift, use the SWIFT documentation for integration
pod 'FinotesCore', '2.5.4'
The –repo-update option should be used the first time pod install is run from terminal.

Then install the same by executing the following command from terminal where your Podfile resides.
Here the —repo-update is added incase your local cocoa pods repository is not up to date.

pod install --repo-update

You can import the FinotesCore to your code using the import statement below.

#import <FinotesCore/Fn.h>

Initialize

You need to call the [Fn initialize:application] function in your appDelegate didFinishLaunchingWithOptions:

#import <FinotesCore/Fn.h>

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [Fn initialize:application];
}

DryRun

During development, you can set the dryRun mode, so that the issues raised will not be sent to the server. Every other feature except the issue sync to server will work as same.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [Fn initialize:application withDryRun:YES withVerbose:YES];
}
When preparing for production release, you need to unset the DryRun flag.

VerboseLog

There are two variations of logging available in finotes, Verbose and Error. You can toggle them using corresponding APIs.
Activating verbose will print all logs in LogCat including error and warning logs.

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [Fn initialize:application withDryRun:YES withVerbose:YES];
}
ErrorLog

If only error and warning logs needs to be printed,

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [Fn initialize:application withDryRun:NO withVerbose:NO];
    [Fn logError:YES];
}

Test

Now that the basic integration of finotes framework is complete, Lets make sure that the dashboard and framework are in sync.

#import <FinotesCore/Fn.h>
#import <FinotesCore/Severity.h>


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [Fn initialize:application withDryRun:NO withVerbose:YES];
    //[Fn reportIssueAt:] allows you to raise custom issues.
    //Refer Custom Issue section by the end of this documentation for more details.
    [Fn reportIssueAt:self withDescription:@"iOS TEST Issue" withSeverity:MINOR];
}

Now run the application in a simulator or real iOS device (with network connection).
Once the application opens up, open finotes dash.
The issue that we raised should be reported.
In-case the issue is not listed, make sure the right app is selected at the top of the dashboard.
Also, do refer to the logs in console, it will list any errors/warnings that might have occured during the integration.

If the error still persists, do contact us at finotes contact email or you may use the chat support at the bottom right corner.

You should remove the [Fn reportIssueAt:] call, else every time the app is run, an issue will be reported.

#import <FinotesCore/Fn.h>
#import <FinotesCore/Severity.h>


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [Fn initialize:application withDryRun:NO withVerbose:YES];	
//  [Fn reportIssueAt:self withDescription:@"iOS TEST Issue" withSeverity:MINOR];
}

Release

Once you are ready to release the application, if you have activated crash reporting using finotes, you need to back up the dSYM file to symbolicate the crashes as they are reported. Locating dSYM file.

Listen for Issue

You can listen for and access every issue in realtime using the [Fn listenForIssues:] API.
You need to add the listener in your AppDelegate file.


    [Fn listenForIssues:@selector(issueFound:) atTarget:self];
}

(void) issueFound:(Issue *) issue {

}

You will be provided with an Issue object that contains all the issue properties that are being synced to the server, making the whole process transparent.
As this callback will be made right after an issue occurrence, you will be able to provide a positive message to the user.

Reporting Issues

Report Network Call Failure

Issues like status code errors, timeout issues and other failures will be reported for all network calls by finotes, from your application.

If you are using NSURLSession with sharedSession then issues in all REST api calls will be reported automatically.

	NSURLSession *session = [NSURLSession sharedSession];
AFNetworking

Incase you are using AFNetworking, then you need to add the below code in your NSURLSessionConfiguration. The part we are interested is

    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration 
    						defaultSessionConfiguration];
    configuration.protocolClasses = [Fn getProtocols:[configuration.protocolClasses mutableCopy] ];
AFHTTPRequestOperationManager

If you try to use ‘AFHTTPRequestOperationManager’, your app might crash.
‘AFHTTPRequestOperationManager’ is outdated in AFNetworking 3.x, we suggest you migrate to ‘NSURLSessionConfiguration’.
You can find out, how to migrate to ‘NSURLSessionConfiguration’ here.

Full code can be found here,

    NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration 
    						defaultSessionConfiguration];
    configuration.protocolClasses = [Fn getProtocols:[configuration.protocolClasses mutableCopy] ];
    AFHTTPSessionManager * myManager = [[AFHTTPSessionManager alloc] 
    						initWithSessionConfiguration:configuration];
    NSURL *URL = [NSURL URLWithString:@"https://app.finotes.com/login"];
    NSURLRequest *request = [NSURLRequest requestWithURL:URL];
    NSURLSessionDataTask *dataTask = [myManager dataTaskWithRequest:request 
    			completionHandler:^(NSURLResponse *response, 
						id responseObject, NSError *error) {
        if (!error) {
        }
    }];
    [dataTask resume];

Whitelist Hosts

Add ObservableDomains array to the project info.plist with the list of domains to be observed, if needed, you may provide a timeout for the api calls along with the host names separated by a coma.

Info.plist observable domains

Incase you are directly editing the info.plist as source-code

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
	<key>ObservableDomains</key>
	<array>
		<string>host.com,5000</string>
		<string>another.host.com</string>
	</array>

Optionally, You may provide a custom timeout for hostnames. This will raise an Issue for any network call to the hosts, that takes more than the specified time for completion.
Do note that specifying timeout will not interfere in any way with your network calls.

Categorize Tickets (Optional)

You will be able to configure how API issues are categorized into tickets using a custom header “X-URLID”. You can set this header in your API calls and when they are reported to finotes dashboard, issues from API calls with same “X-URLID” will be categorized into a single ticket.

    [nsMutableURLRequest setValue:@"loginapi" forHTTPHeaderField:@"X-URLID"];

Any issue in API calls with same X-URLID will be shown as a single ticket in finotes dashboard.

Activity trail

When an issue is raised, inorder to get user screen flow for the current session, you need to extend your Controller from ObservableViewController.

@interface ViewController : UIViewController

@end

Changes to,

@interface ViewController : ObservableViewController

@end
Low Memory Reporting

Extending Controller from ObservableViewController will also allow finotes to report any memory related issues that may occur in your application.

You may use App functions to report different app states along with Controller trail.

#import <FinotesCore/App.h>

@interface AppDelegate ()

@end

@implementation AppDelegate

	- (void)applicationWillResignActive:(UIApplication *)application {
	    [App applicationWillResignActive:self];
	}

	- (void)applicationDidEnterBackground:(UIApplication *)application {
	    [App applicationDidEnterBackground:self];
	}

	- (void)applicationWillEnterForeground:(UIApplication *)application {
	    [App applicationWillEnterForeground:self];
	}

	- (void)applicationDidBecomeActive:(UIApplication *)application {
	    [App applicationDidBecomeActive:self];
	}

	- (void)applicationWillTerminate:(UIApplication *)application {
	    [App applicationWillTerminate:self];
	}
@end

Once Controllers are extended from ObservableViewController and App functions are implemented, if an issue is raised, on finotes dashboard, you will be able to view screen activity for 3 minutes prior to the issue was raised.

Activity Trail
	LoginController:viewDidLoad                  09:21:53:923	1131900928 FREE MEMORY (BYTES)
	LoginController:viewWillAppear               09:21:53:923	1131458560
	LoginController:viewDidAppear                09:21:53:927	1131003904
	UIApplication:applicationDidBecomeActive     09:21:54:081	1130999808
	LoginController:viewDidLoad                  09:22:09:670	1130622976

Custom Activity Trail

You can set custom activity markers in your ios application using [Fn setActivityMarkerAt: marker:]. These markers will be shown along with the activity trail when an issue is reported.

        [Fn setActivityMarkerAt:self marker:@"clicked on payment_package_two"];
Activity Trail
	LoginController:viewDidLoad                           09:21:53:923	1131900928 FREE MEMORY (BYTES)
	LoginController:viewWillAppear                        09:21:53:923	1131458560
	LoginController:viewDidAppear                         09:21:53:927	1131003904
	UIApplication:applicationDidBecomeActive              09:21:54:081	1130999808
	PurchaseController:viewDidLoad                        09:22:09:670	1130622976
        PurchaseController:clicked on payment_package_two     09:40:24:235	1130622976

Function call

Finotes will report any return value issues, exceptions and execution delays that may arise in functions using [Fn call:].
A regular function call will be,


    [self getUserNameFromDb:@"123-sd-12"]
}

-(NSString *) getUserNameFromDb:(NSString *) userId {
    NSString *userName = [[User findById:userId] name];
    return userName;
}

Function “[self getUserNameFromDb:]” call needs to be changed to,

#import <FinotesCore/Observer.h>
#import <FinotesCore/Fn.h>


    [Fn call:@selector(getUserNameFromDb:) target:self withParameters:@"123-sd-12"];
}

-(NSString *) getUserNameFromDb:(NSString *) userId {
    NSString *userName = [[User findById:userId] name];
    return userName;
}

This will allow finotes to raise issue incase the function take more than normal time to execute, or if the function return a nil value, or throws an exception.

You can control all the above said parameters in Observe object.

expectedExecutionTime
#import <FinotesCore/Observer.h>
#import <FinotesCore/Fn.h>



    Observer *observer =  [[Fn observe] expectedExecutionTime:1400];
    NSString *userName = [Fn call:@selector(getUserNameFromDb:) target:self 
    					observer:observer withParameters:@"123-sd-12"];
}

-(NSString *) getUserNameFromDb:(NSString *) userId {
    NSString *userName = [[User findById:userId] name];
    return userName;
}
Returns nil
Exception in function

Here the expectedExecutionTime for the function “[self getUserNameFromDb:]” has been overriden to 1400 milliseconds (default was 1000 milliseconds). If the database query takes more than 1400 milliseconds to return the value or if the returned “userName” is nil, then corresponding issues will be raised.
An issue will be raised when exception occurs inside a function that is called using [Fn call:].

Expected Boolean value

If the function returns a Boolean value, you may set a expected value for the same and during execution if the function returns a value other than the expected value, an issue will be raised.

#import <FinotesCore/Observer.h>
#import <FinotesCore/Fn.h>

    Observer *observer =  [[Fn observe] expectedBooleanValue:YES];
    [Fn call:@selector(isValidUser:) target:self 
    					observer:observer withParameters:@"123-sd-12"];
}

-(BOOL) isValidUser:(NSString *) userId {
}
Return value range

If the function returns a number or decimal value, you may set a range for the same and during execution if the function returns a value outside the specified range, an issue will be raised.

#import <FinotesCore/Observer.h>
#import <FinotesCore/Fn.h>

    Observer *observer =  [[Fn observe]  min:10];
    [Fn call:@selector(getUserPostCount:) target:self 
    					observer:observer withParameters:@"123-sd-12"];
}

-(long) getUserPostCount:(NSString *) userId {
}

Function in Separate Class file

If the function is defined in a separate file and needs to be invoked, then instead of passing “self” you can pass the corresponding object in [Fn call:]

#import <FinotesCore/Observer.h>
#import <FinotesCore/Fn.h>


    DBUtils *dbUtils = [[DBUtils alloc] init];

    Observer *observer =  [[Fn observe] expectedExecutionTime:1400];
    NSString *userName = [Fn call:@selector(getUserNameFromDb:) target:dbUtils 
    					observer:observer withParameters:@"123-sd-12"];
}





@interface DBUtils ()

@end

@implementation DBUtils

-(NSString *) getUserNameFromDb:(NSString *) userId {
    NSString *userName = [[User findById:userId] name];
    return userName;
}

@end

Static Function calls

For static functions, pass corresponding class instead of object in [Fn call:] to invoke static method.

#import <FinotesCore/Observer.h>
#import <FinotesCore/Fn.h>


    Observer *observer =  [[Fn observe] expectedExecutionTime:2000];
    long userTimestamp = [[Fn call:@selector(getUserTimestampFromJSON:) 
    					target:[DBUtils class] observer:observer 
					withParameters:httpResponse] longValue];
}





@interface DBUtils ()

@end

@implementation DBUtils

+(long) getUserTimestampFromJSON:(NSDictionary *) response {
    long userTimestamp = [self processJSONAndFindUserTimestamp:response];
    return userTimestamp;
}


-(NSString *) getUserNameFromDb:(NSString *) userId {
    NSString *userName = [[User findById:userId] name];
    return userName;
}

@end

Chained Function calls

You can connect multiple functions using ‘nextFunctionSignature’ and ‘inClass’ properties in Observe object.

#import <FinotesCore/Observer.h>
#import <FinotesCore/Fn.h>


   - (void) sendChatClicked:(UITapGestureRecognizer *)recognizer {

       // Here function 'onChatSent:' is 
       // expected to be called in under '2000' milliseconds.
       Observer *observer =  [[[Fn observe] expectedChainedExecutionTime:2000]  
				nextFunctionSignature:
					@selector(onChatSent:) 
				inClass:[self class]];
       [Fn call:@selector(sendChat:) target:self observer:observer withParameters:chatMessage];
   }

   - (Boolean) sendChat:(NSString *) message {
   	if([self isValid:message]){
	     [self syncMessage:message];
	}
	return false;
   }

   - (void) onChatSent:(NSString *)chatMessageId {
   	[self chatSyncConfirmed:chatMessageId];
   }

Here ‘onChatSent:’ should be called within 2000 milliseconds after execution of ‘sendChat:’. If the function ‘onChatSent:’ is not called or is delayed then corresponding issues will be raised and reported.

Note,

It is recomended to have no user activity when chaining 2 funtions. lets call functions, A and B. i.e after execution of function A, no user activity (like a click or input) should be required for execution of B.

boolean returns false

Here in ‘sendChat:’ function, if it returns false, an issue will be raised with the function parameters, which will help you dig more into what could have gone wrong.

Test

In-order to check if you have implemented function chaining correctly, set break points inside the chained functions then run application in your simulator or device, and use your application, if you have implemented correctly, You will hit these breakpoints correctly.

Catch Global Exceptions.

In-order to catch uncaught exceptions, You may use [Fn catchExceptions].

#import <FinotesCore/Fn.h>


- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    [Fn initialize:application];
    [Fn catchExceptions];
}

Custom Exceptions

You can report custom exceptions using the [Fn reportExceptionAt:] API.

@try{
    [self fetchUserDetails];
}@catch(NSException *exception){
    [Fn reportExceptionAt:self withException:exception 
    					withSeverity:FATAL];
}

Custom Issue

You can report custom issues using the [Fn reportIssueAt:] API.


//Payment gateway delegate methods.
-(void) paymentCompleted:(NSString *) userIdentifier forType:(NSInteger) type{

}

-(void) paymentFailed:(NSString *) userIdentifier forReason:(NSString *) reason{
    [Fn reportIssueAt:self withDescription:
    			[NSString stringWithFormat:@"Payment failed for %@", reason] 
			withSeverity:FATAL];
}