Building First Class Android Sdks

7ace70cd355db1983dea895fbe01a4ef?s=47 rallat
January 16, 2015

Building First Class Android Sdks

This talk is a summary of the learnings of building fabric.

Based on the original slides from Ty Smith. https://speakerdeck.com/tysmith/andevcon-2014-building-first-class-sdks

7ace70cd355db1983dea895fbe01a4ef?s=128

rallat

January 16, 2015
Tweet

Transcript

  1. Building First Class Android SDK January 15, 2015 #AndroidDevSdks

  2. January 15, 2015 #AndroidDevSdks SDK PEPINO

  3. Israel Ferrer Camacho Senior Software Engineer @rallat

  4. None
  5. None
  6. None
  7. None
  8. Fabric Sample App Cannonball https://github.com/twitterdev

  9. What means to be a First Class SDK?

  10. Modular

  11. None
  12. ABOMINATION

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

  14. Fabric.aar Cache Settings Concurrency Events Network Persistence

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

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

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

    Kit C Kit B KitInterface Interface Features Kit Common Services
  18. Fabric.aar Cache Settings Concurrency Events Network Persistence TwitterCore.aar TweetUi.aar Digits.aar

    TweetComposer.aar Twitter.aar Interface Features Kit Common Services
  19. Extensible

  20. Fabric fabric = new Fabric.Builder(this) .kits(new Crashlytics()) .initializationCallback( new Callback<Fabric>()

    { void success(Fabric fabric) { } void failure(Exception exception) { } }) .build(); Callbacks
  21. 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); }
  22. 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); } ... }
  23. 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); } }
  24. Extensible View Styles <style name="tw__TweetLightStyle"> <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> </style>
  25. Easy to Use Hard to Misuse

  26. >[]+{} Javascript example

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

  28. None
  29. >[]+{} [Object object] >{}+[] Javascript example

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

  31. WAT

  32. WAT

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

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

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

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

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

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

    Fluent Pattern
  39. Adapter Pattern public class TweetViewFetchAdapter<T extends BaseTweetView> extends TweetViewAdapter<T> {

    … } public class TweetViewAdapter<T extends BaseTweetView> extends BaseAdapter { … }
  40. Styling <?xml version="1.0" encoding="utf-8"?> <resources> <style name="CustomDigitsTheme" parent="android:Theme.Material.Light"> <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> </style> </resources>
  41. 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<>() {…});
  42. Detecting Dependencies <manifest … package="com.example.SDK" > <application ... > …

    <meta-data android:value="11235813213455" 
 android:name=“com.fabric.ApiKey” /> </application> </manifest>
  43. 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
  44. 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
  45. public TwitterApiClient { final Session session; public TwitterApiClient(Session session) {

    this.session = session; } public TwitterApiClient() { this.session = Twitter.getSessionManager().getActiveSession(); } } Detecting Dependencies
  46. Minimizing Permissions

  47. Minimizing Permissions <uses-permission android:name="android.permission.INTERNET"/>

  48. Minimizing Permissions crashlytics.setUserEmail(“appuser@domain.com”);

  49. Permissions Detection protected boolean canCheckNetworkState(Context context) { String permission =

    Manifest.permission.ACCESS_NETWORK_STATE; int result = context.checkCallingOrSelfPermission(permission); return (result == PackageManager.PERMISSION_GRANTED); }
  50. Optional Features <manifest … package="com.example.SDK" > <application ... > …

    </application> <uses-feature android:name="android.hardware.camera" android:required=”false” /> </manifest>
  51. Features Detection protected boolean hasCamera(Context context) { PackageManager pm =

    context.getPackageManager(); String camera = PackageManager.FEATURE_CAMERA; return pm.hasSystemFeature(camera); }
  52. Android Version Detection if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) { return formatID(Build.HARDWARE);


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

    e){ }
  54. Intent Detection PackageManager pm = context.getApplicationContext().getPackageManager(); List<ResolveInfo> activities = pm.queryIntentActivities(

    new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH),0); if (activities.size() > 0) { //an app exists that can recognize speech }
  55. 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'
  56. Classpath Detection public class DefaultClient implements Client { private final

    Client client; public DefaultClient() { if (hasOkHttpOnClasspath()) { client = new OkClient(); } else { client = new UrlConnectionClient(); } } … }
  57. Catch unexpected exceptions try { //Do all the things! }

    catch (Exception e){ //Log something meaningful }
  58. 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; }
  59. Gracefully Degrade if (TextUtils.isEmpty(apiKey) if (debuggable){ throw new IllegalArgumentException(“apiKey is

    null!"); } else { Logger.e(…); return null; } }
  60. Testable

  61. Make it Testable/Mockable Avoid static methods

  62. Make it Testable/Mockable Avoid static methods Avoid final classes and

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

    accessing fields directly Utilize interfaces around entry points
  64. 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
  65. 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<Tweet> getTweets() { return network.getTweets(); } }
  66. 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<Tweet> getTweets() { return getNetwork().getTweets(); } … }
  67. Using in Tests package com.example.Tweeter; public class MyTest extends AndroidTestCase

    { Tweeter tweeter; @Override public void setup() { tweeter = mock(Tweeter.class); } ... }
  68. High-performance

  69. Size

  70. Size

  71. 3rd Party Library Mindfulness

  72. 3rd Party Library Mindfulness PROTOBUF KB

  73. 3rd Party Library Mindfulness PROTOBUF KB OURS KB

  74. 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
  75. 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 }
  76. 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
  77. Dalvik Method Count > git clone git@github.com:mihaip/dex-method-counts.git > cd dex-method-counts

    > ant jar > ./dex-method-counts path/to/App.apk Read in 65490 method IDs. <root>: 65490 : 3 accessibilityservice: 6 bluetooth: 2 content: 248 pm: 22 res: 45 com: 53881 example: 283 sdk: 283
  78. Minimize Network Usage 10X SMALLER 100X FASTER XML PROTOBUF

  79. Batching Requests High Power Low Power Idle 24% 30% 63%

    14% 70%
  80. None
  81. Modular

  82. Modular Extensible

  83. Modular Extensible Easy to Use Hard to Misuse

  84. Modular Extensible Easy to Use Hard to Misuse Testable

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

  86. @wendi_ @lientm @tsmith Thanks

  87. #AndroidDevSdks Questions?