Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Building First Class Android Sdks

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

rallat

January 16, 2015
Tweet

More Decks by rallat

Other Decks in Programming

Transcript

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

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

    TweetComposer.aar Twitter.aar Interface Features Kit Common Services
  3. Fabric fabric = new Fabric.Builder(this) .kits(new Crashlytics()) .initializationCallback( new Callback<Fabric>()

    { void success(Fabric fabric) { } void failure(Exception exception) { } }) .build(); Callbacks
  4. 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); }
  5. 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); } ... }
  6. 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); } }
  7. 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>
  8. WAT

  9. WAT

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

    … } public class TweetViewAdapter<T extends BaseTweetView> extends BaseAdapter { … }
  11. 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>
  12. 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<>() {…});
  13. Detecting Dependencies <manifest … package="com.example.SDK" > <application ... > …

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

    this.session = session; } public TwitterApiClient() { this.session = Twitter.getSessionManager().getActiveSession(); } } Detecting Dependencies
  17. Permissions Detection protected boolean canCheckNetworkState(Context context) { String permission =

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

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

    context.getPackageManager(); String camera = PackageManager.FEATURE_CAMERA; return pm.hasSystemFeature(camera); }
  20. 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 }
  21. 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'
  22. Classpath Detection public class DefaultClient implements Client { private final

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

    catch (Exception e){ //Log something meaningful }
  24. 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; }
  25. Make it Testable/Mockable Avoid static methods Avoid final classes and

    accessing fields directly Utilize interfaces around entry points
  26. 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
  27. 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(); } }
  28. 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(); } … }
  29. Using in Tests package com.example.Tweeter; public class MyTest extends AndroidTestCase

    { Tweeter tweeter; @Override public void setup() { tweeter = mock(Tweeter.class); } ... }
  30. 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
  31. 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 }
  32. 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
  33. 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. <root>: 65490 : 3 accessibilityservice: 6 bluetooth: 2 content: 248 pm: 22 res: 45 com: 53881 example: 283 sdk: 283