SlideShare a Scribd company logo
1 of 148
Download to read offline
Scaling an Android App from
1 to 100 Developers with
Modularization
@BenSchwab13
A tale of two apps.
Around June of 2011 two android apps were being built…
Splinky
apply plugin: ‘com.android.application'
Airbnb
apply plugin: ‘com.android.application'
Airbnb
Airbnb
Airbnb
Airbnb
Splinky’s
Last Stand
150
What is modularization?
apply plugin: ‘com.android.application'
App
What is modularization?
apply plugin: ‘com.android.application'
apply plugin: ‘com.android.library’
App
Lib
What is modularization?
Manifest R res
classes.jar
apply plugin: ‘com.android.application'
apply plugin: ‘com.android.library’
App
Lib
apk/aab
Manifest R res
classes.jar
What is modularization?
apply plugin: ‘com.android.application'
apply plugin: ‘com.android.library’
“App depends on Lib”
App
Lib
What is modularization?
Lib2
Lib3
App
Lib1
The first rule of modularization is…
You don’t modularize if you don’t have too.
Project Structure
Report Card
Project Structure
Report Card
Build times
Build times.
Project/gradle.properties
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/
multi_project_builds.html#sec:decoupled_projects
org.gradle.parallel=true
Clean Builds
Time(seconds)
0
50
100
150
200
Hundreds of source files
.2 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40
1 2 3 4# of modules
Project Structure
Report Card
Build times
Code Ownership
Code Ownership (Attribution)
how effort durability
line git blame
🚌
file/class @Owner(GrowthTeam)
module manifest
Murphy’s Law:
“Whatever can go wrong will go wrong”.
Murphy’s Law of code access:
“Whatever they can access,
they will access
(and it will go wrong)”.
Mall
Code Ownership
(Encapsulation)
Ben’s
Coffee Shop
Mall
Code Ownership
(Encapsulation)
Ben’s
Coffee Shop
CoffeeGrinder
Code Ownership
(Encapsulation)
Mall
Murphy’s
Coffee Shop
Ben’s
Coffee Shop
CoffeeGrinder
Code Ownership
(Encapsulation)
Mall
Murphy’s
Coffee Shop
Ben’s Boba
Shop
BobaMachine
Code Ownership
(Encapsulation)
Mall
Murphy’s
Boba Shop?
Ben’s Boba
Shop
BobaMachine
Code Ownership
(Encapsulation)
internal
public
private
protected
Murphy’s
Coffee Shop
Mall
Ben’s Boba
Shop
Boba Machine
Code Ownership
(Encapsulation)
internal
public
private
protected
Mall
Murphy’s
Coffee Shop
Ben’s Boba
Shop
Boba Machine
Project Structure
Report Card
Build times
Code Ownership
App Bundles
Bigger =/= Better
For every 6mb of app size, install conversion
drops by 1%.
Cutting an 100mb app to 10mb would
see download conversion increase by 30%.
The state of modularization at
Airbnb
160+
modules
2-5
minutes
“Lite”
builds
Lots of
lessons
learned…
Airbnb
experiences
listing
homes
listing
Structure your code link your organization
Conway’s Law.
Base
Modularize by feature.
Base (Core)
• “Pure” Infrastructure.
• Strictly no domain knowledge.
• “Could this be open source-able?”
• Owned by Native Infrastructure team.
• Keep it lean. No deprecated code.
Home Listing
Experience
Listing
Modularize by feature.
Feature Modules
•Owned by a single team.
•Encapsulates a single “feature”.
•A single, addressable entry-point.
•Smaller is better.
•A team might own many feature modules.
•Can not depend on other feature modules.
Base
Airbnb
Home Listing
Experience
Listing
Modularize by feature.
App shell
•No feature-specific code.
•No infrastructure-specific code.
•Creates Dagger Component. Base
Project Structure
Report Card
Build times
Code Ownership
App Bundles
Modularize by feature.
Airbnb
Home Listing
Base
Experience Listing
Modularize by feature.
Airbnb
Home Listing
Base
Experience
Listing
Place
List your
space
Mange your
space
Profile Payments Itinerary
Build times?
Airbnb
Home Listing
Base
Experience
Listing
Place
List your
space
Mange your
space
Profile Payments Itinerary
Project Structure
Report Card
Build times
Code Ownership
App Bundles
🏎
Ownership?
Airbnb
Home Listing
Base
Experience
Listing
Place
List your
space
Mange your
space
Profile Payments Itinerary
Ownership?
Airbnb
Home Listing
Base
Experience
Listing
Place
List your
space
Mange your
space
Profile Payments Itinerary
Ownership?
Airbnb
Home Listing
Base
Experience
Listing
Place
List your
space
Mange your
space
Profile Payments Itinerary
Project Structure
Report Card
Build times
Code Ownership
App Bundles
🏎
🤝
Instant Apps / Dynamic Features?
Airbnb
Home Listing
Base
Experience
Listing
Place
List your
space
Manage your
space
Profile Payments Itinerary
Project Structure
Report Card
Build times
Code Ownership
App Bundles
🏎
🤝
✅
Common Challenges
Sharing CodeNavigation Upstrea
Common Challenges
Sharing Code
Navigation
Upstreaming depend
fun launch(context: Context, listingId: Long) {
val listingIntent = Intent(context, HomeListingActivity::class.java).apply {
putExtra("listingId", listingId)
}
context.startActivity(listingIntent)
}
Airbnb
Base
Home Listing Experience Listing
Nearby Experiences
fun launch(context: Context, listingId: Long) {
val listingIntent = Intent(context, HomeListingActivity::class.java).apply {
putExtra("listingId", listingId)
}
context.startActivity(listingIntent)
}
Airbnb
Base
Home Listing Experience Listing
Nearby Experiences
fun launch(context: Context, listingId: Long) {
val listingIntent = Intent(context, ExperienceListingActivity::class.java).apply {
putExtra("listingId", listingId)
}
context.startActivity(listingIntent)
}
Airbnb
Base
Home Listing Experience Listing
Nearby Experiences
fun launch(context: Context, listingId: Long) {
val listingIntent = Intent(context, ExperienceListingActivity::class.java).apply {
putExtra("listingId", listingId)
}
context.startActivity(listingIntent)
}
Airbnb
Base
Home Listing Experience Listing
Nearby Experiences
fun launch(context: Context, listingId: Long) {
val listingIntent = Intent(context, ExperienceListingActivity::class.java).apply {
putExtra("listingId", listingId)
}
context.startActivity(listingIntent)
}
Airbnb
Base
Home Listing Experience Listing
Nearby Experiences
Airbnb
Base
Home Listing Experience Listing
Intents
fun <T> loadClassOrNull(className: String): Class<T>? {
return CLASS_MAP.getOrPut(className) {
try {
Class.forName(className)
} catch (e: ClassNotFoundException) {
// Can't store a null value in the concurrent map
return null
}
}.castOrNull()
object Activities {
fun intentForListing(context: Context, listingId: Long): Intent? {
val args = Bundle().apply { putLong("listingId", listingId) }
return loadClassOrNull<Activity>("com.airbnb.android.ListingActivity")
.let { Intent(context, it) }
.apply { putExtras(args) }
}
}
object Fragments {
fun fragmentForListing(listingId: Long): Fragment? {
val args = Bundle().apply { putLong("listingId", listingId) }
return loadClassOrNull<Fragment>("com.airbnb.android.ListingFragment")
object Activities {
fun intentForListing(context: Context, listingId: Long): Intent? {
val args = Bundle().apply { putLong("listingId", listingId) }
return loadClassOrNull<Activity>("com.airbnb.android.ListingActivity")
.let { Intent(context, it) }
.apply { putExtras(args) }
}
}
object Fragments {
fun fragmentForListing(listingId: Long): Fragment? {
val args = Bundle().apply { putLong("listingId", listingId) }
return loadClassOrNull<Fragment>("com.airbnb.android.ListingFragment")
?.newInstance()
?.apply { this.arguments?.putBundle("args", args) }
}
}
object Fragments {
fun fragmentForListing(listingId: Long): Fragment? {
val args = Bundle().apply { putLong("listingId", listingId) }
return loadClassOrNull<Fragment>("com.airbnb.android.ListingFragment")
?.newInstance()
?.apply { this.arguments?.putBundle("args", args) }
}
}
Intent?
•This is a dynamic feature. Need to download it with PlayCore.
•This is debug build and the module is not present. Toast developer.
•A developer deleted the activity/fragment.
Reflect on all fragment/activity entries and assert present.
Airbnb
Base
Home Listing Experience Listing
Intents
Common Challenges
Sharing Code
Navigation
Upstreaming depend
Common Challenges
Sharing Code
Navigation Upstreaming dependencies
Home Listing
Experience
Listing
Wishlist Intents
Airbnb
Base
*Same dependencies as before. I’m just lazy…
Airbnb
Base
Home Listing
Experience
Listing
Wishlist Intents
Wishlist
Manager
Home Listing
Experience
Listing
Wishlist Intents
Airbnb
Base
Wishlist
Manager
Base
Wishlist
Manager
SearchFilters
ListingFormat
ter
Home Listing
Experience
Listing
Wishlist Intents
Airbnb
Library Modules
lib.WishlistManager
• Owned by a single team.
• No launchable features.
• Provides consumable
dependencies via an interface.
• Forces API design, instead of
sticking if/when cases in other
teams code.
Base
Airbnb
Home ExperienceWishlist Intents
Keep your base lean.Keep your base lean.
Communicating Deprecation
Base
Home Listing
Experience
Listing
Wishlist
Communicating Deprecation
Base
Guava
Home Listing
Experience
Listing
Wishlist
Communicating Deprecation
Base
Guava
Home Listing
Experience
Listing
Wishlist
Murphy’s Law of code access:
“Whatever they can access,
they will access
(and it will go wrong)”.
Communicating Deprecation
Base
Guava
Home Listing
Experience
Listing
Wishlist
Communicating Deprecation
Base
lib.deprecated.Guava
Home Listing
Experience
Listing
Wishlistnew feature
Common Challenges
Sharing Code
Navigation Upstreaming dependencies
Common Challenges
Sharing CodeNavigation
Upstreaming dependencies
Airbnb
Experience ListingHome Listing
class TrebuchetRequest(val keys: Set<TrebuchetKey>)
Base
Experience ListingHome Listing
Base
enum class HomeListingTrebuchetKeys(override val key: String) : TrebuchetKey {
ShowSimilarExperiences(“android.show_similiar_experiences”),
UseMvRx(“android.use_mvrx_home_listing”),
}
Airbnb
Experience ListingHome Listing
enum class ExperienceListingTrebuchetKeys(override val key: String): TrebuchetKey {
ShowSimilarListings(“android.show_similiar_listings”),
UseVideos(“android.use_videos”),
}
Base
Airbnb
Experience ListingHome Listing
Base
Airbnb
class TrebuchetRequest(val keys: Set<TrebuchetKey>)
class TrebuchetKey
Experience ListingHome Listing
Base
Airbnb
Violates no infrastructure in shell.
Violates single module owner.
class TrebuchetRequest(val keys: Set<TrebuchetKey>)
class TrebuchetKey
Experience ListingHome Listing
Base
Airbnb
val trebuchetKeys =
“Plugin” Architecture
class TrebuchetRequest(…)
class TrebuchetKey
interface BaseGraph {
val trebuchetKeys: Set<TrebuchetKey>
}
Experience ListingHome Listing
Base
Airbnb
“Plugin” Architecture
class AirbnbApplication : Application() {
override fun onCreate() {
val baseGraph = object : BaseGraph {
override val trebuchetKeys: Set<TrebuchetKey>
get() {
return mutableSetOf<TrebuchetKey>().apply {
addAll(HomeListingTrebuchetKeys.values())
addAll(ExperienceListingTrebuchetKeys.values())
}
}
}
BaseApplication.onTargetApplicationCreated(this, baseGraph)
}
}
Experience ListingHome Listing
Base
Airbnb
“Plugin” Architecture
class AirbnbApplication : Application() {
override fun onCreate() {
val baseGraph = object : BaseGraph {
override val trebuchetKeys: Set<TrebuchetKey>
get() {
return mutableSetOf<TrebuchetKey>().apply {
addAll(HomeListingTrebuchetKeys.values())
addAll(ExperienceListingTrebuchetKeys.values())
}
}
}
BaseApplication.onTargetApplicationCreated(this, baseGraph)
}
}
Experience ListingHome Listing
Base
Airbnb
“Plugin” Architecture
class AirbnbApplication : Application() {
override fun onCreate() {
val baseGraph = object : BaseGraph {
override val trebuchetKeys: Set<TrebuchetKey>
get() {
return mutableSetOf<TrebuchetKey>().apply {
addAll(HomeListingTrebuchetKeys.values())
addAll(ExperienceListingTrebuchetKeys.values())
}
}
}
BaseApplication.onTargetApplicationCreated(this, baseGraph)
}
}
class AirbnbApplication : Application() {
override fun onCreate() {
val baseGraph = object : BaseGraph {
override val trebuchetKeys: Set<TrebuchetKey>
get() {
return mutableSetOf<TrebuchetKey>().apply {
addAll(HomeListingTrebuchetKeys.values())
addAll(ExperienceListingTrebuchetKeys.values())
}
}
}
BaseApplication.onTargetApplicationCreated(this, baseGraph)
}
}
Experience ListingHome Listing
Base
Airbnb
“Plugin” Architecture
Experience ListingHome Listing
Base
Airbnb
“Plugin” Architecture
class AirbnbApplication : Application() {
override fun onCreate() {
val baseGraph = object : BaseGraph {
override val trebuchetKeys: Set<TrebuchetKey>
get() {
return mutableSetOf<TrebuchetKey>().apply {
addAll(HomeListingTrebuchetKeys.values())
addAll(ExperienceListingTrebuchetKeys.values())
}
}
}
BaseApplication.onTargetApplicationCreated(this, baseGraph)
}
}
class AirbnbApplication : Application() {
override fun onCreate() {
val baseGraph = object : BaseGraph {
override val trebuchetKeys: Set<TrebuchetKey>
get() {
return mutableSetOf<TrebuchetKey>().apply {
addAll(HomeListingTrebuchetKeys.values())
addAll(Feature1TrebuchetKeys.values())
…
addAll(FeatureNTrebuchetKeys.values())
addAll(ExperienceListingTrebuchetKeys.values())
}
}
}
BaseApplication.onTargetApplicationCreated(this, baseGraph)
}
}
Experience
Listing
Home
Listing
Base
Airbnb
“Plugin” Architecture
Feature1Feature1
Managing Multi-module
Projects with Dagger
Airbnb
ExperienceListingHomeListing
Airbnb
AirbnbComponent
ExperienceListingHomeListing
HomeListingComponent ExperienceListingComponent
@ComponentScope
@Singleton
“App scope”
Base
Airbnb
AirbnbComponent
ExperienceListingHomeListing
HomeListingComponent ExperienceListingComponent
@ComponentScope
@Singleton
“App scope”
Base
Airbnb
AirbnbComponent
ExperienceListingHomeListing
HomeListingComponent ExperienceListingComponent
@ComponentScope
@Singleton
“App scope”
Base
AppModule AppModule
@Singleton
“App scope”
@Singleton
“App scope”
Airbnb
AirbnbComponent : AppGraph
ExperienceListingHomeListing
HomeListingComponent ExperienceListingComponent
Base
AppModule AppModule
AppGraph
@Multibinds @Multibinds
Unified Dagger System.
interface BaseGraph {
fun trebuchetKeys(): Set<TrebuchetKey>
}
@Module
abstract class BaseAppModule {
@Multibinds abstract fun trebuchetKeys(): Set<TrebuchetKey>
}
class HomeListingDagger {
@Subcomponent
@ComponentScope
interface HomeListingComponent {
@Subcomponent.Builder
interface Builder {
fun build(): HomeListingComponent
}
}
@Module
abstract class AppModule {
@Binds
@ElementsIntoSet
fun provideTrebuchetKeys() : Set<Trebuchet> {
return HomeListingTrebuchetKeys.values().toSet()
}
}
interface HomeListingAppGraph {
fun p3Builder(): HomeListingComponent.Builder
}
}
Base
Features
Airbnb
}
interface AppGraph
}
interface BaseGraph {
fun trebuchetKeys(): Set<TrebuchetKey>
}
@Module
abstract class BaseAppModule {
@Multibinds abstract fun trebuchetKeys(): Set<Trebuchet
}
class HomeListingDagger {
@Subcomponent
@ComponentScope
interface HomeListingComponent {
@Subcomponent.Builder
interface Builder {
fun build(): HomeListingComponent
}
}
@Module
abstract class AppModule {
@Binds
@ElementsIntoSet
fun provideTrebuchetKeys() : Set<Trebuchet> {
return HomeListingTrebuchetKeys.values().toSet()
}
}
interface HomeListingAppGraph {
fun p3Builder(): HomeListingComponent.Builder
}
}
Base
Features
Airbnb
}
interface AppGraph
}
class HomeListingDagger {
@Subcomponent
@ComponentScope
interface HomeListingComponent {
@Subcomponent.Builder
interface Builder {
fun build(): HomeListingComponent
}
}
@Module
abstract class AppModule {
@Binds
@ElementsIntoSet
fun provideTrebuchetKeys() : Set<Trebuchet> {
return HomeListingTrebuchetKeys.values().toSet()
}
}
interface AppGraph {
fun p3Builder(): HomeListingComponent.Builder
}
Features
Airbnb
interface AppGraph
}
class HomeListingDagger {
@Subcomponent
@ComponentScope
interface HomeListingComponent {
@Subcomponent.Builder
interface Builder {
fun build(): HomeListingComponent
}
}
@Module
abstract class AppModule {
@Binds
@ElementsIntoSet
fun provideTrebuchetKeys() : Set<Trebuchet> {
return HomeListingTrebuchetKeys.values().toSet()
}
}
interface AppGraph {
fun p3Builder(): HomeListingComponent.Builder
}
Features
Airbnb
interface AppGraph
}
class HomeListingDagger {
@Subcomponent
@ComponentScope
interface HomeListingComponent {
@Subcomponent.Builder
interface Builder {
fun build(): HomeListingComponent
}
}
@Module
abstract class AppModule {
@Binds
@ElementsIntoSet
fun provideTrebuchetKeys() : Set<Trebuchet> {
return HomeListingTrebuchetKeys.values().toSet()
}
}
interface AppGraph {
fun p3Builder(): HomeListingComponent.Builder
}
Features
Airbnb
interface AppGraph
}
Airbnb
@Singleton
@Component(modules = [
HomeListingDagger.HomeListingAppModule::class,
ExploreListingDagger.ExploreListingAppModule::class
])
interface AirbnbComponent :
BaseGraph,
HomeListingDagger.HomeListingAppGraph,
ExploreListingDagger.AppGraph
Airbnb
@Singleton
@Component(modules = [
HomeListingDagger.HomeListingAppModule::class,
ExploreListingDagger.ExploreListingAppModule::class
])
interface AirbnbComponent :
BaseGraph,
HomeListingDagger.HomeListingAppGraph,
ExploreListingDagger.AppGraph
Base fun <T : BaseGraph> component(): T
Home Listing
Experience
Listing
Place
List your
space
Mange your
space
Profile Payments Itinerary
Airbnb
@Singleton
@Component(modules = [
HomeListingDagger.HomeListingAppModule::class,
ExploreListingDagger.ExploreListingAppModule::class
])
interface AirbnbComponent :
BaseGraph,
HomeListingDagger.HomeListingAppGraph,
ExploreListingDagger.AppGraph
Base fun <T : BaseGraph> component(): T
Home Listing Place
List your
space
Mange your
space
Profile ItineraryPayments
Experience
Listing
AirbnbApplication Shell
Features
Infrastructure
Libraries
home listing experiencewishlist
lib.wishlistmanager
base
intents
Dagger initialization
Airbnb
Build tooling to support your project structure.
The anatomy of a feature module.
Project/homelisting/
Project/experiencelisting/
build.gradle
TrebuchetKeys.kt
HomeListingDagger.kt
Project/airbnb/
AndroidManifest.xml
res/resources
build.gradle
TrebuchetKeys.kt
ExperienceListingDagger.kt
AndroidManifest.xml
res/resources
AirbnbGraph
AirbnbComponent
build.gradle
settings.gradle
The anatomy of a feature module.
Project/homelisting/
Project/experiencelisting/
build.gradle
TrebuchetKeys.kt
HomeListingDagger.kt
Project/airbnb/
AndroidManifest.xml
res/resources
build.gradle
TrebuchetKeys.kt
ExperienceListingDagger.kt
AndroidManifest.xml
res/resources
AirbnbGraph
AirbnbComponent
build.gradle
settings.gradle
package <%= module_info.qualified_package_name %>
import com.airbnb.android.base.trebuchet.TrebuchetKey
enum class <%= module_info.name_pascal_case %>TrebuchetKeys(override val key: String) : TrebuchetKey
package <%= module_info.qualified_package_name %>
import com.airbnb.android.base.trebuchet.TrebuchetKey
enum class <%= module_info.name_pascal_case %>TrebuchetKeys(override val key: String) : TrebuchetKey
package <%= module_info.qualified_package_name %>
import com.airbnb.android.base.trebuchet.TrebuchetKey
enum class <%= module_info.name_pascal_case %>TrebuchetKeys(override val key: String) : TrebuchetKey
{
…
'TrebuchetKeys.kt' => “#{module_info.main_dir}/#{module_info.name_pascal_case}TrebuchetKeys.kt",
…
}.each do |template, file_name|
erb_template = ERB.new(File.read("#{template_dir}/#{template}"), nil, '-')
File.write(file_name, erb_template.result(binding))
end
ml006617bschwab:android ben_schwab$ bundle exec rake make_module
Module name (with spaces)
home listing
Creating home listing with package name com.airbnb.android.homelisting
Will you be moving/writing Java code in this module? [y/n]
n
Add home listing as a dependency of the flavor.full module? [y/n]
n
Run `./buckw project --skip-build` for intellij to pick up the new module.
Airbnb “Lite”
Clean Builds
Time(seconds)
0
12.5
25
37.5
50
Hundreds of source files
.2 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40
4 5 6 7 8 9 10# of modules
Clean Builds
Time(seconds)
0
12.5
25
37.5
50
Hundreds of source files
.2 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40
4 5 6 7 8 9 10# of modules
Project Structure
Report Card
Build times
Code Ownership
App Bundles
🏎
🤝
✅
Clean Builds
Time(seconds)
0
12.5
25
37.5
50
Hundreds of source files
.2 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40
4 5 6 7 8 9 10# of modules
Project Structure
Report Card
Build times
Code Ownership
App Bundles
🤝
✅
🐢
Clean Builds
Time(seconds)
0
12.5
25
37.5
50
Hundreds of source files
.2 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40
4 5 6 7 8 9 10# of modules
What is a flavor?
build.gradle
android {
    ...
    defaultConfig {...}
    buildTypes {
        debug{...}
        release{...}
    }
    // Specifies one flavor dimension.
    flavorDimensions "version"
    productFlavors {
        demo {
            // Assigns this product flavor to the "version" flavor dimension.
            // This property is optional if you are using only one dimension.
            dimension "version"
            applicationIdSuffix ".demo"
            versionNameSuffix "-demo"
        }
        full {
            dimension "version"
            applicationIdSuffix ".full"
            versionNameSuffix "-full"
        }
    }
}
build.gradle
android {
    ...
    defaultConfig {...}
    buildTypes {
        debug{...}
        release{...}
    }
    // Specifies one flavor dimension.
    flavorDimensions "version"
    productFlavors {
        demo {
            // Assigns this product flavor to the "version" flavor dimension.
            // This property is optional if you are using only one dimension.
            dimension "version"
            applicationIdSuffix ".demo"
            versionNameSuffix "-demo"
        }
        full {
            dimension "version"
            applicationIdSuffix ".full"
            versionNameSuffix "-full"
        }
    }
}
./gradlew :airbnb:installDemoDebug
AirbnbApplication Shell
Features
Infrastructure
Libraries
home listing experiencewishlist
lib.wishlistmanager
base
intents
AirbnbApplication Shell
Features
Infrastructure
Libraries
home listing experiencewishlist
lib.wishlistmanager
base
intents
Flavors flavor.homes flavor.experiencesflavor.full
AirbnbApplication Shell
Features
Infrastructure
Libraries
home listing experiencewishlist
lib.wishlistmanager
base
intents
Flavors flavor.homes flavor.experiencesflavor.full
AirbnbApplication Shell
Features
Infrastructure
Libraries
home listing experiencewishlist
lib.wishlistmanager
base
intents
Flavors flavor.homes flavor.experiencesflavor.full
Airbnb
home listing experiencewishlist
lib.wishlistmanager
base
intents
flavor.homes flavor.experiencesflavor.full
Application Shell
Features
Infrastructure
Libraries
Flavors
Clean Builds
Time(seconds)
0
12.5
25
37.5
50
Hundreds of source files
.2 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40
Full build
Clean Builds
Time(seconds)
0
12.5
25
37.5
50
Time
.2 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40
Full build
Clean Builds
Time(seconds)
0
12.5
25
37.5
50
Time
.2 2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40
Full Build Lite Flavor
1) It needs to be easy to create a flavor.
project.flavors.each { flavor, config ->
project.dependencies.add("${flavor}Api", project(config.entryModule))
}
project.ext.flavors = [
// If you want your flavor to be installed as a separate app for side-by-side
installation, do:
// foo: new FlavorOptions(":favor.foo").useSeparatePackageName()
full: new FlavorOptions(":flavor.full"),
homeListing: new FlavorOptions(“:flavor.homelisting”).useSeparatePackageName(),
]
Airbnb
productFlavors {
project.flavors.each { flavor, config ->
"$flavor" {
dimension 'scope'
if (flavor != 'full') {
versionNameSuffix ".$flavor"
if (config.useSeparatePackageName) {
applicationIdSuffix ".$flavor"
}
}
}
}
}
build.gradle
Specify the the flavor module to install.
project.flavors.each { flavor, config ->
project.dependencies.add("${flavor}Api", project(config.entryModule))
}
Airbnb
productFlavors {
project.flavors.each { flavor, config ->
"$flavor" {
dimension 'scope'
if (flavor != 'full') {
versionNameSuffix ".$flavor"
if (config.useSeparatePackageName) {
applicationIdSuffix ".$flavor"
}
}
}
}
}
build.gradle
Allow side-by-side installation of lite apps.
Create the flavor.
project.flavors.each { flavor, config ->
project.dependencies.add("${flavor}Api", project(config.entryModule))
}
Airbnb build.gradle
project.flavors.each { flavor, config ->
project.dependencies.add("${flavor}Api", project(config.entryModule))
}
Airbnb build.gradle
flavor.homes build.gradle
dependencies {
api project(':homelisting')
api project(':hometour')
}
Airbnb
flavor.homes
home listing
lib.wishlistmanag
base
hometour
Generate the dagger component
1) It needs to be easy to create a flavor.
2) Flavors need to handle missing code gracefully.
AirbnbApplication Shell
Features
Infrastructure
Libraries
home listing experiencewishlist
lib.wishlistmanager
base
intents
Flavors flavor.homes flavor.experiencesflavor.full
AirbnbApplication Shell
Features
Infrastructure
Libraries
home listing experiencewishlist
lib.wishlistmanager
base
intents
Flavors flavor.homes flavor.experiencesflavor.full
3) Can flavors be more than just faster?
1) It needs to be easy to create a flavor.
2) Flavors need to handle missing code gracefully.
Custom Launchers
Module Unloading
@gpeal8Special thanks to:
MvRx
GraphQL
@BenSchwab13
Modularization
Epoxy
Server Driven UI

More Related Content

What's hot

Swagger / Quick Start Guide
Swagger / Quick Start GuideSwagger / Quick Start Guide
Swagger / Quick Start GuideAndrii Gakhov
 
Clean backends with NestJs
Clean backends with NestJsClean backends with NestJs
Clean backends with NestJsAymene Bennour
 
Cypress, Playwright, Selenium, or WebdriverIO? Let the Engineers Speak!
Cypress, Playwright, Selenium, or WebdriverIO? Let the Engineers Speak!Cypress, Playwright, Selenium, or WebdriverIO? Let the Engineers Speak!
Cypress, Playwright, Selenium, or WebdriverIO? Let the Engineers Speak!Applitools
 
Spring Security
Spring SecuritySpring Security
Spring SecurityBoy Tech
 
Android PPT Presentation 2018
Android PPT Presentation 2018Android PPT Presentation 2018
Android PPT Presentation 2018Rao Purna
 
Playwright: A New Test Automation Framework for the Modern Web
Playwright: A New Test Automation Framework for the Modern WebPlaywright: A New Test Automation Framework for the Modern Web
Playwright: A New Test Automation Framework for the Modern WebApplitools
 
Progressive Web App Testing With Cypress.io
Progressive Web App Testing With Cypress.ioProgressive Web App Testing With Cypress.io
Progressive Web App Testing With Cypress.ioKnoldus Inc.
 
iOS Modular Architecture with Tuist
iOS Modular Architecture with TuistiOS Modular Architecture with Tuist
iOS Modular Architecture with Tuist정민 안
 
iOS Architecture
iOS ArchitectureiOS Architecture
iOS ArchitectureJacky Lian
 
BDD WITH CUCUMBER AND JAVA
BDD WITH CUCUMBER AND JAVABDD WITH CUCUMBER AND JAVA
BDD WITH CUCUMBER AND JAVASrinivas Katakam
 
Unreal Engine (For Creating Games) Presentation
Unreal Engine (For Creating Games) PresentationUnreal Engine (For Creating Games) Presentation
Unreal Engine (For Creating Games) PresentationNitin Sharma
 
PowerShell Technical Overview
PowerShell Technical OverviewPowerShell Technical Overview
PowerShell Technical Overviewallandcp
 
Arquitetura Node com NestJS
Arquitetura Node com NestJSArquitetura Node com NestJS
Arquitetura Node com NestJSVanessa Me Tonini
 
Cypress - Best Practices
Cypress - Best PracticesCypress - Best Practices
Cypress - Best PracticesBrian Mann
 
Android Development: The Basics
Android Development: The BasicsAndroid Development: The Basics
Android Development: The BasicsMike Desjardins
 
Vue.js SSR with Nuxt.js and Firebase
Vue.js SSR with Nuxt.js and Firebase Vue.js SSR with Nuxt.js and Firebase
Vue.js SSR with Nuxt.js and Firebase David Pichsenmeister
 
Qt Design Patterns
Qt Design PatternsQt Design Patterns
Qt Design PatternsYnon Perek
 

What's hot (20)

Swagger / Quick Start Guide
Swagger / Quick Start GuideSwagger / Quick Start Guide
Swagger / Quick Start Guide
 
Clean backends with NestJs
Clean backends with NestJsClean backends with NestJs
Clean backends with NestJs
 
Cypress, Playwright, Selenium, or WebdriverIO? Let the Engineers Speak!
Cypress, Playwright, Selenium, or WebdriverIO? Let the Engineers Speak!Cypress, Playwright, Selenium, or WebdriverIO? Let the Engineers Speak!
Cypress, Playwright, Selenium, or WebdriverIO? Let the Engineers Speak!
 
Node js
Node jsNode js
Node js
 
Spring Security
Spring SecuritySpring Security
Spring Security
 
Android PPT Presentation 2018
Android PPT Presentation 2018Android PPT Presentation 2018
Android PPT Presentation 2018
 
Node js Introduction
Node js IntroductionNode js Introduction
Node js Introduction
 
Playwright: A New Test Automation Framework for the Modern Web
Playwright: A New Test Automation Framework for the Modern WebPlaywright: A New Test Automation Framework for the Modern Web
Playwright: A New Test Automation Framework for the Modern Web
 
Progressive Web App Testing With Cypress.io
Progressive Web App Testing With Cypress.ioProgressive Web App Testing With Cypress.io
Progressive Web App Testing With Cypress.io
 
iOS Modular Architecture with Tuist
iOS Modular Architecture with TuistiOS Modular Architecture with Tuist
iOS Modular Architecture with Tuist
 
iOS Architecture
iOS ArchitectureiOS Architecture
iOS Architecture
 
Aidl service
Aidl serviceAidl service
Aidl service
 
BDD WITH CUCUMBER AND JAVA
BDD WITH CUCUMBER AND JAVABDD WITH CUCUMBER AND JAVA
BDD WITH CUCUMBER AND JAVA
 
Unreal Engine (For Creating Games) Presentation
Unreal Engine (For Creating Games) PresentationUnreal Engine (For Creating Games) Presentation
Unreal Engine (For Creating Games) Presentation
 
PowerShell Technical Overview
PowerShell Technical OverviewPowerShell Technical Overview
PowerShell Technical Overview
 
Arquitetura Node com NestJS
Arquitetura Node com NestJSArquitetura Node com NestJS
Arquitetura Node com NestJS
 
Cypress - Best Practices
Cypress - Best PracticesCypress - Best Practices
Cypress - Best Practices
 
Android Development: The Basics
Android Development: The BasicsAndroid Development: The Basics
Android Development: The Basics
 
Vue.js SSR with Nuxt.js and Firebase
Vue.js SSR with Nuxt.js and Firebase Vue.js SSR with Nuxt.js and Firebase
Vue.js SSR with Nuxt.js and Firebase
 
Qt Design Patterns
Qt Design PatternsQt Design Patterns
Qt Design Patterns
 

Similar to Scaling an Android App from 1 to 100 Developers with Modularization

Cross Platform Mobile Apps with the Ionic Framework
Cross Platform Mobile Apps with the Ionic FrameworkCross Platform Mobile Apps with the Ionic Framework
Cross Platform Mobile Apps with the Ionic FrameworkTroy Miles
 
Playing with parse.com
Playing with parse.comPlaying with parse.com
Playing with parse.comJUG Genova
 
Prairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API ResponsesPrairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API Responsesdarrelmiller71
 
Building native mobile apps with word press
Building native mobile apps with word pressBuilding native mobile apps with word press
Building native mobile apps with word pressNikhil Vishnu P.V
 
From System Engineer to Gopher
From System Engineer to GopherFrom System Engineer to Gopher
From System Engineer to GopherI-Fan Wang
 
Build your-own-instagram-filters-with-javascript-202-335 (1)
Build your-own-instagram-filters-with-javascript-202-335 (1)Build your-own-instagram-filters-with-javascript-202-335 (1)
Build your-own-instagram-filters-with-javascript-202-335 (1)Thinkful
 
Automated Testing for Terraform, Docker, Packer, Kubernetes, and More
Automated Testing for Terraform, Docker, Packer, Kubernetes, and MoreAutomated Testing for Terraform, Docker, Packer, Kubernetes, and More
Automated Testing for Terraform, Docker, Packer, Kubernetes, and MoreC4Media
 
Lecture 12 - Maps, AR_VR_aaaaHardware.pptx
Lecture 12 - Maps, AR_VR_aaaaHardware.pptxLecture 12 - Maps, AR_VR_aaaaHardware.pptx
Lecture 12 - Maps, AR_VR_aaaaHardware.pptxNgLQun
 
Compose Camp: Introduction to Kotlin.pptx
Compose Camp: Introduction to Kotlin.pptxCompose Camp: Introduction to Kotlin.pptx
Compose Camp: Introduction to Kotlin.pptxAmruthasriAmaravati
 
Internal Android Library Management (DroidCon SF 2016, Droidcon Italy 2016)
Internal Android Library Management (DroidCon SF 2016, Droidcon Italy 2016)Internal Android Library Management (DroidCon SF 2016, Droidcon Italy 2016)
Internal Android Library Management (DroidCon SF 2016, Droidcon Italy 2016)Kelly Shuster
 
Workshop 15: Ionic framework
Workshop 15: Ionic frameworkWorkshop 15: Ionic framework
Workshop 15: Ionic frameworkVisual Engineering
 
Building Serverless APIs (January 2017)
Building Serverless APIs (January 2017)Building Serverless APIs (January 2017)
Building Serverless APIs (January 2017)Julien SIMON
 
Hosting Your Own OTA Update Service
Hosting Your Own OTA Update ServiceHosting Your Own OTA Update Service
Hosting Your Own OTA Update ServiceQuinlan Jung
 
Alfresco Development Framework Basic
Alfresco Development Framework BasicAlfresco Development Framework Basic
Alfresco Development Framework BasicMario Romano
 
Telerik AppBuilder Presentation for TelerikNEXT Conference
Telerik AppBuilder Presentation for TelerikNEXT ConferenceTelerik AppBuilder Presentation for TelerikNEXT Conference
Telerik AppBuilder Presentation for TelerikNEXT ConferenceJen Looper
 
Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015
Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015
Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015MobileMoxie
 
Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015
Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015
Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015Suzzicks
 
Session-1 edited.pptx
Session-1 edited.pptxSession-1 edited.pptx
Session-1 edited.pptxscienceTech11
 
Progressive Web Application by Citytech
Progressive Web Application by CitytechProgressive Web Application by Citytech
Progressive Web Application by CitytechRitwik Das
 

Similar to Scaling an Android App from 1 to 100 Developers with Modularization (20)

Cross Platform Mobile Apps with the Ionic Framework
Cross Platform Mobile Apps with the Ionic FrameworkCross Platform Mobile Apps with the Ionic Framework
Cross Platform Mobile Apps with the Ionic Framework
 
Playing with parse.com
Playing with parse.comPlaying with parse.com
Playing with parse.com
 
Prairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API ResponsesPrairie DevCon 2015 - Crafting Evolvable API Responses
Prairie DevCon 2015 - Crafting Evolvable API Responses
 
Building native mobile apps with word press
Building native mobile apps with word pressBuilding native mobile apps with word press
Building native mobile apps with word press
 
From System Engineer to Gopher
From System Engineer to GopherFrom System Engineer to Gopher
From System Engineer to Gopher
 
Build your-own-instagram-filters-with-javascript-202-335 (1)
Build your-own-instagram-filters-with-javascript-202-335 (1)Build your-own-instagram-filters-with-javascript-202-335 (1)
Build your-own-instagram-filters-with-javascript-202-335 (1)
 
Automated Testing for Terraform, Docker, Packer, Kubernetes, and More
Automated Testing for Terraform, Docker, Packer, Kubernetes, and MoreAutomated Testing for Terraform, Docker, Packer, Kubernetes, and More
Automated Testing for Terraform, Docker, Packer, Kubernetes, and More
 
Session-1.pptx
Session-1.pptxSession-1.pptx
Session-1.pptx
 
Lecture 12 - Maps, AR_VR_aaaaHardware.pptx
Lecture 12 - Maps, AR_VR_aaaaHardware.pptxLecture 12 - Maps, AR_VR_aaaaHardware.pptx
Lecture 12 - Maps, AR_VR_aaaaHardware.pptx
 
Compose Camp: Introduction to Kotlin.pptx
Compose Camp: Introduction to Kotlin.pptxCompose Camp: Introduction to Kotlin.pptx
Compose Camp: Introduction to Kotlin.pptx
 
Internal Android Library Management (DroidCon SF 2016, Droidcon Italy 2016)
Internal Android Library Management (DroidCon SF 2016, Droidcon Italy 2016)Internal Android Library Management (DroidCon SF 2016, Droidcon Italy 2016)
Internal Android Library Management (DroidCon SF 2016, Droidcon Italy 2016)
 
Workshop 15: Ionic framework
Workshop 15: Ionic frameworkWorkshop 15: Ionic framework
Workshop 15: Ionic framework
 
Building Serverless APIs (January 2017)
Building Serverless APIs (January 2017)Building Serverless APIs (January 2017)
Building Serverless APIs (January 2017)
 
Hosting Your Own OTA Update Service
Hosting Your Own OTA Update ServiceHosting Your Own OTA Update Service
Hosting Your Own OTA Update Service
 
Alfresco Development Framework Basic
Alfresco Development Framework BasicAlfresco Development Framework Basic
Alfresco Development Framework Basic
 
Telerik AppBuilder Presentation for TelerikNEXT Conference
Telerik AppBuilder Presentation for TelerikNEXT ConferenceTelerik AppBuilder Presentation for TelerikNEXT Conference
Telerik AppBuilder Presentation for TelerikNEXT Conference
 
Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015
Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015
Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015
 
Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015
Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015
Life After Mobilegeddon: App Deep Linking Strategies - Pubcon October 2015
 
Session-1 edited.pptx
Session-1 edited.pptxSession-1 edited.pptx
Session-1 edited.pptx
 
Progressive Web Application by Citytech
Progressive Web Application by CitytechProgressive Web Application by Citytech
Progressive Web Application by Citytech
 

Recently uploaded

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
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptkotipi9215
 
Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - InfographicHr365.us smith
 
buds n tech IT solutions
buds n  tech IT                solutionsbuds n  tech IT                solutions
buds n tech IT solutionsmonugehlot87
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfjoe51371421
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio, Inc.
 
Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Andreas Granig
 
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
 
What are the features of Vehicle Tracking System?
What are the features of Vehicle Tracking System?What are the features of Vehicle Tracking System?
What are the features of Vehicle Tracking System?Watsoo Telematics
 
XpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsXpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsMehedi Hasan Shohan
 
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
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...MyIntelliSource, Inc.
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWave PLM
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfkalichargn70th171
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxTier1 app
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideChristina Lin
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmSujith Sukumaran
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackVICTOR MAESTRE RAMIREZ
 
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.
 

Recently uploaded (20)

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
 
chapter--4-software-project-planning.ppt
chapter--4-software-project-planning.pptchapter--4-software-project-planning.ppt
chapter--4-software-project-planning.ppt
 
Asset Management Software - Infographic
Asset Management Software - InfographicAsset Management Software - Infographic
Asset Management Software - Infographic
 
buds n tech IT solutions
buds n  tech IT                solutionsbuds n  tech IT                solutions
buds n tech IT solutions
 
why an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdfwhy an Opensea Clone Script might be your perfect match.pdf
why an Opensea Clone Script might be your perfect match.pdf
 
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed DataAlluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
Alluxio Monthly Webinar | Cloud-Native Model Training on Distributed Data
 
Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024Automate your Kamailio Test Calls - Kamailio World 2024
Automate your Kamailio Test Calls - Kamailio World 2024
 
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
 
What are the features of Vehicle Tracking System?
What are the features of Vehicle Tracking System?What are the features of Vehicle Tracking System?
What are the features of Vehicle Tracking System?
 
XpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software SolutionsXpertSolvers: Your Partner in Building Innovative Software Solutions
XpertSolvers: Your Partner in Building Innovative Software Solutions
 
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...
 
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
Try MyIntelliAccount Cloud Accounting Software As A Service Solution Risk Fre...
 
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...
 
What is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need ItWhat is Fashion PLM and Why Do You Need It
What is Fashion PLM and Why Do You Need It
 
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdfThe Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
The Essentials of Digital Experience Monitoring_ A Comprehensive Guide.pdf
 
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptxKnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
KnowAPIs-UnknownPerf-jaxMainz-2024 (1).pptx
 
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop SlideBuilding Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
Building Real-Time Data Pipelines: Stream & Batch Processing workshop Slide
 
Intelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalmIntelligent Home Wi-Fi Solutions | ThinkPalm
Intelligent Home Wi-Fi Solutions | ThinkPalm
 
Cloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStackCloud Management Software Platforms: OpenStack
Cloud Management Software Platforms: OpenStack
 
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 ...
 

Scaling an Android App from 1 to 100 Developers with Modularization