Android

Resizing webview to match the content size


There has been lots of discussion around methods of resizing the webview to fit the size of its content.

The actual issue can be seen when we are using webview inside a scrollview. In case we wish to show something on top of webview and allow the whole page to scroll, if web view content goes beyond screen size.

If at first we load huge data in webview, it expands itself to fit the whole content, but when we load a smaller data later, it still shows the same previous size, which is way more than size required to display smaller data. Side effect of this is that user can keep scrolling beyond the html content.

Following are few threads where this issue has been discussed.

http://stackoverflow.com/questions/3509158/scroll-view-doest-resize-when-the-content-of-child-view-changes

http://stackoverflow.com/questions/15546416/how-to-shrink-webview-size-dynamically-according-to-its-content

As you can see, most of the people suggest not to use webview inside scroll, and others suggest creation of new instances of webview every time we load a url into it. But both of these are not real solution to the problem.

Here is a small trick which worked for me.

private void setupWebView() {
    webView.getSettings().setJavaScriptEnabled(true);
    webView.setWebViewClient(new WebViewClient() {
        @Override
        public void onPageFinished(WebView view, String url) {
            webView.loadUrl("javascript:MyApp.resize(document.body.getBoundingClientRect().height)");
            super.onPageFinished(view, url);
        }
    });
    webView.addJavascriptInterface(this, "MyApp");
}

@JavascriptInterface
public void resize(final float height) {
    MyActivity.this.runOnUiThread(new Runnable() {
        @Override
        public void run() {
            webView.setLayoutParams(new LinearLayout.LayoutParams(getResources().getDisplayMetrics().widthPixels, (int) (height * getResources().getDisplayMetrics().density)));
        }
    });
}

Different screen orientation on different screen size.


There are many tutorials around on web which tells how to support different screen sizes in android. But there are many tiny little real problems which comes in every day life of android developer while supporting different screen size.

Well I understand we can create different layouts & values for different screen sizes which will ensure my screen is properly scaled and fits properly in different screen size. It also ensures the flexibility of amount of information being displayed on different screen size.

Now if we want the application to appear in portrait on small & normal screen sizes, but if the screen is large or xlarge we want the application to appear in landscape, then how do we go about it?

At first I thought that probably defining different screen orientation in different value folder will get this done. But unfortunately it does not work on all android devices.

So the only method left for us to achieve different screen orientation on different screen size is programmatic.

What we need to do is – at the time of activity creation, get the screen size and accordingly set the orientation.

                int layout = getResources().getConfiguration().screenLayout
				& Configuration.SCREENLAYOUT_SIZE_MASK;
		if (layout == Configuration.SCREENLAYOUT_SIZE_XLARGE
				|| layout == Configuration.SCREENLAYOUT_SIZE_LARGE) {
			setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
		} else {
			setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
		}

Android 3.0 Honeycomb Drag and Drop tutorial


Since Android 3.0 Honeycomb we have drag and drop framework which allows us to move data from one view to another in current layout by simple graphical drag and drop gesture.

The Drag and Drop Framework has ClipData, DragEvent & DragShadowBuilder classes introduced along with couple of helper methods which allows to achieve drag and drop feature.

The drag & drop operation begins when user makes some kind of gesture on screen which indicates the start of drag. For example, user can perform long press on item before he starts dragging it. (Just like it happens on home screen)

When the drag event starts, systems call backs the application to get the representation of data being dragged.
Developer can create this representation be extending DragShadowBuilder class. Developer can also ignore the implementation of DragShadowBuilder and rely on system’s default representation of data (which will be the view itself).

While the view is getting dragged on screen, system generates DragEvents which can be intercepted by application by setting View.setOnDragListener().

OnDragListener interface has callback method public boolean onDrag(View view, DragEvent dragEvent)
which will get called whenever any view is dragged or dropped.

The DragEvent parameter provides getAction() which tells the application which type of action has occurred.

Following types of action can be identified.

DragEvent.ACTION_DRAG_STARTED – Drag event has started
DragEvent.ACTION_DRAG_ENTERED – Drag has brought the drop shadow in view bounds.
DragEvent.ACTION_DRAG_EXITED – Drag has taken the drop shadow out of view bounds.
DragEvent.ACTION_DRAG_LOCATION – Drag is happening and drop shadow is inside view bounds.
DragEvent.ACTION_DROP – Drop has happened inside view bounds.
DragEvent.ACTION_DRAG_ENDED – Drop has happened outside view bounds.

Now lets have a look at a sample code and see how it looks.

To demonstrate the drag and drop, I am going to create a simple grid view and a trash can(Image view). User can drag an item from grid view to trash can to delete that item.

Lets have a look at layout file

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/parent_layout"
    android:layout_width="match_parent"
    android:layout_height="match_parent" >

    <GridView
        android:id="@+id/grid_view"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:horizontalSpacing="10dip"
        android:numColumns="4"
        android:verticalSpacing="10dip" />

    <ImageView
        android:id="@+id/trash_can"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentBottom="true"
        android:layout_centerHorizontal="true"
        android:contentDescription="Delete"
        android:padding="40dip"
        android:src="@drawable/trash_can"
        android:visibility="gone" >
    </ImageView>

</RelativeLayout>

 
Lets fill the child views in grid.

ArrayList drawables;

private BaseAdapter adapter;
private int draggedIndex = -1;

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    drawables = new ArrayList();
    drawables.add(R.drawable.sample_0);
    drawables.add(R.drawable.sample_1);
    drawables.add(R.drawable.sample_2);
    drawables.add(R.drawable.sample_3);
    drawables.add(R.drawable.sample_4);
    drawables.add(R.drawable.sample_5); 
    drawables.add(R.drawable.sample_6);
    drawables.add(R.drawable.sample_7);
    GridView gridView = (GridView) findViewById(R.id.grid_view);
        gridView.setOnItemLongClickListener(MainActivity.this);
    gridView.setAdapter(adapter = new BaseAdapter() {

        @Override
        // Get a View that displays the data at the specified position in
        // the data set.
        public View getView(int position, View convertView,
                ViewGroup gridView) {
            // try to reuse the views.
            ImageView view = (ImageView) convertView;
            // if convert view is null then create a new instance else reuse
            // it
            if (view == null) {
                view = new ImageView(MainActivity.this);
            }
            view.setImageResource(drawables.get(position));
            view.setTag(String.valueOf(position));
            return view;
        }
    
        @Override
        // Get the row id associated with the specified position in the
        // list.
        public long getItemId(int position) {
            return position;
        }

        @Override
        // Get the data item associated with the specified position in the
        // data set.
        public Object getItem(int position) {
            return drawables.get(position);
        }

        @Override
        // How many items are in the data set represented by this Adapter.
        public int getCount() {
            return drawables.size();
        }
    });
}

 

You can see that I’ve set the activity as onItemLongCLickListener. Lets look at on onItemLongClick method

We want to start the drag operation when an item is long clicked, we also want to make the trash can visible.
To achieve this we will have to create ClipData.Item and then we will create ClipData object which be used to start drag.

Since we want both, dragged view and trash can to get the drag events, we will set drag listeners for both of them.

@Override
    public boolean onItemLongClick(AdapterView gridView, View view,
            int position, long row) {
        ClipData.Item item = new ClipData.Item((String) view.getTag());
        ClipData clipData = new ClipData((CharSequence) view.getTag(),
                new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }, item);
        view.startDrag(clipData, new View.DragShadowBuilder(view), null, 0);
        View trashCan = findViewById(R.id.trash_can);
        trashCan.setVisibility(View.VISIBLE);
        trashCan.setOnDragListener(MainActivity.this);

        trashCan.setOnDragListener(MainActivity.this);
        draggedIndex = position;
        return true;
    }

 

Now looks like my activity is playing lot many roles. It is also a drag listener. Lets look at how my onDrag method looks like.

@Override
    public boolean onDrag(View view, DragEvent dragEvent) {
        switch (dragEvent.getAction()) {
        case DragEvent.ACTION_DRAG_STARTED:
            // Drag has started
            // If called for trash resize the view and return true
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.0f);
                view.animate().scaleY(1.0f);
                return true;
            } else // else check the mime type and set the view visibility
            if (dragEvent.getClipDescription().hasMimeType(
                    ClipDescription.MIMETYPE_TEXT_PLAIN)) {
                view.setVisibility(View.GONE);
                return true;

            } else {
                return false;
            }
        case DragEvent.ACTION_DRAG_ENTERED:
            // Drag has entered view bounds
            // If called for trash can then scale it.
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.5f);
                view.animate().scaleY(1.5f);
            }
            return true;
        case DragEvent.ACTION_DRAG_EXITED:
            // Drag exited view bounds
            // If called for trash can then reset it.
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.0f);
                view.animate().scaleY(1.0f);
            }
            view.invalidate();
            return true;
        case DragEvent.ACTION_DRAG_LOCATION:
            // Ignore this event
            return true;
        case DragEvent.ACTION_DROP:
            // Dropped inside view bounds
            // If called for trash can then delete the item and reload the grid
            // view
            if (view.getId() == R.id.trash_can) {
                drawables.remove(draggedIndex);
                draggedIndex = -1;
            }
            adapter.notifyDataSetChanged();
        case DragEvent.ACTION_DRAG_ENDED:
            // Hide the trash can
            new Handler().postDelayed(new Runnable() {

                @Override
                public void run() {
                    findViewById(R.id.trash_can).setVisibility(View.GONE);
                }
            }, 1000l);
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.0f);
                view.animate().scaleY(1.0f);
            } else {
                view.setVisibility(View.VISIBLE);
            }
            // remove drag listeners
            view.setOnDragListener(null);
            return true;

        }
        return false;
    }

 
I think code is self explanatory, this method is called twice for every event and code takes care of identifying for which view it is called for and takes the action accordingly.

The only thing to notice here is that if we return true for DragEvent.ACTION_DRAG_STARTED then only we will get further events of DragEvent.ACTION_DRAG_ENTERED & DragEvent.ACTION_DRAG_EXITED. Otherwise these events will be ignored by system.

Lets put all this together and see how it looks.

/**
 * @author Chitranshu Asthana
 */

package com.example.dragviewexample;

import java.util.ArrayList;

import android.app.Activity;
import android.content.ClipData;
import android.content.ClipDescription;
import android.os.Bundle;
import android.os.Handler;
import android.view.DragEvent;
import android.view.Menu;
import android.view.View;
import android.view.View.OnDragListener;
import android.view.View.OnLongClickListener;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.BaseAdapter;
import android.widget.GridView;
import android.widget.ImageView;

public class MainActivity extends Activity implements OnDragListener,
        OnItemLongClickListener {

    ArrayList drawables;

    private BaseAdapter adapter;
    private int draggedIndex = -1;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        drawables = new ArrayList();
        drawables.add(R.drawable.sample_0);
        drawables.add(R.drawable.sample_1);
        drawables.add(R.drawable.sample_2);
        drawables.add(R.drawable.sample_3);
        drawables.add(R.drawable.sample_4);
        drawables.add(R.drawable.sample_5);
        drawables.add(R.drawable.sample_6);
        drawables.add(R.drawable.sample_7);
        GridView gridView = (GridView) findViewById(R.id.grid_view);
        gridView.setOnItemLongClickListener(MainActivity.this);
        gridView.setAdapter(adapter = new BaseAdapter() {

            @Override
            // Get a View that displays the data at the specified position in
            // the data set.
            public View getView(int position, View convertView,
                    ViewGroup gridView) {
                // try to reuse the views.
                ImageView view = (ImageView) convertView;
                // if convert view is null then create a new instance else reuse
                // it
                if (view == null) {
                    view = new ImageView(MainActivity.this);
                }
                view.setImageResource(drawables.get(position));
                view.setTag(String.valueOf(position));
                return view;
            }

            @Override
            // Get the row id associated with the specified position in the
            // list.
            public long getItemId(int position) {
                return position;
            }

            @Override
            // Get the data item associated with the specified position in the
            // data set.
            public Object getItem(int position) {
                return drawables.get(position);
            }

            @Override
            // How many items are in the data set represented by this Adapter.
            public int getCount() {
                return drawables.size();
            }
        });
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.activity_main, menu);
        return true;
    }

    @Override
    public boolean onDrag(View view, DragEvent dragEvent) {
        switch (dragEvent.getAction()) {
        case DragEvent.ACTION_DRAG_STARTED:
            // Drag has started
            // If called for trash resize the view and return true
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.0f);
                view.animate().scaleY(1.0f);
                return true;
            } else // else check the mime type and set the view visibility
            if (dragEvent.getClipDescription().hasMimeType(
                    ClipDescription.MIMETYPE_TEXT_PLAIN)) {
                view.setVisibility(View.GONE);
                return true;

            } else {
                return false;
            }
        case DragEvent.ACTION_DRAG_ENTERED:
            // Drag has entered view bounds
            // If called for trash can then scale it.
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.5f);
                view.animate().scaleY(1.5f);
            }
            return true;
        case DragEvent.ACTION_DRAG_EXITED:
            // Drag exited view bounds
            // If called for trash can then reset it.
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.0f);
                view.animate().scaleY(1.0f);
            }
            view.invalidate();
            return true;
        case DragEvent.ACTION_DRAG_LOCATION:
            // Ignore this event
            return true;
        case DragEvent.ACTION_DROP:
            // Dropped inside view bounds
            // If called for trash can then delete the item and reload the grid
            // view
            if (view.getId() == R.id.trash_can) {
                drawables.remove(draggedIndex);
                draggedIndex = -1;
            }
            adapter.notifyDataSetChanged();
        case DragEvent.ACTION_DRAG_ENDED:
            // Hide the trash can
            new Handler().postDelayed(new Runnable() {

                @Override
                public void run() {
                    findViewById(R.id.trash_can).setVisibility(View.GONE);
                }
            }, 1000l);
            if (view.getId() == R.id.trash_can) {
                view.animate().scaleX(1.0f);
                view.animate().scaleY(1.0f);
            } else {
                view.setVisibility(View.VISIBLE);
            }
            // remove drag listeners
            view.setOnDragListener(null);
            return true;

        }
        return false;
    }

    @Override
    public boolean onItemLongClick(AdapterView gridView, View view,
            int position, long row) {
        ClipData.Item item = new ClipData.Item((String) view.getTag());
        ClipData clipData = new ClipData((CharSequence) view.getTag(),
                new String[] { ClipDescription.MIMETYPE_TEXT_PLAIN }, item);
        view.startDrag(clipData, new View.DragShadowBuilder(view), null, 0);
        View trashCan = findViewById(R.id.trash_can);
        trashCan.setVisibility(View.VISIBLE);
        trashCan.setOnDragListener(MainActivity.this);

        trashCan.setOnDragListener(MainActivity.this);
        draggedIndex = position;
        return true;
    }
}

 
And this is how it looks.
 

Android JDBC driver for Microsoft SQL Server and Sybase


Recently i shared a post about Configuring and accessing MySQL jdbc driver on android application.

Good thing about MySQL is that it is open source and hence the driver is open source too. Even if we couldn’t find any android compatible driver, it wouldn’t be too difficult to get the source code and port it for android.

But when it comes to MSSQL and Sybase, its a different story. After successfully configuring Mysql JDBC driver I was looking for compatible drivers for MSSQL and unfortunately none of the versions available were compatible.

It was disappointing in start but then I started looking for some hacks around and came across a really wonderful library – jTDS JDBC Driver.

Now good things about this driver is that it is free software available under terms of GNU Lesser General Public License. Which means it is allowed to be used in open source as well as comercial projects.

Another good thing about this JDBC driver is that it supports both MSSQL and Sybase and it is compatible with Android.

jTDS is an open source 100% pure Java (type 4) JDBC 3.0 driver for Microsoft SQL Server (6.5, 7, 2000, 2005 and 2008) and Sybase (10, 11, 12, 15). jTDS is based on FreeTDS and is currently the fastest production-ready JDBC driver for SQL Server and Sybase. jTDS is 100% JDBC 3.0 compatible, supporting forward-only and scrollable/updateable ResultSets, concurrent (completely independent) Statements and implementing all the DatabaseMetaData and ResultSetMetaData methods. Check out the feature matrix for more details.

Quite a few of the commercial JDBC drivers out there are based on jTDS (or FreeTDS), even if they no longer acknowledge this. jTDS has been tested with virtually all JDBC-based database management tools and is the driver of choice for most of these (recommended for DbVisualizer and SQuirreL SQL, distributed with Aqua Data Studio and DataDino). jTDS is also becoming a common choice for enterprise-level applications: it passes both the J2EE 1.3 certification and Hibernate test suites, and is recommended for JBoss, Hibernate, Atlassian JIRA and Confluence and Compiere.

So go ahead and give it a shot.. It worked for me, I am sure you will enjoy working with it too..

PS: Have a look at FAQs before jumping into writing code.

Android 4.1 Jelly Bean Notification Tutorial Part III


In Android 4.1 Jelly Bean Notification Tutorial Part 1 & Part 2 we saw how to create simple notification and how to create styled notifications.

In this post, we will see how to add an action button on notifications and how they exactly look on screen when added.

Add action requires 3 parameters, an icon to be displayed, a text to be displayed beside the icon and an pending intent to be fired when button is clicked.

Intent intent = new Intent(this, MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(getApplicationContext(), 01, intent, Intent.FLAG_ACTIVITY_CLEAR_TASK);

Bitmap smallBitmap = null;
BufferedInputStream buf = null;

try {
	buf = new BufferedInputStream(getAssets().open("me.jpeg"));

	// Create the bitmap to be set in notification.
	smallBitmap = BitmapFactory.decodeStream(buf);
	buf.close();
} catch (Exception ex) {
	ex.printStackTrace();
}

{
	Builder builder = new Notification.Builder(this);
	builder.setSmallIcon(R.drawable.ic_launcher);
	builder.setTicker("3 new messages from Chitranshu");
	builder.setContentTitle("3 new messages from Chitranshu");
	builder.setContentText("+ 5 more");
	builder.setSubText("Click here to go to inbox.");
	builder.setLargeIcon(smallBitmap);
	builder.setAutoCancel(true);
	builder.addAction(android.R.drawable.sym_action_email,"Read Emails", pendingIntent);
	((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(00, builder.build());
}

 

In the screen shot you can see that display of notification action button requires extra display area. The whole notification looks bigger than usual.

But just like styled notifications, the action notification also looses its extended display area when it get pushed down from first position.

Therefore we will also have to setDeleteIntent() which will make sure that our application gets the event whenever the notification is clicked.

Android 4.1 Jelly Bean Notification Tutorial Part II


In Android 4.1 Jelly Bean Notification Tutorial we saw how simple notifications can be build on Android 4.1.

Now let us have a look at 3 new notification styles introduced in Jelly Bean.

There are 3 new classes Notification.InboxStyle, Notification.BigPictureStyle & Notification.BigTextStyle which which helps us in creating notifications with larger content area & picture review.

Let us have a look at how to use these notification styles and how exactly they look on device.

But before we start playing around with these styles, let us create coule of bitmaps for demonstration.

Bitmap smallBitmap = null;
Bitmap largeBitmap = null;
BufferedInputStream buf = null;
try {
	buf = new BufferedInputStream(getAssets().open("android1.jpg"));

	// Create the bitmap to be set in notification.
	largeBitmap = BitmapFactory.decodeStream(buf);
	buf.close();
} catch (Exception ex) {
	ex.printStackTrace();
}

try {
	buf = new BufferedInputStream(getAssets().open("me.jpeg"));

	// Create the bitmap to be set in notification.
	smallBitmap = BitmapFactory.decodeStream(buf);
	buf.close();
} catch (Exception ex) {
	ex.printStackTrace();
}

 
Now since we have the bitmaps ready, lets us have a look at Notification.BigPictureStyle first.

Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setTicker("Android Image downloaded.");
builder.setContentTitle("Android Image downloaded.");
builder.setContentText("View in full screen mode");
builder.setLargeIcon(smallBitmap);
builder.setAutoCancel(true);

Notification.BigPictureStyle bigPicutureStyle = new Notification.BigPictureStyle(builder);
bigPicutureStyle.bigLargeIcon(smallBitmap);
bigPicutureStyle.bigPicture(largeBitmap);
bigPicutureStyle.setBigContentTitle("Android Image downloaded.");
bigPicutureStyle.setSummaryText("Click on the image for full screen preview");

((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(01, bigPicutureStyle.build());

 

And this is how it looks.

You can see that notification has taken bigger content area to display image which looks just awesome.
But there is a catch, the big content area is only assigned to the notification till it is the latest notification in the list. The moment you receive any other notification, it will shrink down and rely on the values set in Notification.Builder for display.

In next code snippet we will see how to use Notification.BigTextStyle as well as we will also see what happens to Notification.BigPictureStyle when the notification is pushed down from top position.

// POST A BIG PICTURE NOTIFICATION
{
	Builder builder = new Notification.Builder(this);
	builder.setSmallIcon(R.drawable.ic_launcher);
	builder.setTicker("Android Image downloaded.");
	builder.setContentTitle("Android Image downloaded.");
        builder.setContentText("View in full screen mode");
	builder.setLargeIcon(smallBitmap);
	builder.setAutoCancel(true);

	Notification.BigPictureStyle bigPicutureStyle = new Notification.BigPictureStyle(builder);
	bigPicutureStyle.bigLargeIcon(smallBitmap);
	bigPicutureStyle.bigPicture(largeBitmap);
	bigPicutureStyle.setBigContentTitle("Android Image downloaded.");
	bigPicutureStyle.setSummaryText("Click on the image for full screen preview");
        ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(01,bigPicutureStyle.build());
}
// AND THEN POST A BIG TEXT NOTIFICATION
{
	Builder builder = new Notification.Builder(this);
	builder.setSmallIcon(R.drawable.ic_launcher);
	builder.setTicker("Big Text Style Notification");
        builder.setContentTitle("BigTextStyle : This is very big content title for demonstration");
        builder.setContentText("BigTextStyle : This is very long summary text for demonstration");
	builder.setLargeIcon(smallBitmap);
	builder.setAutoCancel(true);

	Notification.BigTextStyle bigTextStyle = new Notification.BigTextStyle(builder);
	bigTextStyle.setBigContentTitle("BigTextStyle : This is very big content title for demonstration");
	bigTextStyle.setSummaryText("BigTextStyle : This is very long summary text for demonstration");
	((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(02, bigTextStyle.build());
}

 

Fair enough.. now lets have a look at Notification.InboxStyle

Builder builder = new Notification.Builder(this);
builder.setSmallIcon(R.drawable.ic_launcher);
builder.setTicker("3 new messages from Chitranshu");
builder.setContentTitle("3 new messages from Chitranshu");
builder.setContentText("+ 5 more");
builder.setSubText("Inbox style notification demo");
builder.setLargeIcon(smallBitmap);
builder.setAutoCancel(true);

Notification.InboxStyle inboxStyle = new Notification.InboxStyle(builder);
inboxStyle.setBigContentTitle("3 new messages from Chitranshu");
inboxStyle.setSummaryText("+ 5 more");
((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).notify(03, inboxStyle.build());

 

And this is how it looks when we put all together.

Android 4.1 Jelly Bean Notification Tutorial


Notifications have always been very interesting feature of android. Developers can use them to present important event information in front of user in notification bar, outside of the application’s UI.

Jelly Beans brings mejor enhancements in notification, no application larger, richer notifications that can be expanded and collapsed with a pinch or swipe. Notifications support new types of content, including photos, have configurable priority, and can even include multiple actions.

Let us have a look at few of the samples…

Just like older versions we still got to get the NotificationManager to show any notification.

Next step would be to initiate the Notification. Let us initiate a very simple notification and see how it looks.

For the simplest notification, we will need to set at least a small icon.

Displaying a notification requires us to set an ID for the notification.

package com.example.notificationtest;

import java.io.BufferedInputStream;
import java.io.IOException;

import android.app.Activity;
import android.app.Notification;
import android.app.Notification.Builder;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.Menu;

public class MainActivity extends Activity {

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

		BufferedInputStream buf;
		try {
			buf = new BufferedInputStream(getAssets().open("me.jpeg"));

			// Create the bitmap to be set in notification.
			Bitmap bitmap = BitmapFactory.decodeStream(buf);
			buf.close();

			// Pending intent to be fired when notification is clicked
			Intent intent = new Intent(this, MainActivity.class);
			PendingIntent pendingIntent = PendingIntent.getActivity(this, 01,
					intent, Intent.FLAG_ACTIVITY_CLEAR_TASK);

			// Get the builder to create notification.
			Builder builder = new Notification.Builder(getApplicationContext());
			// Set the first line of text in the platform notification template.
			builder.setContentTitle("Content Title");
			// Set the second line of text in the platform notification
			// template.
			builder.setContentText("Content Text");
			// Set the third line of text in the platform notification template.
			// Don't use if you're also using setProgress(int, int, boolean);
			// they occupy the same location in the standard template.
			builder.setSubText("Sub Text");
			// Set the large number at the right-hand side of the notification.
			// This is equivalent to setContentInfo, although it might show the
			// number in a different font size for readability.
			builder.setNumber(100);
			// Set the "ticker" text which is displayed in the status bar when
			// the notification first arrives.
			builder.setTicker("Notification by Chitranshu Asthana");
			// Set the small icon resource, which will be used to represent the
			// notification in the status bar. The platform template for the
			// expanded view will draw this icon in the left, unless a large
			// icon has also been specified, in which case the small icon will
			// be moved to the right-hand side.
			builder.setSmallIcon(R.drawable.ic_launcher);
			// Add a large icon to the notification (and the ticker on some
			// devices). In the platform template, this image will be shown on
			// the left of the notification view in place of the small icon
			// (which will move to the right side).
			builder.setLargeIcon(bitmap);
			// Supply a PendingIntent to send when the notification is cleared
			// explicitly by the user.
			builder.setDeleteIntent(pendingIntent);
			// Make this notification automatically dismissed when the user
			// touches it. The PendingIntent set with
			// setDeleteIntent(PendingIntent) will be sent when this happens.
			builder.setAutoCancel(true);
			// Set the priority of this notification.
			builder.setPriority(0);

			// Combine all of the options that have been set and return a new
			// Notification object.
			Notification notification = builder.build();

			NotificationManager notificationManger = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
			// Post a notification to be shown in the status bar. If a
			// notification with the same id has already been posted by your
			// application and has not yet been canceled, it will be replaced by
			// the updated information.
			notificationManger.notify(01, notification);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.activity_main, menu);
		return true;
	}

}

 

Well that looks good. Now let us have a look at what else Jelly Beans has.

In addition to the templated styles, we can create our own notification styles using any remote View.

Let us extend the previous code and create 2 different notifications and lets see how it looks.

Let us create a layout for out notification.

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

    <ImageView
        android:id="@+id/notification_main_img"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:src="@drawable/me" />

    <TextView
        android:id="@+id/notification_main_txt"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginLeft="10dip"
        android:layout_toRightOf="@id/notification_main_img"
        android:text="Notification from remote view" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_alignBottom="@id/notification_main_img"
        android:layout_below="@id/notification_main_txt"
        android:layout_toRightOf="@id/notification_main_img"
        android:orientation="horizontal"
        android:weightSum="2" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/notification_main_img"
            android:layout_weight="1"
            android:gravity="center"
            android:text="Sub Text 1" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toRightOf="@id/notification_main_img"
            android:layout_weight="1"
            android:gravity="center"
            android:text="Sub Text 2" />
    </LinearLayout>

</RelativeLayout>

 

Now let us look at the code with two different notifications.

package com.example.notificationtest;

import java.io.BufferedInputStream;
import java.io.IOException;

import android.app.Activity;
import android.app.Notification;
import android.app.Notification.Builder;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.view.Menu;
import android.widget.RemoteViews;

public class MainActivity extends Activity {

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		{
			BufferedInputStream buf;
			try {
				buf = new BufferedInputStream(getAssets().open("me.jpeg"));

				// Create the bitmap to be set in notification.
				Bitmap bitmap = BitmapFactory.decodeStream(buf);
				buf.close();

				// Pending intent to be fired when notification is clicked
				Intent intent = new Intent(this, MainActivity.class);
				PendingIntent pendingIntent = PendingIntent.getActivity(this,
						01, intent, Intent.FLAG_ACTIVITY_CLEAR_TASK);

				// Get the builder to create notification.
				Builder builder = new Notification.Builder(
						getApplicationContext());
				// Set the first line of text in the platform notification
				// template.
				builder.setContentTitle("Content Title");
				// Set the second line of text in the platform notification
				// template.
				builder.setContentText("Content Text");
				// Set the third line of text in the platform notification
				// template.
				// Don't use if you're also using setProgress(int, int,
				// boolean);
				// they occupy the same location in the standard template.
				builder.setSubText("Sub Text");
				// Set the large number at the right-hand side of the
				// notification.
				// This is equivalent to setContentInfo, although it might show
				// the
				// number in a different font size for readability.
				builder.setNumber(100);
				// Set the "ticker" text which is displayed in the status bar
				// when
				// the notification first arrives.
				builder.setTicker("Notification by Chitranshu Asthana");
				// Set the small icon resource, which will be used to represent
				// the
				// notification in the status bar. The platform template for the
				// expanded view will draw this icon in the left, unless a large
				// icon has also been specified, in which case the small icon
				// will
				// be moved to the right-hand side.
				builder.setSmallIcon(R.drawable.ic_launcher);
				// Add a large icon to the notification (and the ticker on some
				// devices). In the platform template, this image will be shown
				// on
				// the left of the notification view in place of the small icon
				// (which will move to the right side).
				builder.setLargeIcon(bitmap);
				// Supply a PendingIntent to send when the notification is
				// cleared
				// explicitly by the user.
				builder.setDeleteIntent(pendingIntent);
				// Make this notification automatically dismissed when the user
				// touches it. The PendingIntent set with
				// setDeleteIntent(PendingIntent) will be sent when this
				// happens.
				builder.setAutoCancel(true);
				// Set the priority of this notification.
				builder.setPriority(1);

				// Combine all of the options that have been set and return a
				// new
				// Notification object.
				Notification notification = builder.build();

				NotificationManager notificationManger = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
				// Post a notification to be shown in the status bar. If a
				// notification with the same id has already been posted by your
				// application and has not yet been canceled, it will be
				// replaced by
				// the updated information.
				notificationManger.notify(01, notification);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		{

			// Pending intent to be fired when notification is clicked
			Intent intent = new Intent(this, MainActivity.class);
			PendingIntent pendingIntent = PendingIntent.getActivity(this, 01,
					intent, Intent.FLAG_ACTIVITY_CLEAR_TASK);
			RemoteViews remoteViews = new RemoteViews(getPackageName(),
					R.layout.notification_layout);
			Builder builder = new Notification.Builder(getApplicationContext());
			// Set the small icon resource, which will be used to represent the
			// notification in the status bar. The platform template for the
			// expanded view will draw this icon in the left, unless a large
			// icon has also been specified, in which case the small icon will
			// be moved to the right-hand side.
			builder.setSmallIcon(R.drawable.ic_launcher);
			// Supply a custom RemoteViews to use instead of the platform
			// template.
			builder.setContent(remoteViews);
			// Set the "ticker" text which is displayed in the status bar
			// when
			// the notification first arrives.
			builder.setTicker("Notification by Chitranshu Asthana (Remote View)");
			// Supply a PendingIntent to send when the notification is
			// cleared
			// explicitly by the user.
			builder.setDeleteIntent(pendingIntent);
			// Make this notification automatically dismissed when the user
			// touches it. The PendingIntent set with
			// setDeleteIntent(PendingIntent) will be sent when this
			// happens.
			builder.setAutoCancel(true);
			// Set the priority of this notification.
			builder.setPriority(0);

			// Combine all of the options that have been set and return a
			// new
			// Notification object.
			Notification notification = builder.build();

			NotificationManager notificationManger = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
			// Post a notification to be shown in the status bar. If a
			// notification with the same id has already been posted by your
			// application and has not yet been canceled, it will be
			// replaced by
			// the updated information.
			notificationManger.notify(02, notification);
		}
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.activity_main, menu);
		return true;
	}

}

 

Thats really interesting.. 🙂

Android 4.1 Jelly Bean View Animation Tutorial


Android 4.1 Jelly Beans released last week and there are lots of exciting new features.. I will try and give an over view of all the features.

To start with let us have a look at new Animations APIs introduced in Android Honeycomb and enhanced in Jelly Beans.

ViewPropertyAnimator introduced in API lvl 12 provides lots of methods which makes simple view animation lot easier than it used to be before.

ViewPropertyAnimator also supports Animator.AnimatorListener (Since API lvl 11) which allows us to handle animation events.

Here is a quick code snippets to demonstrate rotation animation.

		view.animate().rotationY((float) 180.0).setDuration(1000)
				.setListener(new AnimatorListener() {

					public void onAnimationStart(Animator animation) {
						// TODO Auto-generated method stub

					}

					public void onAnimationRepeat(Animator animation) {
						// TODO Auto-generated method stub

					}

					public void onAnimationEnd(Animator animation) {
						view.animate().rotationY((float) 360.0)
								.setDuration(1000);

					}

					public void onAnimationCancel(Animator animation) {
						// TODO Auto-generated method stub

					}
				});

 
Similarly there are other methods to perform Scaling, Fading & translation.
 
Scale Animation
 

view.animate().scaleX((float)2.0).setDuration(1000)
				.setListener(new AnimatorListener() {

					public void onAnimationStart(Animator animation) {
						// TODO Auto-generated method stub

					}

					public void onAnimationRepeat(Animator animation) {
						// TODO Auto-generated method stub

					}

					public void onAnimationEnd(Animator animation) {
						view.animate().scaleX((float) 1.0)
								.setDuration(1000);

					}

					public void onAnimationCancel(Animator animation) {
						// TODO Auto-generated method stub

					}
				});

 
Translate Animation
 

view.animate().translationX((float)100.0).setDuration(1000)
				.setListener(new AnimatorListener() {

					public void onAnimationStart(Animator animation) {
						// TODO Auto-generated method stub

					}

					public void onAnimationRepeat(Animator animation) {
						// TODO Auto-generated method stub

					}

					public void onAnimationEnd(Animator animation) {
						view.animate().translationX((float) 0.0)
								.setDuration(1000);

					}

					public void onAnimationCancel(Animator animation) {
						// TODO Auto-generated method stub

					}
				});

 
Fade-out/Fade-in animation

view.animate().alpha((float)0.0).setDuration(1000)
				.setListener(new AnimatorListener() {

					public void onAnimationStart(Animator animation) {
						// TODO Auto-generated method stub

					}

					public void onAnimationRepeat(Animator animation) {
						// TODO Auto-generated method stub

					}

					public void onAnimationEnd(Animator animation) {
						view.animate().alpha((float) 1.0)
								.setDuration(1000);

					}

					public void onAnimationCancel(Animator animation) {
						// TODO Auto-generated method stub

					}
				});

Android – Simple JSON parsing using jackson APIs


Here is a sample application which demonstrates usage of open source Jackson JSON Processor.

This sample application performs a very simple HTTP GET Request to get JSON object from http://www.kaverisoft.com/careers/assignments/android/a1.php and then uses JSONParser to parse the object.

package com.example.jsonsample;

import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;

import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParseException;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;

import android.app.Activity;
import android.os.Bundle;
import android.view.Menu;

public class MainActivity extends Activity implements Runnable {

	@Override
	public void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		new Thread(this).run();
	}

	@Override
	public boolean onCreateOptionsMenu(Menu menu) {
		getMenuInflater().inflate(R.menu.activity_main, menu);
		return true;
	}

	@Override
	public void run() {
		DefaultHttpClient httpClient = new DefaultHttpClient();
		try {
			HttpResponse response = httpClient
					.execute(new HttpGet(
							"http://www.kaverisoft.com/careers/assignments/android/a1.php"));
			InputStream is = response.getEntity().getContent();
			JsonFactory factory = new JsonFactory();
			JsonParser jsonParser = factory.createJsonParser(is);
			JsonToken token = jsonParser.nextToken();
			ArrayList objectArray = new ArrayList();
			// Expected JSON is an array so if current token is "[" then while
			// we don't get
			// "]" we will keep parsing
			if (token == JsonToken.START_ARRAY) {
				while (token != JsonToken.END_ARRAY) {
					// Inside array there are many objects, so it has to start
					// with "{" and end with "}"
					token = jsonParser.nextToken();
					if (token == JsonToken.START_OBJECT) {
						while (token != JsonToken.END_OBJECT) {
							// Each object has a name which we will use to
							// identify the type.
							token = jsonParser.nextToken();
							if (token == JsonToken.FIELD_NAME) {
								String objectName = jsonParser.getCurrentName();
								// jsonParser.nextToken();
								if (0 == objectName.compareToIgnoreCase("BOOK")) {
									Book book = new Book();
									book.parse(jsonParser);
									objectArray.add(book);
								} else if (0 == objectName
										.compareToIgnoreCase("CAMERA")) {
									Camera camera = new Camera();
									camera.parse(jsonParser);
									objectArray.add(camera);
								} else if (0 == objectName
										.compareToIgnoreCase("MUSIC")) {
									Music music = new Music();
									music.parse(jsonParser);
									objectArray.add(music);
								}
							}
						}
					}
				}
			}
			System.out.println(objectArray);
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

	public class Book {
		private String description;
		private String author;
		private String price;

		public void parse(JsonParser parser) throws JsonParseException,
				IOException {
			JsonToken token = parser.nextToken();
			if (token == JsonToken.START_OBJECT) {
				while (token != JsonToken.END_OBJECT) {
					token = parser.nextToken();
					if (token == JsonToken.FIELD_NAME) {
						String fieldName = parser.getCurrentName();
						// parser.nextToken();
						System.out.println(fieldName);
						if (0 == fieldName.compareToIgnoreCase("description")) {
							this.description = parser.getText();
						} else if (0 == fieldName.compareToIgnoreCase("author")) {
							this.author = parser.getText();
						} else if (0 == fieldName.compareToIgnoreCase("price")) {
							this.price = parser.getText();
						}
					}
				}
			}
		}

		@Override
		public String toString() {
			return "book: { author:" + author + ", description:" + description
					+ ", price:" + price + "}";
		}
	}

	public class Music {
		private String artist;
		private String album;
		private String genre;
		private String title;

		public void parse(JsonParser parser) throws JsonParseException,
				IOException {
			JsonToken token = parser.nextToken();
			if (token == JsonToken.START_OBJECT) {
				while (token != JsonToken.END_OBJECT) {
					token = parser.nextToken();
					if (token == JsonToken.FIELD_NAME) {
						String fieldName = parser.getCurrentName();
						parser.nextToken();
						System.out.println(fieldName);
						if (0 == fieldName.compareToIgnoreCase("artist")) {
							this.artist = parser.getText();
						} else if (0 == fieldName.compareToIgnoreCase("album")) {
							this.album = parser.getText();
						} else if (0 == fieldName.compareToIgnoreCase("genre")) {
							this.genre = parser.getText();
						} else if (0 == fieldName.compareToIgnoreCase("title")) {
							this.title = parser.getText();
						}
					}
				}
			}
		}

		@Override
		public String toString() {
			return "music: { artist:" + artist + ", album:" + album
					+ ", genre:" + genre + ", title:" + title + "}";
		}
	}

	public class Camera {
		private String picture;
		private String model;
		private String price;
		private String make;

		public void parse(JsonParser parser) throws JsonParseException,
				IOException {
			JsonToken token = parser.nextToken();
			if (token == JsonToken.START_OBJECT) {
				while (token != JsonToken.END_OBJECT) {
					token = parser.nextToken();
					if (token == JsonToken.FIELD_NAME) {
						String fieldName = parser.getCurrentName();
						System.out.println(fieldName);
						parser.nextToken();
						if (0 == fieldName.compareToIgnoreCase("picture")) {
							this.picture = parser.getText();
						} else if (0 == fieldName.compareToIgnoreCase("model")) {
							this.model = parser.getText();
						} else if (0 == fieldName.compareToIgnoreCase("price")) {
							this.price = parser.getText();
						} else if (0 == fieldName.compareToIgnoreCase("make")) {
							this.make = parser.getText();
						}
					}
				}
			}
		}

		@Override
		public String toString() {
			return "camera: { picture:" + picture + ", model:" + model
					+ ", price:" + price + ", make:" + make + "}";
		}
	}
}