SlideShare a Scribd company logo
Dark side of Android
apps modularization
David Bilík
@bilikdavid
#selfpromo
> 8 years experience with Android development
> Android Team Lead @AckeeCZ
> Lecturer of Android course at Czech Technical University in Prague
> Focused on architecture, testing and beautiful designs
Why am I here?
Why am I here?
> Modularization became popular topic in 2018
> Every conference had at least one talk “How to modularize your app”
> Since we are hype-oriented programmers we hopped on the train in 2019
> But we’ve hit some bumps along the way
What we expected
> Faster build times
> Improved architecture
> Preparation for instant apps/dynamic features
Modularized architecture
> Main inspiration
Modularized architecture
Modularized architecture
> Looks simple
> But contains multiple shady areas
> Google does not have the answers
> Community does not have all answers
> Because they do not exist
Gradle
Gradle
> Apps contains tens of dependencies
> Not all of them used in all modules
> Good idea to define them in one place
buildSrc
> Leverage buildSrc folder in gradle project
> Contains common code (constants, tasks) used in build.gradle scripts
> Can be written in Kotlin
object Config {

const val minSdk = 26

const val compileSdk = 29

const val targetSdk = 29

val javaVersion = JavaVersion.VERSION_1_8

}

android {

compileSdkVersion Config.compileSdk

defaultConfig {

minSdkVersion Config.minSdk

targetSdkVersion Config.targetSdk

versionCode 1

versionName "1.0"

}

buildSrc/src/main/kotlin/Config.kt
app/build.gradle
buildSrc/src/main/kotlin/Deps.kt
object Deps {



"// Koin

private const val koinVersion = "2.0.1"

const val koin = "org.koin:koin-android:$koinVersion"

const val koinScope = "org.koin:koin-androidx-scope:$koinVersion"

const val koinViewModel = "org.koin:koin-androidx-viewmodel:$koinVersion"

"// Epoxy

private const val epoxyVersion = "3.9.0"

const val epoxy = "com.airbnb.android:epoxy:$epoxyVersion"

const val epoxyProcessor = "com.airbnb.android:epoxy-processor:$epoxyVersion"

"// OkHttp

private const val okHttpVersion = "4.3.1"

const val okHttp = "com.squareup.okhttp3:okhttp:$okHttpVersion"

const val okHttpLoggingInterceptor = “com.squareup.okhttp3:logging-interceptor:$okHttpVersion"

…

app/build.gradle
dependencies {

"// Koin

implementation Deps.koin

implementation Deps.koinViewModel
Version updates
> Automatic Android Studio version check is not available
> Plugins exists but not they are not suitable for us
> So.. we check manually 😞
Shared gradle scripts
> A lot of the build.gradle code will be completely the same
≥ defaultConfig with minSdk, compileOptions, applied plugins, …
> Common code can be extracted and applied in module build.gradle
scripts
> Applied code is merged with the code inside module’s build.gradle script
apply plugin: 'com.android.library'

apply plugin: 'kotlin-android'

apply plugin: 'kotlin-android-extensions'

android {

compileSdkVersion Config.compileSdk

defaultConfig {

minSdkVersion Config.minSdk

targetSdkVersion Config.targetSdk

versionCode 1

versionName "1.0"

}

buildTypes {

…

}

productFlavors {

…

}

compileOptions {

sourceCompatibility = 1.8

targetCompatibility = 1.8

}

…
…

kotlinOptions {

jvmTarget = "1.8"

freeCompilerArgs += "-Xopt-in=kotlin.time.ExperimentalTime"

}

testOptions {

unitTests.all {

setIgnoreFailures(true)

}

}

}

gradle/common-library-script.gradle
apply from: “$rootDir/gradle/common-library-script.gradle”

dependencies {

implementation Deps.junit

implementation Deps.rxJava

implementation Deps.rxAndroid

implementation Deps.appCompat

}

mymodule/build.gradle
android {

productFlavors {

flavorDimensions "api"

devApi {

dimension "api"

}

prodApi {

dimension "api"

}

}

}

gradle/common-library-script.gradle
networking/build.gradle
apply from: “$rootDir/gradle/common-library-script.gradle”

android {

productFlavors {

flavorDimensions "api"

devApi {

dimension "api"

buildConfigField("String", "BASE_URL", ""api-development.myapp.com"")

}

prodApi {

dimension “api"

buildConfigField("String", "BASE_URL", ""api.myapp.com"")

}

}

}
Build variants
Build variants
> Example: flavors defining base url for api environment
> What modules care about this flavor?
≥ :app - control what variant of app to build
≥ :networking - contains buildConfigFields with base api url
Build variants
* What went wrong:

Could not determine the dependencies of task ':events:compileDebugAidl'.

> Could not resolve all task dependencies for configuration ':events:debugCompileClasspath'.

> Could not resolve project :networking.

Required by:

project :events

> Cannot choose between the following variants of project :networking:

- devApiDebugRuntime

- devApiDebugUnitTestCompile

- devApiDebugUnitTestRuntime

- devApiReleaseAndroidTestCompile

- devApiReleaseAndroidTestRuntime

- devApiReleaseApiElements

…

./gradlew assembleDevApiDebug
Build variants
events/build.gradle
android {

defaultConfig {

missingDimensionStrategy "api", "devApi"

}

}
gradle/common-library-script.gradle
android {

…

productFlavors {

flavorDimensions "api"

devApi {

dimension "api"

}

prodApi {

dimension "api"

}

}

…

}
app/build.gradle
android {

…

productFlavors {

flavorDimensions "api"

devApi {

dimension "api"

buildConfigField("String", "BASE_URL", ""api-development.myapp.com"")

}

prodApi {

dimension "api"

buildConfigField("String", "BASE_URL", ""api.myapp.com"")

}

}

…

}
networking/src/main/java/…/ApiDefinition.kt
data class ApiDefinition(

val url: HttpUrl

)

fun provideRetrofit(api: ApiDefinition): Retrofit {

return Retrofit.Builder()

.baseUrl(api.url)

.build()

}

networking/src/main/java/…/RetrofitDI.kt
app/src/main/java/…/ApiDI.kt
fun provideApiDefinition(): ApiDefinition{

return ApiDefinition(

BuildConfig.BASE_URL.toHttpUrl()

)

}
Final tip
> Improve organization of modules with directories
Module folders protip
> Prefix module name with directory for automatic placement
Code sharing
Code sharing
> Our apps (try to) follow Uncle Bob’s Clean Architecture
Clean architecture Android
Shared library module
Split feature module
Shared module
Shared module
Navigation
Navigation
> Feature modules are independent of each other
> ActivityA in :featureA does not have access to ActivityB in :featureB
> unified solution for in-feature and between-feature navigation
Navigation #1 solution
object Intents {

fun startingActivity(context: Context): Intent? {

return loadClass<Activity>(“cz.ackee.sample.StartingActivity”)

"?.let { Intent(context, it) }

}

}

object Fragments {

fun contactsFragment(context: Context): Fragment? {

return loadClass<Fragment>(“cz.ackee.sample.contacts.ContactsFragment”)

"?.let { Fragment.instantiate(context, it.name) }

}

}

navigation/src/main/…/Intents.kt
navigation/src/main/…/Fragments.kt
fun <T> loadClass(className:String): Class<T>? {

return try {

Class.forName(className)

} catch (e: Exception) {

null

} as? Class<T>

}
#1 solution - problems
> Not using typical pattern like MyFragment.newInstance(args)
> Fully qualified class names in Strings not changed in refactorings
Arguments passing
> Problems with passing the arguments through :navigation module
> Does not have access to feature classes
object Fragments {

fun contactDetailFragment(context: Context, contact: Contact): Fragment? {

return loadClass<Fragment>("cz.ackee.sample.contact.ContactDetailFragment")

"?.let { Fragment.instantiate(context, it.name, bundleOf(Arguments.CONTACT_KEY to contact)) }

}

}

object Fragments {

fun contactDetailFragment(context: Context, contact: Parcelable): Fragment? {

return loadClass<Fragment>("cz.ackee.sample.contact.ContactDetailFragment")

"?.let { Fragment.instantiate(context, it.name, bundleOf(Arguments.CONTACT_KEY to contact)) }

}

}

navigation/src/main/…/Fragments.kt
Parcelable solution
> Easiest solution
> Type safety is lost
object Fragments {

fun contactDetailFragment(context: Context, contact: ContactDetailNavArgs): Fragment? {

return ClassesCache.loadClassOrNull<Fragment>("cz.ackee.sample.contact.ContactDetailFragment")

"?.let { Fragment.instantiate(context, it.name, bundleOf(NAV_ARGS_KEY to navArgs)) }

}

}

@Parcelize

data class ContactDetailNavArgs(

val contactId: String,

val name: String

): Parcelable

navigation/src/main/…/navargs/ContactDetailNavArgs.kt
navigation/src/main/…/Fragments.kt
inline fun <reified T: Parcelable> Fragment.navArgs() : T {

return requireArguments().getParcelable(NAV_ARGS_KEY)

}

class ContactDetailFragment : Fragment() {

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

toolbar.title = navArgs<ContactDetailNavArgs>().name

}

}

contacts/src/main/…/ContactDetailFragment.kt
navigation/src/main/…/FragmentKtx.kt
NavArgs solution
> Improved type safety
> More boilerplate
Abstracted navigation
> Introduce abstraction over navigation
> Free Fragments/Activities of knowing details of navigation
interface Navigator {

fun openContactDetail(args: ContactDetailNavArgs)
}

class ContactsListFragment : Fragment() {



private val navigator: Navigator by inject()

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

super.onViewCreated(view, savedInstanceState)

contactsList.setOnContactClickListener { contact "->

navigator.openContactDetail(ContactDetailNavArgs(contact.id, contact.name))

}

}

}

navigation/src/main/…/Navigator.kt
contacts/src/main/…/ContactDetailFragment.kt
Navigation Architecture Component
> Navigator implemented with Navigation Architecture Component
> :navigation module still a library module
> Contains navigation graphs, Navigator interface and implementation of this
interface
<?xml version="1.0" encoding="utf-8"?>

<navigation xmlns:android="http:"//schemas.android.com/apk/res/android"

xmlns:app="http:"//schemas.android.com/apk/res-auto"

android:id="@+id/navigation_graph"

app:startDestination="@+id/navigation_contacts_list">

<fragment

android:id="@+id/navigation_contacts_list"

android:name="cz.bilik.sample.contacts.ContactsListFragment" >

<action

android:id="@+id/navigation_action_open_contact_detail"

app:destination="@id/navigation_contact_detail"

app:enterAnim="@anim/nav_default_enter_anim"

app:exitAnim="@anim/nav_default_exit_anim"

app:popEnterAnim="@anim/nav_default_pop_enter_anim"

app:popExitAnim="@anim/nav_default_exit_anim" "/>

"</fragment>

<fragment

android:id="@+id/navigation_contact_detail"

android:name="cz.bilik.sample.contacts.ContactDetailFragment" "/>

"</navigation>
navigation/src/main/res/values/nav_graph.xml
class NavigationComponentNavigator : Navigator {

private var navigationController: NavController? = null

override fun openContactDetail(navArgs: ContactDetailNavArgs) {

navigationController"?.navigate(

R.id.navigation_action_open_contact_detail,

navArgs.toBundle()

)

}

}

navigation/src/main/…/NavigationComponentNavigator.kt
fun bindController(navigationController: NavController) {

this.navigationController = navigationController

}

fun unbindController() {

this.navigationController = null

}
abstract class NavigationActivity : AppCompatActivity() {

val navigator : NavigationComponentNavigator by inject()

override fun onCreate(savedInstanceState: Bundle?) {

super.onCreate(savedInstanceState)

setContentView(R.layout.activity_navigation)

setupNavigation()

}

private fun setupNavigation() {

navigator.bindController(findNavController(R.id.nav_host_fragment))

}

override fun onDestroy() {

super.onDestroy()

navigator.unbindController()

}

}

navigation/src/main/…/NavigationActivity.kt
Abstracted navigation
> Growing Navigator interface
≥ Create multiple smaller Navigators and NavigationActivity
implements all of them
> Multiple Activity
≥ Multiple navigation graphs with multiple NavigationActivitys
Database
Database
> Room used as database library
> No native support for multimodule projects
> Need to define all entities and DAOs in single Database class
Database per feature
Database per feature
➕ Encapsulated within single module
➕ Each database can have different settings - eg. descructive migrations rule
− Aggregations over mutliple tables not possible
− Multiple connections to database
Single database
Single database
➕ Easy to maintain
− Breaks the encapsulation of the features.
Compromise
> :database module containing definition of RoomDatabase
> Keep DAOs and entities within features
Compromise
Compromise
> :database module depends on all features and define RoomDatabase class
> DI for DAOs must be defined in this module
@Entity(tableName = "contacts")

data class DbContact(

@PrimaryKey(autoGenerate = true) val id: Long = 0,

val eventId: Int,

val name: String

)

features/contacts/…/DbContactfeatures/events/…/DbEvent
@Entity(tableName = "events")

data class DbEvent(

@PrimaryKey val id: Int = 0,

val name: String

)

features/events/…/EventsDao
@Query("""

select events.* from events 

join contacts on (contacts.eventId = events.id) 

where contacts.id "== :contactId

""")

abstract fun getEventForContact(contactId: Long): DbEvent
Testing
Testing
> Where to define utilities in tests?
≥ custom JUnit rules for RxJava/Coroutines
≥ extensions on LiveData to retrieve value once available
≥ …
Testing module
> Define them in one place
> Can’t be placed in :base module test source set folder
> Gradle does not support dependencies on test source sets of different
module in android projects
Testing module
> Separate:testing library module
> Contains also dependencies to common testing dependencies
≥ Mocking framework, testing dependencies for coroutines, AndroidX, …
> Important note - don’t declare this dependencies as testXXX and also don’t
place the code to the test source set folder
fun <T> LiveData<T>.getOrAwaitValue(

time: Long = 2,

timeUnit: TimeUnit = TimeUnit.SECONDS,

afterObserve: () "-> Unit = {}

): T {

…

}
libraries/testing/src/main/java/LiveDataKtx.kt
libraries/testing/build.gradle
dependencies {

api Deps.architectureComponentsTesting

api Deps.mockitoInline

api Deps.mockitoKotlin

api Deps.coroutinesTesting

}
features/contacts/build.gradle
dependencies {

…

testImplementation project(':libraries:testing')

}
Test fixtures
> Same problem with test fixtures of feature module
> How to reuse eg. test doubles in different feature module tests?
Test fixtures
Testing database
Testing database
libraries/database-testing/src/main/java/RoomDatabaseRule.kt
class RoomDatabaseRule : TestWatcher() {

lateinit var database: MyDatabase

override fun starting(description: Description?) {

database = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), MyDatabase"::class.java)

.allowMainThreadQueries()

.build()

}

override fun finished(description: Description?) {

database.close()

}

}
@RunWith(AndroidJUnit4"::class)

class ContactsLocalDataSourceTest {

@get:Rule

val databaseRule = RoomDatabaseRule()

private fun createDataSource(): ContactsLocalDataSource {

return ContactsLocalDataSource(

databaseRule.database.contactsDao()

)

}

…
features/contacts/app/src/test/…/ContactsLocalDataSourceTest.kt
Networking
Networking
> :networking library module with common setup - OkHttpClient, Moshi,
Retrofit
> Each feature contains Retrofit API interface with transfer objects (DTO)
≥ eg. LoginRequest, LoginResponse
Networking
> What about generated classes?
≥ gRPC, Swagger-codegen
> Generation of this classes is handled in :networking module
> Feature modules use this generated classes
SUMMARY
Summary
> Would I modularize new app right from the beginning?
> Have we learned anything?
> What about our expectations?
Questions time 🤗
Thanks for participation!
@bilikdavid

More Related Content

What's hot

Sharper Better Faster Dagger ‡ - Droidcon SF
Sharper Better Faster Dagger ‡ - Droidcon SFSharper Better Faster Dagger ‡ - Droidcon SF
Sharper Better Faster Dagger ‡ - Droidcon SF
Pierre-Yves Ricau
 
Angular 2 Essential Training
Angular 2 Essential Training Angular 2 Essential Training
Angular 2 Essential Training
Patrick Schroeder
 
Introduction to angular 2
Introduction to angular 2Introduction to angular 2
Introduction to angular 2
Dhyego Fernando
 
Angular tutorial
Angular tutorialAngular tutorial
Angular tutorial
Rohit Gupta
 
Exploring Angular 2 - Episode 1
Exploring Angular 2 - Episode 1Exploring Angular 2 - Episode 1
Exploring Angular 2 - Episode 1
Ahmed Moawad
 
Angular modules in depth
Angular modules in depthAngular modules in depth
Angular modules in depth
Christoffer Noring
 
Tech Webinar: Angular 2, Introduction to a new framework
Tech Webinar: Angular 2, Introduction to a new frameworkTech Webinar: Angular 2, Introduction to a new framework
Tech Webinar: Angular 2, Introduction to a new framework
Codemotion
 
Angular2 for Beginners
Angular2 for BeginnersAngular2 for Beginners
Angular2 for Beginners
Oswald Campesato
 
Di code steps
Di code stepsDi code steps
Di code steps
Brian Kiptoo
 
Building maintainable app #droidconzg
Building maintainable app #droidconzgBuilding maintainable app #droidconzg
Building maintainable app #droidconzg
Kristijan Jurković
 
Lecture 32
Lecture 32Lecture 32
Lecture 32
Jannat Khan
 
Introduction to Angular 2
Introduction to Angular 2Introduction to Angular 2
Introduction to Angular 2
Knoldus Inc.
 
Angular 5 presentation for beginners
Angular 5 presentation for beginnersAngular 5 presentation for beginners
Angular 5 presentation for beginners
Imran Qasim
 
Angular2 + rxjs
Angular2 + rxjsAngular2 + rxjs
Angular2 + rxjs
Christoffer Noring
 
Angular 9
Angular 9 Angular 9
Angular 9
Raja Vishnu
 
Angular 8
Angular 8 Angular 8
Angular 8
Sunil OS
 
Angular 2 - The Next Framework
Angular 2 - The Next FrameworkAngular 2 - The Next Framework
Angular 2 - The Next Framework
Commit University
 
React Native custom components
React Native custom componentsReact Native custom components
React Native custom components
Jeremy Grancher
 
Introduction to angular with a simple but complete project
Introduction to angular with a simple but complete projectIntroduction to angular with a simple but complete project
Introduction to angular with a simple but complete project
Jadson Santos
 
Single Page Applications with AngularJS 2.0
Single Page Applications with AngularJS 2.0 Single Page Applications with AngularJS 2.0
Single Page Applications with AngularJS 2.0
Sumanth Chinthagunta
 

What's hot (20)

Sharper Better Faster Dagger ‡ - Droidcon SF
Sharper Better Faster Dagger ‡ - Droidcon SFSharper Better Faster Dagger ‡ - Droidcon SF
Sharper Better Faster Dagger ‡ - Droidcon SF
 
Angular 2 Essential Training
Angular 2 Essential Training Angular 2 Essential Training
Angular 2 Essential Training
 
Introduction to angular 2
Introduction to angular 2Introduction to angular 2
Introduction to angular 2
 
Angular tutorial
Angular tutorialAngular tutorial
Angular tutorial
 
Exploring Angular 2 - Episode 1
Exploring Angular 2 - Episode 1Exploring Angular 2 - Episode 1
Exploring Angular 2 - Episode 1
 
Angular modules in depth
Angular modules in depthAngular modules in depth
Angular modules in depth
 
Tech Webinar: Angular 2, Introduction to a new framework
Tech Webinar: Angular 2, Introduction to a new frameworkTech Webinar: Angular 2, Introduction to a new framework
Tech Webinar: Angular 2, Introduction to a new framework
 
Angular2 for Beginners
Angular2 for BeginnersAngular2 for Beginners
Angular2 for Beginners
 
Di code steps
Di code stepsDi code steps
Di code steps
 
Building maintainable app #droidconzg
Building maintainable app #droidconzgBuilding maintainable app #droidconzg
Building maintainable app #droidconzg
 
Lecture 32
Lecture 32Lecture 32
Lecture 32
 
Introduction to Angular 2
Introduction to Angular 2Introduction to Angular 2
Introduction to Angular 2
 
Angular 5 presentation for beginners
Angular 5 presentation for beginnersAngular 5 presentation for beginners
Angular 5 presentation for beginners
 
Angular2 + rxjs
Angular2 + rxjsAngular2 + rxjs
Angular2 + rxjs
 
Angular 9
Angular 9 Angular 9
Angular 9
 
Angular 8
Angular 8 Angular 8
Angular 8
 
Angular 2 - The Next Framework
Angular 2 - The Next FrameworkAngular 2 - The Next Framework
Angular 2 - The Next Framework
 
React Native custom components
React Native custom componentsReact Native custom components
React Native custom components
 
Introduction to angular with a simple but complete project
Introduction to angular with a simple but complete projectIntroduction to angular with a simple but complete project
Introduction to angular with a simple but complete project
 
Single Page Applications with AngularJS 2.0
Single Page Applications with AngularJS 2.0 Single Page Applications with AngularJS 2.0
Single Page Applications with AngularJS 2.0
 

Similar to Dark side of Android apps modularization

Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023
Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023
Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023
Nicolas HAAN
 
A/B test your Android build setup with ASPoet
A/B test your Android build setup with ASPoetA/B test your Android build setup with ASPoet
A/B test your Android build setup with ASPoet
Boris Farber
 
Mastering the NDK with Android Studio 2.0 and the gradle-experimental plugin
Mastering the NDK with Android Studio 2.0 and the gradle-experimental pluginMastering the NDK with Android Studio 2.0 and the gradle-experimental plugin
Mastering the NDK with Android Studio 2.0 and the gradle-experimental plugin
Xavier Hallade
 
Advanced Dagger talk from 360andev
Advanced Dagger talk from 360andevAdvanced Dagger talk from 360andev
Advanced Dagger talk from 360andev
Mike Nakhimovich
 
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDevKotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
DroidConTLV
 
OO Design and Design Patterns in C++
OO Design and Design Patterns in C++ OO Design and Design Patterns in C++
OO Design and Design Patterns in C++
Ganesh Samarthyam
 
Using advanced C# features in Sharepoint development
Using advanced C# features in Sharepoint developmentUsing advanced C# features in Sharepoint development
Using advanced C# features in Sharepoint development
sadomovalex
 
Building Scalable JavaScript Apps
Building Scalable JavaScript AppsBuilding Scalable JavaScript Apps
Building Scalable JavaScript Apps
Gil Fink
 
From Containerization to Modularity
From Containerization to ModularityFrom Containerization to Modularity
From Containerization to Modularity
oasisfeng
 
Angular performance slides
Angular performance slidesAngular performance slides
Angular performance slides
David Barreto
 
Full Stack React Workshop [CSSC x GDSC]
Full Stack React Workshop [CSSC x GDSC]Full Stack React Workshop [CSSC x GDSC]
Full Stack React Workshop [CSSC x GDSC]
GDSC UofT Mississauga
 
Gradle: One technology to build them all
Gradle: One technology to build them allGradle: One technology to build them all
Gradle: One technology to build them all
Bonitasoft
 
Exploring the power of Gradle in android studio - Basics & Beyond
Exploring the power of Gradle in android studio - Basics & BeyondExploring the power of Gradle in android studio - Basics & Beyond
Exploring the power of Gradle in android studio - Basics & Beyond
Kaushal Dhruw
 
Hacking the Codename One Source Code - Part IV - Transcript.pdf
Hacking the Codename One Source Code - Part IV - Transcript.pdfHacking the Codename One Source Code - Part IV - Transcript.pdf
Hacking the Codename One Source Code - Part IV - Transcript.pdf
ShaiAlmog1
 
Writing modular java script
Writing modular java scriptWriting modular java script
Writing modular java script
IT Weekend
 
[DEPRECATED]Gradle the android
[DEPRECATED]Gradle the android[DEPRECATED]Gradle the android
[DEPRECATED]Gradle the android
Jun Liu
 
OpenDaylight Developer Experience 2.0
 OpenDaylight Developer Experience 2.0 OpenDaylight Developer Experience 2.0
OpenDaylight Developer Experience 2.0
Michael Vorburger
 
React Native for multi-platform mobile applications
React Native for multi-platform mobile applicationsReact Native for multi-platform mobile applications
React Native for multi-platform mobile applications
Matteo Manchi
 
Angular kickstart slideshare
Angular kickstart   slideshareAngular kickstart   slideshare
Angular kickstart slideshare
SaleemMalik52
 
Level Up Your Android Build -Droidcon Berlin 2015
Level Up Your Android Build -Droidcon Berlin 2015Level Up Your Android Build -Droidcon Berlin 2015
Level Up Your Android Build -Droidcon Berlin 2015
Friedger Müffke
 

Similar to Dark side of Android apps modularization (20)

Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023
Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023
Comment développer une application mobile en 8 semaines - Meetup PAUG 24-01-2023
 
A/B test your Android build setup with ASPoet
A/B test your Android build setup with ASPoetA/B test your Android build setup with ASPoet
A/B test your Android build setup with ASPoet
 
Mastering the NDK with Android Studio 2.0 and the gradle-experimental plugin
Mastering the NDK with Android Studio 2.0 and the gradle-experimental pluginMastering the NDK with Android Studio 2.0 and the gradle-experimental plugin
Mastering the NDK with Android Studio 2.0 and the gradle-experimental plugin
 
Advanced Dagger talk from 360andev
Advanced Dagger talk from 360andevAdvanced Dagger talk from 360andev
Advanced Dagger talk from 360andev
 
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDevKotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
Kotlin Multiplatform in Action - Alexandr Pogrebnyak - IceRockDev
 
OO Design and Design Patterns in C++
OO Design and Design Patterns in C++ OO Design and Design Patterns in C++
OO Design and Design Patterns in C++
 
Using advanced C# features in Sharepoint development
Using advanced C# features in Sharepoint developmentUsing advanced C# features in Sharepoint development
Using advanced C# features in Sharepoint development
 
Building Scalable JavaScript Apps
Building Scalable JavaScript AppsBuilding Scalable JavaScript Apps
Building Scalable JavaScript Apps
 
From Containerization to Modularity
From Containerization to ModularityFrom Containerization to Modularity
From Containerization to Modularity
 
Angular performance slides
Angular performance slidesAngular performance slides
Angular performance slides
 
Full Stack React Workshop [CSSC x GDSC]
Full Stack React Workshop [CSSC x GDSC]Full Stack React Workshop [CSSC x GDSC]
Full Stack React Workshop [CSSC x GDSC]
 
Gradle: One technology to build them all
Gradle: One technology to build them allGradle: One technology to build them all
Gradle: One technology to build them all
 
Exploring the power of Gradle in android studio - Basics & Beyond
Exploring the power of Gradle in android studio - Basics & BeyondExploring the power of Gradle in android studio - Basics & Beyond
Exploring the power of Gradle in android studio - Basics & Beyond
 
Hacking the Codename One Source Code - Part IV - Transcript.pdf
Hacking the Codename One Source Code - Part IV - Transcript.pdfHacking the Codename One Source Code - Part IV - Transcript.pdf
Hacking the Codename One Source Code - Part IV - Transcript.pdf
 
Writing modular java script
Writing modular java scriptWriting modular java script
Writing modular java script
 
[DEPRECATED]Gradle the android
[DEPRECATED]Gradle the android[DEPRECATED]Gradle the android
[DEPRECATED]Gradle the android
 
OpenDaylight Developer Experience 2.0
 OpenDaylight Developer Experience 2.0 OpenDaylight Developer Experience 2.0
OpenDaylight Developer Experience 2.0
 
React Native for multi-platform mobile applications
React Native for multi-platform mobile applicationsReact Native for multi-platform mobile applications
React Native for multi-platform mobile applications
 
Angular kickstart slideshare
Angular kickstart   slideshareAngular kickstart   slideshare
Angular kickstart slideshare
 
Level Up Your Android Build -Droidcon Berlin 2015
Level Up Your Android Build -Droidcon Berlin 2015Level Up Your Android Build -Droidcon Berlin 2015
Level Up Your Android Build -Droidcon Berlin 2015
 

Recently uploaded

14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision
ShulagnaSarkar2
 
Benefits of Artificial Intelligence in Healthcare!
Benefits of  Artificial Intelligence in Healthcare!Benefits of  Artificial Intelligence in Healthcare!
Benefits of Artificial Intelligence in Healthcare!
Prestware
 
WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...
WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...
WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...
Luigi Fugaro
 
Enums On Steroids - let's look at sealed classes !
Enums On Steroids - let's look at sealed classes !Enums On Steroids - let's look at sealed classes !
Enums On Steroids - let's look at sealed classes !
Marcin Chrost
 
How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?
ToXSL Technologies
 
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
kgyxske
 
Liberarsi dai framework con i Web Component.pptx
Liberarsi dai framework con i Web Component.pptxLiberarsi dai framework con i Web Component.pptx
Liberarsi dai framework con i Web Component.pptx
Massimo Artizzu
 
ppt on the brain chip neuralink.pptx
ppt  on   the brain  chip neuralink.pptxppt  on   the brain  chip neuralink.pptx
ppt on the brain chip neuralink.pptx
Reetu63
 
INTRODUCTION TO AI CLASSICAL THEORY TARGETED EXAMPLES
INTRODUCTION TO AI CLASSICAL THEORY TARGETED EXAMPLESINTRODUCTION TO AI CLASSICAL THEORY TARGETED EXAMPLES
INTRODUCTION TO AI CLASSICAL THEORY TARGETED EXAMPLES
anfaltahir1010
 
Malibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed RoundMalibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed Round
sjcobrien
 
Oracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptxOracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptx
Remote DBA Services
 
Baha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdf
Baha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdfBaha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdf
Baha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdf
Baha Majid
 
What’s New in Odoo 17 – A Complete Roadmap
What’s New in Odoo 17 – A Complete RoadmapWhat’s New in Odoo 17 – A Complete Roadmap
What’s New in Odoo 17 – A Complete Roadmap
Envertis Software Solutions
 
Oracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptxOracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptx
Remote DBA Services
 
The Rising Future of CPaaS in the Middle East 2024
The Rising Future of CPaaS in the Middle East 2024The Rising Future of CPaaS in the Middle East 2024
The Rising Future of CPaaS in the Middle East 2024
Yara Milbes
 
一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理
dakas1
 
WWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders AustinWWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders Austin
Patrick Weigel
 
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CDKuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
rodomar2
 
UI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
UI5con 2024 - Keynote: Latest News about UI5 and it’s EcosystemUI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
UI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
Peter Muessig
 
Migration From CH 1.0 to CH 2.0 and Mule 4.6 & Java 17 Upgrade.pptx
Migration From CH 1.0 to CH 2.0 and  Mule 4.6 & Java 17 Upgrade.pptxMigration From CH 1.0 to CH 2.0 and  Mule 4.6 & Java 17 Upgrade.pptx
Migration From CH 1.0 to CH 2.0 and Mule 4.6 & Java 17 Upgrade.pptx
ervikas4
 

Recently uploaded (20)

14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision14 th Edition of International conference on computer vision
14 th Edition of International conference on computer vision
 
Benefits of Artificial Intelligence in Healthcare!
Benefits of  Artificial Intelligence in Healthcare!Benefits of  Artificial Intelligence in Healthcare!
Benefits of Artificial Intelligence in Healthcare!
 
WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...
WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...
WMF 2024 - Unlocking the Future of Data Powering Next-Gen AI with Vector Data...
 
Enums On Steroids - let's look at sealed classes !
Enums On Steroids - let's look at sealed classes !Enums On Steroids - let's look at sealed classes !
Enums On Steroids - let's look at sealed classes !
 
How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?How Can Hiring A Mobile App Development Company Help Your Business Grow?
How Can Hiring A Mobile App Development Company Help Your Business Grow?
 
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
一比一原版(sdsu毕业证书)圣地亚哥州立大学毕业证如何办理
 
Liberarsi dai framework con i Web Component.pptx
Liberarsi dai framework con i Web Component.pptxLiberarsi dai framework con i Web Component.pptx
Liberarsi dai framework con i Web Component.pptx
 
ppt on the brain chip neuralink.pptx
ppt  on   the brain  chip neuralink.pptxppt  on   the brain  chip neuralink.pptx
ppt on the brain chip neuralink.pptx
 
INTRODUCTION TO AI CLASSICAL THEORY TARGETED EXAMPLES
INTRODUCTION TO AI CLASSICAL THEORY TARGETED EXAMPLESINTRODUCTION TO AI CLASSICAL THEORY TARGETED EXAMPLES
INTRODUCTION TO AI CLASSICAL THEORY TARGETED EXAMPLES
 
Malibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed RoundMalibou Pitch Deck For Its €3M Seed Round
Malibou Pitch Deck For Its €3M Seed Round
 
Oracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptxOracle Database 19c New Features for DBAs and Developers.pptx
Oracle Database 19c New Features for DBAs and Developers.pptx
 
Baha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdf
Baha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdfBaha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdf
Baha Majid WCA4Z IBM Z Customer Council Boston June 2024.pdf
 
What’s New in Odoo 17 – A Complete Roadmap
What’s New in Odoo 17 – A Complete RoadmapWhat’s New in Odoo 17 – A Complete Roadmap
What’s New in Odoo 17 – A Complete Roadmap
 
Oracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptxOracle 23c New Features For DBAs and Developers.pptx
Oracle 23c New Features For DBAs and Developers.pptx
 
The Rising Future of CPaaS in the Middle East 2024
The Rising Future of CPaaS in the Middle East 2024The Rising Future of CPaaS in the Middle East 2024
The Rising Future of CPaaS in the Middle East 2024
 
一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理一比一原版(USF毕业证)旧金山大学毕业证如何办理
一比一原版(USF毕业证)旧金山大学毕业证如何办理
 
WWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders AustinWWDC 2024 Keynote Review: For CocoaCoders Austin
WWDC 2024 Keynote Review: For CocoaCoders Austin
 
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CDKuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
KuberTENes Birthday Bash Guadalajara - Introducción a Argo CD
 
UI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
UI5con 2024 - Keynote: Latest News about UI5 and it’s EcosystemUI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
UI5con 2024 - Keynote: Latest News about UI5 and it’s Ecosystem
 
Migration From CH 1.0 to CH 2.0 and Mule 4.6 & Java 17 Upgrade.pptx
Migration From CH 1.0 to CH 2.0 and  Mule 4.6 & Java 17 Upgrade.pptxMigration From CH 1.0 to CH 2.0 and  Mule 4.6 & Java 17 Upgrade.pptx
Migration From CH 1.0 to CH 2.0 and Mule 4.6 & Java 17 Upgrade.pptx
 

Dark side of Android apps modularization

  • 1. Dark side of Android apps modularization David Bilík @bilikdavid
  • 2. #selfpromo > 8 years experience with Android development > Android Team Lead @AckeeCZ > Lecturer of Android course at Czech Technical University in Prague > Focused on architecture, testing and beautiful designs
  • 3. Why am I here?
  • 4. Why am I here? > Modularization became popular topic in 2018 > Every conference had at least one talk “How to modularize your app” > Since we are hype-oriented programmers we hopped on the train in 2019 > But we’ve hit some bumps along the way
  • 5. What we expected > Faster build times > Improved architecture > Preparation for instant apps/dynamic features
  • 8. Modularized architecture > Looks simple > But contains multiple shady areas > Google does not have the answers > Community does not have all answers > Because they do not exist
  • 10. Gradle > Apps contains tens of dependencies > Not all of them used in all modules > Good idea to define them in one place
  • 11. buildSrc > Leverage buildSrc folder in gradle project > Contains common code (constants, tasks) used in build.gradle scripts > Can be written in Kotlin
  • 12. object Config { const val minSdk = 26 const val compileSdk = 29 const val targetSdk = 29 val javaVersion = JavaVersion.VERSION_1_8 } android { compileSdkVersion Config.compileSdk defaultConfig { minSdkVersion Config.minSdk targetSdkVersion Config.targetSdk versionCode 1 versionName "1.0" } buildSrc/src/main/kotlin/Config.kt app/build.gradle
  • 13. buildSrc/src/main/kotlin/Deps.kt object Deps { "// Koin private const val koinVersion = "2.0.1" const val koin = "org.koin:koin-android:$koinVersion" const val koinScope = "org.koin:koin-androidx-scope:$koinVersion" const val koinViewModel = "org.koin:koin-androidx-viewmodel:$koinVersion" "// Epoxy private const val epoxyVersion = "3.9.0" const val epoxy = "com.airbnb.android:epoxy:$epoxyVersion" const val epoxyProcessor = "com.airbnb.android:epoxy-processor:$epoxyVersion" "// OkHttp private const val okHttpVersion = "4.3.1" const val okHttp = "com.squareup.okhttp3:okhttp:$okHttpVersion" const val okHttpLoggingInterceptor = “com.squareup.okhttp3:logging-interceptor:$okHttpVersion" … app/build.gradle dependencies { "// Koin implementation Deps.koin implementation Deps.koinViewModel
  • 14.
  • 15. Version updates > Automatic Android Studio version check is not available > Plugins exists but not they are not suitable for us > So.. we check manually 😞
  • 16. Shared gradle scripts > A lot of the build.gradle code will be completely the same ≥ defaultConfig with minSdk, compileOptions, applied plugins, … > Common code can be extracted and applied in module build.gradle scripts > Applied code is merged with the code inside module’s build.gradle script
  • 17. apply plugin: 'com.android.library' apply plugin: 'kotlin-android' apply plugin: 'kotlin-android-extensions' android { compileSdkVersion Config.compileSdk defaultConfig { minSdkVersion Config.minSdk targetSdkVersion Config.targetSdk versionCode 1 versionName "1.0" } buildTypes { … } productFlavors { … } compileOptions { sourceCompatibility = 1.8 targetCompatibility = 1.8 } … … kotlinOptions { jvmTarget = "1.8" freeCompilerArgs += "-Xopt-in=kotlin.time.ExperimentalTime" } testOptions { unitTests.all { setIgnoreFailures(true) } } } gradle/common-library-script.gradle
  • 18. apply from: “$rootDir/gradle/common-library-script.gradle” dependencies { implementation Deps.junit implementation Deps.rxJava implementation Deps.rxAndroid implementation Deps.appCompat } mymodule/build.gradle
  • 19. android { productFlavors { flavorDimensions "api" devApi { dimension "api" } prodApi { dimension "api" } } } gradle/common-library-script.gradle
  • 20. networking/build.gradle apply from: “$rootDir/gradle/common-library-script.gradle” android { productFlavors { flavorDimensions "api" devApi { dimension "api" buildConfigField("String", "BASE_URL", ""api-development.myapp.com"") } prodApi { dimension “api" buildConfigField("String", "BASE_URL", ""api.myapp.com"") } } }
  • 22. Build variants > Example: flavors defining base url for api environment > What modules care about this flavor? ≥ :app - control what variant of app to build ≥ :networking - contains buildConfigFields with base api url
  • 24. * What went wrong: Could not determine the dependencies of task ':events:compileDebugAidl'. > Could not resolve all task dependencies for configuration ':events:debugCompileClasspath'. > Could not resolve project :networking. Required by: project :events > Cannot choose between the following variants of project :networking: - devApiDebugRuntime - devApiDebugUnitTestCompile - devApiDebugUnitTestRuntime - devApiReleaseAndroidTestCompile - devApiReleaseAndroidTestRuntime - devApiReleaseApiElements … ./gradlew assembleDevApiDebug
  • 27. gradle/common-library-script.gradle android { … productFlavors { flavorDimensions "api" devApi { dimension "api" } prodApi { dimension "api" } } … }
  • 28. app/build.gradle android { … productFlavors { flavorDimensions "api" devApi { dimension "api" buildConfigField("String", "BASE_URL", ""api-development.myapp.com"") } prodApi { dimension "api" buildConfigField("String", "BASE_URL", ""api.myapp.com"") } } … }
  • 29. networking/src/main/java/…/ApiDefinition.kt data class ApiDefinition( val url: HttpUrl ) fun provideRetrofit(api: ApiDefinition): Retrofit { return Retrofit.Builder() .baseUrl(api.url) .build() } networking/src/main/java/…/RetrofitDI.kt app/src/main/java/…/ApiDI.kt fun provideApiDefinition(): ApiDefinition{ return ApiDefinition( BuildConfig.BASE_URL.toHttpUrl() ) }
  • 30. Final tip > Improve organization of modules with directories
  • 31.
  • 32.
  • 33. Module folders protip > Prefix module name with directory for automatic placement
  • 35. Code sharing > Our apps (try to) follow Uncle Bob’s Clean Architecture
  • 42. Navigation > Feature modules are independent of each other > ActivityA in :featureA does not have access to ActivityB in :featureB > unified solution for in-feature and between-feature navigation
  • 44. object Intents { fun startingActivity(context: Context): Intent? { return loadClass<Activity>(“cz.ackee.sample.StartingActivity”) "?.let { Intent(context, it) } } } object Fragments { fun contactsFragment(context: Context): Fragment? { return loadClass<Fragment>(“cz.ackee.sample.contacts.ContactsFragment”) "?.let { Fragment.instantiate(context, it.name) } } } navigation/src/main/…/Intents.kt navigation/src/main/…/Fragments.kt fun <T> loadClass(className:String): Class<T>? { return try { Class.forName(className) } catch (e: Exception) { null } as? Class<T> }
  • 45. #1 solution - problems > Not using typical pattern like MyFragment.newInstance(args) > Fully qualified class names in Strings not changed in refactorings
  • 46. Arguments passing > Problems with passing the arguments through :navigation module > Does not have access to feature classes
  • 47. object Fragments { fun contactDetailFragment(context: Context, contact: Contact): Fragment? { return loadClass<Fragment>("cz.ackee.sample.contact.ContactDetailFragment") "?.let { Fragment.instantiate(context, it.name, bundleOf(Arguments.CONTACT_KEY to contact)) } } } object Fragments { fun contactDetailFragment(context: Context, contact: Parcelable): Fragment? { return loadClass<Fragment>("cz.ackee.sample.contact.ContactDetailFragment") "?.let { Fragment.instantiate(context, it.name, bundleOf(Arguments.CONTACT_KEY to contact)) } } } navigation/src/main/…/Fragments.kt
  • 48. Parcelable solution > Easiest solution > Type safety is lost
  • 49. object Fragments { fun contactDetailFragment(context: Context, contact: ContactDetailNavArgs): Fragment? { return ClassesCache.loadClassOrNull<Fragment>("cz.ackee.sample.contact.ContactDetailFragment") "?.let { Fragment.instantiate(context, it.name, bundleOf(NAV_ARGS_KEY to navArgs)) } } } @Parcelize data class ContactDetailNavArgs( val contactId: String, val name: String ): Parcelable navigation/src/main/…/navargs/ContactDetailNavArgs.kt navigation/src/main/…/Fragments.kt
  • 50. inline fun <reified T: Parcelable> Fragment.navArgs() : T { return requireArguments().getParcelable(NAV_ARGS_KEY) } class ContactDetailFragment : Fragment() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) toolbar.title = navArgs<ContactDetailNavArgs>().name } } contacts/src/main/…/ContactDetailFragment.kt navigation/src/main/…/FragmentKtx.kt
  • 51. NavArgs solution > Improved type safety > More boilerplate
  • 52. Abstracted navigation > Introduce abstraction over navigation > Free Fragments/Activities of knowing details of navigation
  • 53. interface Navigator { fun openContactDetail(args: ContactDetailNavArgs) } class ContactsListFragment : Fragment() { private val navigator: Navigator by inject() override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) contactsList.setOnContactClickListener { contact "-> navigator.openContactDetail(ContactDetailNavArgs(contact.id, contact.name)) } } } navigation/src/main/…/Navigator.kt contacts/src/main/…/ContactDetailFragment.kt
  • 54. Navigation Architecture Component > Navigator implemented with Navigation Architecture Component > :navigation module still a library module > Contains navigation graphs, Navigator interface and implementation of this interface
  • 55. <?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http:"//schemas.android.com/apk/res/android" xmlns:app="http:"//schemas.android.com/apk/res-auto" android:id="@+id/navigation_graph" app:startDestination="@+id/navigation_contacts_list"> <fragment android:id="@+id/navigation_contacts_list" android:name="cz.bilik.sample.contacts.ContactsListFragment" > <action android:id="@+id/navigation_action_open_contact_detail" app:destination="@id/navigation_contact_detail" app:enterAnim="@anim/nav_default_enter_anim" app:exitAnim="@anim/nav_default_exit_anim" app:popEnterAnim="@anim/nav_default_pop_enter_anim" app:popExitAnim="@anim/nav_default_exit_anim" "/> "</fragment> <fragment android:id="@+id/navigation_contact_detail" android:name="cz.bilik.sample.contacts.ContactDetailFragment" "/> "</navigation> navigation/src/main/res/values/nav_graph.xml
  • 56. class NavigationComponentNavigator : Navigator { private var navigationController: NavController? = null override fun openContactDetail(navArgs: ContactDetailNavArgs) { navigationController"?.navigate( R.id.navigation_action_open_contact_detail, navArgs.toBundle() ) } } navigation/src/main/…/NavigationComponentNavigator.kt fun bindController(navigationController: NavController) { this.navigationController = navigationController } fun unbindController() { this.navigationController = null }
  • 57. abstract class NavigationActivity : AppCompatActivity() { val navigator : NavigationComponentNavigator by inject() override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_navigation) setupNavigation() } private fun setupNavigation() { navigator.bindController(findNavController(R.id.nav_host_fragment)) } override fun onDestroy() { super.onDestroy() navigator.unbindController() } } navigation/src/main/…/NavigationActivity.kt
  • 58. Abstracted navigation > Growing Navigator interface ≥ Create multiple smaller Navigators and NavigationActivity implements all of them > Multiple Activity ≥ Multiple navigation graphs with multiple NavigationActivitys
  • 60. Database > Room used as database library > No native support for multimodule projects > Need to define all entities and DAOs in single Database class
  • 62. Database per feature ➕ Encapsulated within single module ➕ Each database can have different settings - eg. descructive migrations rule − Aggregations over mutliple tables not possible − Multiple connections to database
  • 64. Single database ➕ Easy to maintain − Breaks the encapsulation of the features.
  • 65. Compromise > :database module containing definition of RoomDatabase > Keep DAOs and entities within features
  • 67. Compromise > :database module depends on all features and define RoomDatabase class > DI for DAOs must be defined in this module
  • 68. @Entity(tableName = "contacts") data class DbContact( @PrimaryKey(autoGenerate = true) val id: Long = 0, val eventId: Int, val name: String ) features/contacts/…/DbContactfeatures/events/…/DbEvent @Entity(tableName = "events") data class DbEvent( @PrimaryKey val id: Int = 0, val name: String ) features/events/…/EventsDao @Query(""" select events.* from events join contacts on (contacts.eventId = events.id) where contacts.id "== :contactId """) abstract fun getEventForContact(contactId: Long): DbEvent
  • 70. Testing > Where to define utilities in tests? ≥ custom JUnit rules for RxJava/Coroutines ≥ extensions on LiveData to retrieve value once available ≥ …
  • 71. Testing module > Define them in one place > Can’t be placed in :base module test source set folder > Gradle does not support dependencies on test source sets of different module in android projects
  • 72. Testing module > Separate:testing library module > Contains also dependencies to common testing dependencies ≥ Mocking framework, testing dependencies for coroutines, AndroidX, … > Important note - don’t declare this dependencies as testXXX and also don’t place the code to the test source set folder
  • 73. fun <T> LiveData<T>.getOrAwaitValue( time: Long = 2, timeUnit: TimeUnit = TimeUnit.SECONDS, afterObserve: () "-> Unit = {} ): T { … } libraries/testing/src/main/java/LiveDataKtx.kt libraries/testing/build.gradle dependencies { api Deps.architectureComponentsTesting api Deps.mockitoInline api Deps.mockitoKotlin api Deps.coroutinesTesting }
  • 75. Test fixtures > Same problem with test fixtures of feature module > How to reuse eg. test doubles in different feature module tests?
  • 79. libraries/database-testing/src/main/java/RoomDatabaseRule.kt class RoomDatabaseRule : TestWatcher() { lateinit var database: MyDatabase override fun starting(description: Description?) { database = Room.inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), MyDatabase"::class.java) .allowMainThreadQueries() .build() } override fun finished(description: Description?) { database.close() } }
  • 80. @RunWith(AndroidJUnit4"::class) class ContactsLocalDataSourceTest { @get:Rule val databaseRule = RoomDatabaseRule() private fun createDataSource(): ContactsLocalDataSource { return ContactsLocalDataSource( databaseRule.database.contactsDao() ) } … features/contacts/app/src/test/…/ContactsLocalDataSourceTest.kt
  • 82. Networking > :networking library module with common setup - OkHttpClient, Moshi, Retrofit > Each feature contains Retrofit API interface with transfer objects (DTO) ≥ eg. LoginRequest, LoginResponse
  • 83. Networking > What about generated classes? ≥ gRPC, Swagger-codegen > Generation of this classes is handled in :networking module > Feature modules use this generated classes
  • 85. Summary > Would I modularize new app right from the beginning? > Have we learned anything? > What about our expectations?