How to write a basic APEX Trigger

Does this picture have anything directly to do with an APEX Trigger? Of course not. However, it does accurately depict the frustration I experienced the absolute first time I tried to write a Trigger.  (Bonus points if you have seen Bloodsport…)

The purpose of this article is to provide a declarative Admin (a.k.a “ButtonClickAdmin“) a decent primer to writing their first Trigger.  I do need to however go through the “declarative before programmatic” tenet – meaning that you always try to make something work through the Salesforce UI before going to a code / programmatic route.  A lot of triggers I’ve seen out there were written before a lot of the new cool features of Salesforce have been released – keep in mind these things:

  • Formulas
    • Salesforce provides you a rich relational pathing ability to grab fields of a Parent or Lookup simply by building it through a formula
  • Workflows
    • You can do Field Updates, Tasks, Emails, and Outbound Messages using Workflow
    • As of Spring ’12, you can now do “cross-object” Field Updates – before Spring ’12 a LOT of Triggers I saw existed just to do this (page 152 of the Spring ’12 Release Notes)
  • Validation Rules
    • Apart from the cross-object stuff, this is the other area where a lot of Triggers are written
      • Which can be OK if you have CRAZY complex computations to perform that you just can’t do with a V-Rule formula…
    • Use V-Rules to do your traditional data validation on fields

Salesforce does have some great documentation to get you started:

I’m going to pick and pull apart the documentation to give you an example of how I wrote a trigger just a while back to automatically add a Chatter post to a parent record when a child record was created.  You could really take this example and apply it to creating a record in any business case however.

First however, let’s dive into how a Trigger is structured…

Structure of a Trigger

trigger AddChatterPostToParent on ChildObject__c (after insert, after update) {

This is defining your trigger.  It’s stating that this is:

  1. a Trigger.
  2. “AddChatterPostToParent” is the name of this trigger
  3. “ChildObject__c” is the object that this trigger will be firing against
  4. The database operations that this trigger will fire on – your choices are “insert, update, delete, and undelete”
    • “before” = you have the ability to CHANGE values of fields
    • “after” = you do NOT have the ability to change values of fields
    • Great post from Salesforce on the pros vs. cons on before/after HERE

When I’m typically writing triggers, I like to put some comments at the beginning to describe what I’m trying to accomplish:

trigger AddChatterPostToParent on ChildObject__c (after insert, after update) {

//////////////////////////////////////
// This trigger creates a Chatter Feed item for the parent record
// when a child record is created
// Created 20120718 Andy Boettcher
// Modified 20120718
//////////////////////////////////////

/* TRIGGER LOGIC WILL GO HERE */

}

Resources Available to You

When writing a trigger, you have a few objects already ready for you to use:

Collections

  • “trigger.new” – this is a collection of the new records you are working with.
  • “trigger.newMap” – this is an indexed collection (map) of the new records you are working with.
  • “trigger.old” – this is a collection of the OLD values of the records you are working with, such as in the case of an update.
  • “trigger.oldMap” – this is an indexed collection (map) of the old records you are working with.

True/False Variables

  • “What kind of action am I working with?”
    • Trigger.isInsert
    • Trigger.isUpdate
    • Trigger.isDelete
    • Trigger.isUnDelete
  • “Is this a before or after action?”
    • Trigger.isBefore
    • Trigger.isAfter

// Run this code if we’re dealing with an Insert only
if(Trigger.isInsert) {
blah blah blah
}

// Run this code if we’re dealing with an Update only
if(Trigger.isUpdate) {
blah blah blah
}

// Run this code if we’re dealing with either an Insert or Update only
if(Trigger.isInsert || Trigger.isUpdate) {
blah blah blah
}

** GOTCHA ALERT! **

Here’s one of the biggest “gotchas” you’ll encounter working with APEX and Triggers – you are already definitely aware that Salesforce has “limits” on just about everything; Limits on objects, fields, data size, etc.  The same goes for APEX, but you have to account for it yourself in your code vs. having Salesforce nicely remind you when you’re near the threshold.  The official term is “bulkification” – meaning that our code has to be able to handle anywhere from a single record up to thousands without hitting a limit.  I will absolutely show and describe how we are dealing with this in the examples that follow.

Trigger Logic

The best way I can suggest bridging the concept of writing a formula / workflow in Salesforce and writing a Trigger is to “speak” what you want to do.  As you’re writing the Trigger, it’s very easy to get stuck on a tangent and over-code your solution.  Speak it, write it down, and then go through your code when you’re done to make sure you didn’t get off track.

  • “On all records coming through my trigger, I want to create a Chatter Post on the parent object.” (this is our example)
  • “On only the records with a value of “Tuesday” on the “Day Of Week” field, I want to create a new record in the “X” object.”

Putting the Trigger Together

trigger AddChatterPostToParent on ChildObject__c (after insert, after update) {

//////////////////////////////////////
// This trigger creates a Chatter Feed item for the parent record
// when a child record is created
// Created 20120718 Andy Boettcher
// Modified 20120718
//////////////////////////////////////

// Create Empty List to hold new items (bulkification)
List<FeedItem> lstFeeds = new List<FeedItem>();

// Loop through Comments and create Feeds
for(ChildObject__c c : trigger.new) {

FeedItem fi = new FeedItem();
fi.Type = ‘TextPost’;
fi.ParentId = c.ParentField__c;
fi.Body = ((Trigger.isInsert) ? ‘created’ : ‘updated’) + ‘ a Child Object on this Parent.’;

lstFeeds.add(fi);

}

// Insert List if there are records contained within
if(lstFeeds.size() > 0) { insert lstFeeds; }

}

Those Pesky Unit Tests

With any APEX code, you need to write a Unit Test before you can deploy this new trigger to a Production environment.  Your Unit Test is meant to basically emulate how your code would be executed, and is structured to test if your desired result is what happens to come out of it.

To create a Unit Test for this Trigger, we need to create a Class that creates a Parent Object first, then creates a Child Object related to the Parent.  To be 100% kosher, we need to then update the new Child Object to cover both sides of the “if” condition for the Chatter Post body.

@isTest
private class AddChatterPostToParent_UnitTest {

static testMethod void testTrigger() {

// Create Parent Object
ParentObject__c p = new ParentObject__c();
p.Name = ‘Test Parent’;

insert p;

// Tell Salesforce that we are starting our test now
test.startTest();

ChildObject__c c = new ChildObject__c();

// Relate this Child to its Parent from the inserted record above
c.ParentId = p.Id;

c.Name = ‘Test Child’;

// Insert Child Record
insert c;

// Update Child Record
update c;

// Tell Salesforce we’re done with our test
test.stopTest();

}
}

Writing your first Trigger is kind of like jumping into the deep end of the pool with a lead weight belt, but that’s what your Sandbox is for.  Tinker, learn, ask questions…leverage the community.  You’ll get it.  =)

Transition from MSSQL to Force.com

I saw an excellent question on the #askForce Twitter feed from @corycowgill today that was “Besides standard Force.com Dev Guide, anyone know good blogs / materials to ramp up a DBA on Force.com?”

I thought, “well heck, I’m an old school Microsoft VB/.NET/MSSQL programmer and I definitely had some ramp-up when transitioning to the Force.com platform.”  I figured I’d have a stab at it – trying to explain what I ran into in hopefully simple english.  =)

Step 1:  Let’s get some common footing.  Name, rank, and serial number soldier!

Let’s tackle the basics first…tables, fields, stored procs, triggers….what are they called in Force.com?

  • Table = Standard and Custom sObject
  • Field = Standard and Custom Field
  • Relationship = Master/Detail and Lookups
  • Programmatibility (stored procs and triggers) = Workflows and APEX (classes and triggers)
  • Index = yes, but not what you’re used to…
  • View = not available in Force.com

Elaborating…

Naming Convention.

  • “Label” vs “API Name”
    • Label is what your users will see that sObject or field as in the interface
    • API Name is what you will reference that sObject or field as in APEX
  • “__c” – this is a suffix you will see appended to the “API Name” of all custom sObjects and fields you create

sObjects.

You have a collection of sObjects called “standard sObjects”.  These are objects that are pre-built into your instance and cannot be deleted.  They are primarily under control by Salesforce (fields can be added as a result of a maintenance release).  You can add your own fields (custom fields) to **MOST** standard sObjects.  The “custom sObject” is a table that you create – fields, security, relationships, etc.  Standard and custom sObjects all live in the same partition – you can access both standard and custom objects from each other.

Examples of standard sObjects = Account, Contact, Event, Task, Opportunity, Campaign, Case.

Examples of custom sObjects = MyObject__c, YourObject__c, YourMom__c.

For sObjects, depending on the Edition you are subscribed to, you can have between 5 and 2,000 custom sObjects.

Fields.  

Just as you have standard and custom sObjects, you have the same with fields.  Standard fields will have names such as “FirstName, LastName, MailingAddress”, where custom fields will have “MyField1__c, MyField2__c, etc”.  Just as with MSSQL, you have the option of defining the data type for a field (I won’t go into all of them, see this link for details).

For fields, depending on the Edition you are subscribed to, you can have between 5 and 800 custom fields per object.

Relationships.

Force.com has two kinds of table relationships:

  • Master-Detail (1:n) – allows a master object to control behaviors of a child object (security model, cascading actions, etc)
  • Lookup (1:n) – links two objects together, but has no bearing on security and the option of cascade actions

You can indeed to many-many (n:n) relationships in Force.com using either of the above relationship types above very similar to how you would use a junction table in MSSQL.

For relationships, you can define up to TWO Master-Detail and FIVE Lookup relationships per sObject.

Programmatibility.

Oh man – this is the biggest spot where I really miss the features of enterprise DBMSs like MSSQL.  There are no stored procs or triggers at the DB level, at least to how you’re used to thinking about them.  Force.com does provide nearly all of the functionality that you’d normally tap via stored procs and triggers, but you have to dive into Workflows (declarative) and APEX classes / triggers (programmatic).

Before you freak out too much – bottom line is YES, between Workflow and APEX, you’ll be able to “proc” away.

In the interest of keeping on task here, I’m not going to dive into APEX on this post – if you want more info, just leave me a comment or hit me on Twitter (@andyboettcher).

Indexes.

This is double-edged sword – I both do and don’t miss being able to index any field.  I was pretty good at optimizing my indexes, but I knew a lot of DBAs who just sucked at it.

Force.com does not do indexing as you’re used to.  There are indexes on certain fields by default, such as:

  • System Time Fields
  • Id
  • Name
  • Any relationship fields
  • Standard sObject-specific
    • Account:  Account Name, Account Owner, Account Record Type, Parent Account
    • Lead:  Company, Email, Lead Owner, Name
    • Contact:  Account Name, Contact Owner, Email, Name, Reports To

Depending on your schema’s needs, you can also contact Salesforce Support if you need indexes on other fields.  I haven’t done this personally, but everyone I have heard of that has done it has been successful (after having a valid and documented use case)

Step 2:  How do I create my schema?  Where’s my trusty Enterprise Manager / Management Studio?

The most raw and fully-functional place you can go is into Setup.

  • Standard Objects:
    • Setup / Customize / (find your object)
    • Fields are located as Setup menu options under each standard sObject
  • Custom Objects:
    • Setup / Create / Objects
    • Fields are located in the “Custom Fields” section on each custom sObject’s configuration page

If you’re feeling really spunky, Salesforce released the read/write “Schema Browser” with Summer ’12 (finally!) that is pretty darned cool.  It’s the closest you’re going to get to an ERD without going to the AppExchange too.

Step 3:  What tips and tricks should I keep in mind while building my Schema?

For the love of everything holy, remember to NORMALIZE!  The double-edged sword of Force.com is that it’s incredibly easy to create sObjects and fields.  Too easy.  Many an instance I’ve walked into with 400 fields in objects that could have been far more properly been done via normalization.  =)

Create a Naming Convention.  Force.com has many examples of how they’ve approached naming conventions (just look through their online documentation or examine their standard sObjects).  Pick one and stick with it.

Along the normalization  lines – think long term and be as generic with field names (as appropriate).  Just because you can maintain seperate “Label” and “API” sObject and field names doesn’t mean you should make a habit of it.  Very little is more frustrating than having to hunt through your schema for a field with two names.


That’s a pretty good start.

Just as with MSSQL or any other DBMS for that matter – your schema is your foundation.  If you have a bad schema, you’re going to have a less-than-optimal application.  Be calm, deliberate, and thorough.  One big thing I try to impart on everyone I talk to about this is just because Force.com is quick and easy – that is no reason to execute that way.  A great saying I heard growing up from my dad while he and I would work on projects around the house – measure twice, cut once.

Questions?  Hit me up in the comments below or on Twitter (@andyboettcher).  Thanks for reading!!

Wrapper (or Helper) Classes – What can I do with them?

Wrapper Classes (I’ll refer to them in here as “WCs”) are a wonderful tool for so many things, but are mostly unknown or under-utilized by a lot of SFDC developers that I’ve met.  Recently on #askForce someone said they were looking for a good article on how WCs can be used without immediately diving into technical specs or the deep nitty-gritties but was unable to find anything, so I said CHALLENGE ACCEPTED!

The name “Wrapper Class” is exactly that.  It’s essentially an object that you can “wrap” data within.  Easy analogy to the real world?  Go buy a coffee maker at Sears.  The box it comes in?  That’s a wrapper class.  You take 50 of those coffee maker boxes on a shipping palette, (back to Salesforce lingo) you have a List<wrapperClass>.

I love WCs.  I would estimate that 80% of my overall projects utilize WCs in one way or another.  My primary use case for them is when I am creating interactive / rich grids of data.

What do I mean?

Typically when displaying a list of data, you create a GETer or a public List<whatever> variable and stick it in a datatable / repeat / etc.  That method totally works, but it’s pretty flat and without some additional wizardry via VF pretty non-functional.  (display-only)

What if I need to render a checkbox, picklist, or some other control / data point that isn’t necessarily on the record natively?  I don’t want to have to go and create a bunch of sObject fields just to tell whether or not I’ve put a checkmark in a box or if certain fields are filled out to render a section.

Enter WCs.

Code Example:  (I take no responsibility for the accuracy of my pseudo-code…Wordpress isn’t an IDE!)

Here’s a standard List<Account> implementation…

List<Account> lstAccounts = [SELECT Id, Name, Field1__c, Field2__c FROM Account WHERE some_field__c = ‘some value’];

<apex:pageBlockTable value=”{!lstAccounts}” var=”a”>
<apex:column headerValue=”Id” value=”{!a.Id}” />
</apex:pageBlocktable>

That totally works and is a valid use case – but let’s get a little fancier.  Let’s throw in a checkbox column and an OutputPanel based on a Boolean value.  My use case here is that I want to have a list of records with checkboxes, and then through a CommandButton perform some sort of action only on the “checked” ones.  Sound similar to a multi-select list view in Salesforce now?  Yup.

First off, we need to think about what we want to put in the box (if you just went out and made a box…how would you know your contents would fit??).  Keeping with our use case, I would say that:

  • I want to have an sObject record
  • I want to have a Boolean value to tell me if it’s “checked” or not

Let’s first create our WC:

public class hClsAccount {

// declare variables just like a grown-up class!
public Account acct {get;set;}
public Boolean bolChecked {get;set;}

// constructor, again just like a grown-up class.
public hClsAccount(Account passAccountRecord) {

// Set initial variables
acct = passAccountRecord;
bolChecked = false;

}
}

With that created, we could just create a GETer of a List<hClsAccount> and display that on our page, but what fun would that be?  We couldn’t go back and run through it just using a GETer.  What I usually do is declare a List<helperClass> at the top of my parent class.

List<hClsAccount> lstAccounts {get;set;}

Within a method (or constructor, VF action, etc) you can fill this list.  Putting the sObject record in the WC constructor is just a script line-saving measure – you don’t have to.  The end result of this method is a fully populated list that you can now reference in VF.

public void fillHelperClass {

// Re-Instantiate List
lstAccounts = new List<hClsAccount>();

// Run your sObject query
for(Account a : [SELECT Id, Name, Field1__c, Field2__c FROM Account WHERE some_field__c = ‘some value’]) {

// Instantiate Wrapper Class
hClsAccount clsA = new hClsAccount(a);
lstAccounts.add(clsA);
}
}

Now, over to VF.  You’ll note that normally when accessing a List<sObject> in an apex:pageBlockTable, you refer to fields in the table as <var>.<field>.  With a wrapper class, you now have another layer in here – the object WITHIN the WC.  Looking above to our WC, to get the “Account Name” field, instead of “{!a.Name}” like you would normally, you’d do “{!a.acct.Name}” – “a” = your PBT var, “acct” = the sObject WITHIN the WC, and then “Name” = the field in the sObject.

<apex:pageBlockTable value=”{!lstAccounts} var=”a”>

<apex:column headerValue=”Do Something?”>
<apex:inputCheckbox value=”{!a.bolChecked}”
</apex:column>

<apex:column headerValue=”Name” value=”{!a.acct.Name}” />

<apex:column headerValue=”Field 1″ value=”{!a.acct.Field1__c}” />

<apex:column headerValue=”Field 2″ value=”{!a.acct.Field2__c}” />

</apex:pageBlockTable>

Looking pretty slick eh?  Yeah.  =)

Now – how to process those records you’ve checked…

public void processCheckedRecords() {

for(hClsAccount h : lstAccounts) {

if(h.bolChecked) {
// Do your logic.
String strAccountName = h.Acct.Name;
}

}

}

What’s next?  Really anything you can think of!  Picklists, boolean logic to render/not render columns, heck…even CSS if you are feeling “violate-y of the MVC”.  You can even have methods IN your WC to do certain things just as you’d do in your parent class.  Pretty slick.  =)

Seems pretty easy reading a blog – go ahead and try it out, give me a shout if you have questions.  (Twitter, comment below, whatever.)

Salesforce Update Field without Query

I had someone call me today and ask about this – and I realized I haven’t shared this neat little nugget of knowledge on here yet.

Business case – I’m looping through stuff and I just need to update a value on another related record for some reason.  On the record I’m looping through, I have the Id of that related record (Lookup, WhoId, WhatId, OwnerId…whatever) – and my usual M.O. is to run an initial loop of my records, pull those Ids into a Set and the SOQL up a List<whatever>, and then just deal with it in the subsequent logic loop.

Totally works – but in the eternal pursuit of streamlining code to stay within the governor limits, I stumbled across this little gem:

Instead of running that initial SOQL query to build the list of related objects to update, you can do a neat little UPSERT (or UPDATE if you like) on a “new” object:

(Using a before-triggered Contact action – trying to update an Account field for this example)

List<Account> lstAccountsToUpdate = new List<Account>();
for(Contact c : trigger.new) {
// Regular Contact Logic (yes, it's pseudo-code)
if blah blah = etc etc, c.AddError(whatever);
// Update Account Object - pass the AccountId from the Contact object into the new object
Account accUpdate = new Account(Id=c.AccountId) 
accUpdate.Field_I_Want_To_Update__c = 'whatever';
lstAccountsToUpdate.add(accUpdate);
}
if(lstAccountsToUpdate.size() > 0) { upsert lstAccountsToUpdate; }

And there ya be – one less SOQL query you have to make.

Taking Notes – Cloud Application

I’m an avid note taker.  On my shelf at home, you’ll find dozens of 100% used notebooks full of notes from business meetings, community events, volunteering, etc.  I would haul these things around but often would have inevitably forgotten the one notebook I needed at home.

Enter Evernote.

Man I love this app.  I have it installed on my work laptop, home computer, Android Phone, and iPad.  (The basic edition is free – you only need to purchase a subscription if you want to start sharing / collaborating on notes with other people.)

No matter where I go – Evernote keeps all of my notes centralized and synchronized between all devices all the time.  Even if I don’t have Internet access at the time, I can view all of my notes, make changes, and next time I’m online the changes will just sync up in the background.

Oh man – what a difference one app makes, and another shining example of how awesome properly thought-out cloud utility apps are.  NICE.

Wednesday Sillies

These past few weeks, I’ve been exclusively coding APEX and Visualforce just about every day.  A lot of it has been correcting other companies bad code or writing test cases to cover the same so I can push new functionality to clients.  Two visuals came to mind as I keep breaking through barriers that I thought would give my fellow Salesforce developers a little giggle today…

I’m not full of myself at all…really.  =)

 

Android Phones and Tablets

I’m an Android freak.

Being on the technical side of the world, I’ve used / configured / fixed just about every device and OS out there.  As a techno-junkie, I’ve tried out just about everything as well and I tell ya I’m hooked on Android.  Holding Apple fanboys / fangirls at bay, yes my household has two iPods and an iPad as well…so don’t start that up.  =)

Getting to this point wasn’t quick…as as a general rule of thumb I don’t exactly embrace the cutting edge of technology.  Being on the cutting edge often leaves you…well…cut and bleeding.  I’m more of a “we’ll wait until Service Pack 1 comes out before drinking the KoolAid on this…” kind of guy.

About 9-10 years ago, when I was deep in the systems-side of my career, I was one of those supergeeks who had a servers (four to be exact) in the basement where I was hosting email, websites, FTP, (you name it) out of the house.  Everything local – tape backups, offsite replication, blah blah blah.  Hosting things elsewhere seemed really dumb to me at the time because “why would I do that when I can have full control of it here?”

Then one day while I was on vacation the power supply in my web server went out and – bottom line – my server was going to be down for two weeks.

(hold on for the Android tie-in…I’m getting there!)

Forget that!

On top of that, <sarcasm>little</sarcasm> things like:

  • My Outlook PST corrupting and the subsequent loss of years of email
  • The tape snapping in my tape backup drive
  • Just plain forgetting to swap tapes
  • External hard drives failing
  • General equipment failures
  • The HOURS upon HOURS upon DAYS of my life given up to maintain local stuff

all one day made me snap.  I made the choice that I was going to shove everything into the “cloud” or other offsite-hosting solutions.

Flash forward to last year.  (keep the faith!  I’m getting to the whole tie-in to Android)

Email and File Sharing:  I use Google Apps to host both my personal and business email and file sharing.  Five different domains all wrapped into a SINGLE interface for email.  Doesn’t matter who I receive email from or send to, it’s all just on one screen and I can access it from any web browser.  Cost…$0.00.  (well, I have to pay for the annual domain registration…)

Calendaring:  I again use Google Calendar to manage my personal, business, and family calendars.  Again, all in one interface on one screen…accessibly from any web browser.  Cost…$0.00.

Web / FTP Hosting:  I get a little more fragmented on this point depending on the need.  GoDaddy for MySQL hosting, HostGator for LAMP sites, and WordPress for blogging.  Cost…varies from $5.00-$7.00/mo.

Home and Business Phone:  I have been a VoIP junkie for years.  Business PBX installs, home installs…whatever.  If the actual technology isn’t the looming replacement for traditional copper lines, the concept sure is.  Just as many things over the years have “ditched the cord”, everything will eventually follow suit.  I’ll get into more detail on how I fully “VoIP’ed” my house in a later posting, but basically my entire house is running off of VoIP.  Personal, business, everything.  Cost…for 3 numbers and all the calling I need to do…$6.00/mo.

OK…so here’s the Android tie-in…

So I got all of this cool stuff in “the cloud” to speak and it’s all running great.  However, I still was tethered to a computer to access anything.

My first experience with a mobile OS was the Blackberry.  Great for email, flat-out horrid for everything else.

My next experience was 18 months with a Palm Pre.  This was my first true experience being able to essentially access anything, at any time, just through the phone.  This was both an incredibly freeing and incredibly confining moment.  I realized “holy smokes!  I can access everything from ANYWHERE!” as well as “holy smokes!  Anyone can access me from ANYWHERE!”.  HA!

When the Palm Pre literally fell apart in my hands (don’t get me started on HP/Palm’s customer support…grrrrr) I was unleashed on the mobile market in August of 2010.  iPhone?  Android?  Something else?  I knew a lot of people who had the iPhone and are vehement supporters of same / lambastes of different that told me that I would be throwing my life away if I didn’t hop on the bandwagon.

I have to give the iPhone credit – it is an incredible device.  It does 99% of everything one may “need” from a smartphone and has great battery life to boot.  Unfortunately of that 99% that it did do, I would only really use like 10% of that, and that 1% overall that the iPhone doesn’t do well I really wanted.  (Did you know that 74% of all statistics are made up on the fly?)

Enter Android.  Being a big Google guy already, they did what I needed:  Fully Unified Calendaring across ALL services.  Exchange, personal and shared Google Calendars (this is what Apple doesn’t do well at all), Facebook, LinkedIn, and a whole host more.  I went with the HTC EVO from Sprint (already a Sprint customer and got a killer plan discount through my employer at the time, plus they have true unlimited data) and haven’t looked back.  Going from Android 2.1 to 2.2, and now to 2.3 there isn’t a single thing that my EVO hasn’t been able to do that I needed it to…with the exception of more than 20 hours of battery life under “normal” conditions.  I use my phone quite a bit, and it’s darn near a real computer anyways…I’m a realist with this…so I don’t get erked off too badly.

Besides accessing stuff, 8MP camera?  Awesome.  HD video?  Awesome.  Only having to carry one device (ditching the point and shoot)?  Priceless.

Phone – cool…done – works great.  The only time I have to really crack open the laptop or take out some paper is when I’m in a meeting.  In one meeting, I see a guy whip out his iPad with a stylus and start taking notes.  Cool!  So here my mind goes on tablets.

In February, I picked up a Gen 1 iPad for the family.  16GB WiFi only at Target.  The girls can’t put it down – (mostly educational) games, Netflix, you name it.  My wife checks her email and Facebook on there now and again, and I just supply the credit card info for iTunes.  Looking at how the use it for just about everything (I’m darn near ready to pick up a second iPad to tell you the truth) my mind started shifting to how I could use one for everything that I do.

With my wonderful success with Android, I started looking around to see how the industry and devices were tracking.  A good friend of mine posts articles around the Internet about such things, and lo and behold today he posted one on an Android 3.0 tablet by Asus – the Transformer.  I really like this concept initially due to the attachable keyboard.  Take the thing to a meeting, take notes, but when you need to really bear down on a document, just snap on the keyboard and and use Google Apps.  Nice.  Throw in that I can extend the same success that I enjoy with the Android phone to a tablet, it seems like a no-brainer.

I know the iPad has bluetooth keyboard devices (we have a keyboard for ours), but I really don’t like what is out there.  The keys are too small for my sausage-like fingers and there is an annoying lag when typing.  Heck, the same thing could come to bear with the Android, but who knows.  I’m up for trying anything.  =)

Ok – so that wraps up my random babbling on Android.  Questions?  Comment.  =)