Method References
http://public.innomdc.com/android-library/method-references/
http://public.innomdc.com/iql-library/
Requirements
- Android 2.2 (API 8) and above
- Permissions in the AndroidManifest.xml
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> |
Include library
You can do it in two ways:
- Download it directly from a link (add jar in Eclipse, Intellij Idea/AndroidStudio). This build contains all required libraries:
APSIS library
IQL 1.0
com.google.code.gson 2.2.4
antlr-runtime 3.5
Include maven dependencies into pom.xml:
... <repositories> <repository> ... <id>innometrics</id> <url>http://nexus.innomdc.com/nexus/content/groups/public/</url> </repository> ... </repositories> ... <dependencies> ... <dependency> <groupId>com.innometrics.android</groupId> <artifactId>Innometrics</artifactId> <version>1.0.0</version> </dependency> ... </dependencies> |
Configure parameters
Static config
Create config file APSIS.xml and place it in res/value folder.
Values for config you can find in the GUI: Install "Android Data Collection" app, create first section (name of your android app), go to Settings tab.
<?xml version="1.0" encoding="utf-8"?> <resources> <string name="innometrics_company_id">1</string> <string name="innometrics_app_key">appkey</string> <string name="innometrics_bucket">bucketname</string> <string name="innometrics_section">sectionname</string> <bool name="innometrics_debug">false</bool> <string name="innometrics_events_limit">1000</string> </resources> |
"innometrics_events_limit" - is not mandatory, but it's important setting that specifies the maximum size of the profile (in number of events) that will be stored locally and controls RAM occupied by the library.
Run-time config
In case you want to separate tracked data to different section, for example based on user's country set in app settings, you can init library with dynamic config (only once during initialization), see reference for available methods.
Create instance of library
In code, before using APSISAPI you need to get instance of APSISAPI class. It could be done in few ways:
Method returns instance of the class:
Method returns instance of the class:
InnometricsAPI innometricsAPI = InnometricsAPI.getInstance(context); |
Method returns instance of the class and synchronize profile with Backend:1
InnometricsAPI innometricsAPI = InnometricsAPI.getInstance(context,true); //Equal to InnometricsAPI innometricsAPI = InnometricsAPI.getInstance(context); innometricsAPI.sync(InnometricsAPI.SyncStrategy.ONLY_PULL); |
Method sets "userProfileID" (by default profileId is hashed ANDROID_ID) as current userprofile ID and returns instance of InnometricsAPI:
InnometricsAPI innometricsAPI = InnometricsAPI.getInstance(context,"userProfileID"); //Equal to InnometricsAPI innometricsAPI = InnometricsAPI.getInstance(context); innometricsAPI.setCanonicalProfileId(userProfileID) |
Example of usage:
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); innometricsAPI = InnometricsAPI.getInstance(this); }} |
Send data to profile
Track user events
Call trackEvent(). It takes two arguments: eventDefinition
- string codename of the event, event - hash containing parameters of this event. For instance to collect data about searches done by user you can use this code:
btnSearch.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { HashMap<String, Object> event = new HashMap<String, Object>(); event.put("search type", searchType.isChecked() ? "advanced" : "Normal"); event.put("search string", searchString.getText().toString()); innometricsAPI.trackEvent("eventSearch", event); } }); |
Track exceptions
Use trackError(Throwable throwable)
:
try { // Some code } catch (Exception e ){ InnometricsAPI.getInstance(context).trackError(e); // Your exception handling } |
Store profile attributes
To set profile attribute use setAttribute(String key, String value)
. This command will create new attribute in current collect application and section, or override it if attribute with the same key already exists.
To get attribute use method getAttributes(String collectApp, String section, GetAttributeListener listener). Parameters
collectApp and
section -
define collection application and section from which you want to get attributes. Listener
- callback function to be executed when requested attribute obtained. Listener
takes as an argument object carrying list of all attributes. You can get a particular attribute by key using method attributes.getDataByName(String Key);
innometricsAPI.getAttribute("web","mondify",new GetAttributeListener() { @Override public void getAttribute(Attribute attributes) { System.out.println("Profile web status = " + attributes.getDataByName("Status")); } }); |
Set session data
Default session data
These session data are set by default to each session:
- "device-model"
- "display-density"
- "phone-type"
- "display-height"
- "display-width"
- "network-operator-name"
- "app-version-code"
- "app-version-name"
- "version-os"
- "android-device-id"
You can change default set of session data with setDefaultSessionData(int[] dataFlags), argument is array of constants:
- APP_CODE - app-version-code, app-version-name
- DYSPLAY - display-height, display-width
- DEVICE_ID - android-device-id
- DEVICE_MODEL - device-model
- VERSION_OS - version-os
- NETWORK_OPERATOR_NAME -network-operator-name
- PHONE_TYPE -phone-type
In this example is default session data will be only "display-height", "display-width" and "phone-type":
api.setDefaultSessionData(new int []{InnoNames.PHONE_TYPE,InnoNames.DYSPLAY}); |
Custom session data
You can add your custom session data with setSessionData(String key, Object value):
api.setSessionData("key","valueObject"); |
Profiles identification and merging
Terms:
- temporary profile ID used in case user isn't authenticated
- canonical profile ID - user is authenticated in your system.
By default, when you init library (first time get instance of APSISAPI) it generates temporary profile ID with hashed from ANDROID_ID.
Once your user is authenticated, you need to set canonical id with mergeProfile(String canonicalProfileID) and then merge data of temporary profile into canonical with mergeProfile():
InnometricsAPI innometrics; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); Button btnLogin = (Button) findViewById(R.id.buttonLogIn); innometrics = InnometricsAPI.getInstance(this); // All events are collected in temporary profile now. // You can set temporary ID with your own algorithm with this method: // innometrics.setTempProfileID(generateTempId()); btnLogin.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // After user have passed authorization you can set his login as profile ID and make it canonical name = textLogin.getText().toString(); innometrics.setCanonicalProfileId(name); //profileId will be hashed name innometrics.mergeProfile(); //Now all events from temporary profile will be merged to canonical one } }); |
Manual profile synchronization
Besides automatic periodical profile synchronization with cloud (frequency could be adjusted, see xml config description), you can do it manually with sync(SyncStrategy strategy).
Strategy is a enumeration defined inside APSISAPI class. Possible values:
- PUSH_AND_PULL - first send all not synced data to server and then get the entire profile.
- PULL_AND_PUSH - first get the entire profile from server and then send not synced data.
- ONLY_PUSH - data only sends, nothing receives.
- ONLY_PULL - profile only receives, nothing sends.
Since synchronization is running in separate thread it's not guaranteed that data will be updated immediately. But you can register listener - a function to be executed when synchronization is completed. To do that you need to implement ApiListener
interface and its single method handlResult(int type).
Type could be:
-
InnoNames.SYNC_SUCCESS
if synchronization succeeds, -
InnoNames.SYNC_FAIL
if it fails.
To register listener you need to call addInnometricsListener(ApiListener listener)
method and pass instance of ApiListener
class.
@Override protected void onCreate(Bundle savedInstanceState) { innometrics = InnometricsAPI.getInstance(getActivity(), userName); // API initialization innometrics.addApiListener(new TestListener()); // register listener in API } // Listener implementation class TestListener implements ApiListener { @Override public void handleResult(int type) { switch (type){ case InnoNames.SYNC_FAIL:{ System.out.println("Synchronization failed"); break; } case InnoNames.SYNC_SUCCESS:{ System.out.println("Synchronization succeed"); break; } } } } api.sync(InnometricsAPI.SyncStrategy.ONLY_PULL); |
IQL
IQL - APSIS Query Language is built to:
- evaluate segments (return true or false for IQL expression)
- and to search and filter data in profile
IQL is also running in a separate thread so it needs registered listener to handle query result. For this purpose IqlListener
interface can be used which works same way as ApiListener
but gets IqlResult
as an argument. To run IQL query call method executeIQL. It takes three arguments:
-
query
- query string, -
listener
- instance ofIqlListener
class, -
filtrate
- boolean flag which defines whether to filter profile or not. Iffiltrate
isfalse
thenIqlResult
will contain indication if something were found or not (true or false) and the whole profile - in that case IQL query will execute faster. If set totrue - IqlResult
will contain corresponding to the query part of the profile.
public boolean executeIQL(String query, IqlListener listener, boolean filtrate) |
After query execution handleResult(IQLResult result)
will be called for all registered listeners and result
argument will contain query result.
IQLResult has following methods:
public boolean isFound() // returns true if at least one record was found public long getCount() // returns number of events which satisfy the condition public JSONObject getFiltredProfile() // returns filtered profile in JSON format public ArrayList getEventData(String name) // returns ArrayList of events filtered by "name" public ArrayList getSessionData(String name) // returns ArrayList of sessions filtered by "name" |
Example:
class MyListener implements IQLListener,ApiListener { private volatile boolean firstPass = true; // implementing two interfaces @Override public void handleResult(int type) { switch (type) { case InnoNames.SYNC_FINISH:{ // running query execution after successful synchronization and pass this object as listener innometricsAPI.executeIQL("collectApp(\"web\").section(\"web\").event(\"140013\").eventData(\"dd-6147\").neq(\"\").inLast(\"events.minute\",5)",this); break; } case InnoNames.SYNC_FAIL:{ Toast.makeText(LoginActivity.this,"Connection Error",Toast.LENGTH_LONG).show(); handler.sendMessage(handler.obtainMessage(2)); break; } } } @Override public void handleIQLResult(IQLResult iqlResult) { // if there are events that match IQL query if (iqlResult.isFound()) { // Getting needed event data from IQL result object ArrayList productIdList = iqlResult.getEventData("dd-6147"); ArrayList groupIdList = iqlResult.getEventData("dd-3795"); Object productID = productIdList.isEmpty() ? null :productIdList.get(0); Object groupID = groupIdList.isEmpty() ? null :groupIdList.get(0); System.out.println("productID = " + productID); System.out.println("groupID = " + groupID); } } } |
Example of filtered profile:
{ "profile": { "attributes": [], "id": "ae0ccae3268f91c5f27a5997e37a17d5", "services": [], "sessions": [ { "collectApp": "web", "services": [ { "data": { "countryCode": 0, "countryName": "Russian Federation", "region": "61", "city": "Rostov-na-donu", "latitude": 47.231293, "longitude": 39.723297, "dmaCode": 0, "areaCode": 0, "metroCode": 0 }, "id": "geo" } ], "data": {}, "events": [ { "services": [], "data": { "dd-2310": "---", "dd-4739": "Garden Easy Chair. Anthracite", "dd-1636": 3, "dd-6147": "201", "dd-5810": "201", "dd-5337": 0, "dd-13746": "54" }, "definitionId": "140013", "id": "nl5brzhf", "createdAt": 1387789099138 }, { "services": [], "data": { "dd-2310": "---", "dd-1636": 3, "dd-6147": "222", "dd-5810": "201", "dd-5337": 0, "dd-13746": "54" }, "definitionId": "140013", "id": "nl5brzhf", "createdAt": 1387789099138 } ], "id": "3jclpn76c1", "section": "676", "modifiedAt": 1387789109962, "createdAt": 1387789094868 } ], "version": "1.0" } } |
For instance call of iqlResult.getEventData("dd-6147");
on this profile will return List<JsonPrimitive>
containing two JSON primitive objects with values "201" and "222".
Saving a battery
There are periodical network synchronization process work in the library. You can suspend them when your app minimizes, goes to sleep or display turns off and resume it when application is active again. In order to do that use methods innometricsAPI.suspendBackgroundSync() andinnometricsAPI.resumeBackgroundSync():
abstract public class AbstractMyActivity extends Activity { InnometricsAPI innometricsAPI; Random r = new Random(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); innometricsAPI = InnometricsAPI.getInstance(this); } @Override protected void onStart() { super.onStart(); innometricsAPI.resumeBackgroundSync(); } @Override protected void onStop() { super.onStop(); innometricsAPI.suspendBackgroundSync(); } } |
In this example defined abstract class extends Activity
class and inherits all other Activity
classes. By doing this you able to override methods onCreate
, onStart
and onStop
.
onCreate
- contains initialization of the library. Since this is singleton initialization it will be called only once during first Activity creation.
onStart
and onStop
- contain sleep and wake up methods of - this allows to reduce energy consumption in minimized application.
Debug
Debug messages can be enabled in innometrics.xml with
<bool name="innometrics_debug">true</bool> |
Now you can see in console log what data is sent to Profile Cloud, what profile ID is used and API URLs:
05-27 08:42:50.169 1320-1357/? D/Inno﹕Send Url https://api.innomdc.com/v1/companies/<companyID>/buckets/<bucketName>/profiles/<profileID>?app_key=<appKey>&max_return_events=1000