Trying to implement the same functionality on Android on different devices, OS versions, manufacturers etc. can be more eventful than it should.
This presentation is a guide about how you can achieve your task at hand without compromising code quality and readability. Specifically, I demonstrate how to use Dependency Injection, the MVP pattern and other techniques to your favour, for graceful handling and degrading experience at production code and automated tests.
13. What are the issues?
protected void onResume() {
super.onResume();
SharedPreferences sharedPreferences =
Glue
PreferenceManager.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor = sharedPreferences.edit();
Business
prefEditor.putBoolean("has_launched_before", true);
Logic
// Will only be available for versions after Froyo
if(VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
editor.apply();
} else {
editor.commit();
}
Glue
}
@iordanis_g #fragVsCode
14. How to unit test?
Good luck!
@iordanis_g #fragVsCode
17. New MainActivity
@Override
public void onResume() {
super.onResume();
editor.putBoolean("has_launched_before", true);
preferencesSaver.save(editor);
}
@iordanis_g #fragVsCode
18. Before & After
protected void onResume() {
super.onResume();
SharedPreferences sharedPreferences =
PreferenceManager
.getDefaultSharedPreferences(this);
SharedPreferences.Editor editor =
sharedPreferences.edit();
prefEditor.putBoolean(
"has_launched_before", true);
// Will only be available for versions
after Froyo
if(VERSION.SDK_INT >=
VERSION_CODES.GINGERBREAD) {
editor.apply();
} else {
editor.commit();
}
}
public void onResume() {
super.onResume();
editor.putBoolean(
"has_launched_before", true);
preferencesSaver.save(editor);
}
@iordanis_g #fragVsCode
19. New MainActivity – Constructors
// Constructor the system will call to instantiate this activity
public MainActivity() {
this(sharedPreferencesEditor(), preferencesSaver());
}
// Constructor that can be used in production and test code
public MainActivity(Editor editor, PreferencesSaver preferencesSaver) {
this.editor = editor;
this.preferencesSaver = preferencesSaver;
}
@iordanis_g #fragVsCode
20. Module classes
public class SharedPreferencesEditorModule {
public static SharedPreferences.Editor sharedPreferencesEditor() {
Context context = getApplication().getApplicationContext();
SharedPreferences preferences = getDefaultSharedPreferences(context);
Editor editor = preferences.edit();
return editor;
}
}
public class PreferencesSaverModule {
public static PreferencesSaver preferencesSaver() {
if(VERSION.SDK_INT >= VERSION_CODES.GINGERBREAD) {
return new ApplyPreferencesSaver();
}
return new CommitPreferencesSaver();
}
}
@iordanis_g #fragVsCode
21. Application
public class MyCoolApplication extends Application {
private static MyCoolApplication application;
@Override
public void onCreate() {
application = this;
}
public static MyCoolApplication getApplication() {
return application;
}
}
@iordanis_g #fragVsCode
22. How to unit test?
@Mock Editor editor;
@Mock PreferencesSaver saver;
@Test
public void canSaveLaunchFlag() {
mockery.checking(new Expectations() {{
atLeast(1).of(editor).putBoolean("has_launched_before", true);
oneOf(saver).save(editor);
}});
MainActivity activity = new MainActivity(editor, saver);
activity.onResume();
}
@iordanis_g #fragVsCode
34. How about Acceptance Tests?
public class ShazamTest extends
ActivityInstrumentationTestCase2<ActivityUnderTest> {
private static final int TIMEOUT = 5000;
private Solo solo;
protected void setUp() {
solo = new Solo(getInstrumentation());
}
}
@iordanis_g #fragVsCode
35. How about Acceptance Tests?
public void testCanShazamOnPhone() {
// Action
// User hits the big Shazam button
View shazamButton = solo.getView(R.id.big_button);
solo.clickOnView(shazamButton);
// Assertions
// Track details are showing on full screen
assertThat(solo.waitForCondition(titleShowsOnFullScreen(),
TIMEOUT), is(true));
}
@iordanis_g #fragVsCode
36. How about Acceptance Tests?
public void testShazamingOnTablet() {
// Action
// Hit small Shazam button and wait for animation to finish
final View shazamButton = solo.getView(R.id.small_button);
solo.clickOnView(shazamButton);
waitForAnimationToFinish();
// Assertions
// Track details are showing on side-panel
assertThat(solo.waitForCondition(titleShowsOnSidePanel(),
TIMEOUT), is(true));
}
@iordanis_g #fragVsCode
39. New Acceptance Test
protected void setUp() {
solo = new Solo(getInstrumentation());
user = new User(solo);
}
public void testCanShowTrackAfterShazaming() {
when(user).shazams();
then(user).seesDetailsForTrack();
}
@iordanis_g #fragVsCode
40. User class
public class User implements Actor, Asserter {
private final Solo solo;
public void shazams() {
Action<Solo, Void> action;
if (isTablet()) {
action = new ShazamTabletButtonAction();
} else {
action = new ShazamPhoneButtonAction();
}
action.actOn(solo);
}
public void seesDetailsForTrack() {
Assertion<Solo> assertion;
if (isTablet()) {
assertion = new TabletDetailsAssertion();
} else {
assertion = new PhoneDetailsAssertion();
}
assertion.assertWith(solo);
}
}
@iordanis_g #fragVsCode
41. Actions
public class ShazamPhoneButtonAction implements Action<Solo, Void> {
public void actOn(Solo solo) {
View bigButton = solo.getView(R.id.big_button);
solo.clickOnView(bigButton);
}
}
public class ShazamTabletButtonAction implements Action<Solo, Void> {
public void actOn(Solo solo) {
View smallButton = solo.getView(R.id.small_button);
solo.clickOnView(smallButton);
}
}
@iordanis_g #fragVsCode
42. Assertions
public class PhoneDetailsAssertion implements Assertion<Solo>{
public void assertWith(Solo solo) {
assertThat(solo.waitForCondition(titleShowsOnFullScreen(),
TIMEOUT), is(true));
}
}
public class TabletDetailsAssertion implements Assertion<Solo> {
public void assertWith(Solo solo) {
waitForAnimationToFinish();
assertThat(solo.waitForCondition(titleShowsOnSidePanel(),
TIMEOUT), is(true));
}
}
@iordanis_g #fragVsCode