Slide 1

Slide 1 text

Building First Class Android SDK January 15, 2015 #AndroidDevSdks

Slide 2

Slide 2 text

January 15, 2015 #AndroidDevSdks SDK PEPINO

Slide 3

Slide 3 text

Israel Ferrer Camacho Senior Software Engineer @rallat

Slide 4

Slide 4 text

No content

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

No content

Slide 7

Slide 7 text

No content

Slide 8

Slide 8 text

Fabric Sample App Cannonball https://github.com/twitterdev

Slide 9

Slide 9 text

What means to be a First Class SDK?

Slide 10

Slide 10 text

Modular

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

ABOMINATION

Slide 13

Slide 13 text

Modularity Fabric.with(this, new Twitter()); Fabric.with(this, new TweetUi(), new TweetComposer());

Slide 14

Slide 14 text

Fabric.aar Cache Settings Concurrency Events Network Persistence

Slide 15

Slide 15 text

Fabric.aar Cache Settings Concurrency Events Network Persistence KitCore Kit Common Services

Slide 16

Slide 16 text

Fabric.aar Cache Settings Concurrency Events Network Persistence KitCore Kit A Kit C Kit B Features Kit Common Services

Slide 17

Slide 17 text

Fabric.aar Cache Settings Concurrency Events Network Persistence KitCore Kit A Kit C Kit B KitInterface Interface Features Kit Common Services

Slide 18

Slide 18 text

Fabric.aar Cache Settings Concurrency Events Network Persistence TwitterCore.aar TweetUi.aar Digits.aar TweetComposer.aar Twitter.aar Interface Features Kit Common Services

Slide 19

Slide 19 text

Extensible

Slide 20

Slide 20 text

Fabric fabric = new Fabric.Builder(this) .kits(new Crashlytics()) .initializationCallback( new Callback() { void success(Fabric fabric) { } void failure(Exception exception) { } }) .build(); Callbacks

Slide 21

Slide 21 text

Extensible Interfaces interface Logger { void d(String tag, String text, Throwable throwable); void v(String tag, String text, Throwable throwable); void i(String tag, String text, Throwable throwable); void w(String tag, String text, Throwable throwable); void e(String tag, String text, Throwable throwable); }

Slide 22

Slide 22 text

Sane Defaults public class DefaultLogger { public DefaultLogger(int logLevel) { this.logLevel = logLevel; } void d(String tag, String text, Throwable throwable) { if (isLoggable(tag, Log.DEBUG)) Log.d(tag, text, throwable); } ... }

Slide 23

Slide 23 text

Extensible Classes class MyApiClient extends TwitterApiClient { interface MyService { @GET(“/1.1/statuses/show.json”) Tweet getTweet(@Query(“id”) int id); } MyService getMyService() { return getService(MyService.class); } }

Slide 24

Slide 24 text

Extensible View Styles <item name="tw__container_bg_color"> @color/tw__tweet_light_container_bg_color </item> <item name="tw__primary_text_color"> @color/tw__tweet_light_primary_text_color </item> <item name="tw__action_color"> @color/tw__tweet_action_color </item>

Slide 25

Slide 25 text

Easy to Use Hard to Misuse

Slide 26

Slide 26 text

>[]+{} Javascript example

Slide 27

Slide 27 text

>[]+{} [Object object] Javascript example

Slide 28

Slide 28 text

No content

Slide 29

Slide 29 text

>[]+{} [Object object] >{}+[] Javascript example

Slide 30

Slide 30 text

>[]+{} [Object object] >{}+[] 0 Javascript example

Slide 31

Slide 31 text

WAT

Slide 32

Slide 32 text

WAT

Slide 33

Slide 33 text

Crashlytics.start(this, 5); Crashlytics.setListener(createCrashlyticsListener()); Crashlytics.setPinningInfo(createPinningInfoProvider()) Crashlytics.getInstance().setDebugMode(true); Initialization

Slide 34

Slide 34 text

Crashlytics.setListener(createCrashlyticsListener()); Crashlytics.setPinningInfo(createPinningInfoProvider()); Crashlytics.getInstance().setDebugMode(true); Crashlytics.start(this, 5); Initialization

Slide 35

Slide 35 text

Crashlytics.start(this, delay, listener, pinningInfo, debugMode); Initialization

Slide 36

Slide 36 text

Crashlytics.start(this, 0, null, null, null, true); Initialization

Slide 37

Slide 37 text

Crashlytics crashlytics = new Crashlytics.Builder() .delay(1) .listener(createCrashlyticsListener()) .pinningInfo(createPinningInfoProvider()) .build(); Fabric.with(this, crashlytics); Fluent Pattern

Slide 38

Slide 38 text

Fabric.with(new Fabric.Builder(this) .kits(new Crashlytics()) .debuggable(true) .logger(new DefaultLogger(Log.VERBOSE)) .looper(getCustomLooper()) .executor(getCustomExecutorService()) .build()); Fluent Pattern

Slide 39

Slide 39 text

Adapter Pattern public class TweetViewFetchAdapter extends TweetViewAdapter { … } public class TweetViewAdapter extends BaseAdapter { … }

Slide 40

Slide 40 text

Styling <item name="android:textColorPrimary"> @android:color/black</item> <item name="android:textColorSecondary"> @android:color/darker_gray</item> <item name="android:windowBackground"> @android:color/white</item> <item name="android:textColorLink">#ff398622</item> <item name="android:colorAccent">#ff398622</item>

Slide 41

Slide 41 text

Intuitive method naming Digits.authenticate(new AuthCallback() { @Override public void success(DigitsSession session, String phoneNumber) { } @Override public void failure(DigitsException error) { } }); adapter.setTweetIds(tweetIds, new LoadCallback<>() {…});

Slide 42

Slide 42 text

Detecting Dependencies …

Slide 43

Slide 43 text

public static Fabric with(Context context) { Context appContext = context.getApplicationContext(); ApplicationInfo ai = context.getPackageManager() .getApplicationInfo(ac.getPackageName(),
 PackageManager.GET_META_DATA); String apiKey = ai.metaData.getString(API_KEY); return new Fabric(appContext, apiKey); } Detecting Dependencies

Slide 44

Slide 44 text

public static Fabric with(Context context) { Context appContext = context.getApplicationContext(); ApplicationInfo ai = context.getPackageManager() .getApplicationInfo(ac.getPackageName(),
 PackageManager.GET_META_DATA); String apiKey = ai.metaData.getString(API_KEY); return new Fabric(appContext, apiKey); } Detecting Dependencies

Slide 45

Slide 45 text

public TwitterApiClient { final Session session; public TwitterApiClient(Session session) { this.session = session; } public TwitterApiClient() { this.session = Twitter.getSessionManager().getActiveSession(); } } Detecting Dependencies

Slide 46

Slide 46 text

Minimizing Permissions

Slide 47

Slide 47 text

Minimizing Permissions

Slide 48

Slide 48 text

Minimizing Permissions crashlytics.setUserEmail(“[email protected]”);

Slide 49

Slide 49 text

Permissions Detection protected boolean canCheckNetworkState(Context context) { String permission = Manifest.permission.ACCESS_NETWORK_STATE; int result = context.checkCallingOrSelfPermission(permission); return (result == PackageManager.PERMISSION_GRANTED); }

Slide 50

Slide 50 text

Optional Features …

Slide 51

Slide 51 text

Features Detection protected boolean hasCamera(Context context) { PackageManager pm = context.getPackageManager(); String camera = PackageManager.FEATURE_CAMERA; return pm.hasSystemFeature(camera); }

Slide 52

Slide 52 text

Android Version Detection if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { return formatID(Build.HARDWARE);
 } return null;

Slide 53

Slide 53 text

Intent Detection try { startActivityForResult(new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH), 0); } catch (ActivityNotFoundException e){ }

Slide 54

Slide 54 text

Intent Detection PackageManager pm = context.getApplicationContext().getPackageManager(); List activities = pm.queryIntentActivities( new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),0); if (activities.size() > 0) { //an app exists that can recognize speech }

Slide 55

Slide 55 text

Classpath Detection private boolean hasOkHttpOnClasspath() { try { Class.forName("com.squareup.okhttp.OkHttpClient"); return true; } catch (ClassNotFoundException e) { } return false; } provided 'com.squareup.okhttp:okhttp:2.0.0'

Slide 56

Slide 56 text

Classpath Detection public class DefaultClient implements Client { private final Client client; public DefaultClient() { if (hasOkHttpOnClasspath()) { client = new OkClient(); } else { client = new UrlConnectionClient(); } } … }

Slide 57

Slide 57 text

Catch unexpected exceptions try { //Do all the things! } catch (Exception e){ //Log something meaningful }

Slide 58

Slide 58 text

Throw expected exceptions /** * Sets the {@link io.fabric.sdk.android.Logger} * @throws java.lang.IllegalArgumentException */ public Builder logger(Logger logger) { if (logger == null) { throw new IllegalArgumentException( "Logger must not be null."); } this.logger = logger; return this; }

Slide 59

Slide 59 text

Gracefully Degrade if (TextUtils.isEmpty(apiKey) if (debuggable){ throw new IllegalArgumentException(“apiKey is null!"); } else { Logger.e(…); return null; } }

Slide 60

Slide 60 text

Testable

Slide 61

Slide 61 text

Make it Testable/Mockable Avoid static methods

Slide 62

Slide 62 text

Make it Testable/Mockable Avoid static methods Avoid final classes and accessing fields directly

Slide 63

Slide 63 text

Make it Testable/Mockable Avoid static methods Avoid final classes and accessing fields directly Utilize interfaces around entry points

Slide 64

Slide 64 text

Make it Testable/Mockable Avoid static methods Avoid final classes and accessing fields directly Utilize interfaces around entry points Provide test package in separate artifact

Slide 65

Slide 65 text

Hard to Test package com.example; public final class Tweeter { private Network network; public static Tweeter getInstance() {...} private Tweeter() { this.network = new Network(); } public List getTweets() { return network.getTweets(); } }

Slide 66

Slide 66 text

Easy to Test package com.example; public final class Tweeter { private Network network; public static Tweeter getInstance() {...} private public Tweeter(Network network) { this.network = new Network() network; } public List getTweets() { return getNetwork().getTweets(); } … }

Slide 67

Slide 67 text

Using in Tests package com.example.Tweeter; public class MyTest extends AndroidTestCase { Tweeter tweeter; @Override public void setup() { tweeter = mock(Tweeter.class); } ... }

Slide 68

Slide 68 text

High-performance

Slide 69

Slide 69 text

Size

Slide 70

Slide 70 text

Size

Slide 71

Slide 71 text

3rd Party Library Mindfulness

Slide 72

Slide 72 text

3rd Party Library Mindfulness PROTOBUF KB

Slide 73

Slide 73 text

3rd Party Library Mindfulness PROTOBUF KB OURS KB

Slide 74

Slide 74 text

How big are Fabric AARs? Fabric: 190kb Crashlytics: 90kb Beta: 13kb Answers: 20kb Twitter API & SSO: 296kb Tweet UI: 120kb Tweet Composer: 5kb Digits: 236kb

Slide 75

Slide 75 text

Reporting Binary Size task reportSdkFootprint << { def sdkProject = project(':clients:SdkProject') def nonSdkProject = project(':clients:NonSdkProject') def crashlyticsFootprint = getSizeDifference( new File("${sdkProject.buildDir}.../Sdk.apk"), new File("${nonSdkProject.buildDir}.../NonSdk.apk") ) println crashlyticsFootprint }

Slide 76

Slide 76 text

Dalvik Method Count >./gradlew assemble … Unable to execute dex: method ID not in [0, 
 0xffff]: 65536 Conversion to Dalvik format failed: Unable to 
 execute dex: method ID not in [0, 0xffff]: 65536

Slide 77

Slide 77 text

Dalvik Method Count > git clone [email protected]:mihaip/dex-method-counts.git > cd dex-method-counts > ant jar > ./dex-method-counts path/to/App.apk Read in 65490 method IDs. : 65490 : 3 accessibilityservice: 6 bluetooth: 2 content: 248 pm: 22 res: 45 com: 53881 example: 283 sdk: 283

Slide 78

Slide 78 text

Minimize Network Usage 10X SMALLER 100X FASTER XML PROTOBUF

Slide 79

Slide 79 text

Batching Requests High Power Low Power Idle 24% 30% 63% 14% 70%

Slide 80

Slide 80 text

No content

Slide 81

Slide 81 text

Modular

Slide 82

Slide 82 text

Modular Extensible

Slide 83

Slide 83 text

Modular Extensible Easy to Use Hard to Misuse

Slide 84

Slide 84 text

Modular Extensible Easy to Use Hard to Misuse Testable

Slide 85

Slide 85 text

Modular Extensible Easy to Use Hard to Misuse Testable High-performance

Slide 86

Slide 86 text

@wendi_ @lientm @tsmith Thanks

Slide 87

Slide 87 text

#AndroidDevSdks Questions?