Slide 1

Slide 1 text

Things That Suck About Android Development by Andy Dyer #mdevcon

Slide 2

Slide 2 text

#mdevcon

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

#mdevcon

Slide 5

Slide 5 text

#mdevcon

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

#mdevcon

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Build times + slow emulator = long feedback loop #mdevcon

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

#mdevcon

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

Fragments #mdevcon

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

#mdevcon

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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

Slide 39

Slide 39 text

andydyer.org @dammitandy #mdevcon