Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Modern Android Development - CodeMash 2023

Modern Android Development - CodeMash 2023

Abstract:

Can you believe Android has been around since 2008? Since it’s release, the Android platform has evolved dramatically. From IDEs to new languages, threading models to architecture components, best practices for "Modern Android" are a moving target.

In this talk, I will take a step back and reflect on the past decade of Android development to guide you to what makes a “modern” android app.

Are you a new developer looking to get started on the Android platform? Are you a seasoned native developer looking to reflect on what had changed over the years?

Michael Yotive

January 12, 2023
Tweet

More Decks by Michael Yotive

Other Decks in Programming

Transcript

  1. 2

  2. 3

  3. 6

  4. 7

  5. 8

  6. • Multiple activities • Some fragments • ArrayAdapter for lists

    • RecyclerView wasn't a thing • ksoap2-android • lol... what? • Lots of onSavedInstanceState and onRestoreInstanceState • ViewModel wasn't a thing • Also, lots of places where I wasn't doing this, which was probably bad. • Universal Image Loader • Picasso/Glide wasn't a thing • Lots of RelativeLayout and nested LinearLayouts (ConstraintLayout wasn't a thing). • No background tasks. At all. Not even an AsyncTask. • No Dependency Injection (other than classes asking for their dependencies). • Zero tests. 9
  7. • Are fragments UI? • To retain or not to

    retain? • Fragments talking to other fragments. • Fragments within fragments. 13
  8. 14

  9. • Some form of: • Multiple Activity, Fragment-less architecture •

    Single Activity, Multiple Fragments • Multiple Activity, Multiple Fragments • Some sort of design pattern: • MVP • MVVM • A dependency injection framework • Most likely Dagger 1 (if not using proguard) • Dagger 2 (if you were brave understood how components worked) • Or factory classes • Event-Based or Reactive Architectures • Event Bus • RxJava • A better Threading model than what Google provided • Functional programming (observable, reactive patterns) • Retrofit and OkHttp for network • GSON for Json Parsing • Crashlytics 15
  10. 17

  11. Java button.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) {

    text.setText("You've clicked a button"); } }); Kotlin button.setOnClickListener { text.text = "You've clicked a button" } 18
  12. 19

  13. Juniors: You will not be able to avoid some of

    the "legacy" Android world, particularly "set in stone" libraries of the past (such as RxJava). Seniors/EMs/CTOs You may have difficulty attracting talent by not moving to modern practices set forth by Google. Have a plan and get the work scheduled. 21
  14. • Separation of Concerns • Drive UI from Data Models

    • Single Source of Truth • Unidirectional Data Flow Source: https:/ /developer.android.com/topic/architecture and https:/ /developer.android.com/modern-android-development
  15. Separation of Concerns • UI Layer (UI Elements, State Holders)

    • Data Layer • Domain Layer (optional - encapsulate business logic) 25
  16. UI Layer • UI elements that render the data on

    the screen. • Views or Composeables • Also made up of State holders 26
  17. sealed class NewsFeedUiState { /** * The feed is still

    loading. */ object Loading : NewsFeedUiState /** * The feed is loaded with the given list of news resources. */ data class Success( /** * The list of news resources contained in this feed. */ val feed: List<SaveableNewsResource> ) : NewsFeedUiState } Taken from the NowInAndroid app
  18. Data Layer • The data layer comprises of repositories that

    each can contain zero to many data sources. • Repositories expose data to the app. • Centralizes changes to data. • Can contain business logic (but may want to use the Domain Layer for this). • Data Source could be Room, Remote (Web Service), DataStore, etc. • Ex: MoviesRepository, PaymentsRepository 28
  19. Domain Layer • Sits in between the UI layer and

    the Data layer. • Classes in this layer are commonly called use cases or interactors. • Google says this is an optional layer. 29
  20. class GetSortedFollowableAuthorsUseCase @Inject constructor( private val authorsRepository: AuthorsRepository, private val

    userDataRepository: UserDataRepository ) { /** * Returns a list of authors with their associated followed state sorted alphabetically by name. */ operator fun invoke(): Flow<List<FollowableAuthor>> = combine( authorsRepository.getAuthors(), userDataRepository.userData ) { authors, userData -> authors.map { author -> FollowableAuthor( ... ) } .sortedBy { it.author.name } } } Taken from the NowInAndroid app
  21. @HiltViewModel class InterestsViewModel @Inject constructor( val userDataRepository: UserDataRepository, getFollowableTopics: GetFollowableTopicsUseCase,

    getSortedFollowableAuthors: GetSortedFollowableAuthorsUseCase ) : ViewModel() { ... val uiState: StateFlow<InterestsUiState> = combine( getSortedFollowableAuthors(), getFollowableTopics(sortBy = TopicSortField.NAME), InterestsUiState::Interests ).stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = InterestsUiState.Loading ) ... } Taken from the NowInAndroid app
  22. Drive UI from Data Models • "You should drive your

    UI from data models, preferably persistent models." 1 • Note: Persistent models • You don't have to use ViewModel if you don't want or it doesn't fit your needs. 1 https:/ /developer.android.com/topic/architecture#drive-ui-from-model 32
  23. Single Source of Truth • It centralizes all the changes

    to a particular type of data in one place. • It protects the data so other types cannot tamper with it. • It makes changes to the data more traceable. 33
  24. Single Source of Truth • In an offline-first application, the

    source of truth for application data is typically a database. • In some cases, the source of truth can be a ViewModel. 34
  25. Unidirectional Data Flow @HiltViewModel class BookmarksViewModel @Inject constructor( private val

    userDataRepository: UserDataRepository, getSaveableNewsResources: GetSaveableNewsResourcesUseCase ) : ViewModel() { val feedUiState: StateFlow<NewsFeedUiState> = getSaveableNewsResources() ... .stateIn( scope = viewModelScope, initialValue = Loading ) ... } Taken from the NowInAndroid app
  26. val feedState by viewModel.feedUiState.collectAsStateWithLifecycle() ... when (feedState) { Loading ->

    LoadingState(modifier) is Success -> if (feedState.feed.isNotEmpty()) { BookmarksGrid(feedState, removeFromBookmarks, modifier) } else { EmptyState(modifier) } } 37
  27. • We have lots of ways to do Unidirectional Data

    Flow: • Do we use Flow or can we make it work with our existing architecture, a hybrid Java/Kotlin codebase with RxJava? • Speaking of RxJava - my codebase is entirely built on RxJava, but I see Google wants us to use Coroutines with Flow/StateFlow. WTF? • I'm a big fan of fragments - can I use Fragments with Compose?2 2 Yes you can 38
  28. 39

  29. 40

  30. 41

  31. 42

  32. class LoginRepository(...) { ... suspend fun makeLoginRequest(jsonBody: String): Result<LoginResponse> {

    // Move the execution of the coroutine to the I/O dispatcher return withContext(Dispatchers.IO) { // Blocking network request code } } } https:/ /developer.android.com/kotlin/coroutines
  33. // Create a new coroutine on the UI thread viewModelScope.launch

    { val jsonBody = "{ username: \"$username\", token: \"$token\"}" // Make the network call and suspend execution until it finishes val result = loginRepository.makeLoginRequest(jsonBody) // Display result of the network request to the user when (result) { is Result.Success<LoginResponse> -> // Happy path else -> // Show error in UI } } https:/ /developer.android.com/kotlin/coroutines
  34. 45

  35. Testing Fundimentals • https:/ /developer.android.com/training/testing/fundamentals • https:/ /developer.android.com/training/testing/fundamentals/what-to-test • https:/

    /developer.android.com/training/dependency-injection/hilt-testing Compose • https:/ /developer.android.com/jetpack/compose/testing 46
  36. 47

  37. 51

  38. Speaking of RxJava -> Coroutines Coroutine -> RxJava suspend fun

    fetchInfo() { ... } rxSingle { fetchInfo() } RxJava -> Coroutine val source = Single.just(listOf(1, 2, 3)) viewModelScope.launch { val result = source.await() } https:/ /github.com/Kotlin/kotlinx.coroutines/tree/master/reactive/kotlinx-coroutines-rx2
  39. The "Modern" Android Stack • UI • Either Compose or

    Views • StateHolder (ViewModel) • Unidirectional Flow • StateFlow • Modularization • Data, Domain, and UI layers • Single Source of Truth • Room, ViewModel, DataStore • Coroutines • Still lots of RxJava out there, though. • Dagger HILT • Retrofit (OkHttp) • Alternatively - Ktor and Kotlinx Serialization • Feature Flagging • Some form of KMM **As of 2023
  40. • Android is big and complicated. • It's easier today,

    but there's a lot of legacy code in the market. • Juniors: Learn RxJava. • Seniors: If needed, strategize how to move away from RxJava. • Juniors + Seniors: Be consistent and have healthy discussions if these patterns suit you. 63
  41. Thank you! Michael Yotive - Research and Useful Links -

    https:/ /androiddev.social/@myotive - https:/ /www.linkedin.com/in/michaelyotive/ - https:/ /github.com/myotive