Kotlin Documentation

Prerequisites for Integration

Android projects with minimum SDK version 14 (Ice-cream Sandwich) or above.
Android projects with target SDK version 28 or above.
Version of Android Gradle plugin should be 3.3.2 or above.

  1. Register with Finotes using the 'Get Started' button in https://finotes.com and login to dashboard.
  2. Use "Add App" to link Android to Finotes.
  3. Integrate Finotes SDK to your application.
  4. Test your integration.

Dependencies

Finotes SDK comes with v4.9.1 of OkHttp dependency packaged.
In case the Android project is using a different version of OkHttp, add the following to your app level build.gradle file. This will enable Gradle to use project version of OkHttp.
Please make sure to replace x.x.x with the version you are using.

build.gradle:
dependencies {
    ...                                            
}

configurations.all() {
    resolutionStrategy.force "com.squareup.okhttp3:okhttp:x.x.x"
}

Current Version 4.3.1

Change log


How to Integrate


Migration for versions 2.6.0 and below: for existing integrations

Step One

In-order to integrate Finotes in Android project, add the 'Maven tag with repo URL' shown below to project level build.gradle

build.gradle:
buildscript {
    repositories {
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:x.x.x'
    }
}
allprojects {
    repositories {
        jcenter()
        maven {
            url "https://finotescore-android.s3.amazonaws.com/release" //Finotes Maven repo link 
        }
    }
}

Step Two


Integrating both FinotesCore and FinotesDebug

To leverage the capabilities of both FinotesCore and FinotesDebug, copy the following code block in app level build.gradle within the dependencies section.

build.gradle:
releaseImplementation('com.finotes:finotescore:4.3.1@aar') {
    transitive = true;
}

debugImplementation('com.finotes:finotesdebug:4.3.1@aar') {
    transitive = true;
}

FinotesDebug SDK contains all features of FinotesCore and:

1. Ability to automatically detect Object level memory leaks.

Proguard

If you are using proguard in release build, you need to add the following to the proguard-rules.pro file.

proguard-rules.pro:
-keep class com.finotes.android.finotescore.* { *; }

-keepclassmembers class * {
    @com.finotes.android.finotescore.annotation.Observe *;
}

Add below line to keep the exact line number and source file name in the stacktrace.

proguard-rules.pro:
-keepattributes SourceFile,LineNumberTable

Please make sure that the mapping file of production build (each build) is backed up, inorder to deobfuscate the stacktrace from Finotes dashboard.
Location of mapping file: project-folder/app/build/outputs/proguard/release/mapping.txt

Initializing Finotes

You need to call Fn.init() function in the onCreate() function of Application class.

Application Class:
class BlogApp: Application() {
    override fun onCreate() {
        super.onCreate()
        Fn.init(this)
    }
}

Testing Integration

Now that the basic integration of Finotes SDK is complete,
Let us make sure that the dashboard and SDK are in sync.

Step One

Add Fn.test() under Fn.init

Application Class:
class BlogApp: Application() {
    override fun onCreate() {
        super.onCreate()
        Fn.init(this)
        Fn.test()
    }
}

Step Two

The application class (here BlogApp), should be registered in your manifest file.

Now run the application in a simulator or real android device (with active network connection).

Step Three

Once the application opens up, open Finotes dash.
The test issue that we raised should be reported.

In-case the issue is not listed, make sure the right app and platform is selected at the top of the dashboard.
If the issue is still not synced in Finotes dashboard, Click Here.

Remember to remove the Fn.test() call, else every time the app is run, an issue will be reported.

Now, Finotes will report all memory leaks, abnormal memory usages and crashes if they occur in your application.

Things to take care in Release Build

Make sure that long() API is not called in release build and proguard rules are added as specified above.

Application Class:
class BlogApp: Application() {
    override fun onCreate() {
        super.onCreate()
        Fn.init(this) 
    }
}

Please make sure that the mapping file of each production build is manually backed up, inorder to deobfuscate the stacktrace from Finotes dashboard.
Location of mapping file: project-folder/app/build/outputs/proguard/release/mapping.txt

Data Points Collected

Finotes SDK automatically collects multiple data points inorder to help developers get indepth information on the issues reported.
Head over to Android Data Points page to explore more on the type of data points collected by the SDK.

Detect memory leaks

Finotes SDK has two flavours, FinotesCore and FinotesDebug. With basic integration, both flavours will automatically report memory leaks in the app Activities and Fragments.
In case basic integration is not complete, please head over to How To Integrate section.

Finotes will auto detect and report Activity and Fragment level memory leaks to Finotes dashboard.
To enable Finotes to track memory leaks in your Service classes, you need to add a single line API Fn.watchLeaks() inside the onDestroy function of your Service Classes.

Your Service Classes:
class ContactsParserService : IntentService() {

    override fun onDestroy() {
        super.onDestroy()
        Fn.watchLeaks(this)
    }
}

FinotesDebug

FinotesDebug flavour of Finotes SDK has the capability to detect detailed object level memory leaks.
When FinotesDebug detects a memory leak in Android application, a notification will be shown to the user. Tapping on the notification will report the issue to Finotes dashboard.

FinotesCore

FinotesCore flavour of Finotes SDK has the capability to detect all activity and service level leaks.
All leaks detected by FinotesCore will be immediately reported to the Finotes dashboard.

Track Network Calls

If the project is using Retrofit or OkHttp (directly) or Volley to make API calls, Finotes will be able to notify of any errors in API calls with just a single line.

Retrofit- no existing okhttpclient

If the existing retrofit code does not use custom client:
val retrofit = Retrofit.Builder()
.addConverterFactory(GsonConverterFactory.create())
.build()

Change to,

Add Finotes client using .client():
val client = OkHttp3Client(OkHttpClient.Builder()).build()
val retrofit = Retrofit.Builder()
    .client(client)
    .addConverterFactory(GsonConverterFactory.create())
    .build()


Retrofit- already using okhttpclient

If the existing retrofit code already uses a custom client:
val retrofit = Retrofit.Builder()
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()

Change to,

Pass the current builder to OkHttp3Client and .build():
val currentBuilder = new OkHttpClient.Builder()
... //Builder customization codes
...
val client = new OkHttp3Client(currentBuilder).build()


OkHttp

Find the code block where the OkHttpClient is initialized
In case more than one OKHTTP client is initialized, change the default client to custom OkHttp3Client provided by Finotes.

If OkHttp is directly used to make API calls
var client = OkHttpClient.Builder().build()

Change to,

var client = OkHttp3Client(OkHttpClient.Builder()).build()


Volley 1.1.0

As volley does not directly support OkHttpClient we need to use HurlStack.
First, you need to add HurlStack class from HurlStack Gist to your project.
Second, find the below function where newRequestQueue is initiated either in a singleton (used as ApiManger for volley) class or Application class.

ApiManager Singleton or Application class:
return Volley.newRequestQueue(applicationContext)

Third, pass the custom HurlStack as a second parameter, replacing the above code.

val requestQueue: RequestQueue? = null
get() {
    if (field == null) {
        return Volley.newRequestQueue(applicationContext, HurlStack())
    }
    return field
}


Forth, add implementation 'com.squareup.okhttp3:okhttp:3.8.0' to your build.gradle.

To integrate volley 1.0.0 please refer to Integrating Volley 1.0.0

Incase, you need more clarity on integrating Volley with HurlStack, Please initiate a chat with our development team directly using chat widget at the bottom right corner.
We will help you overcome any roadblocks that you may have.

Dynamic Path Component

When API call issues are reported, different urls are created as separate ticket.
This can cause large number of tickets generated for the same API incase the url contains an id or any other dynamic path component.

Application Class:
@Observe(urlPatterns = {"https://your-host.com/repos/{owner}/{repo}/contributors",
                                    "https://your-host.com/todos/{id}"})
        
class BlogApp: Application() {
    override fun onCreate() {
        super.onCreate()
        Fn.init(this)
    }
}

Use urlPatterns with @Observe annotation to specify the urls that contains dynamic path component.
Wrap the corresponding dynamic path component or id inside '{}'.

Whitelisting domains

Using the domains key in @Observe annotation, domains can be whitelisted. Once set only HTTP(s) calls made to the whitelisted domains will be tracked by the SDK.

Application Class:
@Observe( domains = {@Domain( hostName = "your-domain.com")
                                , @Domain( hostName = "another-domain.com")} )
    
class BlogApp: Application() {
    override fun onCreate() {
        super.onCreate()
        Fn.init(this)
    }
}

Setting Domain timeout

Setting domain timeout is an extension of whitelisting domains. Developers can set timeout to whitelisted domains using the timeout key along with hostName key.

The timeout value should be in milliseconds. Once the value is set if any of the HTTP(s) calls to the domain takes more than the set amount of time, an issue report will be raised.

Application Class:
@Observe( domains = {@Domain( hostName = "your-domain.com", timeOut = 5000)
                            , @Domain( hostName = "another-domain.com", timeOut = 10000)} )

class BlogApp: Application() {
    override fun onCreate() {
        super.onCreate()
        Fn.init(this)
    }
}

Privacy (Optional feature)

As Finotes reports API call issues, each issue is tagged with corresponding request response headers, request body and associated parameters.
If header fields contain any sensitive data, Finotes provides a global and easy mechanism to mask such header fields using maskHeaders in @Observe annotation as shown in code snippet.
You may provide 1 or more header keys in the 'maskHeaders' field.

Application Class:
@Observe(maskHeaders = {"X-Key", "Accept"})
class BlogApp: Application() {
    override fun onCreate() {
        super.onCreate()
        Fn.init(this)
    }
}

Use case:

Let us say API calls from the app has header field 'X-Key' which contains the authentication token.
It can be masked by providing the 'X-Key' in the field as shown above, masked header fields are filtered out from the app itself as is not sent to the Finotes dashboard, if any issues are raised.
The maskHeaders field is case insensitive and is optional.

Crash Reporting

With basic integration, Finotes will track and report uncaught exceptions to Finotes dashboard automatically.
Head over to How To Integrate section to complete the basic integration.

Block Finotes from Reporting Crashes

In case developers want to prevent Finotes from reporting uncaught exceptions that causes app to force close, use Fn.preventCrashReporting().

Application Class below init() line:
override fun onCreate() {
    super.onCreate()
    Fn.init(this)
    Fn.preventCrashReporting()
}

Report low memory warnings

You may extend the Application class from ObservableApplication. This will report any app level memory issues that may arise in the application.

Application Class:
class BlogApp: Application() {
    override fun onCreate() {
        super.onCreate()
    }
}
Change to
class BlogApp: ObservableApplication() {
    override fun onCreate() {
        super.onCreate()
    }
}

Track screen loading delay

Finotes SDK is capable of auto detecting activity loading delay.
If the an activity takes more than 6 seconds to load then it will be raised as a bug report.

Developers can set custom time to track screen loading delays using @Observe annotation in the application class. Once set, if any of the app activity takes more than the set amount of time, it will be raised as a bug report.

Application Class:
@Observe(screenLoadDelayInSeconds = 3)
class BlogApp: Application() {
    override fun onCreate() {
        super.onCreate()
        Fn.init(this)
    }
}

Report Try..Catch Exceptions

Any exceptions that may have already been caught using try{}catch{} needs to be reported as they might prevent the application from crashing, but at the same time can make app unstable if gone unreported.

Exceptions caused during parsing a JSON object/array:
try {
    val jsonObject = JSONObject(receivedString)
    val identifier = jsonObject.getString("userID")
} catch (JSONException exception) {

    Fn.setActivityMarker(this@ParserClass, receivedString)

    Fn.reportException(this@ParserClass, exception, Severity.MAJOR)

}

Report Custom Issues

You can report custom issues using the Fn.reportIssue() API.

Any where in the project:
override fun paymentCompleted(userIdentifier:String, type:String){
    //Handle post payment
}

override fun paymentFailed(userIdentifier:String, reason:String){

    Fn.setActivityMarker(this@PaymentActivity, "User identifer "+userIdentifier)

    Fn.reportIssue(this@PaymentActivity, "Payment Failed" ,reason)

}

Use case:

In cases where the fail situations can be anticipated, they may be reported using Fn.reportIssue().
You may pass an issue title and detailed description as parameters to report the issue.
As in custom exceptions, you will be able to make use of activity markers to aid you to reproduce the issue when reported.

Track ANR issues

This feature is automatically activated in the SDK.

With basic integration Finotes will track and report UI/ Main thread blocks that can lead to App Not Responding (ANR) situations automatically. The stacktrace available with the issue report will help developers pinpoint the exact line that caused the issue.

Developers can set custom time in milliseconds to track finer ANR issues using ANRThresholdInMilliseconds key in @Observe annotation in the application class. Once set, if the app UI/ Main thread gets blocked for more than the set amount of time to load, it will be raised as a bug report.

Application Class:
@Observe(ANRThresholdInMilliseconds = 1000)
class BlogApp: Application() {
    override fun onCreate() {
        super.onCreate()
        Fn.init(this)
    }
}

With the introduction of ANRThresholdInMilliseconds in @Observe annotation, API Fn.setFrameRateThreshold is now deprecated and will be removed in a future version of the SDK.

Deprecated:
class BlogApp: Application() {
    override fun onCreate() {
        super.onCreate()
        Fn.init(this)
        Fn.setFrameRateThreshold(1000L) //Deprecated
    }
}

Setting custom Activity Markers

Activity markers are events that occur in an application during runtime. Activity and fragment lifecycle events are automatically captured by Finotes.
You can set custom activity markers in the project using Fn.setActivityMarker(). These markers will be shown along with the activity trail when an issue is reported.
Markers are displayed in their chronological order.
There is no limit in the number of custom markers that can be set by the developer. Only when an issue is raised, these activity markers are sent to the server.

Call anywhere is you project:
Fn.setActivityMarker(this@PurchaseActivity, "clicked on payment_package_two")

How activity trail will look like along with reported issue in Finotes dashboard:
ActivityWelcome:onCreate                            11:19:24:469	45.79% FREE MEMORY 
MapActivity:onCreate                                11:19:24:708	44.39%
MapActivity:onStart                                 11:19:26:983	45.55%
MapActivity:onResume                                11:19:27:012	45.19%
ActivityWelcome:onDestroy                           11:19:28:515	44.53%
MapActivity:onPause                                 11:20:17:806	50.45%
PurchaseActivity:onCreate                           11:20:18:106	55.19%
PurchaseActivity:onStart                            11:20:18:404	55.43%
PurchaseActivity:onResume                           11:20:18:906	55.23%
PurchaseActivity:clicked on payment_package_two     11:20:24:235	55.20%

Debug level Activity Markers

Developers can set custom activity markers that are active only in debug builds of the app. This can be achieved using Fn.setActivityMarkerForDebug API.
These markers are not captured in release or production builds.

Call anywhere is you project:
Fn.setActivityMarkerForDebug(this@PurchaseActivity, "User opened invoice")


Obfuscated Activity Trail

When using proguard or other obfuscation tools, the class name in activity trail may be obfuscated.
In order to display meaningful class names in activity trail, developers can set a custom id using @Observe annotation to any class.
Once set 'id' will be shown instead of the obfuscated class name.
Make sure to provide a hardcoded String value to 'id' parameter of @Observe annotation.

Any non-Activity Class:
@Observe(id = "GalleryFragmentClass")
class GalleryFragment: Fragment() {
                        

Map user or device

Setting a custom id for the device on which the app is running will help to pinpoint the device from which the issue reports are generated.
This identifier will be tagged with all issue reports raised in Finotes dashboard.
If this API is invoked multiple times, the latest identifier will be used.

Anywere in code:
Fn.setCustomId(uniqueIdentifier)

Setting Location Coordinates

Finotes SDK takes location data automatically when the application itself has a lock on the coordinates. SDK does not engange GPS or require any extra permission for the same.

Issue callback listener (Optional)

You can listen for and access every issue in realtime using the Fn.listenForIssue() API.
You need to add the listener in your Application class.

Application onCreate
Fn.listenForIssue(IssueFoundListener { 
issueView: IssueView? ->

})

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.
The callback will be in a background thread, make sure to use a UI thread to implement any UI related actions.
This callback is triggered right after an issue occurrence.

Filter re-occurrence of same issue (Optional feature)

Finotes reports all issues to the dashboard, in some cases same issue may reoccur in the same device.
Finotes provides a mechanism to filter out duplicate issues from being raised and reported (from the same device) for a set number of hours using @Observe annotation in Application class.
Default value is 6 hours. Minimum supported is 1 Hour to maximum of 24 hours.

Application Class:
@Observe(issueRaiseFrequencyInHours = 10)
class BlogApp: Application() {
    override fun onCreate() {
        super.onCreate()
        Fn.init(this)
    }
}

Whitelisting Crash Reports

Developers can whitelist crash reporting using Fn.whiteListCrashes() API.

Application Class:
class BlogApp: Application() {

    override fun onCreate() {
        super.onCreate()
        Fn.init(this)
        Fn.whiteListCrashes(NullPointerException.class, OutOfMemoryError.class)
    }
}

Fn.whiteListCrashes() API takes exceptions and error classes as arguments separated by coma.
Once called, only crashes that are whitelisted using this API will be reported in Finotes dashboard.
Whitelisting crashes using Fn.whiteListCrashes() API will not work if Fn.preventCrashReporting() is called.

Track Function Calls

Function tracking is the ability to monitor code-level individual functions during runtime for any potential errors.
SDK provides two different ways to activate function tracking.
1. By using the Fn.call() API to invoke code level function, Finotes SDK will be able to monitor and report runtime issues with detailed data points.
2. By using the Fn.start() and Fn.end() APIs within the function definition, SDK will be able to monitor and report runtime issues with detailed data points.

Regular function call:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_user)
    val userName = getUserNameFromDb("123-sd-12")
}

fun getUserNameFromDb(userId: String): String?{
    return User.findById(userId).getName()
}

Method 1 - Using Fn.call() API

Function tracking using Fn.call():
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_user)
    val userName = Fn.call("getUserNameFromDb", this@UserActivity, "123-sd-12") as String?
}

@Observe
fun getUserNameFromDb(userId: String): String?{
    return User.findById(userId).getName()
}


Once Fn.call() API is invoked, errors like NULL return value, wrong return value, exceptions and function execution delays will be captured and reported to the dashboard.

Things to Note:
1. Replace the traditional method of function invocation with the SDK based mechanism using Fn.call() API.
2. Make sure that the function definition is annotated using @Observe.
3. The access modifier of the function should be set to public.
4. Function parameters should be of an Object type. primitive data types are not supported by Fn.call() API.
5. By default, SDK monitors for NULL return value, negative return value and if the function takes more than 1000 milliseconds to execute.
6. Please refer to the @Observe section to learn more.
7. Exceptions thrown during function execution will be automatically captured and reported.
8. The function parameters that were passed to it during its invocation will be tagged along with the issue in the Finotes dashboard if any are raised.


Method 2 - Using Fn.start() and Fn.end() APIs

Function tracking using Fn.start() and Fn.end():
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_user)
    val userName = getUserNameFromDb("123-sd-12")
}

@Observe
fun getUserNameFromDb(userId: String): String?{
    Fn.start(object : Object() {}, userId)

    //function definition.
    return Fn.end(object : Object() {}, UserDao.getNameWithId(userId))
}


Once Fn.start() and Fn.end() APIs are added to a function definition, errors like NULL return value, wrong return value and function execution delays will be captured and reported to the dashboard.

Things to Note:
1. It is mandatory to call both Fn.start() and Fn.end() functions at the start and end of your function definition.
2. The first parameter of both the APIs should be new Object(){}.
3. Make sure that the function is annotated using @Observe.
4. This method of function tracking supports primitive function parameters as well.
5. By default, SDK monitors for NULL return value, negative return value in case of Boolean and if the function takes more than 1000 milliseconds to execute.
6. Please find the @Observe section to learn more.
7. Use Fn.reportException API to report exceptions thrown during function invocation.
8. The function parameters that are passed to Fn.start() during its invocation will be tagged along with issues reported in the Finotes dashboard.
9. In case there is no return value, just call Fn.end(new Object(){}) at the end of the function definition.


@Observe fields

It is mandatory to annotate every code level function that is to be tracked by the SDK with @Observe annotation.
@Observe annotation allows customization in the way issues are reported during a tracked function execution.


Function ID

In case of function overloading, this field is mandatory. ID field gives the ability for the SDK to track functions in case of function overloading. In the case of tracking such functions, use the id field to assign a unique name for the functions.


Method 1 - Usage of function ID when using Fn.call() API

If the id field is set, make sure to mandatorily pass the id and not the function name in the Fn.call() API.

Function Overriding with Fn.call():
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_user)
    // Here the function name "getUserNameFromDb" is passed in Fn.call().
    // This will trigger the getUserNameFromDb(String userId) function.
    val userName = Fn.call("getUserNameFromDb", this@MainActivity, "123-sd-12") as String?

    // Here the id "getUserNameFromDBWithEmailAndToken" specified in
    // @Observe annotation is passed in Fn.call().
    // This will trigger the getUserNameFromDb(String email, String token) function.
    val userNameFromEmail = Fn.call("getUserNameFromDbWithEmailAndToken", this@MainActivity
                                        , "support@finotes.com", "RENKDS123S") as String?
}

@Observe
fun getUserNameFromDb(userId: String): String?{
    return User.findById(userId).getName()
}

@Observe(id = "getUserNameFromDbWithEmailAndToken")
fun getUserNameFromDb(email: String, token: String): String?{
    return User.findUniqueByProperties(email,token).getName()
}


Method 2 - Usage of function ID when using Fn.start() and Fn.end() APIs

In case of function overloading, simply set a unique name to the id field. This will allow the SDK to track overloaded functions.

Function Overriding with Fn.start() and Fn.end():
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_user)

    // No change needed here
    val userName = getUserNameFromDb("123-sd-12")

    // No change needed here
    val userName = getUserNameFromDb("support@finotes.com", "RENKDS123S")
}

@Observe
fun getUserNameFromDb(userId: String): String?{
    Fn.start(object : Object() {}, userId)
    
    return Fn.end(object : Object() {}, User.findById(userId).getName())
}

//Set a unique name to the functions using "id" field
@Observe(id = "getUserNameFromDbWithEmailAndToken")
fun getUserNameFromDb(email: String, token: String): String?{
    Fn.start(object : Object() {}, email, token)
    
    return Fn.end(object : Object() {}, User.findUniqueByProperties(email,token).getName())
}


Expected Execution Time

By default, if a tracked function takes more than 1000 milliseconds to execute, an issue report will be sent to the dashboard.
Set a custom expected execution time using the field expectedExecutionTime in @Observe annotation.
Once this field is set with a custom value, and the corresponding function execution takes more than the set amount of time, an issue report will be sent to the dashboard.


Expect Null

By default, if a tracked function returns NULL during its execution, an issue report will be sent to the dashboard.
Set the field expectNull to 'true' to prevent SDK from reporting NULL issues.

Setting expectNull using @Observe annotation:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_user)
    ...
}

@Observe(expectNull = true)
fun getUserNameFromDb(userId: String): String?{
    ...
}


Expected Boolean Value

By default, if a tracked function with return type Boolean returns 'false' during its execution, an issue report will be sent to the dashboard.
Instead if an issue report needs to be raised if return value is 'true', set the field expectedBooleanValue to 'false'.

Setting expectedBooleanValue using @Observe annotation:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_user)
    ...
}

@Observe(expectedBooleanValue = false)
fun verifyUser(userId: String): Boolean?{
    ...
}


Set Range Check of Return value

If the tracked function returns a Number type (e.g. Integer, Double, Float). Use min, max, minDecimal, maxDecimal fields to set expected upper and lower limits of return value.
Once set, if the function returns a value outside the min and max values, corresponding issue report will be raised to the Finotes dashboard.

Setting range check of return value using @Observe annotation:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    ...
}

@Observe(min = 200)
fun walletBalanceOfUser(userId: String): Integer?{
    ...
}
@Observe(min = 10, max = 3000)
fun amountTypedByUserForWalletRefill(userId: String): Integer?{
    ...
}

@Observe(minDecimal = 10)
fun getTaxComponentForProduct(productId: String): Double?{
    ...
}
@Observe(minDecimal = 10, maxDecimal = 20)
fun calculateFinalTaxPercentageCollected(userId: String): Double?{
    ...
}


Severity

By default, issues raised from a tracked function will have the default severity level - MAJOR.
Use severity field to change the value. Available severity levels are MINOR, MAJOR, FATAL

Setting Severity using @Observe annotation:
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_user)
    ...
}

@Observe(severity = Severity.MINOR)
fun getUserNameFromDb(userId: String): String?{
    ...
}

Track Feature failures

SDK can track code-level functions and report issues that may arise during its execution.
Please refer Function Tracking section, before continuing with this section.

Feature tracking allows developers to track any feature in the android app by chaining two or more functions.

Take the example of Add To Cart feature in any e-commerce android application.
Consider that the function at the time of tapping the AddToCart button is 'addItemToCart' and 'onItemAddedToCart' is the function that gets called after the items have been successfully added to the cart.

Follow the next steps to enable tracking Add to Cart feature for failure,
1. First function addItemToCart need to be invoked either using Fn.call() API or using Fn.start() and Fn.end() APIs.
2. Second function onItemAddedToCart needs to be invoked using the same API used in step 1.
3. Next, chain both the functions using nextFunctionId and nextFunctionClass fields in @Observe annotation of the first function.
4. Use expectedChainedExecutionTime field in @Observe annotation to set the expected time it will take to execute the second function after the first function.

Once done, SDK monitors if second function 'onItemAddedToCart' is executed after the execution of first function 'addItemToCart'. If the 'onItemAddedToCart' is not executed within expectedChainedExecutionTime in milliseconds (default value is 2000 milliseconds) then a corresponding issue will be raised.


Method 1 - Using Fn.call() API

Add to cart:
class ProductListingActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_product_listing)
        ...
        ...
        addToCartButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Fn.call("addItemToCart", this@ProductListingActivity, item.getId())
                }
            })

    }

    // Here function 'onItemAddedToCart' is expected to be called in under '5000' milliseconds.
    @Observe(nextFunctionId = "onItemAddedToCart",
            nextFunctionClass = ProductListingActivity::class,
        expectedChainedExecutionTime = 5000)
    fun addItemToCart(itemId :String): Boolean{
        if(isValid(itemId)){
            ... 
            ...
            return true
        }
        return false
    }

    fun onApiCallComplete(response :JSONObject){
        if(validResponse(response)){
            Fn.call("onItemAddedToCart", this@ProductListingActivity, response.getString("id"))
        }
    }

    @Observe
    fun onItemAddedToCart(itemId :String){
        showMessage(Messages.CARTED_SUCCESS)
        ...
        ...
    }
}

Things to Note:
1. Here both functions 'addItemToCart' and 'onItemAddedToCart' are invoked using Fn.call() API.
2. Both functions are annotated with @Observe annotation.
3. Both functions are chained using 'nextFunctionId' and 'nextFunctionClass' fields in @Observe annotation in the first function.
4. Here 'nextFunctionId' is the name/ID of the second function and 'nextFunctionClass' is the .class where the second function is defined.
5. 'expectedChainedExecutionTime' in first function 'addItemToCart' overrides time needed to execute second function after execution of first function.


Method 2 - Using Fn.start() and Fn.end() APIs

Add to cart:
class ProductListingActivity: AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_product_listing)
        ...
        ...
        addToCartButton.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    addItemToCart(item.getId())
                }
            })

    }

    // Here function 'onItemAddedToCart' is expected to be called in under '5000' milliseconds.
    @Observe(nextFunctionId = "onItemAddedToCart",
            nextFunctionClass = ProductListingActivity::class,
        expectedChainedExecutionTime = 5000)
    fun addItemToCart(itemId :String): Boolean{
        Fn.start(object : Object() {}, itemId)
        if(isValid(itemId)){
            ... 
            ...
            return true
        }
        return Fn.end(object: Object(){}, false)
    }

    @Override
    fun onApiCallComplete(response :JSONObject){
        if(validResponse(response)){
            onItemAddedToCart(response.getString("id"))
        }
    }

    @Observe
    fun onItemAddedToCart(itemId :String){
        Fn.start(object : Object() {}, itemId)
        showMessage(Messages.CARTED_SUCCESS)
        ...
        ...
        Fn.end(object: Object(){})
    }
}

Things to Note:
1. Both functions 'addItemToCart' and 'onItemAddedToCart' are annotated with @Observe annotation.
2. Both functions are chained using 'nextFunctionId' and 'nextFunctionClass' fields in @Observe annotation in the first function.
3. Here 'nextFunctionId' is the name/ID of the second function and 'nextFunctionClass' is the .class where the second function is defined.
4. 'expectedChainedExecutionTime' in first function 'addItemToCart' overrides time (default is 2000 milliseconds) expected to execute second function after execution of first function.


@Observe fields

Chain two functions using nextFunctionId, nextFunctionClass, expectedChainedExecutionTime fields in @Observe.


Next Function Id

Provide the name/ID of the second function in the nextFunctionId field in the @Observe annotation that is to be chained with the first function.


Next Function Class

Provide the .class (e.g ClassName.class) where second function is defined. Use nextFunctionClass field in the @Observe annotation to set the value.


Expected Chained Execution Time

When two functions are chained, expectedChainedExecutionTime field in @Observe determines the expected time between the execution of two functions.
Issue will be reported to the dashboard if the second function is not called within the expected time after the execution of the first function.
Default time is 2000 milliseconds.

Enable Finotes Logs

You can activate internal Finotes logging using log() API.
Activating log() API will print all logs generated by the SDK in LogCat including error and warning logs.

Application Class:
class BlogApp: Application() {
    override fun onCreate() {
        super.onCreate()
        Fn.init(this)
        Fn.log()
    }
}

When preparing for production release, you need to remove the log API.

Migration for versions 2.6.0 and below: for existing integrations

We have updated our maven access from key based to keyless mechanism.

In-order to migrate to keyless mechanism in Android project, change the code below in project level build.gradle

Existing build.gradle:
allprojects {
    repositories {
        jcenter()
        maven {
            url "s3://finotescore-android/release"
            credentials(AwsCredentials) {
                accessKey = "Access Token"
                secretKey = "Access Token Secret"
            }
        }
    }
}
Change to,

build.gradle:
allprojects {
    repositories {
        jcenter()
        maven {
            url "https://finotescore-android.s3.amazonaws.com/release"
        }
    }
}