$30 off During Our Annual Pro Sale. View Details »

Effective Android Development

Effective Android Development

A presentation of the techniques, tools & process that we employ during our daily work to build high-quality apps for our clients. Not a code-heavy, deep-down into our architecture kinda talk, but a high-level behind the scenes about our process to move from prototyping to design and to production. We want to stay nimble during development, and quickly iterate over features, which we like to accomplish through frequent releases, often paired with A/B testing. At the same time, we are convinced that building robust applications is helped with a Continuous Delivery setup where we highly value an automated test strategy. Working in an agile way while focusing on good architecture, testing and reporting took us some effort, and I’d like to explore how we run our projects trying to be as effective and collaborative as possible...
Presented during the Devoxx conference on Thursday, 10th November 2016.
http://cfp.devoxx.be/2016/talk/VOW-7651/Effective_Android_dev

Filip Maelbrancke

November 10, 2016
Tweet

More Decks by Filip Maelbrancke

Other Decks in Programming

Transcript

  1. Effective Android

    View Slide

  2. Effective (Android) development

    View Slide

  3. AppFoundry
    appfoundry.be

    View Slide

  4. View Slide

  5. –H. Alan Stevens
    Building the right thing is always more important than
    building it right.

    View Slide

  6. Prototyping

    View Slide

  7. Whiteboard

    View Slide

  8. Paper prototyping

    View Slide

  9. View Slide

  10. View Slide

  11. View Slide

  12. View Slide

  13. View Slide

  14. View Slide

  15. Remote configuration

    View Slide

  16. Remote configuration
    - Check flags
    - maintenance
    - upgrade
    - Do specific work (load menu, …)
    - Check and act on feature switches
    Production API Test API
    Load Production config file
    https://xxxxx/2.1.json
    Load Test config file
    https://xxxxx/2.1-test.json

    View Slide

  17. –Martin Fowler
    Feature toggles are a powerful technique, allowing teams
    to modify system behavior without changing code.

    View Slide

  18. Key / value pairs @ Firebase
    Firebase
    Remote
    Config
    Change behaviour at runtime
    Developer console
    dynamic (no app update required)
    rules & conditions
    hosted, scalable, cross platform

    View Slide

  19. View Slide

  20. User feedback

    View Slide

  21. A/B testing

    View Slide

  22. View Slide

  23. View Slide

  24. Architecture

    View Slide

  25. Architecture

    View Slide

  26. MODEL
    PRESENTER
    VIEW
    View Presenter
    View
    View
    Presenter
    Presenter
    Data Manager
    Interactors
    Prefs
    API
    API
    DB
    Network
    service
    Network
    service
    Preferences
    Helper
    Observables
    Observables
    Presentation Layer Domain Layer Data Layer
    STREAM
    Architecture

    View Slide

  27. Model View Presenter
    Model
    Presenter
    View

    View Slide

  28. Model View Presenter
    Model
    Presenter
    View
    Android
    UI

    View Slide

  29. MODEL
    PRESENTER
    VIEW
    View Presenter
    View
    View
    Presenter
    Presenter
    Data Manager
    Interactors
    Prefs
    API
    API
    DB
    Network
    service
    Network
    service
    Preferences
    Helper
    Observables
    Observables
    Presentation Layer Domain Layer Data Layer
    STREAM
    Architecture

    View Slide

  30. Consistent
    Expressive
    Concise
    Verbose

    View Slide

  31. X

    View Slide

  32. Jetbrains
    Kotlin
    Good interoperability with Java
    Modern
    Small standard library (±600kb)
    Statically typed, no runtime overhead
    Reduce common errors
    Reduce boilerplate

    View Slide

  33. Data class
    public class User {


    private String firstName;

    private String lastName;

    private int age;


    public int getAge() {

    return age;

    }


    public void setAge(int age) {

    this.age = age;

    }


    public String getFirstName() {

    return firstName;

    }


    public void setFirstName(String firstName) {

    this.firstName = firstName;

    }


    public String getLastName() {

    return lastName;

    }


    public void setLastName(String lastName) {

    this.lastName = lastName;

    }


    @Override

    public boolean equals(Object o) {

    if (this == o) return true;

    if (o == null || getClass() != o.getClass()) return false;


    User user = (User) o;


    if (age != user.age) return false;

    if (firstName != null ? !firstName.equals(user.firstName) : user.firstName != null) {

    return false;

    }

    return lastName != null ? lastName.equals(user.lastName) : user.lastName == null;

    }


    @Override

    public int hashCode() {

    int result = firstName != null ? firstName.hashCode() : 0;

    result = 31 * result + (lastName != null ? lastName.hashCode() : 0);

    result = 31 * result + age;

    return result;

    }


    @Override

    public String toString() {

    return "User{" +

    "age=" + age +

    ", firstName='" + firstName + '\'' +

    ", lastName='" + lastName + '\'' +

    '}';

    }

    }
    @AutoValue

    public abstract class User {


    public abstract String firstName();


    public abstract String lastName();


    public abstract int age();


    @AutoValue.Builder

    public abstract static class Builder {


    public abstract Builder firstName(String s);


    public abstract Builder lastName(String s);


    public abstract Builder age(int i);

    }


    public static Builder builder() {

    return new AutoParcel_User.Builder();

    }


    public abstract Builder toBuilder();

    }

    View Slide

  34. Data class
    public class User {


    private String firstName;

    private String lastName;

    private int age;


    public int getAge() {

    return age;

    }


    public void setAge(int age) {

    this.age = age;

    }


    public String getFirstName() {

    return firstName;

    }


    public void setFirstName(String firstName) {

    this.firstName = firstName;

    }


    public String getLastName() {

    return lastName;

    }


    public void setLastName(String lastName) {

    this.lastName = lastName;

    }


    @Override

    public boolean equals(Object o) {

    if (this == o) return true;

    if (o == null || getClass() != o.getClass()) return false;


    User user = (User) o;


    if (age != user.age) return false;

    if (firstName != null ? !firstName.equals(user.firstName) : user.firstName != null) {

    return false;

    }

    return lastName != null ? lastName.equals(user.lastName) : user.lastName == null;

    }


    @Override

    public int hashCode() {

    int result = firstName != null ? firstName.hashCode() : 0;

    result = 31 * result + (lastName != null ? lastName.hashCode() : 0);

    result = 31 * result + age;

    return result;

    }


    @Override

    public String toString() {

    return "User{" +

    "age=" + age +

    ", firstName='" + firstName + '\'' +

    ", lastName='" + lastName + '\'' +

    '}';

    }

    }
    data class User(val firstName: String, val lastName: String, val age: Int)
    val user = User("Andy", "Rubin", 53)
    println(user)
    // User(firstName=Andy, lastName=Rubin, age=53)
    val usersBirthDay = user.copy(age = 54) // Named parameters

    println(usersBirthDay) // User(firstName=Andy, firstName=Rubin, age=54)

    View Slide

  35. View Slide

  36. Nullability
    var x: String? = "foo"


    x = null


    x.length // Does not compile


    val y: String = null // Does not compile

    View Slide

  37. Nullability
    if (x != null) {

    x.length // Compiles

    }


    // Safe call

    val length = x?.length // Value null


    // Default value

    val length = if (x != null) x.length else -1 // Value -1


    // Elvis operator

    val length = x?.length ?: -1 // Value -1

    View Slide

  38. Kotlin
    • Properties
    • Data class
    • Function literal (Lambda)
    • Higher order functions
    • Extension functions
    • …

    View Slide

  39. View Slide

  40. Layout
    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:fitsSystemWindows="true">


    android:layout_width="match_parent"

    android:layout_height="wrap_content">


    android:id="@+id/toolbar"

    android:layout_width="match_parent"

    android:layout_height="?attr/actionBarSize"/>




    android:id="@+id/container"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    app:layout_behavior="@string/appbar_scrolling_view_behavior"/>



    View Slide

  41. Layout Anko
    android:layout_width="match_parent"

    android:layout_height="match_parent"

    android:fitsSystemWindows="true">


    android:layout_width="match_parent"

    android:layout_height="wrap_content">


    android:id="@+id/toolbar"

    android:layout_width="match_parent"

    android:layout_height="?attr/actionBarSize"/>




    android:id="@+id/container"

    android:layout_width="match_parent"

    android:layout_height="match_parent"

    app:layout_behavior="@string/appbar_scrolling_view_behavior"/>



    coordinatorLayout {

    fitsSystemWindows = true


    appBarLayout {

    toolBar = toolbar {

    if (SDK_INT >= LOLLIPOP)
    elevation = 4f

    }.lparams(width = matchParent,
    height = actionBarSize())
    }.lparams(width = matchParent)


    changeHandlerFrameLayout()

    .lparams(width = matchParent,
    height = matchParent) {

    behavior = ScrollingBehavior()

    }

    }

    View Slide

  42. Layout

    View Slide

  43. Advanced Layout

    View Slide

  44. Advanced Layout

    View Slide

  45. Anko performance test
    Specs ANKO XML Diff
    ALCATEL

    ONE TOUCH
    Mediatek MT6572

    Dual-core 1.3GHz Cortex-A7

    512MB RAM
    169.33 ms 608.66 ms 359%
    HUAWEI Y300
    Qualcomm MSM8225

    Dual-core 1.0 GHz Cortex-A5
    512 MB RAM
    593.66 ms 3435.33 ms 578%
    HUAWEI Y330
    Mediatek MT6572
    Dual-core 1.3 GHz Cortex-A7
    512MB
    162.33 ms 984 ms 606%
    Samsung
    Galaxy S2
    Exynos 4210 Dual
    Dual-core 1.2 GHz Cortex-A9
    1 GB RAM
    207.33 ms 753.66 ms 363%

    View Slide

  46. Testing

    View Slide

  47. Testing strategy

    View Slide

  48. Confidence
    Refactor possible
    Code handover
    Fix bugs once

    View Slide

  49. Automatic
    On every commit / scheduled
    Unit tests
    UI integration tests
    with Espresso

    View Slide

  50. Test devices

    View Slide

  51. Device testing

    View Slide

  52. View Slide

  53. View Slide

  54. View Slide

  55. View Slide

  56. View Slide

  57. Remote

    View Slide

  58. View Slide

  59. View Slide

  60. Google Cloud Test Lab

    View Slide

  61. Google Cloud Test Lab

    View Slide

  62. Google Cloud Test Lab

    View Slide

  63. Creating software = complex
    Continuous
    integration Ensure quality
    Automate
    high-quality, robust and reliable apps
    tedious / error-prone activities

    View Slide

  64. Reduce risk
    Continuous
    integration Reduce overhead
    Quality Assurance

    View Slide

  65. Automate all the things

    View Slide

  66. Continuous integration
    1
    2
    3
    4
    CODE & COMMIT BUILD & CHECK
    CI PICKUP
    REPORT RESULTS

    View Slide

  67. Build pipeline
    Checkout /
    compile
    Unit tests
    Test
    coverage
    Code
    analysis
    Create
    deployable
    artifact
    Deploy for
    automatic
    QA test
    Trigger
    automated
    QA stage

    View Slide

  68. Static analysis

    View Slide

  69. Auto publish
    Delivery
    Promote APK to production
    Automate
    Google Play alpha / beta
    without additional tools

    View Slide

  70. Continuous
    Delivery

    View Slide

  71. Continuous delivery / deliverable
    Continuous
    Delivery
    Deliverable Push on demand
    Confidence
    of being deployable

    View Slide

  72. Feature-based deployment
    A successful Git branching model
    http://nvie.com/posts/a-successful-git-branching-model/

    View Slide

  73. Code review
    Detect problems early.
    Learn from someone else’s code.

    View Slide

  74. View Slide

  75. View Slide

  76. View Slide

  77. View Slide

  78. View Slide

  79. Test app distribution

    View Slide

  80. Hockey test app distribution

    View Slide

  81. Fabric Beta app distribution

    View Slide

  82. Fabric Beta app distribution

    View Slide

  83. View Slide

  84. Internal QA
    Testing
    Internal alpha testing program
    Beta testing / staged rollout
    of being deployable

    View Slide

  85. Metrics

    View Slide

  86. Crashlytics

    View Slide

  87. Dashboard

    View Slide

  88. Dashing

    View Slide

  89. Questions?
    Filip Maelbrancke
    Consultant @ AppFoundry
    [email protected]
    @fmaelbrancke

    View Slide

  90. Thank you!

    View Slide