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. Things That Suck About
    Android Development
    by Andy Dyer
    #mdevcon

    View full-size slide

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

    View full-size slide

  3. Java 6
    No Java 8 due to Oracle's
    beef with Google*
    #mdevcon

    View full-size slide

  4. Java 6 Lambdas with Retrolambda
    button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
    doSomething(v);
    }
    });
    button.setOnClickListener(v -> doSomething(v));
    #mdevcon

    View full-size slide

  5. Java 6 Streams with RxJava
    Map 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

    View full-size slide

  6. Tools: Weird Stuff
    → Clean Project vs Rebuild Project
    → File > New > Fragment/Activity boilerplate
    → Problems using Git SHA in APK name
    #mdevcon

    View full-size slide

  7. Tools: Pixel Pushing
    → 10 px = ? dp
    → XXXXXHDPI
    → Custom fonts
    → Bottom tab bars, carets on list items, etc.
    #mdevcon

    View full-size slide

  8. Build times
    + slow emulator
    = long feedback loop
    #mdevcon

    View full-size slide

  9. Shorten the
    Feedback Loop
    → New emulator & Instant Run
    → Genymotion emulator
    → Gradle offline mode
    → Launch activity or skeleton app
    → Automated Testing > Manual
    Testing
    #mdevcon

    View full-size slide

  10. 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

    View full-size slide

  11. 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

    View full-size slide

  12. MVP: Model
    public class Shift {
    private long id;
    private long employeeId;
    private DateTime startTime;
    private DateTime endTime;
    // ...
    // Constructor
    // Getters
    }
    #mdevcon

    View full-size slide

  13. MVP: View Interface
    public interface ShiftsView {
    void bindShifts(List shifts);
    void toggleProgressVisibility(boolean visible);
    void showError(Throwable throwable);
    Observable.Transformer bindToLifecycle();
    }
    #mdevcon

    View full-size slide

  14. 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 shifts) { }
    @Override
    public void toggleProgressVisibility(boolean visible) { }
    @Override
    public void showError(Throwable throwable) { }
    }
    #mdevcon

    View full-size slide

  15. MVP: Presenter Interface
    public interface ShiftsPresenter {
    void loadShifts(DateTime startTime, DateTime endTime);
    }
    #mdevcon

    View full-size slide

  16. 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

    View full-size slide

  17. 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

    View full-size slide

  18. 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

    View full-size slide

  19. MVP: Presenter Testing
    private void loadShiftsSuccessfully() {
    when(api.listShifts(any(), any()))
    .thenReturn(Observable.just(new ArrayList()));
    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

    View full-size slide

  20. Android OS
    → Fragmentation
    → Old APIs - ContentProvider, SyncAdapter,
    AccountManager, Camera, AIDL
    → Confusing flags for adjusting the view when
    showing the keyboard, launching intents, etc.
    #mdevcon

    View full-size slide

  21. Keyboard Input Mode
    android:windowSoftInputMode="adjustPan"
    android:windowSoftInputMode="adjustResize|stateHidden"
    android:windowSoftInputMode="dontCoverMyEditText|please"
    #mdevcon

    View full-size slide

  22. 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

    View full-size slide

  23. Fragments
    #mdevcon

    View full-size slide

  24. Unable to execute dex:
    method ID not in [0,
    0xffff]:
    65536
    #mdevcon

    View full-size slide

  25. Practicing Safe DEX
    → Choose your partners (libraries) carefully
    → Get tested regularly (method count check)
    → Use protection (Proguard & lint checks)
    #mdevcon

    View full-size slide

  26. Proguard: build.gradle
    buildTypes {
    debug {
    minifyEnabled true
    proguardFiles getDefaultProguardFile('proguard-android.txt'),
    'proguard-rules.pro'
    testProguardFile 'proguard-test-rules.pro'
    }
    }
    #mdevcon

    View full-size slide

  27. 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.* ;
    }
    #mdevcon

    View full-size slide

  28. 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

    View full-size slide

  29. 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

    View full-size slide

  30. Play Store
    → Everyone expects your app/upgrades to be free
    → Feature ransom app reviews
    → Developer Terms violation process
    #mdevcon

    View full-size slide

  31. Manufacturers
    & Carriers
    → UI customizations and
    bloatware
    → Cheap phones released with
    old OS versions
    → Slow to no upgrades
    #mdevcon

    View full-size slide

  32. "Everything's pretty good
    and getting better"
    — Andy
    #mdevcon

    View full-size slide

  33. andydyer.org
    @dammitandy
    #mdevcon

    View full-size slide