Android development - Tips & Tricks

Hi!

linkedin.com/in/cosminstefan

cosmin@greenerpastures.ro

Goal for today?

Project Setup &
Build optimizations

Optimize Crashlytics
on debug builds

// build.gradle (app)
android {
  ...
  buildTypes {
   
    debug {
      ...
      firebaseCrashlytics {
      
        // If you don't need crash reporting for your debug build,
        // you can speed up your build by disabling mapping file uploading.
        mappingFileUploadEnabled false
      
      }
    }
  }
}

Optimize Crashlytics
on debug builds

// build.gradle (app)
android {
  ...
  buildTypes {
   
    debug {
      ...
      firebaseCrashlytics {
      
        // If you don't need crash reporting for your debug build,
        // you can speed up your build by disabling mapping file uploading.
        mappingFileUploadEnabled false
      
      }
    }
  }
}


<!-- AndroidManifest.xml -->
<meta-data android:name="firebase_crashlytics_collection_enabled"
           android:value="${firebaseCrashlyticsCollectionEnabled}" />

Optimize Crashlytics
on debug builds

// build.gradle (app)
android {
  ...
  buildTypes {
   
    debug {
      ...
      firebaseCrashlytics {
      
        // If you don't need crash reporting for your debug build,
        // you can speed up your build by disabling mapping file uploading.
        mappingFileUploadEnabled false
      
        // Or you can opt out of reporting completely
        // (Make sure you enable the flag on release builds)
        manifestPlaceholders.firebaseCrashlyticsCollectionEnabled = false
      }
    }
  }
}


<!-- AndroidManifest.xml -->
<meta-data android:name="firebase_crashlytics_collection_enabled"
           android:value="${firebaseCrashlyticsCollectionEnabled}" />

 Avoid compiling unnecessary resources

// build.gradle (app)
android {
  ...
  productFlavors {
    
    staging {
      ...
    
      // The following configuration limits the "staging" flavor to using
      // EN gresources and xxxhdpi screen-density resources.
      resConfigs "en", "xxxhdpi"
    }
    
    ...
  }
}

Profile your build

./gradlew clean

./gradlew --profile --offline --rerun-tasks assembleFlavorDebug

Dependencies versioning

// build.gradle (root)
ext {
  ...

  // Timber - https://github.com/JakeWharton/timber
  timber = "com.jakewharton.timber:timber:4.7.1"
  
  
  
}

Dependencies versioning

// build.gradle (root)
ext {
  ...

  // Timber - https://github.com/JakeWharton/timber
  timber = "com.jakewharton.timber:timber:4.7.1"
  
  // Retrofit - https://github.com/square/retrofit/releases
  retrofitVersion = '2.8.1'
  retrofit = [
    core: "com.squareup.retrofit2:retrofit:$retrofitVersion",
    gson: "com.squareup.retrofit2:converter-gson:$retrofitVersion"
  ]
  
  
}

Dependencies versioning

// build.gradle (root)
ext {
  ...

  // Timber - https://github.com/JakeWharton/timber
  timber = "com.jakewharton.timber:timber:4.7.1"
  
  // Retrofit - https://github.com/square/retrofit/releases
  retrofitVersion = '2.8.1'
  retrofit = [
    core: "com.squareup.retrofit2:retrofit:$retrofitVersion",
    gson: "com.squareup.retrofit2:converter-gson:$retrofitVersion"
  ]
  
  // AndroidX - https://developer.android.com/jetpack/androidx/releases/
  androidWorkManagerVersion = '2.3.4'
  androidx = [
    appcompat   : "androidx.appcompat:appcompat:1.2.0",
    fragment    : "androidx.fragment:fragment-ktx:1.2.5",
    workManager : [
      runtime   : "androidx.work:work-runtime:$androidWorkManagerVersion",
      rx        : "androidx.work:work-rxjava2:$androidWorkManagerVersion",
    ],
  ]  
}

Dependencies versioning

// build.gradle (app)
dependencies {
  ...

  // Timber
  implementation timber

  // Retrofit
  implementation retrofit.core
  implementation retrofit.gson

  // AndroidX
  implementation androidx.fragment
  implementation androidx.workManager.runtime
  implementation androidx.workManager.rx

}

Firebase BOM
versioning

// build.gradle (app)
dependencies {
  ...

  // Declare the dependencies for the desired Firebase products
  // by specifying versions
  implementation 'com.google.firebase:firebase-auth:19.4.0'
  implementation 'com.google.firebase:firebase-firestore-ktx:21.7.1'
  implementation 'com.google.firebase:firebase-storage-ktx:19.2.0'
}

Firebase BOM
versioning

// build.gradle (app)
dependencies {
  ...
  // Import the BoM for the Firebase platform
  implementation platform('com.google.firebase:firebase-bom:25.12.0')

  // Declare the dependencies for the desired Firebase products
  // without specifying versions
  implementation 'com.google.firebase:firebase-auth'
  implementation 'com.google.firebase:firebase-firestore-ktx'
}

Passwords & sensitive info

// build.gradle (app)
android {
  ...

  signingConfigs {
    release {
      // DON'T DO THIS!!
      storeFile file("myapp.keystore")
      storePassword "password123"
      keyAlias "thekey"
      keyPassword "password789"
    }
  }
}

Passwords & sensitive info

// gradle.properties
KEYSTORE_PASSWORD=password123
KEY_PASSWORD=password789
// build.gradle (app)
android {
  ...

  signingConfigs {
    release {
      try {
        storeFile file("myapp.keystore")
        storePassword KEYSTORE_PASSWORD
        keyAlias "thekey"
        keyPassword KEY_PASSWORD
      }
      catch (ex) {
        throw new InvalidUserDataException("Missing in gradle.properties")
      }
    }
  }
}

How about the code?

Mistakes are proof that you're trying!

Early mistake detection -
StrictMode

// Application.kt

fun onCreate() {

  if (BuildConfig.DEBUG) {
    StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder()
      .detectDiskReads()
      .detectDiskWrites()
      .detectNetwork() 
      // or .detectAll() for all detectable problems
      .penaltyLog()
      .build())
      
    StrictMode.setVmPolicy(VmPolicy.Builder()
      .detectLeakedSqlLiteObjects()
      .detectLeakedClosableObjects()
      .penaltyLog()
      .penaltyDeath()
      .build())
  }
  
  super.onCreate()
  
  ...
}

Life's too short to manually write Parcelable code

@Parcelize


@Parcelize
class Person(val name: String, val age: Int) : Parcelable

@Parcelize
enum class BookType: Parcelable { HISTORY, DRAMA, FICTION, BIO }

@Parcelize
class Book(val author: Person, val type: BookType) : Parcelable

sealed class Result : Parcelable {

  @Parcelize
  object Failure : Result

  @Parcelize
  object Error : Result

  @Parcelize
  class Success : Result
}
apply plugin: 'kotlin-android-extensions'

Be pragmatic about resources

Split style files
by semantics


<!-- styles.xml >
<resources>
  ...
  
  <style name="Widget.MyApp" parent="android:Widget" />

</resources>


<!-- styles-text.xml >
<resources>
  ...
  
  <!-- Base text appearance styles -->
  <style name="TextAppearance.MyApp" parent="TextAppearance.AppCompat" />
  
  <style name="TextAppearance.MyApp.Regular">
    <item name="android:fontFamily">@font/my_font_regular</item>
  </style>
  
</resources>

Use colors and dimens as palletes

<!-- colors.xml -->
<resources>
  ...

  <!-- DON'T do this -->  
  <color name="button_foreground">#FFFFFF</color>
  <color name="button_background">#2A91BD</color>


</resources>

Use colors and dimens as palletes

<!-- colors.xml -->
<resources>
  ...
  
  <!-- color pallete -->
  <color name="white">#FFFFFF</color>
  <color name="brand_blue">#3B92BC</color>
  

</resources>

Use colors and dimens as palletes

<!-- colors.xml -->
<resources>
  ...
  
  <!-- color pallete -->
  <color name="white">#FFFFFF</color>
  <color name="brand_blue">#3B92BC</color>

  <!-- specialised colors -->
  <color name="button_foreground">@color/white</color> 
  <color name="button_background">@color/brand_blue</color> 
  
</resources>
<!-- styles.xml -->
<resources>

  <style name="Widget.MyApp.ConfirmButton">
    <item name="android:foreground">@color/button_foreground</item>
    <item name="android:background">@color/button_background</item>
  </style>
</resources>

Use colors and dimens as palletes

<!-- dimens.xml -->
<resources>
  ...

  <!-- font sizes -->
  <dimen name="font_small">12sp</dimen>
  <dimen name="font_normal">15sp</dimen
  <dimen name="font_large">18sp</dimen>

  <!-- typical spacing between two views -->
  <dimen name="spacing_tiny">4dp</dimen>
  <dimen name="spacing_small">10dp</dimen>
  <dimen name="spacing_normal">14dp</dimen>
  <dimen name="spacing_large">24dp</dimen>

  <!-- typical sizes of views -->
  <dimen name="button_height_short">32dp</dimen>
  <dimen name="button_height_normal">40dp</dimen>
  <dimen name="button_height_tall">60dp</dimen>

</resources>

Theme != Style

Styles are a collection of view attributes
 

Themes are a collection of named resources, useful broadly across an app

Make use of attributes

<!-- themes.xml -->
<style name="Theme.MyApp" parent="…">
  <item name="colorPrimary">@color/teal_500</item>
  <item name="colorSecondary">@color/pink_200</item>
  <item name="android:windowBackground">@color/white</item>
</style>

Make use of attributes

<!-- themes.xml -->
<style name="Theme.MyApp" parent="…">
  <item name="colorPrimary">@color/teal_500</item>
  <item name="colorSecondary">@color/pink_200</item>
  <item name="android:windowBackground">@color/white</item>
</style>


<!-- my_layout.xml -->
<FrameLayout …
  android:background="?attr/colorSurface">
  
<TextView …
  android:textAppearance="?attr/textAppearanceHeadline5"

Android Developer's Toolbox

Memory Leak detection

HTTP & Throwables inspector

Casting of connected devices

Native rendering for After Effects animations

Graphical Assets generators

Android Developer's everyday toolbelt

Retrofit - type-safe HTTP client

Picasso - image loading & caching
Koin - lightweight dependency injection

Moshi - JSON parsing
Timber - simplified logging
Mockk - mocking for Kotlin

A few things to keep in mind

Thank you!

Made with Slides.com