SlideShare a Scribd company logo
1 of 55
Download to read offline
Nicolas HAAN - 24/01/2023
Comment développer une
application Android en 8 semaines ?
BAM en quelques mots
• 120 experts du développement mobile
• 3 domaines d’expertise (Développement,
Produit et design)
• + 150 projets (UEFA, TF1, URGO, Pass
Culture)
• 2 bureaux en France (Paris et Nantes)
M33
La tribe native de BAM
The project
The project
• 2 developers, 8 weeks
• A finished MVP, not a POC
• State of the art technical stack,
architecture and testing strategy
• A code base ready to taken over
• A new app from scratch
Architecture
Gradle
Modularisation
Dependency
management
Unit Testing
UI layer
Architecture?
Let's go clean!
Clean Architecture
Google Architecture guidelines
Google Architecture guidelines
Google Architecture guidelines
• Separation of concerns
• Drive UI from data models
• Single source of truth
• Unidirectional Data Flow
Modular architecture
FeatureScreen
FeatureViewModel
FeatureUseCase FeatureRepository
FeatureDataSource
ApiService
DATA
DOMAIN
UI
FeatureScreen
FeatureViewModel
FeatureUseCase FeatureRepository
FeatureDataSource
ApiService
Transformer le state
exposé par le VM en
vues affichables,
récupérer les actions
utilisateur
aggrégation des données des
UseCase pour construire le state
de l'écran,
récupérer les évènements de la
Vue et les dispatcher vers les
UseCases
Implémentation des règles
métiers, agrégations des
sources de données,
abstraction des repositories
Single Source of Truth de
la donnée, règles métiers
du cache et de la
persistence
abstraction de
l'implémentation de la
datasource, préparation
des paramètres des WS,
mapping Dto <-> Domain
appel réseau, parsing
des JSON, remontée
des erreurs
Architecture
• Strict separation of concern
• Heavy modularisation (~22 modules for 5 screens)
• Confident that it will scale well and can be taken over
Gradle:
Beyond Android Studio Template
Groovy or KTS?
KTS promises
• Available since Android Gradle Plugin 4.1.0 (August 2020)
• The Kotlin syntax we all know and love available for Gradle!
• Autocompletion and type safety in Gradle
• The future of Gradle configuration files
Groovy or KTS?
• In 2022, templates are still using Groovy, even in Flamingo
• Manual migration
• Autocompletion not really useful in day to day use
• Not all libraries and open source projects have adopted it
🤷
Modularisation:
Convention plugins
Sharing build logic
"Historical method"
subprojects {
tasks.withType(Test::class.java) {
// ...
}
}
allprojects {
repositories {
// ..
}
apply(plugin = "jacoco")
jacoco {
toolVersion = "0.8.7"
}
}
• Using subprojects
and allprojects
closures
• Not very flexible
• Not explicit from a
module point of view
Sharing build logic
modular gradle files
apply from: "$rootDir/dependencies.gradle"
apply from: "$rootDir/gradle/baseFeature.gradle"
apply from: "$rootDir/gradle/flavors.gradle"
• doesn't scale well
• Doesn't work well with
KTS (see here and here)
Sharing build logic
Convention plugin
• create convention
plugins to be applied to
relevant modules
• There can be several
modular and
composable plugins
• Plugins are explicitly
applied to each module
buildSrc
androidBase.kts
flavors.kts
core/moduleX
...
domain/moduleY
Sharing build logic
Convention plugin
plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-kapt")
id("org.jlleitschuh.gradle.ktlint")
}
android {
compileSdk = findIntProperty("compileSdkVersion")
defaultConfig {
minSdk = findIntProperty("minSdkVersion")
targetSdk = findIntProperty("targetSdkVersion")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
Sharing build logic
Convention plugin
plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-kapt")
id("org.jlleitschuh.gradle.ktlint")
}
android {
compileSdk = findIntProperty("compileSdkVersion")
defaultConfig {
minSdk = findIntProperty("minSdkVersion")
targetSdk = findIntProperty("targetSdkVersion")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
testOptions {
unitTests.isReturnDefaultValues = true
unitTests.all {
it.testLogging {
events = setOf(
TestLogEvent.STARTED,
TestLogEvent.PASSED,
TestLogEvent.SKIPPED,
TestLogEvent.FAILED
)
showStandardStreams = true
}
}
}
flavorDimensions.add("environment")
productFlavors {
register("dev") {
dimension = "environment"
}
register("staging") {
dimension = "environment"
}
register("prod") {
dimension = "environment"
}
}
}
dependencies {
implementation(Koin.android)
implementation(Koin.compose)
}
fun findStringProperty(propertyName: String): String {
return findProperty(propertyName) as String
}
fun findIntProperty(propertyName: String): Int {
return (findProperty(propertyName) as String).toInt()
}
plugins {
id("com.client.app.convention.androidBase")
id("com.client.app.convention.flavors")
}
android {
namespace = "com.client.app.logging"
}
dependencies {
implementation(libs.appcenter.crashes)
}
moduleX/build.gradle.kts
Sharing build logic
Convention plugin
plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-kapt")
id("org.jlleitschuh.gradle.ktlint")
}
android {
compileSdk = findIntProperty("compileSdkVersion")
defaultConfig {
minSdk = findIntProperty("minSdkVersion")
targetSdk = findIntProperty("targetSdkVersion")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
testOptions {
unitTests.isReturnDefaultValues = true
unitTests.all {
it.testLogging {
events = setOf(
TestLogEvent.STARTED,
TestLogEvent.PASSED,
TestLogEvent.SKIPPED,
TestLogEvent.FAILED
)
showStandardStreams = true
}
}
}
flavorDimensions.add("environment")
productFlavors {
register("dev") {
dimension = "environment"
}
register("staging") {
dimension = "environment"
}
register("prod") {
dimension = "environment"
}
}
}
dependencies {
implementation(Koin.android)
implementation(Koin.compose)
}
fun findStringProperty(propertyName: String): String {
return findProperty(propertyName) as String
}
fun findIntProperty(propertyName: String): Int {
return (findProperty(propertyName) as String).toInt()
}
plugins {
id("com.client.app.convention.androidBase")
id("com.client.app.convention.flavors")
}
android {
namespace = "com.client.app.logging"
}
dependencies {
implementation(libs.appcenter.crashes)
}
moduleX/build.gradle.kts
Sharing build logic
Convention plugin
plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-kapt")
id("org.jlleitschuh.gradle.ktlint")
}
android {
compileSdk = findIntProperty("compileSdkVersion")
defaultConfig {
minSdk = findIntProperty("minSdkVersion")
targetSdk = findIntProperty("targetSdkVersion")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
testOptions {
unitTests.isReturnDefaultValues = true
unitTests.all {
it.testLogging {
events = setOf(
TestLogEvent.STARTED,
TestLogEvent.PASSED,
TestLogEvent.SKIPPED,
TestLogEvent.FAILED
)
showStandardStreams = true
}
}
}
flavorDimensions.add("environment")
productFlavors {
register("dev") {
dimension = "environment"
}
register("staging") {
dimension = "environment"
}
register("prod") {
dimension = "environment"
}
}
}
dependencies {
implementation(Koin.android)
implementation(Koin.compose)
}
fun findStringProperty(propertyName: String): String {
return findProperty(propertyName) as String
}
fun findIntProperty(propertyName: String): Int {
return (findProperty(propertyName) as String).toInt()
}
plugins {
id("com.client.app.convention.androidBase")
id("com.client.app.convention.flavors")
}
android {
namespace = "com.client.app.logging"
}
dependencies {
implementation(libs.appcenter.crashes)
}
moduleX/build.gradle.kts
Sharing build logic
Convention plugin
plugins {
id("com.android.library")
id("kotlin-android")
id("kotlin-kapt")
id("org.jlleitschuh.gradle.ktlint")
}
android {
compileSdk = findIntProperty("compileSdkVersion")
defaultConfig {
minSdk = findIntProperty("minSdkVersion")
targetSdk = findIntProperty("targetSdkVersion")
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables {
useSupportLibrary = true
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = JavaVersion.VERSION_11.toString()
}
buildTypes {
release {
isMinifyEnabled = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
testOptions {
unitTests.isReturnDefaultValues = true
unitTests.all {
it.testLogging {
events = setOf(
TestLogEvent.STARTED,
TestLogEvent.PASSED,
TestLogEvent.SKIPPED,
TestLogEvent.FAILED
)
showStandardStreams = true
}
}
}
flavorDimensions.add("environment")
productFlavors {
register("dev") {
dimension = "environment"
}
register("staging") {
dimension = "environment"
}
register("prod") {
dimension = "environment"
}
}
}
dependencies {
implementation(Koin.android)
implementation(Koin.compose)
}
fun findStringProperty(propertyName: String): String {
return findProperty(propertyName) as String
}
fun findIntProperty(propertyName: String): Int {
return (findProperty(propertyName) as String).toInt()
}
moduleX/build.gradle.kts
plugins {
id("com.client.app.convention.androidBase")
id("com.client.app.convention.flavors")
}
android {
namespace = "com.client.app.logging"
}
dependencies {
implementation(libs.appcenter.crashes)
}
Sharing build logic
Next step: explicit convention plugin
class AndroidLibraryJacocoConventionPlugin : Plugin<Project> {
override fun apply(target: Project) {
with(target) {
with(pluginManager) {
apply("org.gradle.jacoco")
apply("com.android.library")
}
val extension =
extensions.getByType<LibraryAndroidComponentsExtension>()
configureJacoco(extension)
}
}
}
Dependencies:
refreshVersions library
Dependencies
dependencies {
implementation 'androidx.core:core-ktx:1.9.0'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material3:material3:1.0.1"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1'
implementation 'androidx.activity:activity-compose:1.6.1'
implementation "com.google.accompanist:accompanist-webview:0.28.0"
testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
}
Dependencies
Dependencies
Custom dependency management
• No built-in mechanism to
centralise dependencies*
• Custom dependencies
management breaks Android
Studio dependencies update
Dependencies
refreshVersions library
• Centralize dependencies
declarations and versions in
versions.properties
• Helpful gradle task for
version updates and even
migration!
Dependencies
refreshVersions library
./gradlew refreshVersions
Dependencies
refreshVersions library: migration
dependencies {
val composeVersion: String = project.properties["composeVersion"] as String
implementation("androidx.core:core-ktx:1.7.0")
implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1")
implementation("androidx.activity:activity-compose:1.3.1")
implementation("androidx.compose.ui:ui:$composeVersion")
implementation("androidx.compose.ui:ui-tooling-preview:$composeVersion")
implementation("androidx.compose.material3:material3:1.0.0-alpha02")
implementation("net.openid:appauth:0.11.1")
testImplementation("junit:junit:4.13.2")
androidTestImplementation("androidx.test.ext:junit:1.1.3")
androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0")
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$composeVersion")
debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion")
debugImplementation("androidx.compose.ui:ui-test-manifest:$composeVersion")
}
./gradlew refreshVersionsMigrate
Dependencies
refreshVersions library: migration
dependencies {
implementation(AndroidX.core.ktx)
implementation(AndroidX.lifecycle.runtime.ktx)
implementation(AndroidX.activity.compose)
implementation(AndroidX.compose.ui)
implementation(AndroidX.compose.ui.toolingPreview)
implementation(AndroidX.compose.material3)
implementation("net.openid:appauth:_")
testImplementation(Testing.junit4)
androidTestImplementation(AndroidX.test.ext.junit)
androidTestImplementation(AndroidX.test.espresso.core)
androidTestImplementation(AndroidX.compose.ui.testJunit4)
debugImplementation(AndroidX.compose.ui.tooling)
debugImplementation(AndroidX.compose.ui.testManifest)
}
RefreshVersions
Other features
• Support gradle plugins versions as well
• RefreshVersionsBot (~Dependabot)
• Support for Gradle VersionCatalog
• Migration still works after initial migration!
dependencies {
implementation(AndroidX.compose.ui)
implementation(AndroidX.compose.ui.toolingPreview)
implementation(AndroidX.compose.material3)
implementation("com.google.accompanist:accompanist-webview:0.28.0")
}
RefreshVersions
Other features
• Support gradle plugins versions as well
• RefreshVersionsBot (~Dependabot)
• Support for Gradle VersionCatalog
• Migration still works after initial migration!
dependencies {
implementation(AndroidX.compose.ui)
implementation(AndroidX.compose.ui.toolingPreview)
implementation(AndroidX.compose.material3)
implementation(libs.accompanist.webview)
}
New Gradle features and trends since 3~4 years
• Kotlin script (KTS)
• Convention plugins
• Convention plugins (explicit)
• pluginManagement { } block
• dependencyResolutionManagement { } block
• Version catalog
+ modularisation
+ third party libraries 🤯
Unit Testing:
the Outside-In strategy
Unit testing
Common issues
• fragile tests
• many test doubles ("mocks") to implement
• testing private methods
• testing behaviours (implementations)
• Unit tests different from acceptance tests
• Should I test pass-through classes?
Unit testing
Common issues
FeatureScreen
FeatureViewModel
FeatureUseCase FeatureRepository
FeatureDataSource
ApiService
Backend
Outside-In Strategy
Testing each class in isolation
FeatureScreen
FeatureViewModel
FeatureUseCase FeatureRepository
FeatureDataSource
ApiService
MockWebServer
Unit tests
mocked JSON files
@Test
fun `Application ToBeCompleted has a correct UI header`() = runTest {
val contractReference = "CFR20221025MHP37ZZ"
val validJson =
readMockFile("mocked-responses/api/v1/Applications/$contractReference/details")
restartKoinWithMockedWebServer(
responses = listOf(validJson),
featureModule = featureModule,
testKoinModule = testKoinModule,
)
val viewModel: ApplicationDetailViewModel =
get(parameters = { parametersOf(contractReference) })
viewModel.onAction(ApplicationDetailScreenAction.OnResume)
viewModel.uiState.test {
val details =
awaitForItemIs(ApplicationDetailScreenState.Details::class)
assertEquals(ToBeCompleted, details.finalState)
}
}
// GIVEN
// WHEN
// THEN
// THEN
mockWebServer
Koin
Turbine
Generate the network model:
Using OpenAPI generator
OpenAPI
• The specification used by Swagger tools
• Describes the API (endpoints, objects...)
• Can be used as a contract between the
backend and the frontend
• Can be used to generate the code of the
client and/or the server
spec.json
OpenAPI Generator
*Api.kt
*Dto.kt
OpenAPI Generator
spec.json
OpenAPI Generator
/**
* Tweet delete by Tweet ID
* Delete specified Tweet (in the path) by ID.
* @param id The ID of the Tweet to be deleted.
* @return TweetDeleteResponse
* ...
*/
fun deleteTweetById(id: kotlin.String) : TweetDeleteResponse
data class Tweet (
/* Unique identifier of this Tweet. This is returned
as a string in order to avoid complications with
languages and tools that cannot handle large integers.
*/
@Json(name = "id")
val id: kotlin.String,
/* The content of the Tweet. */
@Json(name = "text")
val text: kotlin.String,
/* Unique identifier of this User.*/
@Json(name = "author_id")
val authorId: kotlin.String? = null,
// ...
OpenAPI Generator
• Allows to integrate BFF updates in a blink of an eye
• Quite flexible to integrate
• see more: https://www.bam.tech/article/customize-openapi-generator-output-for-
your-android-app
UI layer:
full Compose!
Jetpack Compose
• Really easy to implement the client Design System
• Works really well with MVI approach
• Webviews are buggy
• Jetpack Compose navigation s*cks!
Thank you!

More Related Content

Similar to Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023

Android 101 - Introduction to Android Development
Android 101 - Introduction to Android DevelopmentAndroid 101 - Introduction to Android Development
Android 101 - Introduction to Android DevelopmentAndy Scherzinger
 
Microservices DevOps on Google Cloud Platform
Microservices DevOps on Google Cloud PlatformMicroservices DevOps on Google Cloud Platform
Microservices DevOps on Google Cloud PlatformSunnyvale
 
From Containerization to Modularity
From Containerization to ModularityFrom Containerization to Modularity
From Containerization to Modularityoasisfeng
 
React Native for multi-platform mobile applications
React Native for multi-platform mobile applicationsReact Native for multi-platform mobile applications
React Native for multi-platform mobile applicationsMatteo Manchi
 
DIとトレイとによるAndroid開発の効率化
DIとトレイとによるAndroid開発の効率化DIとトレイとによるAndroid開発の効率化
DIとトレイとによるAndroid開発の効率化Tomoharu ASAMI
 
Level Up Your Android Build -Droidcon Berlin 2015
Level Up Your Android Build -Droidcon Berlin 2015Level Up Your Android Build -Droidcon Berlin 2015
Level Up Your Android Build -Droidcon Berlin 2015Friedger Müffke
 
10 ways to make your code rock
10 ways to make your code rock10 ways to make your code rock
10 ways to make your code rockmartincronje
 
Modularity and Domain Driven Design; a killer Combination? - Tom de Wolf & St...
Modularity and Domain Driven Design; a killer Combination? - Tom de Wolf & St...Modularity and Domain Driven Design; a killer Combination? - Tom de Wolf & St...
Modularity and Domain Driven Design; a killer Combination? - Tom de Wolf & St...NLJUG
 
Serverless Container with Source2Image
Serverless Container with Source2ImageServerless Container with Source2Image
Serverless Container with Source2ImageQAware GmbH
 
Serverless containers … with source-to-image
Serverless containers  … with source-to-imageServerless containers  … with source-to-image
Serverless containers … with source-to-imageJosef Adersberger
 
Современная архитектура Android-приложений - Archetype / Степан Гончаров (90 ...
Современная архитектура Android-приложений - Archetype / Степан Гончаров (90 ...Современная архитектура Android-приложений - Archetype / Степан Гончаров (90 ...
Современная архитектура Android-приложений - Archetype / Степан Гончаров (90 ...Ontico
 
Cassandra Summit 2014: Highly Scalable Web Application in the Cloud with Cass...
Cassandra Summit 2014: Highly Scalable Web Application in the Cloud with Cass...Cassandra Summit 2014: Highly Scalable Web Application in the Cloud with Cass...
Cassandra Summit 2014: Highly Scalable Web Application in the Cloud with Cass...DataStax Academy
 
بررسی چارچوب جنگو
بررسی چارچوب جنگوبررسی چارچوب جنگو
بررسی چارچوب جنگوrailsbootcamp
 
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013DuckMa
 
Building Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture ComponentsBuilding Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture ComponentsHassan Abid
 
Building production-quality apps with Node.js
Building production-quality apps with Node.jsBuilding production-quality apps with Node.js
Building production-quality apps with Node.jsmattpardee
 
Angular 2 for Java Developers
Angular 2 for Java DevelopersAngular 2 for Java Developers
Angular 2 for Java DevelopersYakov Fain
 
Full Stack React Workshop [CSSC x GDSC]
Full Stack React Workshop [CSSC x GDSC]Full Stack React Workshop [CSSC x GDSC]
Full Stack React Workshop [CSSC x GDSC]GDSC UofT Mississauga
 

Similar to Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023 (20)

Android 101 - Introduction to Android Development
Android 101 - Introduction to Android DevelopmentAndroid 101 - Introduction to Android Development
Android 101 - Introduction to Android Development
 
Microservices DevOps on Google Cloud Platform
Microservices DevOps on Google Cloud PlatformMicroservices DevOps on Google Cloud Platform
Microservices DevOps on Google Cloud Platform
 
From Containerization to Modularity
From Containerization to ModularityFrom Containerization to Modularity
From Containerization to Modularity
 
React Native for multi-platform mobile applications
React Native for multi-platform mobile applicationsReact Native for multi-platform mobile applications
React Native for multi-platform mobile applications
 
DIとトレイとによるAndroid開発の効率化
DIとトレイとによるAndroid開発の効率化DIとトレイとによるAndroid開発の効率化
DIとトレイとによるAndroid開発の効率化
 
Kunal bhatia resume mass
Kunal bhatia   resume massKunal bhatia   resume mass
Kunal bhatia resume mass
 
Level Up Your Android Build -Droidcon Berlin 2015
Level Up Your Android Build -Droidcon Berlin 2015Level Up Your Android Build -Droidcon Berlin 2015
Level Up Your Android Build -Droidcon Berlin 2015
 
10 ways to make your code rock
10 ways to make your code rock10 ways to make your code rock
10 ways to make your code rock
 
Modularity and Domain Driven Design; a killer Combination? - Tom de Wolf & St...
Modularity and Domain Driven Design; a killer Combination? - Tom de Wolf & St...Modularity and Domain Driven Design; a killer Combination? - Tom de Wolf & St...
Modularity and Domain Driven Design; a killer Combination? - Tom de Wolf & St...
 
Serverless Container with Source2Image
Serverless Container with Source2ImageServerless Container with Source2Image
Serverless Container with Source2Image
 
Serverless containers … with source-to-image
Serverless containers  … with source-to-imageServerless containers  … with source-to-image
Serverless containers … with source-to-image
 
Современная архитектура Android-приложений - Archetype / Степан Гончаров (90 ...
Современная архитектура Android-приложений - Archetype / Степан Гончаров (90 ...Современная архитектура Android-приложений - Archetype / Степан Гончаров (90 ...
Современная архитектура Android-приложений - Archetype / Степан Гончаров (90 ...
 
Cassandra Summit 2014: Highly Scalable Web Application in the Cloud with Cass...
Cassandra Summit 2014: Highly Scalable Web Application in the Cloud with Cass...Cassandra Summit 2014: Highly Scalable Web Application in the Cloud with Cass...
Cassandra Summit 2014: Highly Scalable Web Application in the Cloud with Cass...
 
بررسی چارچوب جنگو
بررسی چارچوب جنگوبررسی چارچوب جنگو
بررسی چارچوب جنگو
 
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013
Matteo Gazzurelli - Andorid introduction - Google Dev Fest 2013
 
Building Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture ComponentsBuilding Modern Apps using Android Architecture Components
Building Modern Apps using Android Architecture Components
 
Grails 101
Grails 101Grails 101
Grails 101
 
Building production-quality apps with Node.js
Building production-quality apps with Node.jsBuilding production-quality apps with Node.js
Building production-quality apps with Node.js
 
Angular 2 for Java Developers
Angular 2 for Java DevelopersAngular 2 for Java Developers
Angular 2 for Java Developers
 
Full Stack React Workshop [CSSC x GDSC]
Full Stack React Workshop [CSSC x GDSC]Full Stack React Workshop [CSSC x GDSC]
Full Stack React Workshop [CSSC x GDSC]
 

Recently uploaded

Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfCionsystems
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsArshad QA
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...OnePlan Solutions
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionSolGuruz
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...kellynguyen01
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️anilsa9823
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...soniya singh
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comFatema Valibhai
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providermohitmore19
 
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AIABDERRAOUF MEHENNI
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...stazi3110
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdfWave PLM
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationkaushalgiri8080
 
Test Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendTest Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendArshad QA
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfkalichargn70th171
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...ICS
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataBradBedford3
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...harshavardhanraghave
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Modelsaagamshah0812
 

Recently uploaded (20)

Active Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdfActive Directory Penetration Testing, cionsystems.com.pdf
Active Directory Penetration Testing, cionsystems.com.pdf
 
Software Quality Assurance Interview Questions
Software Quality Assurance Interview QuestionsSoftware Quality Assurance Interview Questions
Software Quality Assurance Interview Questions
 
Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...Advancing Engineering with AI through the Next Generation of Strategic Projec...
Advancing Engineering with AI through the Next Generation of Strategic Projec...
 
Diamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with PrecisionDiamond Application Development Crafting Solutions with Precision
Diamond Application Development Crafting Solutions with Precision
 
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
Short Story: Unveiling the Reasoning Abilities of Large Language Models by Ke...
 
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online  ☂️
CALL ON ➥8923113531 🔝Call Girls Kakori Lucknow best sexual service Online ☂️
 
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
Russian Call Girls in Karol Bagh Aasnvi ➡️ 8264348440 💋📞 Independent Escort S...
 
HR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.comHR Software Buyers Guide in 2024 - HRSoftware.com
HR Software Buyers Guide in 2024 - HRSoftware.com
 
TECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service providerTECUNIQUE: Success Stories: IT Service provider
TECUNIQUE: Success Stories: IT Service provider
 
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AISyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
SyndBuddy AI 2k Review 2024: Revolutionizing Content Syndication with AI
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
Building a General PDE Solving Framework with Symbolic-Numeric Scientific Mac...
 
5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf5 Signs You Need a Fashion PLM Software.pdf
5 Signs You Need a Fashion PLM Software.pdf
 
Project Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanationProject Based Learning (A.I).pptx detail explanation
Project Based Learning (A.I).pptx detail explanation
 
Test Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and BackendTest Automation Strategy for Frontend and Backend
Test Automation Strategy for Frontend and Backend
 
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdfThe Ultimate Test Automation Guide_ Best Practices and Tips.pdf
The Ultimate Test Automation Guide_ Best Practices and Tips.pdf
 
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
The Real-World Challenges of Medical Device Cybersecurity- Mitigating Vulnera...
 
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer DataAdobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
Adobe Marketo Engage Deep Dives: Using Webhooks to Transfer Data
 
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
Reassessing the Bedrock of Clinical Function Models: An Examination of Large ...
 
Unlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language ModelsUnlocking the Future of AI Agents with Large Language Models
Unlocking the Future of AI Agents with Large Language Models
 

Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023

  • 1. Nicolas HAAN - 24/01/2023 Comment développer une application Android en 8 semaines ?
  • 2. BAM en quelques mots • 120 experts du développement mobile • 3 domaines d’expertise (Développement, Produit et design) • + 150 projets (UEFA, TF1, URGO, Pass Culture) • 2 bureaux en France (Paris et Nantes)
  • 3. M33
  • 6. The project • 2 developers, 8 weeks • A finished MVP, not a POC • State of the art technical stack, architecture and testing strategy • A code base ready to taken over • A new app from scratch
  • 12. Google Architecture guidelines • Separation of concerns • Drive UI from data models • Single source of truth • Unidirectional Data Flow
  • 15. FeatureScreen FeatureViewModel FeatureUseCase FeatureRepository FeatureDataSource ApiService Transformer le state exposé par le VM en vues affichables, récupérer les actions utilisateur aggrégation des données des UseCase pour construire le state de l'écran, récupérer les évènements de la Vue et les dispatcher vers les UseCases Implémentation des règles métiers, agrégations des sources de données, abstraction des repositories Single Source of Truth de la donnée, règles métiers du cache et de la persistence abstraction de l'implémentation de la datasource, préparation des paramètres des WS, mapping Dto <-> Domain appel réseau, parsing des JSON, remontée des erreurs
  • 16. Architecture • Strict separation of concern • Heavy modularisation (~22 modules for 5 screens) • Confident that it will scale well and can be taken over
  • 18. Groovy or KTS? KTS promises • Available since Android Gradle Plugin 4.1.0 (August 2020) • The Kotlin syntax we all know and love available for Gradle! • Autocompletion and type safety in Gradle • The future of Gradle configuration files
  • 19. Groovy or KTS? • In 2022, templates are still using Groovy, even in Flamingo • Manual migration • Autocompletion not really useful in day to day use • Not all libraries and open source projects have adopted it
  • 20. 🤷
  • 22. Sharing build logic "Historical method" subprojects { tasks.withType(Test::class.java) { // ... } } allprojects { repositories { // .. } apply(plugin = "jacoco") jacoco { toolVersion = "0.8.7" } } • Using subprojects and allprojects closures • Not very flexible • Not explicit from a module point of view
  • 23. Sharing build logic modular gradle files apply from: "$rootDir/dependencies.gradle" apply from: "$rootDir/gradle/baseFeature.gradle" apply from: "$rootDir/gradle/flavors.gradle" • doesn't scale well • Doesn't work well with KTS (see here and here)
  • 24. Sharing build logic Convention plugin • create convention plugins to be applied to relevant modules • There can be several modular and composable plugins • Plugins are explicitly applied to each module buildSrc androidBase.kts flavors.kts core/moduleX ... domain/moduleY
  • 25. Sharing build logic Convention plugin plugins { id("com.android.library") id("kotlin-android") id("kotlin-kapt") id("org.jlleitschuh.gradle.ktlint") } android { compileSdk = findIntProperty("compileSdkVersion") defaultConfig { minSdk = findIntProperty("minSdkVersion") targetSdk = findIntProperty("targetSdkVersion") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 }
  • 26. Sharing build logic Convention plugin plugins { id("com.android.library") id("kotlin-android") id("kotlin-kapt") id("org.jlleitschuh.gradle.ktlint") } android { compileSdk = findIntProperty("compileSdkVersion") defaultConfig { minSdk = findIntProperty("minSdkVersion") targetSdk = findIntProperty("targetSdkVersion") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } buildTypes { release { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } testOptions { unitTests.isReturnDefaultValues = true unitTests.all { it.testLogging { events = setOf( TestLogEvent.STARTED, TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED ) showStandardStreams = true } } } flavorDimensions.add("environment") productFlavors { register("dev") { dimension = "environment" } register("staging") { dimension = "environment" } register("prod") { dimension = "environment" } } } dependencies { implementation(Koin.android) implementation(Koin.compose) } fun findStringProperty(propertyName: String): String { return findProperty(propertyName) as String } fun findIntProperty(propertyName: String): Int { return (findProperty(propertyName) as String).toInt() } plugins { id("com.client.app.convention.androidBase") id("com.client.app.convention.flavors") } android { namespace = "com.client.app.logging" } dependencies { implementation(libs.appcenter.crashes) } moduleX/build.gradle.kts
  • 27. Sharing build logic Convention plugin plugins { id("com.android.library") id("kotlin-android") id("kotlin-kapt") id("org.jlleitschuh.gradle.ktlint") } android { compileSdk = findIntProperty("compileSdkVersion") defaultConfig { minSdk = findIntProperty("minSdkVersion") targetSdk = findIntProperty("targetSdkVersion") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } buildTypes { release { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } testOptions { unitTests.isReturnDefaultValues = true unitTests.all { it.testLogging { events = setOf( TestLogEvent.STARTED, TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED ) showStandardStreams = true } } } flavorDimensions.add("environment") productFlavors { register("dev") { dimension = "environment" } register("staging") { dimension = "environment" } register("prod") { dimension = "environment" } } } dependencies { implementation(Koin.android) implementation(Koin.compose) } fun findStringProperty(propertyName: String): String { return findProperty(propertyName) as String } fun findIntProperty(propertyName: String): Int { return (findProperty(propertyName) as String).toInt() } plugins { id("com.client.app.convention.androidBase") id("com.client.app.convention.flavors") } android { namespace = "com.client.app.logging" } dependencies { implementation(libs.appcenter.crashes) } moduleX/build.gradle.kts
  • 28. Sharing build logic Convention plugin plugins { id("com.android.library") id("kotlin-android") id("kotlin-kapt") id("org.jlleitschuh.gradle.ktlint") } android { compileSdk = findIntProperty("compileSdkVersion") defaultConfig { minSdk = findIntProperty("minSdkVersion") targetSdk = findIntProperty("targetSdkVersion") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } buildTypes { release { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } testOptions { unitTests.isReturnDefaultValues = true unitTests.all { it.testLogging { events = setOf( TestLogEvent.STARTED, TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED ) showStandardStreams = true } } } flavorDimensions.add("environment") productFlavors { register("dev") { dimension = "environment" } register("staging") { dimension = "environment" } register("prod") { dimension = "environment" } } } dependencies { implementation(Koin.android) implementation(Koin.compose) } fun findStringProperty(propertyName: String): String { return findProperty(propertyName) as String } fun findIntProperty(propertyName: String): Int { return (findProperty(propertyName) as String).toInt() } plugins { id("com.client.app.convention.androidBase") id("com.client.app.convention.flavors") } android { namespace = "com.client.app.logging" } dependencies { implementation(libs.appcenter.crashes) } moduleX/build.gradle.kts
  • 29. Sharing build logic Convention plugin plugins { id("com.android.library") id("kotlin-android") id("kotlin-kapt") id("org.jlleitschuh.gradle.ktlint") } android { compileSdk = findIntProperty("compileSdkVersion") defaultConfig { minSdk = findIntProperty("minSdkVersion") targetSdk = findIntProperty("targetSdkVersion") testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" vectorDrawables { useSupportLibrary = true } } compileOptions { sourceCompatibility = JavaVersion.VERSION_11 targetCompatibility = JavaVersion.VERSION_11 } kotlinOptions { jvmTarget = JavaVersion.VERSION_11.toString() } buildTypes { release { isMinifyEnabled = true proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) } } testOptions { unitTests.isReturnDefaultValues = true unitTests.all { it.testLogging { events = setOf( TestLogEvent.STARTED, TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED ) showStandardStreams = true } } } flavorDimensions.add("environment") productFlavors { register("dev") { dimension = "environment" } register("staging") { dimension = "environment" } register("prod") { dimension = "environment" } } } dependencies { implementation(Koin.android) implementation(Koin.compose) } fun findStringProperty(propertyName: String): String { return findProperty(propertyName) as String } fun findIntProperty(propertyName: String): Int { return (findProperty(propertyName) as String).toInt() } moduleX/build.gradle.kts plugins { id("com.client.app.convention.androidBase") id("com.client.app.convention.flavors") } android { namespace = "com.client.app.logging" } dependencies { implementation(libs.appcenter.crashes) }
  • 30. Sharing build logic Next step: explicit convention plugin class AndroidLibraryJacocoConventionPlugin : Plugin<Project> { override fun apply(target: Project) { with(target) { with(pluginManager) { apply("org.gradle.jacoco") apply("com.android.library") } val extension = extensions.getByType<LibraryAndroidComponentsExtension>() configureJacoco(extension) } } }
  • 32. Dependencies dependencies { implementation 'androidx.core:core-ktx:1.9.0' implementation "androidx.compose.ui:ui:$compose_version" implementation "androidx.compose.material3:material3:1.0.1" implementation "androidx.compose.ui:ui-tooling-preview:$compose_version" implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.5.1' implementation 'androidx.activity:activity-compose:1.6.1' implementation "com.google.accompanist:accompanist-webview:0.28.0" testImplementation 'junit:junit:4.13.2' androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version" }
  • 34. Dependencies Custom dependency management • No built-in mechanism to centralise dependencies* • Custom dependencies management breaks Android Studio dependencies update
  • 35. Dependencies refreshVersions library • Centralize dependencies declarations and versions in versions.properties • Helpful gradle task for version updates and even migration!
  • 37. Dependencies refreshVersions library: migration dependencies { val composeVersion: String = project.properties["composeVersion"] as String implementation("androidx.core:core-ktx:1.7.0") implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.3.1") implementation("androidx.activity:activity-compose:1.3.1") implementation("androidx.compose.ui:ui:$composeVersion") implementation("androidx.compose.ui:ui-tooling-preview:$composeVersion") implementation("androidx.compose.material3:material3:1.0.0-alpha02") implementation("net.openid:appauth:0.11.1") testImplementation("junit:junit:4.13.2") androidTestImplementation("androidx.test.ext:junit:1.1.3") androidTestImplementation("androidx.test.espresso:espresso-core:3.4.0") androidTestImplementation("androidx.compose.ui:ui-test-junit4:$composeVersion") debugImplementation("androidx.compose.ui:ui-tooling:$composeVersion") debugImplementation("androidx.compose.ui:ui-test-manifest:$composeVersion") } ./gradlew refreshVersionsMigrate
  • 38. Dependencies refreshVersions library: migration dependencies { implementation(AndroidX.core.ktx) implementation(AndroidX.lifecycle.runtime.ktx) implementation(AndroidX.activity.compose) implementation(AndroidX.compose.ui) implementation(AndroidX.compose.ui.toolingPreview) implementation(AndroidX.compose.material3) implementation("net.openid:appauth:_") testImplementation(Testing.junit4) androidTestImplementation(AndroidX.test.ext.junit) androidTestImplementation(AndroidX.test.espresso.core) androidTestImplementation(AndroidX.compose.ui.testJunit4) debugImplementation(AndroidX.compose.ui.tooling) debugImplementation(AndroidX.compose.ui.testManifest) }
  • 39. RefreshVersions Other features • Support gradle plugins versions as well • RefreshVersionsBot (~Dependabot) • Support for Gradle VersionCatalog • Migration still works after initial migration! dependencies { implementation(AndroidX.compose.ui) implementation(AndroidX.compose.ui.toolingPreview) implementation(AndroidX.compose.material3) implementation("com.google.accompanist:accompanist-webview:0.28.0") }
  • 40. RefreshVersions Other features • Support gradle plugins versions as well • RefreshVersionsBot (~Dependabot) • Support for Gradle VersionCatalog • Migration still works after initial migration! dependencies { implementation(AndroidX.compose.ui) implementation(AndroidX.compose.ui.toolingPreview) implementation(AndroidX.compose.material3) implementation(libs.accompanist.webview) }
  • 41. New Gradle features and trends since 3~4 years • Kotlin script (KTS) • Convention plugins • Convention plugins (explicit) • pluginManagement { } block • dependencyResolutionManagement { } block • Version catalog + modularisation + third party libraries 🤯
  • 43. Unit testing Common issues • fragile tests • many test doubles ("mocks") to implement • testing private methods • testing behaviours (implementations) • Unit tests different from acceptance tests • Should I test pass-through classes?
  • 44. Unit testing Common issues FeatureScreen FeatureViewModel FeatureUseCase FeatureRepository FeatureDataSource ApiService Backend
  • 45. Outside-In Strategy Testing each class in isolation FeatureScreen FeatureViewModel FeatureUseCase FeatureRepository FeatureDataSource ApiService MockWebServer Unit tests
  • 47. @Test fun `Application ToBeCompleted has a correct UI header`() = runTest { val contractReference = "CFR20221025MHP37ZZ" val validJson = readMockFile("mocked-responses/api/v1/Applications/$contractReference/details") restartKoinWithMockedWebServer( responses = listOf(validJson), featureModule = featureModule, testKoinModule = testKoinModule, ) val viewModel: ApplicationDetailViewModel = get(parameters = { parametersOf(contractReference) }) viewModel.onAction(ApplicationDetailScreenAction.OnResume) viewModel.uiState.test { val details = awaitForItemIs(ApplicationDetailScreenState.Details::class) assertEquals(ToBeCompleted, details.finalState) } } // GIVEN // WHEN // THEN // THEN mockWebServer Koin Turbine
  • 48. Generate the network model: Using OpenAPI generator
  • 49. OpenAPI • The specification used by Swagger tools • Describes the API (endpoints, objects...) • Can be used as a contract between the backend and the frontend • Can be used to generate the code of the client and/or the server
  • 51. OpenAPI Generator spec.json OpenAPI Generator /** * Tweet delete by Tweet ID * Delete specified Tweet (in the path) by ID. * @param id The ID of the Tweet to be deleted. * @return TweetDeleteResponse * ... */ fun deleteTweetById(id: kotlin.String) : TweetDeleteResponse data class Tweet ( /* Unique identifier of this Tweet. This is returned as a string in order to avoid complications with languages and tools that cannot handle large integers. */ @Json(name = "id") val id: kotlin.String, /* The content of the Tweet. */ @Json(name = "text") val text: kotlin.String, /* Unique identifier of this User.*/ @Json(name = "author_id") val authorId: kotlin.String? = null, // ...
  • 52. OpenAPI Generator • Allows to integrate BFF updates in a blink of an eye • Quite flexible to integrate • see more: https://www.bam.tech/article/customize-openapi-generator-output-for- your-android-app
  • 54. Jetpack Compose • Really easy to implement the client Design System • Works really well with MVI approach • Webviews are buggy • Jetpack Compose navigation s*cks!