Tutorial #101: Getting started with Flurry Analytics in Android

Hello everyone!

Now-a-days, developers keep track of user activity and generate useful statistics about an application. Hence, analytics plays an important role in mobile applications. There are plenty of analytics solutions that exists in the market today. For example, ACRA is a library enabling Android Application to automatically post their crash reports to a GoogleDoc form. It is targetted to Android application developers to help them get data from their applications when they crash or behave erroneously.

Through this post, we will learn how to integrate the get started with the Flurry Analytics SDK in Android. The Flurry Analytics SDK provides you with the tools and resources you need to gain a deep level of understanding about your user’s behavior in your apps. Set up advanced analysis of complex events, with metrics, segments and funnels to better track your user’s habits and performance.

Pre-requisites: Eclipse IDE, Android SDK, Flurry Developer Account

Step 1: Sign up for Flurry developer account

First, you need to create a developer account by signing up over here. Once the account is created, download the required SDK. Now, login into your account and create a new application that will display all the analytical information.

Step 2: Create Android application project

Launch Eclipse IDE and a create a new Android application project called AndroidFlurryAnalyticsDemo with package name com.app.flurry.android. Copy the FlurryAnalytics.jar file inside the libs folder of your Android project.

Step 3: Initialize Flurry and simulate a crash

Create a new class called FlurryApplication that extends the Application class in Android. This class will initialize Flurry on application launch.

FlurryApplication.java

package com.app.flurry.application;

import android.app.Application;
import com.app.flurry.utils.AppConstants;
import com.flurry.android.FlurryAgent;

public class FlurryApplication extends Application {
	@Override
	public void onCreate() {
	super.onCreate();

        //set log enabled
        FlurryAgent.setLogEnabled(true);

        //set log events
        FlurryAgent.setLogEvents(true);
        
        // initialize Flurry
        FlurryAgent.init(this, AppConstants.FLURRY_API_KEY);
    }
}

Next, let us simulate a crash event in our Activity class as follows!

MainActivity.java

package com.app.flurry.main;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import com.app.flurry.android.R;
import com.app.flurry.utils.AppConstants;
import com.flurry.android.FlurryAgent;

public class MainActivity extends Activity {

	private Button m_objBtnPrint;
	private String m_strName = null;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);

		m_objBtnPrint = (Button)findViewById(R.id.btnPrint);
		m_objBtnPrint.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				FlurryAgent.onEvent("click button");	
				simulateCrashEvent();
			}
		});
	}

	public void simulateCrashEvent(){
		try{
			m_strName.concat("This will result in null pointer exception");
		}catch(Exception ex){
			ex.printStackTrace();
			FlurryAgent.onError("error", "Error occurred on click of button", ex); 		
		}
		FlurryAgent.logEvent("print button clicked");	
	}

	@Override
	protected void onStart(){
		super.onStart();
		FlurryAgent.onStartSession(this, AppConstants.FLURRY_API_KEY);
	}

	@Override
	protected void onStop(){
		super.onStop();		
		FlurryAgent.onEndSession(this);
	}
}

activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="${relativePackage}.${activityClass}" >

    <TextView
        android:id="@+id/txt1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/hello_world" />

    <Button
        android:id="@+id/btnPrint"
        android:layout_below="@+id/txt1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="@string/print" />

</RelativeLayout>

AndroidManifest.xml

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.app.flurry.android"
    android:versionCode="1"
    android:versionName="1.0" >

    <uses-sdk
        android:minSdkVersion="14"
        android:targetSdkVersion="19" />

    <!-- required permission -->
    <uses-permission android:name="android.permission.INTERNET" />
    <!-- optional permission - highly recommended -->
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
    <!-- optional permission -->
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />

    <application
        android:name="com.app.flurry.application.FlurryApplication"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name" >
        <activity
            android:name="com.app.flurry.main.MainActivity"
            android:label="@string/app_name" >
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <meta-data
            android:name="com.google.android.gms.version"
            android:value="@integer/google_play_services_version" />
    </application>

</manifest>

Save all changes and run the application on an Android device. If no errors occur, then you should see the following output from the dashboard page of the Flurry application.

crashes_page

events_page

users_page

Source code for this tutorial can be found over here

Reference: Flurry SDK Developer’s page

Display contacts from address book in Android

In my last post, I had explained how to add new contacts using the Contacts Provider component in Android. Moving on, we will now learn how to display existing phone book contacts using the concept of Cursors. The Cursor interface provides random read-write access to the result set returned by a database query.

As mentioned on the developer’s page, cursor implementations are not required to be synchronized so code using a Cursor from multiple threads should perform its own synchronization when using the Cursor.

Through this post, we will learn how to display existing phone book and Skype contacts using Cursors.

Pre-requisites: Eclipse IDE, Android SDK (Target API level: 19)

Open the Eclipse IDE and create a new Activity class called DisplayContactListActivity in any of your existing Android projects and add the following code!

DisplayContactListActivity.java

package com.example;

import android.app.Activity;
import android.content.ContentResolver;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.provider.ContactsContract.Data;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;

public class DisplayContactListActivity extends Activity {

	private Button m_objBtnSkypeContact;
	private Button m_objBtnPhoneContact;
	private TextView m_objDisplayContact;

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_display_contact);

		m_objBtnSkypeContact = (Button)findViewById(R.id.btnSkypeContact);
		m_objBtnPhoneContact = (Button)findViewById(R.id.btnPhoneContact);
		m_objDisplayContact = (TextView)findViewById(R.id.txtDisplayContact);

		//skype phone contact button event
		m_objBtnSkypeContact.setOnClickListener(new View.OnClickListener() {

			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				try{
					m_objDisplayContact.setText("");
					getSkypeContactsList();
				}catch(Exception e){
					e.printStackTrace();
				}
			}
		});


		//phone contact button event
		m_objBtnPhoneContact.setOnClickListener(new View.OnClickListener() {
			@Override
			public void onClick(View v) {
				// TODO Auto-generated method stub
				try{
					m_objDisplayContact.setText("");
					getPhoneContactsList();
				}catch(Exception ex){
					ex.printStackTrace();
				}
			}
		});
	}

	/**
	 * 
	 * This function is used to display the list of Skype contacts
	 */
	private void getSkypeContactsList() {
		StringBuffer strSkypeContactBuffer = new StringBuffer();
		Cursor objCursor = getContentResolver().query(
				Data.CONTENT_URI,
				new String[] { Data.CONTACT_ID, Data.DATA1 },
				Data.MIMETYPE + "= ?", 
				new String[] { "vnd.android.cursor.item/com.skype.android.skypecall.action" },
				null);

		while (objCursor != null && objCursor.moveToNext()) {
			long contact = objCursor.getLong(0);
			String skype = objCursor.getString(1);

			Log.i("ContactsApp" , " " + "contact " + contact + " has skype username: " + skype);
			strSkypeContactBuffer.append("Contact " + contact + " has skype username: " + skype + "\n");

		}

		//display contact details
		m_objDisplayContact.setText(strSkypeContactBuffer.toString());
	}

	/**
	 * 
	 * 
	 * This function is used to display the existing phone contacts
	 */
	private void getPhoneContactsList() {
		StringBuffer strPhoneContactBuffer = new StringBuffer();
		ContentResolver objContentResolver = getContentResolver();
		Cursor objCursor = objContentResolver.query(ContactsContract.Contacts.CONTENT_URI,
				null, null, null, null);
		if (objCursor.getCount() > 0) {
			while (objCursor.moveToNext()) {
				String id = objCursor.getString(objCursor.getColumnIndex(ContactsContract.Contacts._ID));
				String name = objCursor.getString(objCursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
				if (Integer.parseInt(objCursor.getString(
						objCursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {
					Cursor objContactCursor = objContentResolver.query(
							ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
							null,
							ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = ?",
							new String[]{id}, null);
					while (objContactCursor.moveToNext()) {
						String phoneNo = objContactCursor.getString(objContactCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
						Log.i("ContactsApp", "Name: " + name + ", Phone No: " + phoneNo);
						strPhoneContactBuffer.append("Name: " + name + ", Phone No: " + phoneNo + "\n");
						
					}
					objContactCursor.close();
				}
			}
		}

		//display contact details
		m_objDisplayContact.setText(strPhoneContactBuffer.toString());
	}
}

activity_display_contact.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical" >

    <Button
        android:id="@+id/btnPhoneContact"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:text="@string/lbl_phone_contact" />

    <Button
        android:id="@+id/btnSkypeContact"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/btnPhoneContact"
        android:layout_centerInParent="true"
        android:text="@string/lbl_skype_contact" />

    <TextView
        android:id="@+id/txtDisplayContact"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@+id/btnSkypeContact"
        android:layout_centerInParent="true" />

</RelativeLayout>

strings.xml

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <!-- Display contact demo -->
    <string name="lbl_phone_contact">Show Phone contacts</string>
    <string name="lbl_skype_contact">Show Skype contacts</string>
</resources>

No changed are to be made to the AndroidManifest.xml file. Run the application on an Android device and you should be able to see the following output!

Screenshot_2015-11-09-09-04-47

Now, click on any of the buttons to retrieve the contact list.

That’s it then for this Android tip. Stay tuned for more! 🙂

Display loading indicator using AngularJS

AngularJS allows developers to extend HTML with new attributes called Directives. In simple terms, an AngularJS directive is essentially a function that executes when the Angular compiler finds it in the DOM (Document Object Model). Directives are extended HTML attributes with the prefix ng-. For instance, the ng-model directive binds the value of HTML controls (input, select, textarea) to application data.

On the other hand, Services are substitutable objects that are wired together using dependency injection (DI). Developers can use services to organize and share code across your app. To use an Angular service, you add it as a dependency for the component (controller, service, filter or directive).

Through this post, we will learn how to display a simple loading indicator using AngularJS directives and services. The loading indicator thus implemented can be displayed while executing AJAX requests to remote web services.

Pre-requisites: Eclipse IDE for Java EE developers, Apache Tomcat 7.0

Step 1: Launch the Eclipse IDE and create a new Dynamic Web Project called AngularSpinnerDemo with target server runtime as Apache Tomcat 7.

create_dynamic_web_project

Now, let’s create our own directive that will display the spinner when the DOM is loaded.

Step 2: Create directive for Spinner

Create a new JavaScript file called spinner_directive.js that will include our custom directive. As the DOM (Document Object Model) is compiled by AngularJS, the directive controllers and link functions execute at different parts of the compile life-cycle.

spinner_directive.js

app.directive('spinner', function () {
    return {
        restrict: 'E',
        template: [
          '<span>',
          '  <img ng-show="showSpinner" ng-src="{{url}}" style="padding-right: 7px; width: {{ spinnerSize }}; vertical-align: middle" />',
          '  <span ng-show="loadingText && showSpinner">{{ loadingText }}</span>',
          '  <span ng-show="doneText && !showSpinner">{{ doneText }}</span>',
          '</span>'
        ].join(''),
        replace: true,
        scope: {
            id: '@',
            group: '@?',
            showSpinner: '@?',
            loadingText: '@?',
            doneText: '@?',
            onRegisterComplete: '&?',
            url: '@'
        },
        controller: function ($scope, $attrs, spinnerService) {
            // Register the spinner with the spinner service.
            spinnerService._register($scope);
            // Invoke the onRegisterComplete expression, if any.
            // Expose the spinner service for easy access.
            $scope.onRegisterComplete({ $spinnerService: spinnerService });
        },
        link: function (scope, elem, attrs) {
            // Check for pre-defined size aliases and set pixel width accordingly.
            if (attrs.hasOwnProperty('size')) {
                attrs.size = attrs.size.toLowerCase();
            }
            switch (attrs.size) {
                case 'tiny':
                    scope.spinnerSize = '15px';
                    break;
                case 'small':
                    scope.spinnerSize = '25px';
                    break;
                case 'medium':
                    scope.spinnerSize = '35px';
                    break;
                case 'large':
                    scope.spinnerSize = '75px';
                    break;
                default:
                    scope.spinnerSize = '50px';
                    break;
            }
        }
    };
});

Step 3: Create service for Spinner

Create a new JavaScript file called spinner_service.js that will include the definition of our spinner service as follows!

spinner_service.js

app.factory('spinnerService', function () {
    var cache = {};
    return {

        // A private function intended for spinner directives to register themselves with the service.
        _register: function (spinnerScope) {
            // If no id is passed in, throw an exception.
            if (!spinnerScope.id) {
                throw new Error("A spinner must have an ID to register with the spinner service.");
            }

            // Add our spinner directive's scope to the cache.
            cache[spinnerScope.id] = spinnerScope;
        },

        // A private function exposed just in case the user really needs to manually unregister a spinner.
        _unregister: function (spinnerId) {
            delete cache[spinnerId];
        },

        // A private function that will remove an entire spinner group if needed.
        _unregisterGroup: function (group) {
            for (var spinnerId in cache) {
                if (cache.hasOwnProperty(spinnerId)) {
                    if (cache[spinnerId].group === group) {
                        delete cache[spinnerId];
                    }
                }
            }
        },

        // A private function that will clear out all spinners from the cache.
        _unregisterAll: function () {
            for (var spinnerId in cache) {
                if (cache.hasOwnProperty(pinnerId)) {
                    delete cache[spinnerId];
                }
            }
        },

        // Show the specified spinner.
        // If loadingText is specified, replace the loadingText specified on the directive as we show the spinner.
        show: function (spinnerId, loadingText) {

            $("body").find("#loading").addClass("mydiv");
            if (cache.hasOwnProperty(spinnerId)) {
                var spinnerScope = cache[spinnerId];
                spinnerScope.showSpinner = true;
                if (loadingText !== undefined) {
                    spinnerScope.loadingText = loadingText;
                }

            }
        },

        // Hide the specified spinner.
        // If doneText is specified, replace the doneText specified on the directive as we hide the spinner.
        hide: function (spinnerId, doneText) {

            if (cache.hasOwnProperty(spinnerId)) {
                var spinnerScope = cache[spinnerId];
                $("body").find("#loading").removeClass("mydiv");
                spinnerScope.showSpinner = false;

                if (doneText !== undefined) {
                    spinnerScope.doneText = doneText;
                }

            }
        }

    };
});

Step 4: Create controller

spinnercntrl.js

app.controller("spinnercntrl", function ($scope,spinnerService) {

	//initially hide the spinner
	spinnerService.hide('mySpinner');

	/*method called on click of show spinner button*/
	$scope.showSpinner = function(){
		
		spinnerService.show('mySpinner');
		
		//hide the spinner after 3 seconds
		setTimeout(function(){ 
			spinnerService.hide('mySpinner');
		}, 3000);

	};

});

Finally, run the application on the Tomcat server. If no errors occur, then you should see the loading indicator on click of the Show spinner button.

output_1

output_2

Source code for this tutorial can be found over here.

Reference: AngularJS Developer Guide