Friday 29 November 2013

Passing Objects in Intents: Parcelables and More

Hey everyone,
For this post, I thought I’d revisit the topic of Intents. In the past I’ve talked about passing Intents in between classes with simple, primitive data (see Passing Information between Activities) – turns out it’s a relatively easy task, and a core concept within the Android framework. However, the much harder task is passing your own classes (objects) in between Activities, and moreover, potentially passing lists of objects.
In this post, I’ll go through an example of writing an object that implements the Parcelable interface.
Consider a scenario where you have a list of Venues (i.e. Restaurants, Hotels, Clubs, anything with fields for lat, long, name, address, etc). Your user makes a request and gets back a list of these Venues. You then want to pass these results to a new Activity – maybe it’s a mapping Activity (as it was in my case), but regardless the goal is to pass this list of Venues from Activity A to Activity B without having to make the same request twice.
The first step is defining your object. The object must implement the Parcelable interface. Why you might ask? Well let’s step back and think about what’s happening here. Within Android, much of the inter-process (inter-Activity) communication is done through light weight data structures known as Parcels. In fact, both Intents and Bundles (two objects you’ve probably encountered before…) implement this Parcelable interface.
But how does this inter-process communication work? An Android process first converts an object into a byte stream which is then sent to another process (i.e. another Activity). This second process then reads in the byte stream and converts it back into an object: this exchange is more commonly known as serialization or marshalling. But how do the two Activities know what to do to serialize and de-serialize your object? For primitives like ints, doubles, even Strings, serialization is trivial as these primitives are already in byte form. And so this is where the Parcelable interface comes in.
By implementing the Parcelable interface, you are essentially giving the OS instructions on how to serialize and de-serialize your object. Conceptually this may be difficult to picture, but luckily Android has made the code for this super simply – in fact you are only required to override a few methods. With that, let’s take a look at what exactly needs to be done in the implementation:

public class ParcelableVenue implements Parcelable {
 
    private double lat, lon;
 
    private String name, address;
 
    public ParcelableVenue(double lat, double lon, String name, String address) {
        this.lat = lat;
        this.lon = lon;
        this.name = name;
        this.address = address;
    }
 
    public ParcelableVenue(Parcel source) {
        // TODO implement
    }
 
    public GeoPoint getGeoPoint() {
        return new GeoPoint((int) (lat * 1e6), (int) (lon * 1e6));
    }
 
    public String getName() {
        return name;
    }
 
    public String getAddress() {
        return address;
    }
 
    @Override
    public int describeContents() {                     // OVERRIDE METHOD #1
        return 0;
    }
 
    @Override
    public void writeToParcel(Parcel dest, int flags) { // OVERRIDE METHOD #2
        dest.writeDouble(this.lat);
        dest.writeDouble(this.lon);
        dest.writeString(this.name);
        dest.writeString(this.address);
    }
 
    public static final Parcelable.Creator<ParcelableVenue> CREATOR = new Parcelable.Creator<ParcelableVenue>() {
 
        // TODO implement
 
    };
 
}
 
So far we have the basic structure of our ParcelableVenue object – it has a few simple fields as well as some standard getters; nothing special there. You’ll then notice that there are two methods we need to override. The first is the describeContents() method. Typically returning 0 suffices unless you have numerous parcelable objects and require special serialization for some. The method itself is meant to return a bit mask that identifies the serialized object. In my case, I just return 0.
The second method is the writeToParcel(Parcel dest, int flags) method. The meat of the conversion happens here. In this method you are passed a destination Parcel which is eventually serialized and sent to the end process. Thus you simply need to write your object’s data into this parcel. Luckily, some simple write methods are given to you, such as writeDouble(), writeString(), writeInt(), writeIntArray(), etc. The flags parameter simply tells the writeToParcel() method how the object should be written.
Once these two methods are overridden, every class that implements the Parcelable interface then needs to have a static Parcelable.Creator object named CREATOR. Let’s step back one more time – where are we at this point? Well so far we’ve flattened the object and written it to a Parcel object. Our object is in essence nothing but a byte stream now, so the only thing that’s left to do is un-flatten it and convert it back into an object using this CREATOR object!
Our creator object is pretty simple and need only look like:

public class ParcelableVenue implements Parcelable {
 
    private double lat, lon;
 
    private String name, address;
 
    public ParcelableVenue(double lat, double lon, String name, String address) {
        this.lat = lat;
        this.lon = lon;
        this.name = name;
        this.address = address;
    }
 
    public ParcelableVenue(Parcel source) {
        // TODO implement
    }
 
    // ...
 
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeDouble(this.lat);
        dest.writeDouble(this.lon);
        dest.writeString(this.name);
        dest.writeString(this.address);
    }
 
    public static final Parcelable.Creator<ParcelableVenue> CREATOR = new Parcelable.Creator<ParcelableVenue>() {
 
        @Override
        public ParcelableVenue createFromParcel(Parcel source) {
            return new ParcelableVenue(source); // RECREATE VENUE GIVEN SOURCE
        }
 
        @Override
        public ParcelableVenue[] newArray(int size) {
            return new ParcelableVenue[size]; // CREATING AN ARRAY OF VENUES
        }
 
    };
 
}
 
And so we see that the very last step is simply to write another constructor for our ParcelableVenue class which initializes an object given a Parcel. This can be done with:
 
public class ParcelableVenue implements Parcelable {
 
    private double lat, lon;
 
    private String name, address;
 
    // ...
 
    public ParcelableVenue(Parcel source) {
        this.lat = source.readDouble();
        this.lon = source.readDouble();
        this.name = source.readString();
        this.address = source.readString();
    }
 
    // ...
 
}
The order here is important – the first double read will be the first double written as, again, it is a byte stream.
And that’s it! Once we have our ParcelableVenue, we can then do things like:

public class ClubsListActivity extends ListActivity {
 
    private List<Club> clubs;
 
    private Button mapViewButton;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.club_list);
 
        mapViewButton = (Button) findViewById(R.id.switch_map_view);
        mapViewButton.setOnClickListener(new OnClickListener() {
 
            @Override
            public void onClick(View v) {
                // PREPARE NEW INTENT TO SEND TO MAP ACTIVITY
                Intent i = new Intent(ClubsListActivity.this, VenueMapView.class);
                 
                // INITIALIZE NEW ARRAYLIST AND POPULATE
                ArrayList<ParcelableVenue> overlays = new ArrayList<ParcelableVenue>();
                for (Club c : clubs) {
                    overlays.add(new ParcelableVenue(c.getLat(), c.getLon(), c.getName(), c.getAddress()));
                }
                 
                // EMBED INTO INTENT
                i.putParcelableArrayListExtra("venues", overlays);
                startActivity(i);
            }
 
        });
 
        SharedPreferences sp = getSharedPreferences(Constants.DB, Context.MODE_PRIVATE);
        double lat = (double) sp.getFloat(Constants.SP_PREV_LAT, (float) Constants.DEFAULT_LAT);
        double lon = (double) sp.getFloat(Constants.SP_PREV_LON, (float) Constants.DEFAULT_LON);
 
        // GET CLUBS NEAR YOU WITH LAT LON
    }
}
 
The receiving side then looks like:
 
public class VenueMapView extends MapActivity {
 
    private MapView map;
 
    private List<ParcelableVenue> venues;
 
    @Override
    protected void onCreate(Bundle icicle) {
        super.onCreate(icicle);
        setContentView(R.layout.mapview);
 
        SharedPreferences sp = getSharedPreferences(Constants.DB, Context.MODE_PRIVATE);
        double lat = (double) sp.getFloat(Constants.SP_PREV_LAT, (float) Constants.DEFAULT_LAT);
        double lon = (double) sp.getFloat(Constants.SP_PREV_LON, (float) Constants.DEFAULT_LON);
 
        map = (MapView) findViewById(R.id.venue_map); // INIT MAP
        map.setBuiltInZoomControls(true); // SET MAP CONFIGURATIONS
        map.getController().setCenter(new GeoPoint((int) (lat * 1e6), (int) (lon * 1e6)));
        int maxZoom = map.getMaxZoomLevel();
        map.getController().setZoom(maxZoom - 3);
 
        Intent i = getIntent(); // RETRIEVE OUR INTENT
        venues = i.getParcelableArrayListExtra("venues"); // GET PARCELABLE VENUES
 
        List<Overlay> mapOverlays = map.getOverlays();
        Drawable icon = this.getResources().getDrawable(R.drawable.good_pin);
 
        VenueOverlay vo = new VenueOverlay(icon, this); // INIT OVERLAY MARKERS
        List<OverlayItem> points = new ArrayList<OverlayItem>();
        for (ParcelableVenue v : venues) {
            OverlayItem o = new OverlayItem(v.getGeoPoint(), v.getName(), v.getAddress());
            points.add(o);
        }
 
        vo.addOverlayList(points);
 
        // ADD VENUE OVERLAYS TO MAP
        mapOverlays.add(vo);
    }
}
 
I won’t say too much about what I’m doing with my Maps Activity – maybe I’ll save this for a future tutorial. The important thing to see is how I send a list of ParcelableVenue objects, and then retrieve them on the other side. As you can probably see by now, there’s nothing hard code wise when implementing a Parcelable object. The difficulty typically stems from an incomplete understanding of how Android sends data from process to process – and this is understandable as almost all of this is abstracted away so that we only need to put simple values into Bundles and Parcels and voila things magically appear on the other side. But once you dig a little more into what’s happening behind the scenes, then all of this serialization and Parcelable implementation makes much more sense.
And with that I send you off again! Happy coding and hope this helped.
 
 

 
 
 

 

Crop image in circular shape in android

Hello Friends,

Today i am sharing  cropping an image and convert it into circular shape. Following Class will helps you to convert an image into circular shape.



There is two method i am posting here for cropping rounded images.

import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Path;
import android.graphics.PorterDuff.Mode;
import android.graphics.PorterDuffXfermode;
import android.graphics.Rect;
import android.graphics.RectF;

public class GraphicsUtil 
{
public Bitmap getCircleBitmap(Bitmap bitmap, int pixels) {
Bitmap output = Bitmap.createBitmap(bitmap.getWidth(),
bitmap.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(output);

final int color = 0xffff0000;
final Paint paint = new Paint();
final Rect rect = new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight());
final RectF rectF = new RectF(rect);

paint.setAntiAlias(true);
paint.setDither(true);
paint.setFilterBitmap(true);
canvas.drawARGB(0, 0, 0, 0);
paint.setColor(color);
canvas.drawOval(rectF, paint);

paint.setColor(Color.BLUE);
paint.setStyle(Paint.Style.STROKE);
paint.setStrokeWidth((float) 4);
paint.setXfermode(new PorterDuffXfermode(Mode.SRC_IN));
canvas.drawBitmap(bitmap, rect, rect, paint);

return output;
}

public Bitmap getRoundedShape(Bitmap scaleBitmapImage) {
int targetWidth = 125;
int targetHeight = 125;
Bitmap targetBitmap = Bitmap.createBitmap(targetWidth, 
targetHeight,Bitmap.Config.ARGB_8888);

Canvas canvas = new Canvas(targetBitmap);
Path path = new Path();
path.addCircle(((float) targetWidth - 1) / 2,
((float) targetHeight - 1) / 2,
(Math.min(((float) targetWidth), 
((float) targetHeight)) / 2),
Path.Direction.CCW);

canvas.clipPath(path);
Bitmap sourceBitmap = scaleBitmapImage;
canvas.drawBitmap(sourceBitmap, 
new Rect(0, 0, sourceBitmap.getWidth(),
sourceBitmap.getHeight()), 
new Rect(0, 0, targetWidth,
targetHeight), null);
return targetBitmap;
}


}

MainActivity.Class
import android.app.Activity;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.provider.MediaStore;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.Toast;

public class MainActivity extends Activity implements OnClickListener 
{

// keep track of camera capture intent
final int CAMERA_CAPTURE = 1;
// keep track of cropping intent
final int PIC_CROP = 2;
// captured picture uri
private Uri picUri;

/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) 
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

// retrieve a reference to the UI button
Button captureBtn = (Button) findViewById(R.id.capture_btn);
// handle button clicks
captureBtn.setOnClickListener(this);
}

/**
* Click method to handle user pressing button to launch camera
*/
public void onClick(View v) 
{
if (v.getId() == R.id.capture_btn) 
{
try 
{
// use standard intent to capture an image
Intent captureIntent = new Intent(
MediaStore.ACTION_IMAGE_CAPTURE);
// we will handle the returned data in onActivityResult
startActivityForResult(captureIntent, CAMERA_CAPTURE);
}
catch (ActivityNotFoundException anfe) 
{
// display an error message
String errorMessage = "Whoops - your device doesn't support capturing images!";
Toast toast = Toast.makeText(this, errorMessage,Toast.LENGTH_SHORT);
toast.show();
}
}
}

/**
* Handle user returning from both capturing and cropping the image
*/
protected void onActivityResult(int requestCode, int resultCode, Intent data)
{
if (resultCode == RESULT_OK) 
{
// user is returning from capturing an image using the camera
if (requestCode == CAMERA_CAPTURE) 
{
// get the Uri for the captured image
picUri = data.getData();
// carry out the crop operation
performCrop();
}
// user is returning from cropping the image
else if (requestCode == PIC_CROP) 
{
// get the returned data
Bundle extras = data.getExtras();
// get the cropped bitmap
Bitmap thePic = extras.getParcelable("data");
// retrieve a reference to the ImageView
ImageView picView = (ImageView) findViewById(R.id.picture);
// display the returned cropped image

GraphicsUtil graphicUtil = new GraphicsUtil();
//picView.setImageBitmap(graphicUtil.getRoundedShape(thePic));
picView.setImageBitmap(graphicUtil.getCircleBitmap(thePic, 16));
}
}
}

/**
* Helper method to carry out crop operation
*/
private void performCrop() 
{
// take care of exceptions
try {
// call the standard crop action intent (the user device may not
// support it)
Intent cropIntent = new Intent("com.android.camera.action.CROP");
// indicate image type and Uri
cropIntent.setDataAndType(picUri, "image/*");
// set crop properties
cropIntent.putExtra("crop", "true");
// indicate aspect of desired crop
cropIntent.putExtra("aspectX", 1);
cropIntent.putExtra("aspectY", 1);
// indicate output X and Y
cropIntent.putExtra("outputX", 256);
cropIntent.putExtra("outputY", 256);
// retrieve data on return
cropIntent.putExtra("return-data", true);
// start the activity - we handle returning in onActivityResult
startActivityForResult(cropIntent, PIC_CROP);
}
// respond to users whose devices do not support the crop action
catch (ActivityNotFoundException anfe) {
// display an error message
String errorMessage = "Whoops - your device doesn't support the crop action!";
Toast toast = Toast
.makeText(this, errorMessage, Toast.LENGTH_SHORT);
toast.show();
}
}
}

I will be happy if you will provide your feedback or follow this blog. Any suggestion and help will be appreciated.
Thank you :)

External Font Across the Android Application

Hello Friends, Today i am going to share Custom Font style of text view for whole application.

Sometimes in Android app we may need to maintain same font across application. At point of time previews post is not sufficient.

Here below Example shows how to use External font across the application.

Here First I have downloaded ".ttf” file from google. Created a new folder “font” in assets folder and paste your recently downloaded font file.





we can do that by just extending your class from android TextView.

Below code is to my customized TextView class(CustomFontTextView.java)

import android.content.Context;
import android.graphics.Typeface;
import android.util.AttributeSet;
import android.widget.TextView;

public class CustomFontTextView extends TextView
{
public CustomFontTextView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
setFont();
}

public CustomFontTextView(Context context, AttributeSet attrs)
{
// call the constructor which has the complete definition
this(context, attrs, 0);
}

public CustomFontTextView(Context context)
{
super(context);
}

public void setFont()
{
String ttf_fontPath = "fonts/TIMES_SQ.TTF";
Typeface font = Typeface.createFromAsset(getContext().getAssets(),ttf_fontPath);
setTypeface(font);
}
}
In our Layout insted of declare <TextView/> tag we just specify <com.hb.CustomFontTextView/>
in any were in our application.
 <com.hb.CustomFontTextView
        android:id="@+id/textview"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_centerVertical="true"
        android:text="Hello Android"
        android:textColor="#65E561"
        android:textSize="45dp" />
Then external font will be applied to Text.
I will be happy if you will provide your feedback or follow this blog. Any suggestion and help will be appreciated.
Thank you :)