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

The Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfThe Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfPower Karaoke
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVshikhaohhpro
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...MyIntelliSource, Inc.
 
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
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptkotipi9215
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software DevelopersVinodh Ram
 
Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...aditisharan08
 
Engage Usergroup 2024 - The Good The Bad_The Ugly
Engage Usergroup 2024 - The Good The Bad_The UglyEngage Usergroup 2024 - The Good The Bad_The Ugly
Engage Usergroup 2024 - The Good The Bad_The UglyFrank van der Linden
 
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
 
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
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEOrtus Solutions, Corp
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)OPEN KNOWLEDGE GmbH
 
cybersecurity notes for mca students for learning
cybersecurity notes for mca students for learningcybersecurity notes for mca students for learning
cybersecurity notes for mca students for learningVitsRangannavar
 
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝soniya singh
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsAlberto González Trastoy
 
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
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...Christina Lin
 
Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - InfographicHr365.us smith
 
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
 

Recently uploaded (20)

The Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdfThe Evolution of Karaoke From Analog to App.pdf
The Evolution of Karaoke From Analog to App.pdf
 
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...Call Girls In Mukherjee Nagar 📱  9999965857  🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
Call Girls In Mukherjee Nagar 📱 9999965857 🤩 Delhi 🫦 HOT AND SEXY VVIP 🍎 SE...
 
Optimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTVOptimizing AI for immediate response in Smart CCTV
Optimizing AI for immediate response in Smart CCTV
 
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
Steps To Getting Up And Running Quickly With MyTimeClock Employee Scheduling ...
 
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
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.ppt
 
Professional Resume Template for Software Developers
Professional Resume Template for Software DevelopersProfessional Resume Template for Software Developers
Professional Resume Template for Software Developers
 
Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...Unit 1.1 Excite Part 1, class 9, cbse...
Unit 1.1 Excite Part 1, class 9, cbse...
 
Engage Usergroup 2024 - The Good The Bad_The Ugly
Engage Usergroup 2024 - The Good The Bad_The UglyEngage Usergroup 2024 - The Good The Bad_The Ugly
Engage Usergroup 2024 - The Good The Bad_The Ugly
 
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
 
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
 
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASEBATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
BATTLEFIELD ORM: TIPS, TACTICS AND STRATEGIES FOR CONQUERING YOUR DATABASE
 
Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)Der Spagat zwischen BIAS und FAIRNESS (2024)
Der Spagat zwischen BIAS und FAIRNESS (2024)
 
cybersecurity notes for mca students for learning
cybersecurity notes for mca students for learningcybersecurity notes for mca students for learning
cybersecurity notes for mca students for learning
 
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
Call Girls in Naraina Delhi 💯Call Us 🔝8264348440🔝
 
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time ApplicationsUnveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
Unveiling the Tech Salsa of LAMs with Janus in Real-Time Applications
 
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...
 
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
ODSC - Batch to Stream workshop - integration of Apache Spark, Cassandra, Pos...
 
Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - Infographic
 
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...
 

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!