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.

E0ac1256093c733a5d5a26085b90966f?s=128

Andy Dyer

March 18, 2016
Tweet

Transcript

  1. Things That Suck About Android Development by Andy Dyer #mdevcon

  2. #mdevcon

  3. "Everything's amazing and nobody's happy" — Louis CK #mdevcon

  4. #mdevcon

  5. #mdevcon

  6. Java 6 No Java 8 due to Oracle's beef with

    Google* #mdevcon
  7. Java 6 Lambdas with Retrolambda button.setOnClickListener(new View.OnClickListener() { @Override public

    void onClick(View v) { doSomething(v); } }); button.setOnClickListener(v -> doSomething(v)); #mdevcon
  8. 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
  9. #mdevcon

  10. Tools: Weird Stuff → Clean Project vs Rebuild Project →

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

    XXXXXHDPI → Custom fonts → Bottom tab bars, carets on list items, etc. #mdevcon
  12. Build times + slow emulator = long feedback loop #mdevcon

  13. Shorten the Feedback Loop → New emulator & Instant Run

    → Genymotion emulator → Gradle offline mode → Launch activity or skeleton app → Automated Testing > Manual Testing #mdevcon
  14. 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
  15. 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
  16. MVP: Model public class Shift { private long id; private

    long employeeId; private DateTime startTime; private DateTime endTime; // ... // Constructor // Getters } #mdevcon
  17. 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
  18. 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
  19. MVP: Presenter Interface public interface ShiftsPresenter { void loadShifts(DateTime startTime,

    DateTime endTime); } #mdevcon
  20. 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
  21. 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
  22. 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
  23. 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
  24. #mdevcon

  25. Android OS → Fragmentation → Old APIs - ContentProvider, SyncAdapter,

    AccountManager, Camera, AIDL → Confusing flags for adjusting the view when showing the keyboard, launching intents, etc. #mdevcon
  26. Keyboard Input Mode android:windowSoftInputMode="adjustPan" android:windowSoftInputMode="adjustResize|stateHidden" android:windowSoftInputMode="dontCoverMyEditText|please" #mdevcon

  27. 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
  28. Fragments #mdevcon

  29. Unable to execute dex: method ID not in [0, 0xffff]:

    65536 #mdevcon
  30. Practicing Safe DEX → Choose your partners (libraries) carefully →

    Get tested regularly (method count check) → Use protection (Proguard & lint checks) #mdevcon
  31. Proguard: build.gradle buildTypes { debug { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'),

    'proguard-rules.pro' testProguardFile 'proguard-test-rules.pro' } } #mdevcon
  32. 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
  33. 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
  34. #mdevcon

  35. 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
  36. Play Store → Everyone expects your app/upgrades to be free

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

    phones released with old OS versions → Slow to no upgrades #mdevcon
  38. "Everything's pretty good and getting better" — Andy #mdevcon

  39. andydyer.org @dammitandy #mdevcon