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

Android Architecture Components

Android Architecture Components

Apresentada no DevFest Sul de Minas 2018

2dc2a52d38ee8c184deeceaadf9ed6ba?s=128

Rafael Toledo

November 24, 2018
Tweet

More Decks by Rafael Toledo

Other Decks in Programming

Transcript

  1. Rafael Toledo Lavras | Varginha | Sul de Minas Android

    Architecture Components
  2. Arquitetura no Android Lavras | Varginha | Sul de Minas

  3. Componentes Base Activity Broadcast Receiver Service Content Provider

  4. Arquiteturas na Comunidade MVC Nenhuma MVP MVVM

  5. Até que... no Google I/O 2017

  6. Architecture Components 2017 LiveData Lifecycle Room ViewModel

  7. Architecture Components 2017 LiveData Lifecycle Room ViewModel Paging

  8. Architecture Components 2018 Work Manager Navigation

  9. Architecture Components 2018 - agora parte do Android Jetpack

  10. Android Jetpack

  11. O que são Architecture Components? Lavras | Varginha | Sul

    de Minas
  12. São componentes para facilitar a implementação de tarefas comuns em

    apps Android
  13. Lifecycles Lavras | Varginha | Sul de Minas

  14. Lifecycle Componentes de lifecycle realizam ações reagindo a eventos de

    ciclo de vida de componentes do Android. Eles ajudam a escrever código mais fáceis de ler e manter e, frequentemente, mais enxutos.
  15. internal class MyLocationListener( private val context: Context, private val callback:

    (Location) -> Unit ) { fun start() { // connect to system location service } fun stop() { // disconnect from system location service } }
  16. class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) { myLocationListener = MyLocationListener(this) { location -> // update UI } } ... }
  17. class MyActivity : AppCompatActivity() { ... public override fun onStart()

    { super.onStart() myLocationListener.start() // manage other components that need to respond // to the activity lifecycle } public override fun onStop() { super.onStop() myLocationListener.stop() // manage other components that need to respond // to the activity lifecycle } }
  18. public override fun onStart() { super.onStart() Util.checkUserStatus { result ->

    // e se esse callback for chamado DEPOIS que a activity encerrar? if (result) { myLocationListener.start() } } }
  19. Lifecycle: Estados e Eventos

  20. class MyObserver : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun connectListener() { ...

    } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun disconnectListener() { ... } }
  21. class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener

    override fun onCreate(...) { myLocationListener = MyLocationListener(this, lifecycle) { location -> // update UI } Util.checkUserStatus { result -> if (result) { myLocationListener.enable() } } } }
  22. internal class MyLocationListener(private val context: Context, private val lifecycle: Lifecycle,

    private val callback: (Location) -> Unit) { private var enabled = false @OnLifecycleEvent(Lifecycle.Event.ON_START) fun start() { if (enabled) { // connect } } fun enable() { enabled = true if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { // connect if not connected } } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun stop() { // disconnect if connected } }
  23. internal class MyLocationListener(private val context: Context, private val lifecycle: Lifecycle,

    private val callback: (Location) -> Unit) { private var enabled = false @OnLifecycleEvent(Lifecycle.Event.ON_START) fun start() { if (enabled) { // connect } } fun enable() { enabled = true if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { // connect if not connected } } @OnLifecycleEvent(Lifecycle.Event.ON_STOP) fun stop() {
  24. LiveData Lavras | Varginha | Sul de Minas

  25. LiveData É uma classe observável que guarda dados, porém, diferentemente

    de um observable comum, ela respeita eventos de ciclo de vida de outros componentes, como Activity, Fragment e Services.
  26. class NameViewModel : ViewModel() { // Create a LiveData with

    a String val currentName: MutableLiveData<String> by lazy { MutableLiveData<String>() } // Rest of the ViewModel... }
  27. class NameViewModel : ViewModel() { // Create a LiveData with

    a String val currentName: MutableLiveData<String> by lazy { MutableLiveData<String>() } // Rest of the ViewModel... }
  28. class NameActivity : AppCompatActivity() { private lateinit var mModel: NameViewModel

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // Get the ViewModel. mModel = ViewModelProviders.of(this).get(NameViewModel::class.java) // Create the observer which updates the UI. val nameObserver = Observer<String> { newName -> // Update the UI, in this case, a TextView. mNameTextView.text = newName } // Observe the LiveData, passing in this activity as the LifecycleOwner and the observer. mModel.currentName.observe(this, nameObserver) } }
  29. // Atualizando objetos LiveData mButton.setOnClickListener { val anotherName = "John

    Doe" mModel.currentName.setValue(anotherName) }
  30. // Estendendo LiveData class StockLiveData(symbol: String) : LiveData<BigDecimal>() { private

    val mStockManager = StockManager(symbol) private val mListener = { price: BigDecimal -> value = price } override fun onActive() { mStockManager.requestPriceUpdates(mListener) } override fun onInactive() { mStockManager.removeUpdates(mListener) } }
  31. ViewModel Lavras | Varginha | Sul de Minas

  32. ViewModel É uma classe projetada para guardar e gerenciar dados

    relativos a UI de uma forma que permita aos dados sobreviverem a mudanças de configuração, como por exemplo a mudança de orientação
  33. class MyViewModel : ViewModel() { private lateinit var users: MutableLiveData<List<User>>

    fun getUsers(): LiveData<List<User>> { if (!::users.isInitialized) { users = MutableLiveData() loadUsers() } return users } private fun loadUsers() { // Faça alguma tarefa assíncrona para obter os usuários. } }
  34. class MyActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    // Cria um ViewModel da primeira vez que o método onCreate() // da Activity for chamado. Activities recriadas recebem // a mesma instância de MyViewModel criada pela primeira Activity. val model = ViewModelProviders.of(this).get(MyViewModel::class.java) model.getUsers().observe(this, Observer<List<User>>{ users -> // atualiza a UI }) } }
  35. ViewModel Lavras | Varginha | Sul de Minas Compartilhando instâncias

    entre Fragments
  36. class SharedViewModel : ViewModel() { val selected = MutableLiveData<Item>() fun

    select(item: Item) { selected.value = item } }
  37. class MasterFragment : Fragment() { private lateinit var itemSelector: Selector

    private lateinit var model: SharedViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) model = activity?.run { ViewModelProviders.of(this).get(SharedViewModel::class.java) } ?: throw Exception("Activity inválida") itemSelector.setOnClickListener { item -> // Atualiza a UI } } }
  38. class DetailFragment : Fragment() { private lateinit var model: SharedViewModel

    override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) model = activity?.run { ViewModelProviders.of(this).get(SharedViewModel::class.java) } ?: throw Exception("Activity inválida") model.selected.observe(this, Observer<Item> { item -> // Atualiza a UI }) } }
  39. Room Lavras | Varginha | Sul de Minas

  40. Room Room é uma biblioteca de persistência de dados que

    fornece uma camada de abstração sobre o SQLite, permitindo o acesso de dados robusto, ao mesmo tempo em que permite utilizar todo o poder do banco de dados SQLite
  41. Room

  42. @Entity data class User( @PrimaryKey var uid: Int, @ColumnInfo(name =

    "first_name") var firstName: String?, @ColumnInfo(name = "last_name") var lastName: String? )
  43. @Dao interface UserDao { @Query("SELECT * FROM user") fun getAll():

    List<User> @Query("SELECT * FROM user WHERE uid IN (:userIds)") fun loadAllByIds(userIds: IntArray): List<User> @Query("SELECT * FROM user WHERE first_name LIKE :first AND " + "last_name LIKE :last LIMIT 1") fun findByName(first: String, last: String): User ... }
  44. @Dao interface UserDao { ... @Insert fun insertAll(vararg users: User)

    @Delete fun delete(user: User) }
  45. @Database(entities = arrayOf(User::class), version = 1) abstract class AppDatabase :

    RoomDatabase() { abstract fun userDao(): UserDao } val db = Room.databaseBuilder( applicationContext, AppDatabase::class.java, "database-name" ).build()
  46. Room - outras features - Migrations - FTS (Full Text

    Search) - a partir da versão 2.1 - Paginação - em conjunto com a Paging
  47. Paging Lavras | Varginha | Sul de Minas

  48. Paging A Paging Library fornece APIs para o carregamento progressivo

    de dados em um RecyclerView. Os dados podem ser advindos tanto de uma fonte de dados locais (banco de dados), quanto remotos (API)
  49. class ConcertViewModel { fun search(query: String): ConcertSearchResult { val boundaryCallback

    = ConcertBoundaryCallback(query, myService, myCache) // Use a LiveData object to communicate your network's state back // to your app's UI, as in the following example. Note that error // handling isn't shown in this snippet. // val loadingState: LiveData<MyNetworkState> = // boundaryCallback.loadingState } }
  50. class ConcertBoundaryCallback( private val query: String, private val service: MyService,

    private val cache: MyLocalCache ) : PagedList.BoundaryCallback<Concert>() { // Requests initial data from the network, replacing all content currently // in the database. override fun onZeroItemsLoaded() { requestAndReplaceInitialData(query) } // Requests additional data from the network, appending the results to the // end of the database's existing data. override fun onItemAtEndLoaded(itemAtEnd: Concert) { requestAndAppendData(query, itemAtEnd.key) }
  51. Paging Lavras | Varginha | Sul de Minas Integração com

    Room
  52. @Dao interface ConcertDao { // The Int type parameter tells

    Room to use a PositionalDataSource // object, with position-based loading under the hood. @Query("SELECT * FROM concerts ORDER BY date DESC") fun concertsByDate(): DataSource.Factory<Int, Concert> }
  53. class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: LiveData<PagedList<Concert>> =

    LivePagedListBuilder( concertDao.concertsByDate(), /* page size */ 20).build() }
  54. class ConcertActivity : AppCompatActivity() { public override fun onCreate(savedInstanceState: Bundle?)

    { super.onCreate(savedInstanceState) val viewModel = ViewModelProviders.of(this) .get(ConcertViewModel::class.java!!) val recyclerView = findViewById(R.id.concert_list) val adapter = ConcertAdapter() viewModel.concertList.observe(this, { pagedList -> adapter.submitList(pagedList) }) recyclerView.setAdapter(adapter) } }
  55. class ConcertAdapter() : PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) { fun onBindViewHolder(holder: ConcertViewHolder, position:

    Int) { val concert = getItem(position) if (concert != null) { holder.bindTo(concert) } else { // Null defines a placeholder item - PagedListAdapter automatically // invalidates this row when the actual object is loaded from the // database. holder.clear() } } ... }
  56. class ConcertAdapter() : PagedListAdapter<Concert, ConcertViewHolder>(DIFF_CALLBACK) { ... companion object {

    private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<Concert>() { // Concert details may have changed if reloaded from the database, // but ID is fixed. override fun areItemsTheSame(oldConcert: Concert, newConcert: Concert): Boolean = oldConcert.id == newConcert.id override fun areContentsTheSame(oldConcert: Concert, newConcert: Concert): Boolean = oldConcert == newConcert } }
  57. Navigation Lavras | Varginha | Sul de Minas

  58. Navigation O Navigation Architecture Component simplifica a implementação de navegação

    entre "destinos" do seu aplicativo. Um conjunto de "destinos" compõe o grafo de navegação do seu aplicativo.
  59. Navigation

  60. Navigation - Editor Visual

  61. <?xml version="1.0" encoding="utf-8"?> <navigation xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" app:startDestination="@id/blankFragment"> <fragment android:id="@+id/blankFragment"

    android:name="com.example.cashdog.cashdog.BlankFragment" android:label="fragment_blank" tools:layout="@layout/fragment_blank" > <action android:id="@+id/action_blankFragment_to_blankFragment2" app:destination="@id/blankFragment2" /> </fragment> <fragment android:id="@+id/blankFragment2" android:name="com.example.cashdog.cashdog.BlankFragment2" android:label="fragment_blank_fragment2" tools:layout="@layout/fragment_blank_fragment2" /> </navigation>
  62. Navigation - Destination (Fragment ou Activity) - Action - Argument

  63. <?xml version="1.0" encoding="utf-8"?> <navigation xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" xmlns:android="http://schemas.android.com/apk/res/android" app:startDestination="@id/blankFragment"> <fragment android:id="@+id/blankFragment"

    android:name="com.example.cashdog.cashdog.BlankFragment" android:label="Blank" tools:layout="@layout/fragment_blank" /> </navigation>
  64. <fragment android:id="@+id/blankFragment" android:name="com.example.cashdog.cashdog.BlankFragment" android:label="fragment_blank" tools:layout="@layout/fragment_blank" > <action android:id="@+id/action_blankFragment_to_blankFragment2" app:destination="@id/blankFragment2" />

    </fragment>
  65. <fragment android:id="@+id/blankFragment" android:name="com.example.cashdog.cashdog.BlankFragment" android:label="fragment_blank" tools:layout="@layout/fragment_blank" > <action android:id="@+id/action_blankFragment_to_blankFragment2" app:destination="@id/blankFragment2" />

    </fragment>
  66. <fragment android:id="@+id/confirmationFragment" android:name="com.example.cashdog.cashdog.ConfirmationFragment" android:label="fragment_confirmation" tools:layout="@layout/fragment_confirmation"> <argument android:name="amount" android:defaultValue=”0” /> </fragment>

  67. <fragment android:id="@+id/confirmationFragment" android:name="com.example.cashdog.cashdog.ConfirmationFragment" android:label="fragment_confirmation" tools:layout="@layout/fragment_confirmation"> <argument android:name="amount" android:defaultValue=”0” /> </fragment>

  68. <android.support.constraint.ConstraintLayout ...> <fragment android:id="@+id/nav_host_fragment" android:name="androidx.navigation.fragment.NavHostFragment" android:layout_width="0dp" android:layout_height="0dp" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent"

    app:layout_constraintBottom_toBottomOf="parent" app:defaultNavHost="true" app:navGraph="@navigation/mobile_navigation" /> </android.support.constraint.ConstraintLayout>
  69. viewTransactionsButton.setOnClickListener { view -> view.findNavController().navigate(R.id.viewTransactionsAction) } // Activity override fun

    onSupportNavigateUp() = findNavController(R.id.nav_host_fragment).navigateUp()
  70. WorkManager Lavras | Varginha | Sul de Minas

  71. WorkManager O WorkManager é uma API que facilita a criação

    de tarefas assíncronas e condicionar a execução delas. Você pode criar uma task e decidir se ela deve ser executada imediatamente ou após determinado período ou condição.
  72. class FirstWorker : Worker() { override fun doWork(): Result {

    TODO() } }
  73. class FirstWorker : Worker() { override fun doWork(): Result {

    return Result.SUCCESS } }
  74. class FirstWorker : Worker() { override fun doWork(): Result {

    return Result.SUCCESS } }
  75. class FirstWorker : Worker() { override fun doWork(): Result {

    return Result.RETRY } }
  76. class FirstWorker : Worker() { override fun doWork(): Result {

    inputData applicationContext } }
  77. class FirstWorker : Worker() { override fun doWork(): Result {

    inputData applicationContext } }
  78. // Agendamento Simples val firstWork = OneTimeWorkRequestBuilder<FirstWorker>() .build() WorkManager.getInstance().enqueue(firstWork)

  79. // Agendamento Simples - com parâmetros val firstWork = OneTimeWorkRequestBuilder<FirstWorker>()

    .setInputData(Data.Builder() .putString("key", "value") .build()) .build() WorkManager.getInstance().enqueue(firstWork)
  80. // Agendamento Simples - com parâmetros val firstWork = OneTimeWorkRequestBuilder<FirstWorker>()

    .setInputData(mapOf("value" to "key").toWorkData()) .build() WorkManager.getInstance().enqueue(firstWork) // Dentro do método doWork() inputData.getString("key", "Default Value")
  81. // Agendamento após intervalo val firstWork = OneTimeWorkRequestBuilder<FirstWorker>() .setInitialDelay(15, TimeUnit.MINUTES)

    .build() WorkManager.getInstance().enqueue(firstWork)
  82. // Agendamento com Constraints val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresCharging(true)

    .setRequiresBatteryNotLow(true) .build() val firstWork = OneTimeWorkRequestBuilder<FirstWorker>() .setConstraints(constraints) .setInputData(mapOf("value" to "key").toWorkData()) .build()
  83. // Agendamento periódico val periodicWork = PeriodicWorkRequestBuilder<FirstWorker>(1, TimeUnit.DAYS).build() WorkManager.getInstance().enqueue(periodicWork)

  84. // Encadeamento de Tasks val firstWork = OneTimeWorkRequestBuilder<FirstWorker>() .build() val

    secondWork = OneTimeWorkRequestBuilder<SecondWorker>() .build() WorkManager.getInstance().beginWith(firstWork) .then(secondWork).enqueue()
  85. WorkManager - é executado após o sistema obter um wakelock

    - não é necessário requisitar - é executado numa thread em background - não deve iniciar nenhuma nova thread - deve retornar um status - não pode rodar pra sempre - ~10 minutos
  86. DataBinding Lavras | Varginha | Sul de Minas

  87. DataBinding A DataBinding é uma biblioteca de suporte que permite

    fazer a ligação de componentes de UI com fontes de dados de forma declarativa, em vez de programaticamente.
  88. findViewById<TextView>(R.id.sample_text).apply { text = viewModel.userName }

  89. findViewById<TextView>(R.id.sample_text).apply { text = viewModel.userName } <TextView android:text="@{viewmodel.userName}" />

  90. <layout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto"> <data> <variable name="viewmodel" type="com.myapp.data.ViewModel" /> </data> <ConstraintLayout...

    /> <!-- UI layout's root element --> </layout>
  91. @BindingAdapter("app:goneUnless") fun goneUnless(view: View, visible: Boolean) { view.visibility = if

    (visible) View.VISIBLE else View.GONE }
  92. DataBinding - Sem mais findViewById - 2-way binding - DataSource

    pode ser um LiveData - DataBinding v2
  93. Rafael Toledo speakerdeck.com/rafaeltoledo Lavras | Varginha | Sul de Minas

    Obrigado!
  94. Perguntas? Android Architecture Components Lavras | Varginha | Sul de

    Minas