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

Building First Class Android Sdks

Sponsored · Ship Features Fearlessly Turn features on and off without deploys. Used by thousands of Ruby developers.
Avatar for rallat 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

Avatar for rallat

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