Wednesday, November 21, 2012
Hey rook, abstract activities cannot be launched
I'm not a programming master by any means, but this one felt like a rookie mistake. I was hunting down an InstantiationException. Below is a helper class used to launch a new activity simply by taking in an activity's name in the constructor, then launching that activity in an onClick() event.
public class ActivityLauncher implements OnClickListener {
Class<? extends Activity> mClass;
Bundle mBundle;
public ActivityLauncher(Class<? extends Activity> clazz) {
this(clazz, new Bundle());
}
public ActivityLauncher(Class<? extends Activity> clazz, Bundle bundle) {
mClass = clazz;
mBundle = bundle;
}
@Override
public void onClick(View v) {
launchActivity(v.getContext(), mClass, mBundle);
}
}
The launch activity method comes from another helper class. See that one below:
public static void launchActivity(Context context,
Class<? extends Activity> clazz, Bundle extras) {
Intent intent = new Intent(context, clazz);
intent.putExtras(extras);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
This structure works great to maintain clean code. Whenver you need to set an onClick() listener that simply launches another class, just new up an instance of ActivityLauncher, passing it the name of the class you want to start. If you need to pass some values to the activity being started, pass in a bundle along with the activity class name. Where did I make a rookie mistake? I passed in an activity that was actually an abstract class. That code compiles, but it leads to InstantiationException, a runtime exception. It's not quite clear because the constructor is invoked in the tangle of Android source code and not in any code that I've written. I, however, should know that one can't instantiate an abstract class, which also means one can't launch an activity that's defined as an abstract class.
Sunday, November 11, 2012
From now on, I'm putting off choosing a cloud service
It can be tedious to make network calls during the development of a cloud-based application. It's possible to make thousands of calls across the wire. There is a way to reduce the impact of the network during development -- wait until later to decide on a cloud-based solution.
One of the things that stuck with me
from an Agile conference last spring was Bob Martin's talk about
putting off database implementation as long as possible. He described
an application on which he and a partner worked where he knew the
application would eventually need to store information in a database.
For the most part databases are going to be used in the same way –
handling reads and writes of data.
I knew that if I wanted to create an
application with a cloud-based backend, that choosing which service
to use could be a big deal. But I wanted to minimize the impact of
the backend on my application as much as possible and I didn't like the idea of calling though to a cloud-based network during application development.
Having experience with both Google'sApp Engine and Amazon's Simple DB, I found both to have good and bad.
Before hearing Uncle Bob speak, the first thing I did was consider
which cloud-based storage solution to pick. I spent most of my time
weighing the pros and cons of each service. Having worked with Amazon
Simple DB in the past, including on some proof of concept projects at
Northwoods, I knew Amazon was a reliable, and well documented
service. But at the beginning of this project, I ended up choosing
Google's AppEngine. AppEngine seemed to have the easiest cloud
solution to implement. With AppEngine, Google offered an Eclipse
plugin that made it simple to simulate a cloud environment. AppEngine also provides a starter project that wires up the network
access ahead of time. The project even implements a push notification architecture.
However in experimenting with
AppEngine, I found my implementation started to become closely tied
with the implementation of the cloud-based backend. I had cloud-based
database calls in nearly every activity. These activities also were tied to Googles object-relational model. I started to think that it wasn't in my application's best interest to have proxy objects that can only talk to one
kind of service.
The tighter coupling with Google also
became more apparent when it decided to bring its Cloud to Device
Messaging service out of the beta phase and into full production.
There was enough of a difference migrating to the new version of the
service that it didn't make sense to further bind the application to
Google. There was far too much left to be done on the actual program,
and what if by the time it was done, Google's Cloud service changed
again?
Turns out what I ended up doing (and
should have done in the first place) was put off the real database
stuff. The best way I've found to write a cloud application is to create an
interface that will serve as my application's face to the cloud. I
can simulate interaction with the cloud by making calls through this
interface to a class that implements this interface and returns the
expected data. Essentially I can implement an in memory database
using HashMaps that function as if I were making calls to a reliable
and fast network. When the time comes to actually choose a cloud
solution, I'll use the adapter pattern and create a class that
implements my application's data access interface as well as the
interface provided by the cloud solution provider of my choosing. In
this adapter class is where I can put the logic to handle network
conditions. One of the major changes I'll have to keep track of is
creating the UI screens that must be present to handle loading data
from the network. In this architecture, those pages can get lost
since the “network” calls return asynchronously during
development.
This approach has had many merits,
especially when it comes to mocking and test-driven development.
Although the Google AppEngine solution allowed me to debug with
breakpoints on a local server, this approach of defining my own
interface has allowed me to create mocks without a concern for what
is happening on the other side of the wire. Since the database interface is injected into a base activity, it is available to all other activities that inherit from it. This is useful for mocking and test driving with Robolectric. This architecture also has forced me to keep
logic in the application layer while keeping the cloud database with
the sole responsibility of storing and retrieving data.
General Architecture
Saturday, November 3, 2012
A method organization strategy for Android activities
Using the principles of low-coupling
and high cohesion, our class files shouldn't balloon out of control
to the point where single line method signatures get lost amongst the
trees, but it doesn't hurt to have an extra layer of organization on
which developers can agree. It's an inevitable fact that we have to
read each other's code. The list below reflects how I organize
methods in an Android Activity class at the moment. I wouldn't mind
making this an exhaustive list so if you got any ideas....speak up.. :).
- Constructors – default followed by paramterized (public, then package, then private).
- Any method that overides Activity lifecycle events, starting with onCreate(). The rest should follow in the order that they are called on the activity lifecycle stack.
- Next come any classes that override an interface. These should all be appended with the @Override annotation.
- Include the @Override annotation also on any methods that override an abstract class implementation. First list the methods you are forced to override from the abstract class, followed by the methods that you chose to override.
- Next come any methods that are being optionally overriden from any concrete superclass.
- Then we list our public methods that are specific to this class. I like to start with the public methods that have more meat to them, followed by the getters and setters.
- Package-level implementations are next.
- Private methods follow the package-level ones.
- After the private methods come the abstract methods. I like the abstract methods down here so that when another programmer is looking at this abstract class and wants to find out which methods this class does not implement, he or she can scroll to the bottom. Those one-line method signatures don't get lost among the methods that have a heavier implementation.
Tuesday, October 23, 2012
Simple test for AlertDialog using Robolectric
EDIT (10/30/2014): All references to the method shadowOf() is actually a reference to Robolectric.shadowOf(). I have Robolectric saved as a favorite in Eclipse, which automatically creates a static import for it.
---
Anyone who has tried to use the Android testing framework knows it can be painful, mostly because of the slowness of feedback loop and the setup required to run tests. Robolectric eases this pain. If you're writing an Android application and haven't discovered Robolectric, do yourself a favor. First, it makes it possible to run unit tests without needing to connect an Android device or an emulator. Read more about it here.
---
Anyone who has tried to use the Android testing framework knows it can be painful, mostly because of the slowness of feedback loop and the setup required to run tests. Robolectric eases this pain. If you're writing an Android application and haven't discovered Robolectric, do yourself a favor. First, it makes it possible to run unit tests without needing to connect an Android device or an emulator. Read more about it here.
Ever tried to test the appearance of a
dialog on screen? The Android testing framework makes it hard.
Robolectric makes it easy.
Lets test the apperance of a dialog on
screen with the help of a simple sample activity.
This activity, cleverly called
ChangePasswordActivity, helps a user change his password. The
activity has a field to enter a new password and a second field to
confirm the new password entered. Once satisfied the user can click
the 'Change Password' button to send the changes to the database.
For those of you that might have
trouble visualizing what such an activity might look like, see below.
Below are the field
declarations.
public
class
ChangePasswordActivity
extends
BaseActivity
{
private
EditText
newPassword;
private
Button
changePasswordButton;
private
EditText
confirmNewPassword;
//some
code here
protected
void
inflateViewObjects()
{
newPassword
=
(EditText)
findViewById(id.change_password_new_et);
findViewById(id.change_password_new_et);
confirmNewPassword
=
(EditText)
findViewById(id.change_password_new_confirm_et);
findViewById(id.change_password_new_confirm_et);
changePasswordButton
= (Button)
findViewById(id.change_password_btn);
findViewById(id.change_password_btn);
}
//some code here
}
//some code here
}
Next
let's look at the code that gets invoked when a user pushes the
'Change Password' button.
void
handleSubmitNewPasswordClick()
{
if
(meetsPasswordChangeCriteria())
{
sendPasswordChangeToDatabase();
showConfirmationToast();
finishActivity();
}
}
Before the new password is sent to the
database, it must meet certain criteria. For instance, both of the
fields should contain text, and that text should match. If any of
that criteria is not met, the password is not persisted.
Let's look at the
meetsPasswordChangeCriteria() method.
private
boolean
meetsPasswordChangeCriteria()
{
boolean
criteriaMet =
true;
if
(passwordFieldBlank())
{
criteriaMet
= false;
createPasswordFieldsBlankDialog();
}
if
(passwordFieldsMatch()
&&
criteriaMet) {
criteriaMet
= false;
createPasswordsNotMatchingDialog();
}
return
criteriaMet;
}
Here
we see a couple of conditions are tested. If a condition is not met, a
dialog is created informing the user of the status of the password
change. We'll look at the first condition.
The
passwordFieldIsBlank() method helps set the flag that tells the calling
method, in this case handleSubmitNewPasswordClick(), whether or not
the criteria was met. We'll assume in this case it was not. That
means the passwordFieldIsBlank() method returns true.
Lets
have a look at that simple method.
private
boolean
passwordFieldBlank()
{
return
newPassword.getText().toString().equals("")
||
confirmNewPassword.getText().toString().equals("");
}
Pretty
basic. Check to see if the two fields have any text. If one does not, then this method returns true, which, in the previous
method, meetsPasswordChangeCriteria(), causes the criteriaMet flag to be set to false before
creating a dialog that prompts the user that there is some missing
information. The screen then looks like the one below.
So
finally we get to the reason for this post. How do we create a unit
test that checks to see if this dialog appears?
If
you're reading this test case, you're probably new to Robolectric.
Well, that's fine, I am too. But the following method is an example of a typical
test method in Robolectric and hopefully you're past the point of
setting up your Robolectric test project.
What
we're going to do with this test is create an activity, ensure the
newPassword and confirmPassword fields are blank and then we're going
to programatically push the 'Change Password' button. Then its up to
Robolectric – and its beautiful concept of shadow classes. Let's
look at the code and then I'll talk about it.
public
void
itShowsDialogIfAFieldIsBlank()
{
setUpActivity();
setEditText(R.id.update_email_edittext,
"");
setEditText(R.id.update_username_edittext,
"");
pushUpdateButton();
AlertDialog
alert =
ShadowAlertDialog.getLatestAlertDialog();
ShadowAlertDialog
sAlert =
shadowOf(alert);
assertThat(sAlert.getTitle().toString(),
equalTo(activity.getString(R.string.all_fields_required_)));
}
The
setUpActivity() method encapsulates the setup needed for
instantiating an activity. Right now, think of it as a black box.
Just know that after that method is called, we have a valid instance
of the ChangePasswordActivity class.
The
method setEditText() passes the id of the EditText field we wish to
set the text of and then a string value of the text we wish to be
set. In this case it's blank. The pushUpdateButton() encapsulates
pushing the 'Confirm Password' button. That is just a matter of
calling findViewById() to locate the button element and then calling
performClick on it.
Once
that's done we know that our activity should display a dialog like
the one in the last photo. But how do know. We use the static method
getLatestAlertDialog() of the ShadowAlertDialog class which looks at
the current activity and finds the id of the last dialog that was
show. The activity class holds on to this information.
Once
we have that AlertDialog, we need to make some assertions about it.
Well it would be helpful to know things like the title that is shown
in the dialog. Normally the AlertDialog class doesn't expose a
property that would allow us to examine the title of the dialog, but
if we look at the shadow implementation that Robolectric created, we
can get access to properties that might be useful to test. In this
case, we're asserting that last dialog has a title that says “All
fields required”. Actually here that string value is held in the
string resource file. That way if it ever needs changed, we only need
to change it in one place.
In this assert method we're using the equalTo() method that comes part of the hamcrest core matchers library. You'll have to import org.hamcrest.CoreMatchers. I'm doing a static import. I could have just as easily used assertEquals(string1, string2), but I find that the CoreMatchers library offers some advantages in readability. We can dive into that another time.
The purpose here was to hopefully help someone test something that might have once seemed
untestable.
Subscribe to:
Posts (Atom)


