Slide 1

Slide 1 text

Rafael Toledo Lavras | Varginha | Sul de Minas Android Architecture Components

Slide 2

Slide 2 text

Arquitetura no Android Lavras | Varginha | Sul de Minas

Slide 3

Slide 3 text

Componentes Base Activity Broadcast Receiver Service Content Provider

Slide 4

Slide 4 text

Arquiteturas na Comunidade MVC Nenhuma MVP MVVM

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

Architecture Components 2017 LiveData Lifecycle Room ViewModel

Slide 7

Slide 7 text

Architecture Components 2017 LiveData Lifecycle Room ViewModel Paging

Slide 8

Slide 8 text

Architecture Components 2018 Work Manager Navigation

Slide 9

Slide 9 text

Architecture Components 2018 - agora parte do Android Jetpack

Slide 10

Slide 10 text

Android Jetpack

Slide 11

Slide 11 text

O que são Architecture Components? Lavras | Varginha | Sul de Minas

Slide 12

Slide 12 text

São componentes para facilitar a implementação de tarefas comuns em apps Android

Slide 13

Slide 13 text

Lifecycles Lavras | Varginha | Sul de Minas

Slide 14

Slide 14 text

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.

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

class MyActivity : AppCompatActivity() { private lateinit var myLocationListener: MyLocationListener override fun onCreate(...) { myLocationListener = MyLocationListener(this) { location -> // update UI } } ... }

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

public override fun onStart() { super.onStart() Util.checkUserStatus { result -> // e se esse callback for chamado DEPOIS que a activity encerrar? if (result) { myLocationListener.start() } } }

Slide 19

Slide 19 text

Lifecycle: Estados e Eventos

Slide 20

Slide 20 text

class MyObserver : LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_RESUME) fun connectListener() { ... } @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE) fun disconnectListener() { ... } }

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

LiveData Lavras | Varginha | Sul de Minas

Slide 25

Slide 25 text

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.

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

// Atualizando objetos LiveData mButton.setOnClickListener { val anotherName = "John Doe" mModel.currentName.setValue(anotherName) }

Slide 30

Slide 30 text

// Estendendo LiveData class StockLiveData(symbol: String) : LiveData() { private val mStockManager = StockManager(symbol) private val mListener = { price: BigDecimal -> value = price } override fun onActive() { mStockManager.requestPriceUpdates(mListener) } override fun onInactive() { mStockManager.removeUpdates(mListener) } }

Slide 31

Slide 31 text

ViewModel Lavras | Varginha | Sul de Minas

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

class MyViewModel : ViewModel() { private lateinit var users: MutableLiveData> fun getUsers(): LiveData> { if (!::users.isInitialized) { users = MutableLiveData() loadUsers() } return users } private fun loadUsers() { // Faça alguma tarefa assíncrona para obter os usuários. } }

Slide 34

Slide 34 text

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>{ users -> // atualiza a UI }) } }

Slide 35

Slide 35 text

ViewModel Lavras | Varginha | Sul de Minas Compartilhando instâncias entre Fragments

Slide 36

Slide 36 text

class SharedViewModel : ViewModel() { val selected = MutableLiveData() fun select(item: Item) { selected.value = item } }

Slide 37

Slide 37 text

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

Slide 38

Slide 38 text

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 -> // Atualiza a UI }) } }

Slide 39

Slide 39 text

Room Lavras | Varginha | Sul de Minas

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

Room

Slide 42

Slide 42 text

@Entity data class User( @PrimaryKey var uid: Int, @ColumnInfo(name = "first_name") var firstName: String?, @ColumnInfo(name = "last_name") var lastName: String? )

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

@Dao interface UserDao { ... @Insert fun insertAll(vararg users: User) @Delete fun delete(user: User) }

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

Room - outras features - Migrations - FTS (Full Text Search) - a partir da versão 2.1 - Paginação - em conjunto com a Paging

Slide 47

Slide 47 text

Paging Lavras | Varginha | Sul de Minas

Slide 48

Slide 48 text

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)

Slide 49

Slide 49 text

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 = // boundaryCallback.loadingState } }

Slide 50

Slide 50 text

class ConcertBoundaryCallback( private val query: String, private val service: MyService, private val cache: MyLocalCache ) : PagedList.BoundaryCallback() { // 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) }

Slide 51

Slide 51 text

Paging Lavras | Varginha | Sul de Minas Integração com Room

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

class ConcertViewModel(concertDao: ConcertDao) : ViewModel() { val concertList: LiveData> = LivePagedListBuilder( concertDao.concertsByDate(), /* page size */ 20).build() }

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

class ConcertAdapter() : PagedListAdapter(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() } } ... }

Slide 56

Slide 56 text

class ConcertAdapter() : PagedListAdapter(DIFF_CALLBACK) { ... companion object { private val DIFF_CALLBACK = object : DiffUtil.ItemCallback() { // 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 } }

Slide 57

Slide 57 text

Navigation Lavras | Varginha | Sul de Minas

Slide 58

Slide 58 text

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.

Slide 59

Slide 59 text

Navigation

Slide 60

Slide 60 text

Navigation - Editor Visual

Slide 61

Slide 61 text

Slide 62

Slide 62 text

Navigation - Destination (Fragment ou Activity) - Action - Argument

Slide 63

Slide 63 text

Slide 64

Slide 64 text

Slide 65

Slide 65 text

Slide 66

Slide 66 text

Slide 67

Slide 67 text

Slide 68

Slide 68 text

Slide 69

Slide 69 text

viewTransactionsButton.setOnClickListener { view -> view.findNavController().navigate(R.id.viewTransactionsAction) } // Activity override fun onSupportNavigateUp() = findNavController(R.id.nav_host_fragment).navigateUp()

Slide 70

Slide 70 text

WorkManager Lavras | Varginha | Sul de Minas

Slide 71

Slide 71 text

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.

Slide 72

Slide 72 text

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

Slide 73

Slide 73 text

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

Slide 74

Slide 74 text

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

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

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

Slide 78

Slide 78 text

// Agendamento Simples val firstWork = OneTimeWorkRequestBuilder() .build() WorkManager.getInstance().enqueue(firstWork)

Slide 79

Slide 79 text

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

Slide 80

Slide 80 text

// Agendamento Simples - com parâmetros val firstWork = OneTimeWorkRequestBuilder() .setInputData(mapOf("value" to "key").toWorkData()) .build() WorkManager.getInstance().enqueue(firstWork) // Dentro do método doWork() inputData.getString("key", "Default Value")

Slide 81

Slide 81 text

// Agendamento após intervalo val firstWork = OneTimeWorkRequestBuilder() .setInitialDelay(15, TimeUnit.MINUTES) .build() WorkManager.getInstance().enqueue(firstWork)

Slide 82

Slide 82 text

// Agendamento com Constraints val constraints = Constraints.Builder() .setRequiredNetworkType(NetworkType.CONNECTED) .setRequiresCharging(true) .setRequiresBatteryNotLow(true) .build() val firstWork = OneTimeWorkRequestBuilder() .setConstraints(constraints) .setInputData(mapOf("value" to "key").toWorkData()) .build()

Slide 83

Slide 83 text

// Agendamento periódico val periodicWork = PeriodicWorkRequestBuilder(1, TimeUnit.DAYS).build() WorkManager.getInstance().enqueue(periodicWork)

Slide 84

Slide 84 text

// Encadeamento de Tasks val firstWork = OneTimeWorkRequestBuilder() .build() val secondWork = OneTimeWorkRequestBuilder() .build() WorkManager.getInstance().beginWith(firstWork) .then(secondWork).enqueue()

Slide 85

Slide 85 text

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

Slide 86

Slide 86 text

DataBinding Lavras | Varginha | Sul de Minas

Slide 87

Slide 87 text

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.

Slide 88

Slide 88 text

findViewById(R.id.sample_text).apply { text = viewModel.userName }

Slide 89

Slide 89 text

findViewById(R.id.sample_text).apply { text = viewModel.userName }

Slide 90

Slide 90 text

Slide 91

Slide 91 text

@BindingAdapter("app:goneUnless") fun goneUnless(view: View, visible: Boolean) { view.visibility = if (visible) View.VISIBLE else View.GONE }

Slide 92

Slide 92 text

DataBinding - Sem mais findViewById - 2-way binding - DataSource pode ser um LiveData - DataBinding v2

Slide 93

Slide 93 text

Rafael Toledo speakerdeck.com/rafaeltoledo Lavras | Varginha | Sul de Minas Obrigado!

Slide 94

Slide 94 text

Perguntas? Android Architecture Components Lavras | Varginha | Sul de Minas