Friday, March 18, 2011

My Last Day at eBay

This is The End

Today is my last day at eBay. I have been there for nearly four years and it has been a remarkable experience. When I joined eBay, I had come from a startup called Ludi Labs. One thing I realized while at Ludi Labs was that "scalability" was largely a hypothetical term to me. I thought I understood the keys to making an application function at web scale, but it unfortunately it was very theoretical knowledge. None of the startups I had worked at had ever really hit that kind of scale. Hence my interest in working a large scale web company, like eBay.

Four years later and I realize how very limited my theoretical knowledge of scalability really was. Sure I understood the basic principles, but there are definitely many subtleties involved in implementing these principles. I won't now claim to be a scalability expert, but I have certainly learned some real world lessons.

When I first joined eBay, I worked with the infrastructure team on eBay's homegrown presentation technology. A lot of this technology was based on what works at scale: high volume web pages, working in dozens of different languages, running on servers spread across multiple data centers, and built by hundreds of engineers around the globe. I went on to help many of the different teams at eBay adopt and optimize their use of eBay's presentation technology. As such I got opportunities to work on many of the major pages of eBay: home page, search results, view item, my eBay, just to name a few. Often my experience was limited to helping the team solve some site speed issues, but I still got a chance to understand how each of these highly complex web pages could function at massive scale.

Of course the last couple of years of my career at eBay was dominated by mobile. It's been an exciting time, as our mobile team saw massive growth both in terms of users and their usage (measured using the best possible metric: money) as well as in terms of the team behind the technology. It's had some great moments as well, such as seeing eBay named by Fast Company as the #2 most innovative company in mobile.

This leads to the obvious question of why I am leaving eBay. The future for mobile at eBay is very bright. However it's one of those cases where the change is not about where I'm coming from, but about where I'm going. In this case that's Bump Technologies. I'm very excited about the opportunity at Bump. They already have a great product that they've built in just two years. However, I think that two years from now the current product will look very limited in comparison. It's going to be an exciting time.

Wednesday, March 02, 2011

Honeycomb Drag and Drop Basics

If you are thinking about developing an Android tablet app, one of the most exciting things has got to be the drag and drop capabilities that were added to Android as part of Honeycomb. No wait a minute, aren't there a lot more interesting features than drag and drop in Honeycomb? There are definitely a lot of great features, but drag and drop is one of the most interesting in my opinion. It's a perfect fit for tablets, where you can have this nice big graphical objects to drag and drop. I think it really challenges mobile designers and developers to rethink our interaction models. If you read through that tutorial I just linked to, you will learn about many of the intricacies of drag and drop Android style. I thought I'd provide an even simpler example for those of you who prefer the Cliff Notes versions of things.

First we will start with a simple UI, a 2x2 grid with a cute little image that we will drag and drop around this grid. Here is what it looks like:

DnD UI

I used a simple TableLayout for this, along with some basic border background Drawables. Here's the XML for it:

<TableLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent">
  <TableRow>
    <LinearLayout android:layout_width="640px"
        android:layout_height="345px"
        android:id="@+id/topLeft"
        android:background="@drawable/border">
        <ImageView android:id="@+id/droid"
            android:src="@drawable/bugdroid"
            android:layout_width="150px"
            android:layout_height="150px"
         />
    </LinearLayout>
    <LinearLayout android:layout_width="640px"
        android:layout_height="345px"
        android:id="@+id/topRight"
        android:background="@drawable/border2"
    />    
  </TableRow>
  <TableRow>
    <LinearLayout android:layout_width="640px"
        android:layout_height="345px"
        android:id="@+id/bottomLeft"
        android:background="@drawable/border2"
    />
    <LinearLayout android:layout_width="640px"
        android:layout_height="345px"
        android:id="@+id/bottomRight"
        android:background="@drawable/border"
    />    
  </TableRow>  
</TableLayout>

Nothing special so far. All of the magic happens in the code. Here's the basics of the Activity that uses this XML.

public class DndActivity extends Activity {

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.grid);
  View droid = findViewById(R.id.droid);
  droid.setOnLongClickListener(new OnLongClickListener(){
   @Override
   public boolean onLongClick(View v) {
    ClipData data = ClipData.newPlainText("foo","bar");
    DragShadowBuilder shadowBuilder = new DragShadowBuilder();
    v.startDrag(data, shadowBuilder, v, 0);
    return true;
   }
  });
  findViewById(R.id.topLeft).setOnDragListener(new BoxDragListener());
  findViewById(R.id.bottomLeft).setOnDragListener(new BoxDragListener());
  findViewById(R.id.topRight).setOnDragListener(new BoxDragListener());
  findViewById(R.id.bottomRight).setOnDragListener(new BoxDragListener());
 }
}

Here we just set the contentView, then we get a handle on the droid image. We set its onLongClickListener. I think the long click will become the de facto way to initiate drag and drop in Android apps. You could use some other event of course, but long click feels right. The key thing we need to do to initiate drag and drop is invoke the startDrag method. To do that, we have to create a ClipData object and DragShadowBuilder. You can use the ClipData to put all kinds of interesting data/metadata that will be passed around to the drop zones (Views) in your app. In this case I didn't need anything fancy, so I put something minimal and arbitrary. For the DragShadowBuilder, you might want to do something fancy to make the dragging look cool. I used a default one and it doesn't do much at all. You also need to set a "localData" object. This can be anything. Since my local data is the image itself, I chose to pass the ImageView that will be passed in to the onLongClick method. Now you can call startDrag. Finally, make sure you return true to let the OS know that drag and drop is a go.

Next in the code you will see that for each of the cells in my table, I have set an OnDragListener object. I've set this to a new instance of BoxDragListener for each of the cells. This is an inner class of DndActivity:

class BoxDragListener implements OnDragListener{
 boolean insideOfMe = false;
 Drawable border = null;
 Drawable redBorder = getResources().getDrawable(R.drawable.border3);
 @Override
 public boolean onDrag(View self, DragEvent event) {
  if (event.getAction() == DragEvent.ACTION_DRAG_STARTED){
   border = self.getBackground();
   self.setBackgroundDrawable(redBorder);
  } else if (event.getAction() == DragEvent.ACTION_DRAG_ENTERED){ 
   insideOfMe = true;
  } else if (event.getAction() == DragEvent.ACTION_DRAG_EXITED){
   insideOfMe = false;
  } else if (event.getAction() == DragEvent.ACTION_DROP){
   if (insideOfMe){
    View view = (View) event.getLocalState();
    ViewGroup owner = (ViewGroup) view.getParent();
    owner.removeView(view);
    LinearLayout container = (LinearLayout) self;
    container.addView(view);
   }
  } else if (event.getAction() == DragEvent.ACTION_DRAG_ENDED){
   self.setBackgroundDrawable(border);     
  }
  return true;
 }
}

This guy will get a stream of DragEvents fired at its onDrag method. You can determine what kind of DragEvent it is by calling getAction on the event. The first one you will get is an ACTION_DRAG_STARTED event. For this we want to change the border of our cell to red to let the user know that they can drop the image in this cell. Here's what that looks like:

Enter the red zone

Next we want to keep track if the user's finger has entered into a drop zone. So we look for the ACTION_DRAG_ENTERED and toggle a local boolean. Similarly, we look for ACTION_DRAG_EXITED in case the user changed their mind and picked a different zone to drop the image into. Next we look for the ACTION_DROP. If the object is inside our drop zone, then we update the UI by removing it from its previous parent and adding it to the drop zone. Finally, we listen for ACTION_DRAG_ENDED to know when the drag and dropping is finished. We then restore the borders to their original color. That's it!

Update: The code above will not draw a shadow when you drag the image around the screen. There is a very easy way to get a shadow and that is to pass the View to it the DragShadowBuilder constructor, i.e. new DragShadowBuilder(v); in the above code. That will cause the image in the sample app to be used as the shadow. Be careful using this technique though. If the View that you are dragging is big/complex, then you might take a performance hit (UI becomes sluggish.) In that case you might want to subclass DragShadowBuilder and override its onDragShadow method to draw something simpler for the shadow. I've been playing around with this and StackViews however, and it has worked great with no need to subclass DragShadowBuilder. Be sure to set the android:hardwareAccelerated="true" attribute in your AndroidManifest.xml either on the Activity or the whole Application.