Slide 1

Slide 1 text

Continuous integration and deployment on Android (plus some sweets) DINO KOVAČ / ŽELJKO PLESAC ANDROID ENGINEERS @ INFINUM

Slide 2

Slide 2 text

ABOUT INFINUM • independent design and development agency • 90 employees • 15 Android Engineers • hundreds of projects

Slide 3

Slide 3 text

01 EARLY PROBLEMS

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

TITLE Nunc commodo mi dictum nisi iaculis ullamcorper donec bibendum.

Slide 7

Slide 7 text

02 CODE QUALITY

Slide 8

Slide 8 text

REGRESSIONS • the worst kind of bugs • some of them are easily detectable!

Slide 9

Slide 9 text

STATIC ANALYSIS TOOLS • lint • checkstyle • findbugs • pmd

Slide 10

Slide 10 text

ANDROID LINT • common android specific issues and best practices • LinearLayout orientation • using method introduced in SDK level > minSdk • missing super calls

Slide 11

Slide 11 text

CHECKSTYLE • helps to keep a consistent code style • highly configurable using config file • works well in combination with AS code style if(url.equals(otherUrl)){
 System.out.print("it's equal!");
 }
 
 if(url.equals(otherUrl))
 System.out.print("it's equal!");
 
 if (url.equals(otherUrl)) {
 System.out.print("it's equal!");
 }

Slide 12

Slide 12 text

PMD, FINDBUGS • warn you about common Java pitfalls • findbugs works on class files • pmd works on source files

Slide 13

Slide 13 text

JAVA.NET.URL

Slide 14

Slide 14 text

JAVA.NET.URL @Test
 public void urlEquals() throws Exception {
 URL url = new URL("https://infinum.co");
 URL otherUrl = new URL("https://www.infinum.co");
 
 Assert.assertEquals(url, otherUrl);
 }

Slide 15

Slide 15 text

No content

Slide 16

Slide 16 text

JAVA.NET.URL - JAVADOCS • Two hosts are considered equivalent if both host names can be resolved into the same IP addresses; else if either host name can't be resolved, the host names must be equal without regard to case; or both host names equal to null. • Since hosts comparison requires name resolution, this operation is a blocking operation.

Slide 17

Slide 17 text

MATH.ABS • does not always return positive numbers! • integer values range from -2147483648 to 2147483647 Assert.assertTrue(Math.abs(Integer.MIN_VALUE) >= 0);

Slide 18

Slide 18 text

03 TESTING

Slide 19

Slide 19 text

TESTING APPROACHES • instrumentation tests - robotium / espresso • unit tests - plain JUnit • functional tests - robolectric

Slide 20

Slide 20 text

TESTS • we use a combination of unit tests and functional tests • centered around the clients needs and the specification

Slide 21

Slide 21 text

GREAT LIBRARIES • JUnit 4 • Mockito • OkHttp MockWebServer • Dagger 2 • assertj-android

Slide 22

Slide 22 text

@Test
 public void correctLoginRequest() throws Exception {
 String user = "testUserName";
 String pass = "testPass0&";
 
 enqueueResponse("sdk-login-response.json");
 Sdk.login(user, pass, mock(SdkCallback.class));
 
 RecordedRequest request = takeLastRequest();
 
 assertThat(request.getMethod()).isEqualTo("POST");
 assertThat(request.getPath()).isEqualTo("/login");
 assertThat(request.getHeader(“Accept")) .isEqualTo("application/json");
 assertThat(request.getHeader(“Content-Type")) .isEqualTo("application/x-www-form-urlencoded");
 assertThat(request.getBody().readUtf8()) .isEqualTo("login=" + user + "&password=" + "testPass0%26");
 }

Slide 23

Slide 23 text

CONTINUOUS INTEGRATION • each push to the git repo triggers a build on the CI service • CI service checks out the code, runs static analysis and tests • if the build fails, you get notified • if you break the build you fix it!

Slide 24

Slide 24 text

CIRCLE CI • hosted CI service • nice android support • configurable via .yml file • support for saving and displaying build artifacts

Slide 25

Slide 25 text

CIRCLE CI - DOWNSIDES • integrates only with github • no support for x86 emulators • your code is ‘in the cloud’

Slide 26

Slide 26 text

No content

Slide 27

Slide 27 text

BROKEN BUILDS • clients want their features done yesterday • project managers / product owners don’t care about broken builds • ‘we’ll fix the build after the release’

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

No content

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

No content

Slide 32

Slide 32 text

04 DEPLOYMENT

Slide 33

Slide 33 text

TITLE Nunc commodo mi dictum nisi iaculis ullamcorper donec bibendum.

Slide 34

Slide 34 text

AUTOMATIC DEPLOY FOR CLIENTS • for each push to the labs branch the release build is built • the CI build number is added to versionName: ‘1.0.0-b45’ • the .apk is uploaded to Infinum Labs • the last commit message is used as changelog

Slide 35

Slide 35 text

TAKEAWAY Try stuff out and see what works for you!

Slide 36

Slide 36 text

HOW TO HANDLE PROBLEMS IN PRODUCTION?

Slide 37

Slide 37 text

UNRESPONSIVE APPS

Slide 38

Slide 38 text

Android OS is multithreaded - there are other threads beside main UI thread.

Slide 39

Slide 39 text

ENFORCE RULES • don’t block the main thread - even regular users will notice flickering • get familiar with multithreading components • use Traceview and dmtracedump

Slide 40

Slide 40 text

STRICT MODE • use Strict mode in debug builds - set penalty death • detectDiskReads() • detectDiskWrites()

Slide 41

Slide 41 text

DETECT ANR’S • ANRWatchDog (link) • detects Android ANRs (Application Not Responding) problems • can either crash your application or notify you via callback

Slide 42

Slide 42 text

CRASH FAST

Slide 43

Slide 43 text

CRASH YOU APPLICATIONS AS SOON AS POSSIBLE • Square’s approach to handling crashes (presentation and video) • organise your code in a way that it crashes as soon as possible • returning null values is evil

Slide 44

Slide 44 text

public class Person { private String name; private String surname; public Person(String name, String surname) { this.name = name; this.surname = surname; } … }

Slide 45

Slide 45 text

public static String getFullName(Person person) { return person.getName() + person.getSurname(); }

Slide 46

Slide 46 text

public static String getFullName(Person person) { if(person != null){ return person.getName() + person.getSurname(); } else{ return null; } }

Slide 47

Slide 47 text

public static String getFullName(Person person) { if (person == null) { throw new IllegalStateException("Person cannot be null!”); } return person.getName() + person.getSurname(); }

Slide 48

Slide 48 text

LOG AND MEASURE

Slide 49

Slide 49 text

LOG AND MEASURE YOUR CRASHES • many great tools (Crashlytics, AppsDynamics, Crittercism) • analyse your crashes • custom ROMs causing crashes? • cheap, low quality devices? • frequency of crashes?

Slide 50

Slide 50 text

No content

Slide 51

Slide 51 text

I don’t care about warnings, only errors. - KING HENRIK VIII.

Slide 52

Slide 52 text

THE TRY-CATCH BLOCK AND EXCEPTIONS • you should care about your handled exceptions • they have to be logged and analysed • should contain useful information

Slide 53

Slide 53 text

public class ProfilePresenterImpl implements ProfilePresenter{ public void showPersonData() { view.showFullName(PersonUtils.getFullName(person))); view.showBirthday(PersonUtils.getFormattedBirthday(person)) view.hideLoadingDialog(); } }

Slide 54

Slide 54 text

public class ProfilePresenterImpl implements ProfilePresenter{ public void showPersonData() { String fullName = PersonUtils.getFullName(person)); if(fullName != null){ view.showFullName(PersonUtils.getFullName(person))); } view.showBirthday(PersonUtils.getFormattedBirthday(person)) view.hideLoadingDialog(); } }

Slide 55

Slide 55 text

public class ProfilePresenterImpl implements ProfilePresenter{ public void showPersonData() { try{ String fullName = PersonUtils.getFullName(person)); if(fullName != null){ view.showFullName(PersonUtils.getFullName(person))); } view.showBirthday(PersonUtils.getFormattedBirthday(person))) } catch(Exception e){ e.printStackTrace(); view.showErrorDialog(); } } }

Slide 56

Slide 56 text

TIMBER • Utility on top of Android's default Log class • by Jake Wharton • can be configured

Slide 57

Slide 57 text

CRASH REPORTING TREE private static class CrashReportingTree extends Timber.Tree {
 
 @Override
 protected void log(int priority, String tag, String message, Throwable t) {
 if (priority == Log.VERBOSE || priority == Log.DEBUG) {
 return;
 }
 
 // will write to the crash report but NOT to logcat
 Crashlytics.log(message);
 
 if (t != null) {
 Crashlytics.logException(t);
 }
 }
 }

Slide 58

Slide 58 text

CRASH REPORTING TREE @Override
 public void onCreate() {
 super.onCreate(); if (BuildConfig.DEBUG) {
 Timber.plant(new Timber.DebugTree());
 } else { Fabric.with(this, new Crashlytics());
 Timber.plant(new CrashReportingTree());
 }
 }

Slide 59

Slide 59 text

public class ProfilePresenterImpl implements ProfilePresenter{ public void showPersonData() { try{ String fullName = PersonUtils.getFullName(person)); if(fullName != null){ view.showFullName(PersonUtils.getFullName(person))); } view.showBirthday(PersonUtils.getFormattedBirthday(person ))));} } catch(Exception e){ Timber.e(e, “Failure in “ + getClass().getSimpleName()); view.showErrorDialog(); } } }

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

HIDE CRASHES FROM YOUR USERS

Slide 62

Slide 62 text

Crashes are just exceptions, which are not handled by your application*. * IN MOST CASES

Slide 63

Slide 63 text

APP CRASH HANDLERS • define custom app crash handler in everyone of your production builds • avoid ugly system dialogs • watch out for app restart loops!

Slide 64

Slide 64 text

public class AppCrashHandler implements Thread.UncaughtExceptionHandler {
 
 private Activity liveActivity;
 
 public AppCrashHandler(Application application) {
 application.registerActivityLifecycleCallbacks(new Application.ActivityLifecycleCallbacks() { 
 @Override
 public void onActivityResumed(Activity activity) {
 liveActivity = activity;
 }
 @Override
 public void onActivityPaused(Activity activity) {
 liveActivity = null;
 }
 });
 }
 @Override
 public void uncaughtException(Thread thread, Throwable ex) {
 if(liveActivity != null){
 Intent intent = new Intent(getApplicationContext(), MainActivity.class);
 intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
 liveActivity.finish();
 liveActivity.startActivity(intent);
 }
 
 System.exit(0);
 }
 } CUSTOM CRASH HANDLER

Slide 65

Slide 65 text

APPLICATION CLASS @Override
 public void onCreate() {
 super.onCreate();
 Thread.setDefaultUncaughtExceptionHandler(new AppCrashHandler(this));
 }

Slide 66

Slide 66 text

→ EXAMPLE

Slide 67

Slide 67 text

GOOGLE PLAY FEATURES

Slide 68

Slide 68 text

UTILISE GOOGLE PLAY TOOLS • alpha/beta test • staged rollouts

Slide 69

Slide 69 text

ROUNDUP.

Slide 70

Slide 70 text

Care Minimise Hide

Slide 71

Slide 71 text

Thank you! [email protected] / @DINO_BLACKSMITH [email protected] / @ZELJKOPLESAC Visit infinum.co or find us on social networks: infinum.co infinumco infinumco infinum YES, WE ARE HIRING!