SlideShare une entreprise Scribd logo
1  sur  148
Télécharger pour lire hors ligne
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

Contenu connexe

Tendances

[Android] DI in multimodule application
[Android] DI in multimodule application[Android] DI in multimodule application
[Android] DI in multimodule applicationOleg Mazhukin
 
Developing Faster with Swagger
Developing Faster with SwaggerDeveloping Faster with Swagger
Developing Faster with SwaggerTony Tam
 
20221131_레츠스위프트_2022_iOS개발에서_알아두면_좋은것들.pdf
20221131_레츠스위프트_2022_iOS개발에서_알아두면_좋은것들.pdf20221131_레츠스위프트_2022_iOS개발에서_알아두면_좋은것들.pdf
20221131_레츠스위프트_2022_iOS개발에서_알아두면_좋은것들.pdf정민 안
 
Introduction to Swagger
Introduction to SwaggerIntroduction to Swagger
Introduction to SwaggerKnoldus Inc.
 
Maven Basics - Explained
Maven Basics - ExplainedMaven Basics - Explained
Maven Basics - ExplainedSmita Prasad
 
Angular based enterprise level frontend architecture
Angular based enterprise level frontend architectureAngular based enterprise level frontend architecture
Angular based enterprise level frontend architectureHimanshu Tamrakar
 
Introduction to Django
Introduction to DjangoIntroduction to Django
Introduction to DjangoJames Casey
 
Native hook mechanism in Android Bionic linker
Native hook mechanism in Android Bionic linkerNative hook mechanism in Android Bionic linker
Native hook mechanism in Android Bionic linkerKevin Mai-Hsuan Chia
 
ORM: Object-relational mapping
ORM: Object-relational mappingORM: Object-relational mapping
ORM: Object-relational mappingAbhilash M A
 
DSC IIITL Flutter Workshop
DSC IIITL Flutter WorkshopDSC IIITL Flutter Workshop
DSC IIITL Flutter WorkshopDSCIIITLucknow
 
Dynamic Security Analysis & Static Security Analysis for Android Apps.
Dynamic Security Analysis & Static Security Analysis for Android Apps.Dynamic Security Analysis & Static Security Analysis for Android Apps.
Dynamic Security Analysis & Static Security Analysis for Android Apps.VodqaBLR
 
Introduction to jest
Introduction to jestIntroduction to jest
Introduction to jestpksjce
 
Getting started with flutter
Getting started with flutterGetting started with flutter
Getting started with flutterrihannakedy
 

Tendances (20)

[Android] DI in multimodule application
[Android] DI in multimodule application[Android] DI in multimodule application
[Android] DI in multimodule application
 
Developing Faster with Swagger
Developing Faster with SwaggerDeveloping Faster with Swagger
Developing Faster with Swagger
 
20221131_레츠스위프트_2022_iOS개발에서_알아두면_좋은것들.pdf
20221131_레츠스위프트_2022_iOS개발에서_알아두면_좋은것들.pdf20221131_레츠스위프트_2022_iOS개발에서_알아두면_좋은것들.pdf
20221131_레츠스위프트_2022_iOS개발에서_알아두면_좋은것들.pdf
 
Laravel Introduction
Laravel IntroductionLaravel Introduction
Laravel Introduction
 
Introduction to Swagger
Introduction to SwaggerIntroduction to Swagger
Introduction to Swagger
 
Maven Basics - Explained
Maven Basics - ExplainedMaven Basics - Explained
Maven Basics - Explained
 
Maven Overview
Maven OverviewMaven Overview
Maven Overview
 
Angular based enterprise level frontend architecture
Angular based enterprise level frontend architectureAngular based enterprise level frontend architecture
Angular based enterprise level frontend architecture
 
Introduction to Django
Introduction to DjangoIntroduction to Django
Introduction to Django
 
React Server Side Rendering with Next.js
React Server Side Rendering with Next.jsReact Server Side Rendering with Next.js
React Server Side Rendering with Next.js
 
Native hook mechanism in Android Bionic linker
Native hook mechanism in Android Bionic linkerNative hook mechanism in Android Bionic linker
Native hook mechanism in Android Bionic linker
 
Xke spring boot
Xke spring bootXke spring boot
Xke spring boot
 
ORM: Object-relational mapping
ORM: Object-relational mappingORM: Object-relational mapping
ORM: Object-relational mapping
 
DSC IIITL Flutter Workshop
DSC IIITL Flutter WorkshopDSC IIITL Flutter Workshop
DSC IIITL Flutter Workshop
 
Flutter
FlutterFlutter
Flutter
 
Dynamic Security Analysis & Static Security Analysis for Android Apps.
Dynamic Security Analysis & Static Security Analysis for Android Apps.Dynamic Security Analysis & Static Security Analysis for Android Apps.
Dynamic Security Analysis & Static Security Analysis for Android Apps.
 
Introduction to jest
Introduction to jestIntroduction to jest
Introduction to jest
 
Reverse Engineering Android Application
Reverse Engineering Android ApplicationReverse Engineering Android Application
Reverse Engineering Android Application
 
Getting started with flutter
Getting started with flutterGetting started with flutter
Getting started with flutter
 
React
ReactReact
React
 

Similaire à Scaling your Android App 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
 
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
 

Similaire à Scaling your Android App 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
 

Dernier

Salesforce Introduced Zero Copy Partner Network to Simplify the Process of In...
Salesforce Introduced Zero Copy Partner Network to Simplify the Process of In...Salesforce Introduced Zero Copy Partner Network to Simplify the Process of In...
Salesforce Introduced Zero Copy Partner Network to Simplify the Process of In...CloudMetic
 
How to install and activate eGrabber JobGrabber
How to install and activate eGrabber JobGrabberHow to install and activate eGrabber JobGrabber
How to install and activate eGrabber JobGrabbereGrabber
 
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypseTomasz Kowalczewski
 
Jax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined DeckJax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined DeckMarc Lester
 
OpenChain @ LF Japan Executive Briefing - May 2024
OpenChain @ LF Japan Executive Briefing - May 2024OpenChain @ LF Japan Executive Briefing - May 2024
OpenChain @ LF Japan Executive Briefing - May 2024Shane Coughlan
 
BusinessGPT - Security and Governance for Generative AI
BusinessGPT  - Security and Governance for Generative AIBusinessGPT  - Security and Governance for Generative AI
BusinessGPT - Security and Governance for Generative AIAGATSoftware
 
Sinoville Clinic ](+27832195400*)[🏥Abortion Pill Prices Sinoville ● Women's A...
Sinoville Clinic ](+27832195400*)[🏥Abortion Pill Prices Sinoville ● Women's A...Sinoville Clinic ](+27832195400*)[🏥Abortion Pill Prices Sinoville ● Women's A...
Sinoville Clinic ](+27832195400*)[🏥Abortion Pill Prices Sinoville ● Women's A...Abortion Clinic
 
architecting-ai-in-the-enterprise-apis-and-applications.pdf
architecting-ai-in-the-enterprise-apis-and-applications.pdfarchitecting-ai-in-the-enterprise-apis-and-applications.pdf
architecting-ai-in-the-enterprise-apis-and-applications.pdfWSO2
 
Alluxio Monthly Webinar | Simplify Data Access for AI in Multi-Cloud
Alluxio Monthly Webinar | Simplify Data Access for AI in Multi-CloudAlluxio Monthly Webinar | Simplify Data Access for AI in Multi-Cloud
Alluxio Monthly Webinar | Simplify Data Access for AI in Multi-CloudAlluxio, Inc.
 
Prompt Engineering - an Art, a Science, or your next Job Title?
Prompt Engineering - an Art, a Science, or your next Job Title?Prompt Engineering - an Art, a Science, or your next Job Title?
Prompt Engineering - an Art, a Science, or your next Job Title?Maxim Salnikov
 
Transformer Neural Network Use Cases with Links
Transformer Neural Network Use Cases with LinksTransformer Neural Network Use Cases with Links
Transformer Neural Network Use Cases with LinksJinanKordab
 
Weeding your micro service landscape.pdf
Weeding your micro service landscape.pdfWeeding your micro service landscape.pdf
Weeding your micro service landscape.pdftimtebeek1
 
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...Andrea Goulet
 
What is a Recruitment Management Software?
What is a Recruitment Management Software?What is a Recruitment Management Software?
What is a Recruitment Management Software?NYGGS Automation Suite
 
^Clinic ^%[+27788225528*Abortion Pills For Sale In soweto
^Clinic ^%[+27788225528*Abortion Pills For Sale In soweto^Clinic ^%[+27788225528*Abortion Pills For Sale In soweto
^Clinic ^%[+27788225528*Abortion Pills For Sale In sowetokasambamuno
 
The Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test AutomationThe Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test AutomationElement34
 
From Theory to Practice: Utilizing SpiraPlan's REST API
From Theory to Practice: Utilizing SpiraPlan's REST APIFrom Theory to Practice: Utilizing SpiraPlan's REST API
From Theory to Practice: Utilizing SpiraPlan's REST APIInflectra
 
Food Delivery Business App Development Guide 2024
Food Delivery Business App Development Guide 2024Food Delivery Business App Development Guide 2024
Food Delivery Business App Development Guide 2024Chirag Panchal
 
Community is Just as Important as Code by Andrea Goulet
Community is Just as Important as Code by Andrea GouletCommunity is Just as Important as Code by Andrea Goulet
Community is Just as Important as Code by Andrea GouletAndrea Goulet
 

Dernier (20)

Salesforce Introduced Zero Copy Partner Network to Simplify the Process of In...
Salesforce Introduced Zero Copy Partner Network to Simplify the Process of In...Salesforce Introduced Zero Copy Partner Network to Simplify the Process of In...
Salesforce Introduced Zero Copy Partner Network to Simplify the Process of In...
 
How to install and activate eGrabber JobGrabber
How to install and activate eGrabber JobGrabberHow to install and activate eGrabber JobGrabber
How to install and activate eGrabber JobGrabber
 
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse
[GeeCON2024] How I learned to stop worrying and love the dark silicon apocalypse
 
Jax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined DeckJax, FL Admin Community Group 05.14.2024 Combined Deck
Jax, FL Admin Community Group 05.14.2024 Combined Deck
 
Abortion Clinic In Johannesburg ](+27832195400*)[ 🏥 Safe Abortion Pills in Jo...
Abortion Clinic In Johannesburg ](+27832195400*)[ 🏥 Safe Abortion Pills in Jo...Abortion Clinic In Johannesburg ](+27832195400*)[ 🏥 Safe Abortion Pills in Jo...
Abortion Clinic In Johannesburg ](+27832195400*)[ 🏥 Safe Abortion Pills in Jo...
 
OpenChain @ LF Japan Executive Briefing - May 2024
OpenChain @ LF Japan Executive Briefing - May 2024OpenChain @ LF Japan Executive Briefing - May 2024
OpenChain @ LF Japan Executive Briefing - May 2024
 
BusinessGPT - Security and Governance for Generative AI
BusinessGPT  - Security and Governance for Generative AIBusinessGPT  - Security and Governance for Generative AI
BusinessGPT - Security and Governance for Generative AI
 
Sinoville Clinic ](+27832195400*)[🏥Abortion Pill Prices Sinoville ● Women's A...
Sinoville Clinic ](+27832195400*)[🏥Abortion Pill Prices Sinoville ● Women's A...Sinoville Clinic ](+27832195400*)[🏥Abortion Pill Prices Sinoville ● Women's A...
Sinoville Clinic ](+27832195400*)[🏥Abortion Pill Prices Sinoville ● Women's A...
 
architecting-ai-in-the-enterprise-apis-and-applications.pdf
architecting-ai-in-the-enterprise-apis-and-applications.pdfarchitecting-ai-in-the-enterprise-apis-and-applications.pdf
architecting-ai-in-the-enterprise-apis-and-applications.pdf
 
Alluxio Monthly Webinar | Simplify Data Access for AI in Multi-Cloud
Alluxio Monthly Webinar | Simplify Data Access for AI in Multi-CloudAlluxio Monthly Webinar | Simplify Data Access for AI in Multi-Cloud
Alluxio Monthly Webinar | Simplify Data Access for AI in Multi-Cloud
 
Prompt Engineering - an Art, a Science, or your next Job Title?
Prompt Engineering - an Art, a Science, or your next Job Title?Prompt Engineering - an Art, a Science, or your next Job Title?
Prompt Engineering - an Art, a Science, or your next Job Title?
 
Transformer Neural Network Use Cases with Links
Transformer Neural Network Use Cases with LinksTransformer Neural Network Use Cases with Links
Transformer Neural Network Use Cases with Links
 
Weeding your micro service landscape.pdf
Weeding your micro service landscape.pdfWeeding your micro service landscape.pdf
Weeding your micro service landscape.pdf
 
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...
Entropy, Software Quality, and Innovation (presented at Princeton Plasma Phys...
 
What is a Recruitment Management Software?
What is a Recruitment Management Software?What is a Recruitment Management Software?
What is a Recruitment Management Software?
 
^Clinic ^%[+27788225528*Abortion Pills For Sale In soweto
^Clinic ^%[+27788225528*Abortion Pills For Sale In soweto^Clinic ^%[+27788225528*Abortion Pills For Sale In soweto
^Clinic ^%[+27788225528*Abortion Pills For Sale In soweto
 
The Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test AutomationThe Strategic Impact of Buying vs Building in Test Automation
The Strategic Impact of Buying vs Building in Test Automation
 
From Theory to Practice: Utilizing SpiraPlan's REST API
From Theory to Practice: Utilizing SpiraPlan's REST APIFrom Theory to Practice: Utilizing SpiraPlan's REST API
From Theory to Practice: Utilizing SpiraPlan's REST API
 
Food Delivery Business App Development Guide 2024
Food Delivery Business App Development Guide 2024Food Delivery Business App Development Guide 2024
Food Delivery Business App Development Guide 2024
 
Community is Just as Important as Code by Andrea Goulet
Community is Just as Important as Code by Andrea GouletCommunity is Just as Important as Code by Andrea Goulet
Community is Just as Important as Code by Andrea Goulet
 

Scaling your Android App With Modularization