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

Android Architecture Components - Introduction

Android Architecture Components - Introduction

As the developers we constantly ask ourselves a question - what architecture should we follow? MVP, MVVM, MVC - those are only a few which we know. Fortunately, our problems come to an end! Recently Google published a set of libraries called Architecture Components. During the presentation, we’ll find out how to use a database library Room, how to get control over activity lifecycle and how to be up to date with LiveData.

Paulina Szklarska

June 26, 2018
Tweet

More Decks by Paulina Szklarska

Other Decks in Technology

Transcript

  1. @p_szklarska Paulina Szklarska Android Developer @ DroidsOnRoids Toast Wrocław meetups

    co-org GDG Wrocław co-org Women Techmakers Wrocław lead cat owner travel enthusiast @p_szklarska @pszklarska @pszklarska
  2. @p_szklarska 1. What are Architecture Components? 2. Room 3. Lifecycle-aware

    components 1. Lifecycle Components 2. ViewModel 3. Live Data 4. Application architecture example (*) Agenda
  3. @p_szklarska ARCHITECTURE COMPONENTS ROOM LIFECYCLE COMPONENTS VIEW MODEL LIVE DATA

    PAGING LIBRARY @p_szklarska NAVIGATION COMPONENTS WORK MANAGER
  4. @p_szklarska Room Entity Single database table DAO Defines methods to

    access database Database Holds entities and DAOs @p_szklarska Abstraction layer over SQLite
  5. @p_szklarska Why another database library? •less boilerplate code (thanks to

    annotations) •SQL statement compile-time validation (less crashes) •full compatibility with LiveData and RxJava •easy to use •easy to test •easy to migrate @p_szklarska
  6. @p_szklarska data class Cat( val id: Int, val name: String,

    val url: String )a Entity @p_szklarska
  7. @p_szklarska @Dao interface CatDao { @Insert fun insert(cat: Cat) @Update

    fun update(cat: Cat) @Delete fun delete(cat: Cat) }a DAO
  8. @p_szklarska @Dao interface CatDao { @Query("SELECT * FROM kittens") fun

    getAllCats(): List<Cat> @Insert fun insert(cat: Cat) @Update fun update(cat: Cat) @Delete fun delete(cat: Cat) }a DAO
  9. @p_szklarska @Dao interface CatDao { @Query("SELECT * FROM kittens") fun

    getAllCats(): List<Cat> @Query("SELECT * FROM kittens WHERE id=:id") fun getCatById(id: Int): List<Cat> }a DAO
  10. @p_szklarska @Database(entities = [(Cat::class)], version = 1) abstract class CatDatabase

    : RoomDatabase() { abstract fun catDao(): CatDao }a Database
  11. @p_szklarska @Database(entities = [(Cat::class)], version = 1) abstract class CatDatabase

    : RoomDatabase() { abstract fun catDao(): CatDao private val DB_NAME = "catDatabase.db" fun create(context: Context): CatDatabase { return Room.databaseBuilder( context, CatDatabase::class.java, DB_NAME).build() } }a Database
  12. @p_szklarska @RunWith(AndroidJUnit4::class) class CatEntityTest { private lateinit var catDao: CatDao

    private lateinit var catDatabase: CatDatabase @Before fun setUp() { val context = InstrumentationRegistry.getTargetContext() catDatabase = Room .inMemoryDatabaseBuilder( context, CatDatabase::class.java) .build() catDao = catDatabase.catDao() }a @After fun tearDown() { catDatabase.close() }a }a Testing
  13. @p_szklarska @RunWith(AndroidJUnit4::class) class CatEntityTest { private lateinit var catDao: CatDao

    private lateinit var catDatabase: CatDatabase @Before fun setUp() { val context = InstrumentationRegistry.getTargetContext() catDatabase = Room .inMemoryDatabaseBuilder( context, CatDatabase::class.java) .build() catDao = catDatabase.catDao() }a @After fun tearDown() { catDatabase.close() }a }a Testing
  14. @p_szklarska @RunWith(AndroidJUnit4::class) class CatEntityTest { ... @Test fun saveAndReadCat() {

    val fluffyToBeSaved = Cat(name = "Fluffy") catDao.insert(fluffyToBeSaved) val fluffyFromDatabase = catDao.getCatsByName("Fluffy") assertEquals(fluffyToBeSaved, fluffyFromDatabase) } ... } Testing
  15. @p_szklarska Room - tl;dr • less boilerplate code • SQL

    statement compile-time validation • full compatibility with other Architecture Components and RxJava • easy to use • easy to test • easy to migrate
  16. @p_szklarska class LocationActivity : AppCompatActivity(), LocationListener { lateinit var locationManager:

    LocationManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) locationManager = LocationManager() }a override fun onStart() { super.onStart() locationManager.addLocationListener() }a override fun onStop() { locationManager.removeLocationListener() super.onStop() }a override fun onLocationChanged(location: Location) { // only this is interesting }a }a LifecycleObserver
  17. @p_szklarska class LocationActivity : AppCompatActivity(), LocationListener { lateinit var locationManager:

    LocationManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) locationManager = LocationManager() }a override fun onStart() { super.onStart() locationManager.addLocationListener() }a override fun onStop() { locationManager.removeLocationListener() super.onStop() }a override fun onLocationChanged(location: Location) { // only this is interesting }a }a LifecycleObserver
  18. @p_szklarska class LocationManager {a fun addLocationListener() {a // register location

    listener }a fun removeLocationListener() {a // unregister location listener }a }a LifecycleObserver
  19. @p_szklarska class LocationManager() : LifecycleObserver {a fun addLocationListener() {a //

    register location listener }a fun removeLocationListener() {a // unregister location listener }a }a LifecycleObserver
  20. @p_szklarska class LocationManager(lifecycleOwner: LifecycleOwner) : LifecycleObserver {a init {a lifecycleOwner.lifecycle.addObserver(this)

    }b fun addLocationListener() {a // register location listener }a fun removeLocationListener() {a // unregister location listener }a }a LifecycleObserver
  21. @p_szklarska class LocationManager(lifecycleOwner: LifecycleOwner) : LifecycleObserver {a init {a lifecycleOwner.lifecycle.addObserver(this)

    }b @OnLifecycleEvent(Lifecycle.Event.ON_START) fun addLocationListener() {a // register location listener }a @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun removeLocationListener() {a // unregister location listener }a }a LifecycleObserver
  22. @p_szklarska class LocationActivity : AppCompatActivity(), LocationListener { lateinit var locationManager:

    LocationManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) locationManager = LocationManager()this }a override fun onStart() { super.onStart() locationManager.addLocationListener() }b override fun onStop() { locationManager.removeLocationListener() super.onStop() }b override fun onLocationChanged(location: Location) { // only this is interesting }a }a LifecycleObserver
  23. @p_szklarska class LocationActivity : AppCompatActivity(), LocationListener { lateinit var locationManager:

    LocationManager override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) locationManager = LocationManager(this) }a override fun onLocationChanged(location: Location) { // only this is interesting }a }a LifecycleObserver
  24. @p_szklarska Lifecycle Observer It’s the LocationManager which cares about the

    Lifecycle. So it should be able to do it without the activity babysitting itself. I’m sure if you look at your code today, your onStart(), onStop() methods are like, at least 20, 30 lines of code. We want them to be zero lines of code. ~Yigit Boyar, Software Enginner, Google, Google I/O ‚17
  25. @p_szklarska ViewModel ACTIVITY ACTIVITY RECREATED ViewModel • don’t worry about

    UI data source • data will wait for us • data will be always updated even after activity recreating Rotation
  26. @p_szklarska class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) val viewModel = ViewModelProviders .of(this) .get(MainViewModel::class.java) }a }a ViewModel
  27. @p_szklarska class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) val viewModel = ViewModelProviders .of(this) .get(MainViewModel::class.java) showKitties(viewModel.kittiesNames) }a }a ViewModel
  28. @p_szklarska class MainViewModel : ViewModel() { private val kittiesNamesLiveData =

    MutableLiveData<List<String>>() val kittiesNames = kittiesNamesLiveData as LiveData<List<String>> }a LiveData
  29. @p_szklarska class MainViewModel : ViewModel() { private val kittiesNamesLiveData =

    MutableLiveData<List<String>>() init { repository.fetchNewKitties { kittiesNamesLiveData.postValue(it) }a } val kittiesNames = kittiesNamesLiveData as LiveData<List<String>> }a LiveData
  30. @p_szklarska class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) val viewModel = ViewModelProviders .of(this) .get(MainViewModel::class.java) showKitties(viewModel.kittiesNames) }a }a LiveData
  31. @p_szklarska class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) val viewModel = ViewModelProviders .of(this) .get(MainViewModel::class.java) }a }a LiveData
  32. @p_szklarska class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) val viewModel = ViewModelProviders .of(this) .get(MainViewModel::class.java) viewModel.kittiesNames.observe(this, Observer { kitties -> showKitties(kitties) }) }a }a LiveData
  33. @p_szklarska private val userList = MutableLiveData<List<User>>() private val usernamesList: LiveData<List<String>>

    = Transformations.map(userList, { users -> users.map { it.name } }) LiveData - Transformation
  34. @p_szklarska <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="pl.pszklarska.databinding.HomeViewModel"

    /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text=“@{viewModel.kittyName}" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text=“@{Integer.toString(viewModel.kittyAge)}" /> </LinearLayout> </layout> LiveData + DataBinding
  35. @p_szklarska <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="pl.pszklarska.databinding.HomeViewModel"

    /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text=“@{viewModel.kittyName}" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text=“@{Integer.toString(viewModel.kittyAge)}" /> </LinearLayout> </layout> LiveData + DataBinding
  36. @p_szklarska <?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"> <data> <variable name="viewModel" type="pl.pszklarska.databinding.HomeViewModel"

    /> </data> <LinearLayout android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text=“@{viewModel.kittyName}" /> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text=“@{Integer.toString(viewModel.kittyAge)}" /> </LinearLayout> </layout> LiveData + DataBinding
  37. @p_szklarska class HomeViewModel : ViewModel() { private val kittyRepository =

    KittyRepository() val kittyName = ObservableField<String>() val kittyAge = ObservableInt() init { kittyRepository.receiveNewKitties { kittyName.set(it.name) kittyAge.set(it.age) }a }a }a LiveData + DataBinding
  38. @p_szklarska class HomeViewModel : ViewModel() { private val kittyRepository =

    KittyRepository() val kittyName = MutableLiveData<String>() val kittyAge = MutableLiveData<Int>() init { kittyRepository.receiveNewKitties { kittyName.postValue(it.name) kittyAge.postValue(it.age) }a }a }a LiveData + DataBinding
  39. @p_szklarska class HomeActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) val binding: ActivityMainBinding = DataBindingUtil.setContentView( this, R.layout.activity_main) val viewModel = ViewModelProviders.of(this) .get(HomeViewModel::class.java) binding.viewModel = viewModel binding.setLifecycleOwner(this) }a }a LiveData + DataBinding
  40. @p_szklarska @Dao interface CatDao { @Query("SELECT * FROM kittens") fun

    getAllCats(): LiveData<List<Cat>> }a LiveData + Room
  41. @p_szklarska LiveData - tl;dr •keeps your UI always updated (follows

    observer pattern) •no memory leaks (data bound with lifecycle) •no data sent to stopped activity
  42. @p_szklarska Architecture example •MVVM (Model-View-ViewModel) •ViewModel stores data for View

    ACTIVITY FRAGMENT VIEWMODEL LiveData REPOSITORY View ViewModel Model
  43. @p_szklarska class MainViewModel( private val repository: KittyRepository ) : ViewModel()

    { private val kittiesNames = MutableLiveData<List<String>>() init { repository.fetchNewKitties { kittiesNamesLiveData.postValue(it) }a }a val kittiesNamesLiveData = kittiesNames as LiveData<List<String>> }a Architecture Example
  44. @p_szklarska class MainViewModelFactory : ViewModelProvider.NewInstanceFactory() { private val repository =

    KittyRepository() override fun <T : ViewModel> create(modelClass: Class<T>): T { return MainViewModel(repository) as T } } Architecture Example
  45. @p_szklarska class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) }a }a Architecture Example
  46. @p_szklarska class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val viewModel = ViewModelProviders.of(this, MainViewModelFactory()) .get(MainViewModel::class.java) }a }a Architecture Example
  47. @p_szklarska class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val viewModel = ViewModelProviders.of(this, MainViewModelFactory()) .get(MainViewModel::class.java) viewModel.kitties.observe(this, Observer { kitties -> // show kitties list }) }a }a Architecture Example
  48. @p_szklarska class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val viewModel = ViewModelProviders.of(this, MainViewModelFactory()) .get(MainViewModel::class.java) viewModel.kitties.observe(this, Observer { kitties -> // show kitties list }) kittyDetailsList.setOnItemClickListener { kittyName -> viewModel.requestKittyDetails(kittyName) } }a }a Architecture Example
  49. @p_szklarska class MainViewModel( private val repository: KittyRepository ) : ViewModel()

    { private val kittiesNamesLiveData = MutableLiveData<List<String>>() init { repository.fetchNewKitties { kittiesNamesLiveData.postValue(it) }a }a val kitties = kittiesNamesLiveData as LiveData<List<String>> }a Architecture Example
  50. @p_szklarska class MainViewModel( private val repository: KittyRepository ) : ViewModel()

    { private val kittiesNamesLiveData = MutableLiveData<List<String>>() private val kittyDetailsLiveData = MutableLiveData<Kitty>() init { repository.fetchNewKitties { kittiesNamesLiveData.postValue(it) }a }a val kitties = kittiesNamesLiveData as LiveData<List<String>> val kittyDetails = kittyDetailsLiveData as LiveData<Kitty> }a Architecture Example
  51. @p_szklarska class MainViewModel( private val repository: KittyRepository ) : ViewModel()

    { private val kittiesNamesLiveData = MutableLiveData<List<String>>() private val kittyDetailsLiveData = MutableLiveData<Kitty>() init { repository.fetchNewKitties { kittiesNamesLiveData.postValue(it) }a }a fun requestKittyDetails(name: String) { val kittyDetails = repository.fetchKittyDetails(name) kittyDetailsLiveData.postValue(kittyDetails) }a val kitties = kittiesNamesLiveData as LiveData<List<String>> val kittyDetails = kittyDetailsLiveData as LiveData<Kitty> }a Architecture Example
  52. @p_szklarska class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val viewModel = ViewModelProviders.of(this, MainViewModelFactory()) .get(MainViewModel::class.java) viewModel.kitties.observe(this, Observer { kitties -> // show kitties list }) kittyDetailsList.setOnItemClickListener { kittyName -> viewModel.requestKittyDetails(kittyName) }a }a }a Architecture Example
  53. @p_szklarska class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val viewModel = ViewModelProviders.of(this, MainViewModelFactory()) .get(MainViewModel::class.java) viewModel.kitties.observe(this, Observer { kitties -> // show kitties list }) kittyDetailsList.setOnItemClickListener { kittyName -> viewModel.requestKittyDetails(kittyName) }a viewModel.kittyDetails.observe(this, Observer { kitty -> // show kitty details }) }a }a Architecture Example
  54. @p_szklarska class MainViewModelTest { @Test fun `observer is notified when

    kitty details are requested`() { }a }a Architecture Example
  55. @p_szklarska @RunWith(MockitoJUnitRunner::class) class MainViewModelTest { @Mock private lateinit var repository:

    KittyRepository @InjectMocks private lateinit var viewModel: MainViewModel @Test fun `observer is notified when kitty details are requested`() { }a }a Architecture Example
  56. @p_szklarska @RunWith(MockitoJUnitRunner::class) class MainViewModelTest { @Rule @JvmField val rule =

    InstantTaskExecutorRule() @Mock private lateinit var repository: KittyRepository @InjectMocks private lateinit var viewModel: MainViewModel @Test fun `observer is notified when kitty details are requested`() { }a }a Architecture Example
  57. @p_szklarska @RunWith(MockitoJUnitRunner::class) class MainViewModelTest { @Rule @JvmField val rule =

    InstantTaskExecutorRule() @Mock private lateinit var observer: Observer<Kitty> @Mock private lateinit var repository: KittyRepository @InjectMocks private lateinit var viewModel: MainViewModel @Test fun `observer is notified when kitty details are requested`() { viewModel.kittyDetails.observeForever(observer) }a }a Architecture Example
  58. @p_szklarska @RunWith(MockitoJUnitRunner::class) class MainViewModelTest { @Rule @JvmField val rule =

    InstantTaskExecutorRule() @Mock private lateinit var observer: Observer<Kitty> @Mock private lateinit var repository: KittyRepository @InjectMocks private lateinit var viewModel: MainViewModel private val kitty = Kitty("Fluffy", 5) @Test fun `observer is notified when kitty details are requested`() { viewModel.kittyDetails.observeForever(observer) whenever(repository.fetchKittyDetails(any())).thenReturn(kitty) }a }a Architecture Example
  59. @p_szklarska @RunWith(MockitoJUnitRunner::class) class MainViewModelTest { @Rule @JvmField val rule =

    InstantTaskExecutorRule() @Mock private lateinit var observer: Observer<Kitty> @Mock private lateinit var repository: KittyRepository @InjectMocks private lateinit var viewModel: MainViewModel private val kitty = Kitty("Fluffy", 5) @Test fun `observer is notified when kitty details are requested`() { viewModel.kittyDetails.observeForever(observer) whenever(repository.fetchKittyDetails(any())).thenReturn(kitty) viewModel.requestKittyDetails("Fluffy") }a }a Architecture Example
  60. @p_szklarska @RunWith(MockitoJUnitRunner::class) class MainViewModelTest { @Rule @JvmField val rule =

    InstantTaskExecutorRule() @Mock private lateinit var observer: Observer<Kitty> @Mock private lateinit var repository: KittyRepository @InjectMocks private lateinit var viewModel: MainViewModel private val kitty = Kitty("Fluffy", 5) @Test fun `observer is notified when kitty details are requested`() { viewModel.kittyDetails.observeForever(observer) whenever(repository.fetchKittyDetails(any())).thenReturn(kitty) viewModel.requestKittyDetails("Fluffy") verify(observer).onChanged(kitty) }a }a Architecture Example
  61. @p_szklarska Resources Android Architecture Documentation: https://developer.android.com/topic/libraries/ architecture/index.html Guide To App

    Architecture: https://developer.android.com/topic/libraries/ architecture/guide.html Google Samples Repository: https://github.com/googlesamples/android-architecture- components Medium series about Architecture Components: https://medium.com/@pszklarska