Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Things That Suck About Android Development

Things That Suck About Android Development

Developing for Android can be an emotional rollercoaster. We alternate from pure childlike joy when new tools & APIs are announced to feelings of impending doom when we have to do anything involving camera support or ContentProviders. Talk to anyone who has been working with Android for long and the conversation inevitably turns to “war stories”, recounting hard battles fought and won (or at least survived) while building apps for our favorite mobile platform.

Are you new to Android development? If so, this session will show you where the dragons are so you can avoid many of the common pitfalls. If you’re a veteran, you’ll certainly recognize some of these frustrations while hopefully learning about some others you’ve been fortunate enough to elude.

From the Java language and development tools to frameworks/APIs and the Play Store, nothing is sacred.

Andy Dyer

March 18, 2016
Tweet

More Decks by Andy Dyer

Other Decks in Programming

Transcript

  1. Java 6 Lambdas with Retrolambda button.setOnClickListener(new View.OnClickListener() { @Override public

    void onClick(View v) { doSomething(v); } }); button.setOnClickListener(v -> doSomething(v)); #mdevcon
  2. Java 6 Streams with RxJava Map<Long, Position> positionsMap = getPositionsMap();

    Observable.from(getPayrollHours()) .filter(payrollHour -> payrollHour.getUserId() == userId) .groupBy(payrollHour -> payrollHour.getPositionId()) .flatMap(group -> { Position position = positionsMap.get(group.getKey()); return group.toList().map(hours -> new PositionSummary(position, hours)); }) .toList() .toBlocking() .firstOrDefault(new ArrayList<>()); #mdevcon
  3. Tools: Weird Stuff → Clean Project vs Rebuild Project →

    File > New > Fragment/Activity boilerplate → Problems using Git SHA in APK name #mdevcon
  4. Tools: Pixel Pushing → 10 px = ? dp →

    XXXXXHDPI → Custom fonts → Bottom tab bars, carets on list items, etc. #mdevcon
  5. Shorten the Feedback Loop → New emulator & Instant Run

    → Genymotion emulator → Gradle offline mode → Launch activity or skeleton app → Automated Testing > Manual Testing #mdevcon
  6. Tools: Testing → Default Android architecture is not easily testable

    → Two different test flavors - androidTest for instrumentation tests and test for unit tests → Flaky emulator performance when running more than a few instrumentation tests #mdevcon
  7. Testing Tips → Separate activities/fragments from business logic with a

    design pattern like Model-View-Presenter → Favor unit tests over instrumentation tests. They run much faster. → Turn off animations in Settings > Developer Options to fix emulator flakiness. → It will be hard at first. Power through it! #mdevcon
  8. MVP: Model public class Shift { private long id; private

    long employeeId; private DateTime startTime; private DateTime endTime; // ... // Constructor // Getters } #mdevcon
  9. MVP: View Interface public interface ShiftsView { void bindShifts(List<Shift> shifts);

    void toggleProgressVisibility(boolean visible); void showError(Throwable throwable); <T> Observable.Transformer<T, T> bindToLifecycle(); } #mdevcon
  10. MVP: View Implementation public class ShiftsFragment extends RxFragment implements ShiftsView

    { @Override public void onViewCreated(View view, Bundle savedInstanceState) { ShiftsPresenter presenter = new ShiftsPresenterImpl(this, api, schedulerTransformer); presenter.loadShifts(getArguments().getLong(ARG_SHIFT_ID)); } @Override public void bindShifts(List<Shift> shifts) { } @Override public void toggleProgressVisibility(boolean visible) { } @Override public void showError(Throwable throwable) { } } #mdevcon
  11. MVP: Presenter Implementation public class ShiftsPresenterImpl implements ShiftsPresenter { public

    ShiftsPresenterImpl(ShiftsView view, MyRetrofitApi api, Observable.Transformer schedulerTransformer) { // Set fields } @Override public void loadShifts(DateTime startTime, DateTime endTime) { view.toggleProgressVisibility(true); api.listShifts(startTime, endTime) .doOnNext(response -> view.toggleProgressVisibility(false)) .doOnError(response -> view.toggleProgressVisibility(false)) .compose(schedulerTransformer) .compose(view.bindToLifecycle()) .subscribe(shifts -> view.bindShifts(shifts), throwable -> view.showError(throwable)); } } #mdevcon
  12. MVP: Presenter Testing public class ShiftsPresenterTest { @Mock private ShiftsView

    shiftsView; @Mock private MyRetrofitApi api; private ShiftsPresenter presenter; @Before public void setUp() throws Exception { MockitoAnnotations.initMocks(this); when(shiftsView.bindToLifecycle()) .thenReturn(new EmptyLifecycleTransformer<>()); presenter = new ShiftsPresenter(shiftsView, api, new ImmediateTransformer<>()); } #mdevcon
  13. MVP: Presenter Testing @Test public void itShowsAndHidesProgressIndicator() { loadShiftsSuccessfully(); verify(shiftsView).toggleProgressVisibility(true);

    verify(shiftsView).toggleProgressVisibility(false); } @Test public void itBindsShifts() { loadShiftsSuccessfully(); verify(shiftsView).bindShifts(anyList()); } #mdevcon
  14. MVP: Presenter Testing private void loadShiftsSuccessfully() { when(api.listShifts(any(), any())) .thenReturn(Observable.just(new

    ArrayList<Shift>())); presenter.loadShifts(DateTime.now(), DateTime.now()); } @Test public void itShowsErrorIfRequestFails() { Throwable expectedError = new Exception("Boom!"); when(api.listShifts(any(), any())).thenReturn(Observable.error(expectedError)); presenter.loadShifts(DateTime.now(), DateTime.now()); verify(shiftsView).showError(eq(expectedError)); } } #mdevcon
  15. Android OS → Fragmentation → Old APIs - ContentProvider, SyncAdapter,

    AccountManager, Camera, AIDL → Confusing flags for adjusting the view when showing the keyboard, launching intents, etc. #mdevcon
  16. Intent Flags public static Intent newIntent(Context context, long id) {

    Intent intent = new Intent(context, TopActivity.class); intent.putExtra(EXTRA_ID, id); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); return intent; } #mdevcon
  17. Practicing Safe DEX → Choose your partners (libraries) carefully →

    Get tested regularly (method count check) → Use protection (Proguard & lint checks) #mdevcon
  18. Proguard: proguard-rules.pro ### Retrofit, OkHttp, and RxJava -keepattributes Signature -keepattributes

    *Annotation* -keep class com.squareup.okhttp.** { *; } -keep interface com.squareup.okhttp.** { *; } -dontwarn com.squareup.okhttp.** -dontwarn rx.** -dontwarn retrofit.** -keep class retrofit.** { *; } -keepclasseswithmembers class * { @retrofit.http.* <methods>; } #mdevcon
  19. Proguard: proguard-test-rules.pro -ignorewarnings # only if you're sure the remaining

    ones can be ignored -keepattributes *Annotation* -dontnote junit.framework.** -dontnote junit.runner.** -dontwarn android.test.** -dontwarn android.support.test.** -dontwarn org.junit.** -dontwarn org.hamcrest.** -dontwarn com.squareup.javawriter.JavaWriter -dontwarn org.mockito.** -keep class com.example.app.di.** { *; } #mdevcon
  20. Play Services → Consumes almost half of 65K method limit

    → Did you setup your debug SHA in the Developers Console? / Why isn't this JSON config file working? → Hard to test on emulators → APIs that require OAuth (i.e. Drive) → Constantly changing & evolving #mdevcon
  21. Play Store → Everyone expects your app/upgrades to be free

    → Feature ransom app reviews → Developer Terms violation process #mdevcon
  22. Manufacturers & Carriers → UI customizations and bloatware → Cheap

    phones released with old OS versions → Slow to no upgrades #mdevcon