Introduction to Android Architecture Components

Introduction to Android Architecture Components

Android OS was released more than ten years, and a lot of best practices were published and promoted.

As Android developers, we face many challenges like handling life-cycle events, persisting data, maintaining view state, etc. Our constant struggle for a good architecture was not left unnoticed, Google stepped up and gave us their own take on the topic in the form of Architecture Components.

During this talk, we will explore how API of LiveData, ViewModel, Lifecycle and Room libraries.

2b0404a5db1a74f01bf3bf94d142e28c?s=128

Alex Zhukovich

March 23, 2019
Tweet

Transcript

  1. Introduction to Android Architecture Components @Alex_Zhukovich alexzh.com Andrzej Jóźwiak ajoz.github.io

  2. Android JetPack

  3. https://android-developers.googleblog.com/2018/05/use-android-jetpack-to-accelerate-your.html

  4. Support Libraries AndroidX

  5. https://github.com/foobaz42/CompanyContacts

  6. Activity ViewModel Repository Local data source Remote data source

  7. ViewModel Lifecycle LiveData Room

  8. Lifecycle

  9. public class TestFragment extends Fragment{ @Override void onStart() { registerComponent1(...);

    registerComponent2(...); registerComponent3(...); } @Override void onStop() { unregisterComponent1(...); unregisterComponent2(...); unregisterComponent3(...); } }
  10. public class TestActivity extends AppCompatActivity { private Component component1; @Override

    void onCreate(Bundle bundle) { Component1 = new Component( this // Lifecycle object ) } ... } public class Component1 implements LifecycleObserver { public Component1(Lifecycle lifecycle) { lifecycle.addObserver(this) } @OnLifecycleEvent(Lifecycle.Event.ON_START) void start() { ... } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) void stop() { ... } @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) void destroy() { lifecycle.removeObserver(this) } }
  11. Lifecycle Lifecycle Observer Lifecycle Owner Lifecycle Registry

  12. public abstract class Lifecycle { public enum State { DESTROYED,

    INITIALIZED, CREATED, STARTED, RESUMED; } public enum Event { ON_CREATE, ON_START, ON_RESUME, ON_PAUSE, ON_STOP, ON_DESTROY, ON_ANY } }
  13. Fragment AppCompatActivity LifecycleService

  14. 700 ms

  15. ViewModel

  16. View Business Logic Data Repository UI Events Data Change Events

    Create Update Delete Read
  17. View ViewModel Model UI Events Property Change Events Create Update

    Delete Read ViewModel data Model Change Events
  18. ? custom Application with static fields or stopping any configuration

    changes in the AndroidManifest.xml onSaveInstanceState(Bundle) onCreate(Bundle) Object onRetainNonConfigurationInstance() Object getLastNonConfigurationInstance() retaining a Fragment with setRetainInstance(true)
  19. large amounts of business related data data that takes long

    to load data that takes long to process partial data that is still loading data that is hard to serialize data that is NOT related to View data that is NOT related to the Activity context data that is NOT Lifecycle reference ViewModel
  20. class TeamsViewModel(): ViewModel() { fun someDomainRelatedMethod() { // very very

    very import stuff ... } }
  21. class TeamsActivity : AppCompatActivity(){ // It is not necessary to

    hold this reference private lateinit var viewModel: TeamsViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) viewModel = ViewModelProviders.of(this) .get(TeamsViewModel::class.java) // we can use it now or later ... viewModel.someDomainRelatedMethod() } }
  22. LiveData

  23. data holder observed within a lifecycle Observer paired with LifecycleOwner

    this is NOT a Stream<A> this is NOT an Rx Observable<A> this is NOT a Sequence<A> LiveData<A> MutableLiveData<A> MediatorLiveData<A>
  24. LiveData + ViewModel

  25. class TeamsViewModel(): ViewModel() { private val teams: MutableLiveData<List<Team>> by lazy

    { MutableLiveData().also { loadTeams() } } fun loadTeams() { // Async operations to load data // from database/cache/cloud teams.value = // values } fun getTeams(): LiveData<List<Team>> { return teams } }
  26. class TeamsActivity : AppCompatActivity(){ private lateinit var viewModel: TeamsViewModel override

    fun onCreate(savedInstanceState: Bundle?) { // ... viewModel = ViewModelProviders.of(this) .get(TeamsViewModel::class.java) viewModel.getTeams().observe( this, Observer<List<Team>>{ teams -> // update UI with the acquired data } ) } }
  27. class TeamsViewModel(apl: Application): AndroidViewModel(apl) { fun someDomainRelatedMethod() { // Application

    instance is available through // - getApplication() method in Java // - application property in Kotlin } }
  28. class TeamsViewModel( private val teamRepository: TeamRepository ): ViewModel() { fun

    fetchTeams(): LiveData<List<Team>> { // accessing the injected dependency return teamRepository.fetchTeams() } }
  29. class TeamsFactory( private val repository: TeamRepository ) : ViewModelProvider.Factory {

    override fun create( modelClass: Class): TeamsViewModel { if (modelClass.isAssignableFrom( TeamsViewModel::class.java)) { return TeamsViewModel(repository) } throw IllegalArgumentException(“Unknown model class!”) } }
  30. class TeamsActivity : AppCompatActivity(){ private lateinit var viewModel: TeamsViewModel override

    fun onCreate(savedInstanceState: Bundle?) { val repository = //injecting the dependency viewModel = ViewModelProviders // ViewModelProvider.Factory has to be specified // manually .of(this, TeamsFactory(repository)) .get(TeamsViewModel::class.java) } }
  31. MediatorLiveData<X> X X X X Y Y Y Y values

    returned by the “Mediator” func: X -> Y source: LiveData<X> func: X -> Y Transformations.map(source, func) values returned by the “Source”
  32. ViewModel LiveData<A> NOT well suited for single Events by default

    models ONLY happy path there is NO error “channel” NO API contract for error handling NO way to determine the amount of results LIMITED amount of operators
  33. Room

  34. None
  35. API 1 API 8 API 21 API 26 API 28

    API 3 API 11 API 24 API 27 SQLite 3.4 SQLite 3.5 SQLite 3.6 SQLite 3.7 SQLite 3.8 SQLite 3.9 SQLite 3.18 SQLite 3.19 SQLite 3.22
  36. val db = dbHelper.readableDatabase // Define a projection that specifies

    which columns from the database // you will actually use after this query. val projection = arrayOf(BaseColumns._ID, FeedEntry.COLUMN_NAME_TITLE, FeedEntry.COLUMN_NAME_SUBTITLE) // Filter results WHERE "title" = 'My Title' val selection = "${FeedEntry.COLUMN_NAME_TITLE} = ?" val selectionArgs = arrayOf("My Title") // How you want the results sorted in the resulting Cursor val sortOrder = "${FeedEntry.COLUMN_NAME_SUBTITLE} DESC" val cursor = db.query( FeedEntry.TABLE_NAME, // The table to query projection, // The array of columns to return (pass null to get all) selection, // The columns for the WHERE clause selectionArgs, // The values for the WHERE clause null, // don't group the rows null, // don't filter by row groups sortOrder // The sort order )
  37. val db = dbHelper.readableDatabase // Define a projection that specifies

    which columns from the database // you will actually use after this query. val projection = arrayOf(BaseColumns._ID, FeedEntry.COLUMN_NAME_TITLE, FeedEntry.COLUMN_NAME_SUBTITLE) // Filter results WHERE "title" = 'My Title' val selection = "${FeedEntry.COLUMN_NAME_TITLE} = ?" val selectionArgs = arrayOf("My Title") // How you want the results sorted in the resulting Cursor val sortOrder = "${FeedEntry.COLUMN_NAME_SUBTITLE} DESC" val cursor = db.query( FeedEntry.TABLE_NAME, // The table to query projection, // The array of columns to return (pass null to get all) selection, // The columns for the WHERE clause selectionArgs, // The values for the WHERE clause null, // don't group the rows null, // don't filter by row groups sortOrder // The sort order ) SELECT id, title, subTitle FROM Feed WHERE title = ? ORDER BY subTitle DESC
  38. SELECT * FROM employees WHERE team_id = :teamId

  39. @Dao interface EmployeeDao { @Query("SELECT * FROM employees WHERE team_id

    = :teamId") fun getEmployeesByTeamId(): List<Employee> }
  40. @Insert fun insert(employee: Employee): Long @Insert fun insertListOfEmployees(employees: List<Employee>) @Insert

    fun insertEmployees(vararg employee: Employee): List<Long>
  41. @Update fun updateEmployee(employee: Employee): Int @Update fun updateEmployees(employees: List<Employee>): Int

    @Update fun updateEmployee(employee: Employee)
  42. @Delete fun deleteEmployee(employee: Employee): Int @Delete fun deleteEmployee(employees: List<Employee>) @Delete

    fun deleteEmployee(vararg employee: Employee): Int
  43. @Dao interface EmployeesDAO { @Query("SELECT * FROM employees WHERE team_id

    = :teamId") fun getEmployeesByTeamId(teamId: Long): LiveData<List<Employee>> @Insert(onConflict = OnConflictStrategy. REPLACE) fun insertEmployee(employee: Employee) @Update fun updateEmployee(employee: Employee): Int @Delete fun deleteEmployee(employee: Employee): Int }
  44. @Entity data class Employee( @PrimaryKey val id: Long, val name:

    String, val phone: String)
  45. @Entity(tableName = "employees") data class Employee( @PrimaryKey @ColumnInfo(name = "employee_id")

    val id: Long, @ColumnInfo(name = "employee_name") val name: String, @ColumnInfo(name = "employee_phone") val phone: String)
  46. @Database(entities = [Team::class, Employee::class], version = 1) abstract class ContactsDatabase:

    RoomDatabase() { abstract fun teamsDao(): TeamsDAO abstract fun employeesDao(): EmployeesDAO }
  47. @Alex_Zhukovich alexzh.com Andrzej Jóźwiak ajoz.github.io