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. public class TestFragment extends Fragment{ @Override void onStart() { registerComponent1(...);

    registerComponent2(...); registerComponent3(...); } @Override void onStop() { unregisterComponent1(...); unregisterComponent2(...); unregisterComponent3(...); } }
  2. 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) } }
  3. 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 } }
  4. View ViewModel Model UI Events Property Change Events Create Update

    Delete Read ViewModel data Model Change Events
  5. ? 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)
  6. 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
  7. 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() } }
  8. 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>
  9. 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 } }
  10. 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 } ) } }
  11. class TeamsViewModel(apl: Application): AndroidViewModel(apl) { fun someDomainRelatedMethod() { // Application

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

    fetchTeams(): LiveData<List<Team>> { // accessing the injected dependency return teamRepository.fetchTeams() } }
  13. 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!”) } }
  14. 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) } }
  15. 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”
  16. 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
  17. 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
  18. 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 )
  19. 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
  20. @Dao interface EmployeeDao { @Query("SELECT * FROM employees WHERE team_id

    = :teamId") fun getEmployeesByTeamId(): List<Employee> }
  21. @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 }
  22. @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)
  23. @Database(entities = [Team::class, Employee::class], version = 1) abstract class ContactsDatabase:

    RoomDatabase() { abstract fun teamsDao(): TeamsDAO abstract fun employeesDao(): EmployeesDAO }