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

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.

Alex Zhukovich

March 23, 2019
Tweet

More Decks by Alex Zhukovich

Other Decks in Programming

Transcript

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

    View Slide

  2. Android JetPack

    View Slide

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

    View Slide

  4. Support
    Libraries
    AndroidX

    View Slide

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

    View Slide

  6. Activity ViewModel Repository
    Local data
    source
    Remote data
    source

    View Slide

  7. ViewModel
    Lifecycle
    LiveData Room

    View Slide

  8. Lifecycle

    View Slide

  9. public class TestFragment extends Fragment{
    @Override
    void onStart() {
    registerComponent1(...);
    registerComponent2(...);
    registerComponent3(...);
    }
    @Override
    void onStop() {
    unregisterComponent1(...);
    unregisterComponent2(...);
    unregisterComponent3(...);
    }
    }

    View Slide

  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)
    }
    }

    View Slide

  11. Lifecycle
    Lifecycle
    Observer
    Lifecycle
    Owner
    Lifecycle
    Registry

    View Slide

  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
    }
    }

    View Slide

  13. Fragment
    AppCompatActivity
    LifecycleService

    View Slide

  14. 700 ms

    View Slide

  15. ViewModel

    View Slide

  16. View
    Business
    Logic
    Data
    Repository
    UI Events
    Data
    Change
    Events
    Create
    Update
    Delete
    Read

    View Slide

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

    View Slide

  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)

    View Slide

  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

    View Slide

  20. class TeamsViewModel(): ViewModel() {
    fun someDomainRelatedMethod() {
    // very very very import stuff ...
    }
    }

    View Slide

  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()
    }
    }

    View Slide

  22. LiveData

    View Slide

  23. data holder
    observed within a
    lifecycle
    Observer paired
    with
    LifecycleOwner
    this is NOT a
    Stream
    this is NOT an Rx
    Observable
    this is NOT a
    Sequence
    LiveData
    MutableLiveData
    MediatorLiveData

    View Slide

  24. LiveData + ViewModel

    View Slide

  25. class TeamsViewModel(): ViewModel() {
    private val teams: MutableLiveData> by lazy {
    MutableLiveData().also {
    loadTeams()
    }
    }
    fun loadTeams() {
    // Async operations to load data
    // from database/cache/cloud
    teams.value = // values
    }
    fun getTeams(): LiveData> {
    return teams
    }
    }

    View Slide

  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>{ teams ->
    // update UI with the acquired data
    }
    )
    }
    }

    View Slide

  27. class TeamsViewModel(apl: Application): AndroidViewModel(apl) {
    fun someDomainRelatedMethod() {
    // Application instance is available through
    // - getApplication() method in Java
    // - application property in Kotlin
    }
    }

    View Slide

  28. class TeamsViewModel(
    private val teamRepository: TeamRepository
    ): ViewModel() {
    fun fetchTeams(): LiveData> {
    // accessing the injected dependency
    return teamRepository.fetchTeams()
    }
    }

    View Slide

  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!”)
    }
    }

    View Slide

  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)
    }
    }

    View Slide

  31. MediatorLiveData
    X X X X
    Y Y Y Y
    values returned by the
    “Mediator”
    func: X -> Y
    source:
    LiveData
    func: X -> Y
    Transformations.map(source, func)
    values returned by the “Source”

    View Slide

  32. ViewModel LiveData
    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

    View Slide

  33. Room

    View Slide

  34. View Slide

  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

    View Slide

  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
    )

    View Slide

  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

    View Slide

  38. SELECT * FROM employees WHERE team_id = :teamId

    View Slide

  39. @Dao
    interface EmployeeDao {
    @Query("SELECT * FROM employees WHERE team_id = :teamId")
    fun getEmployeesByTeamId(): List
    }

    View Slide

  40. @Insert
    fun insert(employee: Employee): Long
    @Insert
    fun insertListOfEmployees(employees: List)
    @Insert
    fun insertEmployees(vararg employee: Employee): List

    View Slide

  41. @Update
    fun updateEmployee(employee: Employee): Int
    @Update
    fun updateEmployees(employees: List): Int
    @Update
    fun updateEmployee(employee: Employee)

    View Slide

  42. @Delete
    fun deleteEmployee(employee: Employee): Int
    @Delete
    fun deleteEmployee(employees: List)
    @Delete
    fun deleteEmployee(vararg employee: Employee): Int

    View Slide

  43. @Dao
    interface EmployeesDAO {
    @Query("SELECT * FROM employees WHERE team_id = :teamId")
    fun getEmployeesByTeamId(teamId: Long): LiveData>
    @Insert(onConflict = OnConflictStrategy.
    REPLACE)
    fun insertEmployee(employee: Employee)
    @Update
    fun updateEmployee(employee: Employee): Int
    @Delete
    fun deleteEmployee(employee: Employee): Int
    }

    View Slide

  44. @Entity
    data class Employee(
    @PrimaryKey val id: Long,
    val name: String,
    val phone: String)

    View Slide

  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)

    View Slide

  46. @Database(entities = [Team::class, Employee::class], version = 1)
    abstract class ContactsDatabase: RoomDatabase() {
    abstract fun teamsDao(): TeamsDAO
    abstract fun employeesDao(): EmployeesDAO
    }

    View Slide

  47. @Alex_Zhukovich
    alexzh.com
    Andrzej Jóźwiak
    ajoz.github.io

    View Slide