$30 off During Our Annual Pro Sale. View Details »

Android Training Program - Portugal, Aula 5

ATP Portugal
November 18, 2020

Android Training Program - Portugal, Aula 5

Aula #5: Listas, listas e mais listas 🥞

Por vezes temos de carregar e processar dados bastante pesados. Como é que o conseguimos fazer sem que a nossa aplicação não bloqueie?

- RecyclerView
- Bibliotecas externas
- Retrofit
- Glide
- Paging 3

ATP Portugal

November 18, 2020
Tweet

More Decks by ATP Portugal

Other Decks in Education

Transcript

  1. • Sejam excelentes uns para os outros • Fale mais

    alto se vir ou ouvir alguma coisa • O assédio não é tolerado • Pratique "Sim e" um ao outro Código de conduta Mais informações: http://bit.ly/2IhF0l3
  2. Andres-Leonardo Martinez-Ortiz Google Carlos Mota Formador Renato Almeida Formador @davilagrau

    @cafonsomota @tallnato Equipa Daniela Ferreira Gestora de comunidades
  3. • 12 aulas • 1h30 cada aula • ~1 aula

    por semana • 14 Outubro a 16 Dezembro • YouTube live • Suporte assíncrono contínuo via Discord/email • Todo o código disponível no GitHub Photo by Arif Riyanto on Unspla O programa
  4. #0 14 de Outubro Pronto para começar #1 21 de

    Outubro Bem-vindos ao Android #2 28 de Outubro Fundações I #3 04 de Novembro Fundações II #4 11 de Novembro Fundações III #5 18 de Novembro Listas, listas e mais listas #6 25 de Novembro Jetpack, Jetpack, Jetpack! #7 - #8 02 - 03 de Dezembro Firebase #9 - #10 09 - 10 de Dezembro MLKit & TensorFlow #11 16 de Dezembro Resumo Semana Semana Calendário ✅ ✅ ✅ ✅ Direto ✅
  5. Sumário Photo by Mika Baumeister on Unsplash • Resumo da

    aula anterior • RecyclerView • Retrofit • Glide • Paging • Kotlin para principiantes • Sexta-Feira negra
  6. • Permite separar os dados da sua representação gráfica •

    Está associado a uma Activity ou Fragment • Continua a ser executado mesmo que a aplicação não esteja visível • Permanece em memória quando uma Activity é reconstruída • Não deve aceder à interface gráfica da aplicação ViewModel
  7. • Padrão de desenvolvimento de software • Um objeto (Observable)

    notifica os interessados (Observers) ◦ Quando este valor é alterado • Os interessados subscrevem para receber as atualizações Observável? Observable Observer Observer Observer ...
  8. atualizar atualizar atualizar atualizar desenha o ecrã desenha o ecrã

    desenha o ecrã objetivo: 60fps UI-thread Operações assíncronas
  9. • A aplicação não é fluida ◦ Alguns frames podem

    não ser desenhados • ANR podem ser lançados pelo sistema ◦ ANR = Activity Not Responding • Má experiência para o utilizador Operações assíncronas Counter isn’t responding
  10. • Service • Threads • IntentService • AsyncTasks • WorkManager

    • JobScheduler • DownloadManager • AlarmManager • Coroutines Operações assíncronas Soluções
  11. Allow Snapchat to access this device’s location? Ao instalar todas

    as permissões requisitadas são dadas Funcionalidades do sistemas precisam de autorização explícita do utilizador As permissões vão sendo cada vez mais restritivas: - Apenas enquanto estamos a utilizar a aplicação - Permitir apenas uma única vez Android ... Android 6.0 … Android 10 Android 11
  12. Android 11 • Permissão única • O utilizador tem de

    permitir o acesso contínuo à localização ◦ Maior segurança ◦ Poupança de bateria • Revogação de permissões para aplicações que não são utilizadas
  13. • É a evolução da ListView ◦ Com uma maior

    performance e flexibilidade • Permite criar uma lista de objetos facilmente • Esta lista tanto pode ser horizontal como vertical ◦ Dependendo do LayoutManager definido • É possibilidade adicionar animações (incríveis) por cada item modificado RecyclerView
  14. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } Como utilizar? Criar uma Activity/Fragment MainActivity.kt
  15. class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) } } Como utilizar? Criar uma Activity/Fragment MainActivity.kt ⚠ Não esquecer de a definir no AndroidManifest.xml
  16. <?xml version="1.0" encoding="utf-8"?> <LinearLayout ... <ImageView android:id="@+id/iv_user_image" android:layout_width="35dp" android:layout_height="35dp" android:layout_weight="0"

    android:contentDescription="@string/description_user_profile" ndroid:scaleType="centerCrop"/> ... </LinearLayout> Como utilizar? … e como cada item deve ser res/layout/item_story.xml
  17. <?xml version="1.0" encoding="utf-8"?> <LinearLayout ... <ImageView android:id="@+id/iv_user_image" android:layout_width="35dp" android:layout_height="35dp" android:layout_weight="0"

    android:contentDescription="@string/description_user_profile" ndroid:scaleType="centerCrop"/> ... </LinearLayout> Como utilizar? … e como cada item deve ser
  18. private fun setup() { findViewById<RecyclerView>(R.id.rv_feed).apply { setHasFixedSize(true) layoutManager = LinearLayoutManager(context)

    adapter = FeedAdapter(Dogs.dogs) } } Como utilizar? Definir a RecyclerView na Activity MainActivity.kt
  19. private fun setup() { findViewById<RecyclerView>(R.id.rv_feed).apply { setHasFixedSize(true) layoutManager = LinearLayoutManager(context)

    adapter = FeedAdapter(Dogs.dogs) } } Como utilizar? Definir a RecyclerView na Activity MainActivity.kt
  20. private fun setup() { findViewById<RecyclerView>(R.id.rv_feed).apply { setHasFixedSize(true) layoutManager = LinearLayoutManager(context)

    adapter = FeedAdapter(Dogs.dogs) } } Como utilizar? Definir a RecyclerView na Activity MainActivity.kt
  21. private fun setup() { findViewById<RecyclerView>(R.id.rv_feed).apply { setHasFixedSize(true) layoutManager = LinearLayoutManager(context)

    adapter = FeedAdapter(Dogs.dogs) } } Como utilizar? Definir a RecyclerView na Activity MainActivity.kt
  22. private fun setup() { findViewById<RecyclerView>(R.id.rv_feed).apply { setHasFixedSize(true) layoutManager = LinearLayoutManager(context,

    HORIZONTAL, false)) adapter = FeedAdapter(Dogs.dogs) } } Como utilizar? Definir a RecyclerView na Activity MainActivity.kt
  23. private fun setup() { findViewById<RecyclerView>(R.id.rv_feed).apply { setHasFixedSize(true) layoutManager = LinearLayoutManager(context,

    HORIZONTAL, false)) adapter = FeedAdapter(Dogs.dogs) } } Como utilizar? Definir a RecyclerView na Activity MainActivity.kt
  24. private fun setup() { findViewById<RecyclerView>(R.id.rv_feed).apply { setHasFixedSize(true) layoutManager = LinearLayoutManager(context,

    HORIZONTAL, false)) adapter = FeedAdapter(Dogs.dogs) } } Como utilizar? Definir a RecyclerView na Activity MainActivity.kt
  25. class FeedAdapter constructor(val dogs: List<Dog>) : RecyclerView.Adapter<FeedAdapter.MainViewHolder>() { override fun

    onCreateViewHolder(group: ViewGroup, type: Int): MainViewHolder override fun getItemCount(): Int override fun onBindViewHolder(holder: MainViewHolder, position: Int) } Como utilizar? Cria o Adapter FeedAdapter.kt
  26. class FeedAdapter constructor(val dogs: List<Dog>) : RecyclerView.Adapter<FeedAdapter.MainViewHolder>() { override fun

    onCreateViewHolder(group: ViewGroup, type: Int): MainViewHolder { val inflater = LayoutInflater.from(parent.context) return MainViewHolder(inflater.inflate(R.layout.item_feed, parent, false)) } } Como utilizar? Cria o Adapter FeedAdapter.kt
  27. class FeedAdapter constructor(val dogs: List<Dog>) : RecyclerView.Adapter<FeedAdapter.MainViewHolder>() { override fun

    onCreateViewHolder(group: ViewGroup, type: Int): MainViewHolder { val inflater = LayoutInflater.from(parent.context) return MainViewHolder(inflater.inflate(R.layout.item_feed, parent, false)) } override fun getItemCount() = dogs.size } Como utilizar? Cria o Adapter FeedAdapter.kt
  28. class FeedAdapter constructor(val dogs: List<Dog>) : RecyclerView.Adapter<FeedAdapter.MainViewHolder>() { override fun

    onCreateViewHolder(group: ViewGroup, type: Int): MainViewHolder { val inflater = LayoutInflater.from(parent.context) return MainViewHolder(inflater.inflate(R.layout.item_feed, parent, false)) } override fun getItemCount() = dogs.size override fun onBindViewHolder(holder: MainViewHolder, position: Int) { val feed = dogs[position] holder.userImage.setImageResource(feed.picture) holder.userName.text = feed.name holder.image.setImageResource(feed.picture) } Como utilizar? Cria o Adapter FeedAdapter.kt
  29. class FeedAdapter constructor(val dogs: List<Dog>) : RecyclerView.Adapter<FeedAdapter.MainViewHolder>() { override fun

    onCreateViewHolder(group: ViewGroup, type: Int): MainViewHolder { val inflater = LayoutInflater.from(parent.context) return MainViewHolder(inflater.inflate(R.layout.item_feed, parent, false)) } override fun getItemCount() = dogs.size override fun onBindViewHolder(holder: MainViewHolder, position: Int) { val feed = dogs[position] holder.userImage.setImageResource(feed.picture) holder.userName.text = feed.name holder.image.setImageResource(feed.picture) } class MainViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val userImage = itemView.iv_user_image!! val userName = itemView.tv_user_name!! val image = itemView.iv_image!! } } Como utilizar? Cria o Adapter FeedAdapter.kt
  30. • Cliente REST fortemente tipado • Facilita a transferência de

    JSON através de um webservice ◦ JSON ou outro tipos de dados Retrofit https://github.com/square/retrofit
  31. Resultado da API { "bred_for": "Small rodent hunting, lapdog", "breed_group":

    "Toy", "height": { "imperial": "9 - 11.5", "metric": "23 - 29" }, "id": 1, "life_span": "10 - 12 years", "name": "Affenpinscher", "origin": "Germany, France", "temperament": "Stubborn, Curious, Playful, Adventurous, Active, Fun-loving", "weight": { "imperial": "6 - 13", "metric": "3 - 6" } } data class Dog ( val bred_for : String, val breed_group : String, val height : Height, val id : Int, val life_span : String, val name : String, val origin : String, val temperament : String, val weight : Weight ) data class Height ( val imperial : String, val metric : String ) data class Weight ( val imperial : String, val metric : String )
  32. Resultado da API "height": { "imperial": "9 - 11.5", "metric":

    "23 - 29" } data class Height ( val imperial : String, val metric : String )
  33. Resultado da API "weight": { "imperial": "6 - 13", "metric":

    "3 - 6" } data class Weight ( val imperial : String, val metric : String )
  34. Resultado da API { "bred_for": "Small rodent hunting, lapdog", "breed_group":

    "Toy", "height": {...}, "id": 1, "life_span": "10 - 12 years", "name": "Affenpinscher", "origin": "Germany, France", "temperament": "Stubborn, Curious, Playful, Adventurous, Active, Fun-loving", "weight": {...} }, ... data class Dog ( val bred_for : String, val breed_group : String, val height : Height, val id : Int, val life_span : String, val name : String, val origin : String, val temperament : String, val weight : Weight )
  35. Agora mais bonito ✨ { "bred_for": "Small rodent hunting, lapdog",

    "breed_group": "Toy", "height": {...}, "id": 1, "life_span": "10 - 12 years", "name": "Affenpinscher", "origin": "Germany, France", "temperament": "Stubborn, Curious, Playful, Adventurous, Active, Fun-loving", "weight": {...} }, ... data class Dog( @Json(name = "bred_for") val bredFor: String, @Json(name = "breed_group") val breedGroup: String, val height: Height, val id: Int, @Json(name = "life_span") val lifeSpan: String, val name: String, val origin: String, val temperament: String, val weight: Weight )
  36. • Biblioteca de conversão de JSON para Kotlin e Java

    • Optimizada para Android • Compatível Retrofit Moshi
  37. dependencies { ... def retrofitVersion = '2.9.0' implementation "com.squareup.retrofit2:retrofit:$retrofitVersion" implementation

    "com.squareup.retrofit2:converter-moshi:$retrofitVersion" } Como utilizar? Importar a biblioteca app/build.gradle
  38. interface DogApiClient { @GET("breeds") fun getBreeds() : Call<List<Dog>> } Como

    utilizar? A interface DogApiClient.kt Permite realizar trabalho em background
  39. class ApiProvider { private val retrofit = Retrofit.Builder() .baseUrl("https://api.thedogapi.com/v1") .addConverterFactory(MoshiConverterFactory.create())

    .build() fun getDogApi(): DogApiClient { return retrofit.create<DogApiClient>() } } Como utilizar? A interface ApiProvider.kt
  40. class ApiProvider { private val retrofit = Retrofit.Builder() .baseUrl("https://api.thedogapi.com/v1/") .addConverterFactory(MoshiConverterFactory.create())

    .build() fun getDogApi(): DogApiClient { return retrofit.create<DogApiClient>() } } Como utilizar? A interface ApiProvider.kt Por causa da inferência de tipo não precisamos de colocar o tipo
  41. private val provider = ApiProvider() private val dogApi = provider.getDogApi()

    fun getDogs() { dogApi.getBreeds() .enqueue(object : Callback<List<Dog>> { override fun onResponse(call: Call<List<Dog>>, response: Response<List<Dog>>) { if (response.isSuccessful) { val dogs = response.body() Log.d(TAG, "dogs $dogs") } } override fun onFailure(call: Call<List<Dog>>, t: Throwable) { Log.e(TAG, "Error loading dogs", t) } }) } Como utilizar? Obter os cães
  42. private val provider = ApiProvider() private val dogApi = provider.getDogApi()

    fun getDogs() { dogApi.getBreeds() .enqueue(object : Callback<List<Dog>> { override fun onResponse(call: Call<List<Dog>>, response: Response<List<Dog>>) { if (response.isSuccessful) { val dogs = response.body() Log.d(TAG, "dogs $dogs") } } override fun onFailure(call: Call<List<Dog>>, t: Throwable) { Log.e(TAG, "Error loading dogs", t) } }) } Como utilizar? Obter os cães
  43. private val provider = ApiProvider() private val dogApi = provider.getDogApi()

    fun getDogs() { dogApi.getBreeds() .enqueue(object : Callback<List<Dog>> { override fun onResponse(call: Call<List<Dog>>, response: Response<List<Dog>>) { if (response.isSuccessful) { val dogs = response.body() Log.d(TAG, "dogs $dogs") } } override fun onFailure(call: Call<List<Dog>>, t: Throwable) { Log.e(TAG, "Error loading dogs", t) } }) } Como utilizar? Obter os cães
  44. private val provider = ApiProvider() private val dogApi = provider.getDogApi()

    fun getDogs() { dogApi.getBreeds() .enqueue(object : Callback<List<Dog>> { override fun onResponse(call: Call<List<Dog>>, response: Response<List<Dog>>) { if (response.isSuccessful) { val dogs = response.body() Log.d(TAG, "dogs $dogs") } } override fun onFailure(call: Call<List<Dog>>, t: Throwable) { Log.e(TAG, "Error loading dogs", t) } }) } Como utilizar? Obter os cães
  45. private val provider = ApiProvider() private val dogApi = provider.getDogApi()

    fun getDogs() { dogApi.getBreeds() .enqueue(object : Callback<List<Dog>> { override fun onResponse(call: Call<List<Dog>>, response: Response<List<Dog>>) { if (response.isSuccessful) { val dogs = response.body() Log.d(TAG, "dogs $dogs") } } override fun onFailure(call: Call<List<Dog>>, t: Throwable) { Log.e(TAG, "Error loading dogs", t) } }) } Como utilizar? Obter os cães
  46. private val provider = ApiProvider() private val dogApi = provider.getDogApi()

    fun getDogs() { dogApi.getBreeds() .enqueue(object : Callback<List<Dog>> { override fun onResponse(call: Call<List<Dog>>, response: Response<List<Dog>>) { if (response.isSuccessful) { val dogs = response.body() Log.d(TAG, "dogs $dogs") } } override fun onFailure(call: Call<List<Dog>>, t: Throwable) { Log.e(TAG, "Error loading dogs", t) } }) } Como utilizar? Obter os cães
  47. dogs [Dog(bredFor=null, breedGroup=null, height=Height(imperial=9 - 11.5, metric=23 - 29), id=1,

    lifeSpan=null, name=Affenpinscher, origin=Germany, France, temperament=Stubborn, Curious, Playful, Adventurous, Active, Fun-loving, weight=Weight(imperial=6 - 13, metric=3 - 6)), Dog(bredFor=null, breedGroup=null, height=Height(imperial=25 - 27, metric=64 - 69), id=2, lifeSpan=null, name=Afghan Hound, origin=Afghanistan, Iran, Pakistan, temperament=Aloof, Clownish, Dignified, Independent, Happy, weight=Weight(imperial=50 - 60, metric=23 - 27)), Dog(bredFor=null, breedGroup=null, height=Height(imperial=30, metric=76), id=3, lifeSpan=null, name=African Hunting Dog, origin=, temperament=Wild, Hardworking, Dutiful, weight=Weight(imperial=44 - 66, metric=20 - 30)), Dog(bredFor=null, breedGroup=null, height=Height(imperial=21 - 23, metric=53 - 58), id=4, lifeSpan=null, name=Airedale Terrier, origin=United Kingdom, England, temperament=Outgoing, Friendly, Alert, Confident, Intelligent, Courageous, weight=Weight(imperial=40 - 65, metric=18 - 29)), Dog(bredFor=null, breedGroup=null, height=Height(imperial=28 - 34, metric=71 - 86), id=5, lifeSpan=null, name=Akbash Dog, origin=, temperament=Loyal, Independent... Resultado
  48. • Glide é uma biblioteca de carregamento de imagens para

    Android • Rápida e eficiente • Código aberto, disponível no GitHub Glide https://github.com/bumptech/glide
  49. • Descarrega, descodifica e mostra ◦ Imagens ◦ GIF’s •

    Permite também redimensionar imagens • Cache automática e simplificada das imagens • Expõe uma API flexível e simples de utilizar Vantagens
  50. • Permitir uma transição suave e rápido em qualquer tipo

    de lista de imagens • Eficiente a carregar, redimensionar e descarregar imagens facilmente Objetivos
  51. • DiskCacheStrategy.ALL ◦ Faz cache de tudo • DiskCacheStrategy.AUTOMATIC ◦

    Tenta escolher a melhor estratégia de forma inteligente de acordo com a origem • DiskCacheStrategy.DATA ◦ Faz cache da imagem original no disco antes de a descodificar • DiskCacheStrategy.NONE ◦ Não faz cache de nada • DiskCacheStrategy.RESOURCE ◦ Faz cache da imagem depois de algum processamento Estratégia de cache
  52. // Em Activities Glide.with(activity) // Em Fragments Glide.with(fragment) // Ou

    com o Context Glide.with(context) Como utilizar? No código Glide.with( ) .load(url) .into(imageView)
  53. • Versão melhorada e otimizada do seu antecessor - Paging

    2 • Suporte direto para Kotlin e Flow • Permite carregamentos assíncronos • Muito mais simples de integrar • Facilidade de adicionar um item de carregamento ◦ Que indica o utilizador que novos dados estão a ser descarregados Paging 3
  54. class DogPagingSource(private val repository: Repository) : PagingSource<Int, Breed>() { override

    suspend fun load(params: LoadParams<Int>): LoadResult<Int, Breed> { val nextPage = params.key ?: 1 val breeds = repository.getPagedBreedsList(nextPage) return LoadResult.Page( data = breeds, prevKey = if (nextPage == 1) null else nextPage -1, nextKey = nextPage + 1 ) } } Como utilizar? PagingSource DogsPagingSource.kt
  55. class DogPagingSource(private val repository: Repository) : PagingSource<Int, Breed>() { override

    suspend fun load(params: LoadParams<Int>): LoadResult<Int, Breed> { val nextPage = params.key ?: 1 val breeds = repository.getPagedBreedsList(nextPage) return LoadResult.Page( data = breeds, prevKey = if (nextPage == 1) null else nextPage -1, nextKey = nextPage + 1 ) } } Como utilizar? PagingSource DogsPagingSource.kt
  56. class DogPagingSource(private val repository: Repository) : PagingSource<Int, Breed>() { override

    suspend fun load(params: LoadParams<Int>): LoadResult<Int, Breed> { val nextPage = params.key ?: 1 val breeds = repository.getPagedBreedsList(nextPage) return LoadResult.Page( data = breeds, prevKey = if (nextPage == 1) null else nextPage -1, nextKey = nextPage + 1 ) } } Como utilizar? PagingSource DogsPagingSource.kt
  57. class ListViewModel: ViewModel() { private val repository = Repository() val

    breedsListByPage = Pager(PagingConfig(pageSize = 20), pagingSourceFactory = { DogPagingSource(repository) }).flow } Como utilizar? ViewModel ListViewModel.kt
  58. class ListViewModel: ViewModel() { private val repository = Repository() val

    breedsListByPage = Pager(PagingConfig(pageSize = 20), pagingSourceFactory = { DogPagingSource(repository) }).flow } Como utilizar? ViewModel ListViewModel.kt
  59. class ListViewModel: ViewModel() { private val repository = Repository() val

    breedsListByPage = Pager(PagingConfig(pageSize = 20), pagingSourceFactory = { DogPagingSource(repository) }).flow } Como utilizar? ViewModel ListViewModel.kt
  60. class ListViewModel: ViewModel() { private val repository = Repository() val

    breedsListByPage = Pager(PagingConfig(pageSize = 20), pagingSourceFactory = { DogPagingSource(repository) }).flow } Como utilizar? ViewModel ListViewModel.kt
  61. class ListActivity : AppCompatActivity() { val viewModel by viewModels<ListViewModel>() override

    fun onCreate(savedInstanceState: Bundle?) { ... lifecycleScope.launch{ viewModel.breedsListByPage.collect { val adapter = findViewById<RecyclerView>(R.id.rv_breeds).adapter as BreedsAdapter adapter.submitData(it) } } } } Como utilizar? Activity ListActivity.kt
  62. class ListActivity : AppCompatActivity() { val viewModel by viewModels<ListViewModel>() override

    fun onCreate(savedInstanceState: Bundle?) { ... lifecycleScope.launch{ viewModel.breedsListByPage.collect { val adapter = findViewById<RecyclerView>(R.id.rv_breeds).adapter as BreedsAdapter adapter.submitData(it) } } } } Como utilizar? Activity ListActivity.kt
  63. class ListActivity : AppCompatActivity() { val viewModel by viewModels<ListViewModel>() override

    fun onCreate(savedInstanceState: Bundle?) { ... lifecycleScope.launch{ viewModel.breedsListByPage.collect { val adapter = findViewById<RecyclerView>(R.id.rv_breeds).adapter as BreedsAdapter adapter.submitData(it) } } } } Como utilizar? Activity ListActivity.kt
  64. class BreedsAdapter() : ListAdapter<Breed, BreedsAdapter.BreedsViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup,

    viewType: Int): BreedsViewHolder { val inflater = LayoutInflater.from(parent.context) return BreedsViewHolder(inflater.inflate(R.layout.item_breed, parent, false)) } override fun onBindViewHolder(holder: BreedsViewHolder, position: Int) { val breed = getItem(position) holder.breed.text = breed!!.name } } Como utilizar? Adapter BreedsAdapter.kt
  65. class BreedsAdapter() : ListAdapter<Breed, BreedsAdapter.BreedsViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup,

    viewType: Int): BreedsViewHolder { val inflater = LayoutInflater.from(parent.context) return BreedsViewHolder(inflater.inflate(R.layout.item_breed, parent, false)) } override fun onBindViewHolder(holder: BreedsViewHolder, position: Int) { val breed = getItem(position) holder.breed.text = breed!!.name } } Como utilizar? Adapter BreedsAdapter.kt
  66. class BreedsAdapter() : PagingDataAdapter<Breed, BreedsAdapter.BreedsViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup,

    viewType: Int): BreedsViewHolder { val inflater = LayoutInflater.from(parent.context) return BreedsViewHolder(inflater.inflate(R.layout.item_breed, parent, false)) } override fun onBindViewHolder(holder: BreedsViewHolder, position: Int) { val breed = getItem(position) holder.breed.text = breed!!.name } } Como utilizar? Adapter BreedsAdapter.kt
  67. class BreedsAdapter() : PagingDataAdapter<Breed, BreedsAdapter.BreedsViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup,

    viewType: Int): BreedsViewHolder { val inflater = LayoutInflater.from(parent.context) return BreedsViewHolder(inflater.inflate(R.layout.item_breed, parent, false)) } override fun onBindViewHolder(holder: BreedsViewHolder, position: Int) { val breed = getItem(position) holder.breed.text = breed!!.name } } Como utilizar? Adapter BreedsAdapter.kt
  68. class BreedsAdapter() : PagingDataAdapter<Breed, BreedsAdapter.BreedsViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup,

    viewType: Int): BreedsViewHolder { val inflater = LayoutInflater.from(parent.context) return BreedsViewHolder(inflater.inflate(R.layout.item_breed, parent, false)) } override fun onBindViewHolder(holder: BreedsViewHolder, position: Int) { val breed = getItem(position) holder.breed.text = breed!!.name } } Como utilizar? Adapter BreedsAdapter.kt
  69. object Singleton { } Singleton ☝ public class Singleton {

    private static Singleton instance; private Singleton(){} public static Singleton getInstance(){ if(instance == null){ instance = new Singleton(); } return instance; } }
  70. interface Kennel { fun getDog(name: String) } val kennel =

    object : Kennel { override fun getDog(name: String) {...} } SAM (Single Abstract Method)
  71. interface Kennel { fun getDog(name: String) } val kennel =

    object : Kennel { override fun getDog(name: String) {...} } val kennel = Kennel { name -> ...} SAM (Single Abstract Method)
  72. Aplicar uma máscara sobre uma view ⚪ <?xml version="1.0" encoding="utf-8"?>

    <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle"> <solid android:color="@color/colorPrimary" /> <corners android:topRightRadius="25dp" android:topLeftRadius="25dp" android:bottomRightRadius="25dp" android:bottomLeftRadius="25dp"/> </shape>
  73. Aplicar uma máscara sobre uma view ⚪ <?xml version="1.0" encoding="utf-8"?>

    <shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval" />
  74. Dividir o ecrã Para ativar: 1. Botão direito sobre a

    tab que queremos dividir 2. “Split Vertically”
  75. Android Studio ⌨ Mover linhas de código - Bloco ctrl

    shift + ⬆ + https://developer.android.com/studio/intro/keyboard-shortcuts ⬇
  76. Android Studio ⌨ Mover linhas de código - Bloco cmd

    shift + ⬆ + https://developer.android.com/studio/intro/keyboard-shortcuts ⬇
  77. Android Studio ⌨ Mover linhas de código - Bloco cmd

    shift + ⬆ + https://developer.android.com/studio/intro/keyboard-shortcuts ⬇
  78. Android Studio ⌨ Mover linhas de código - Bloco cmd

    shift + ⬆ + https://developer.android.com/studio/intro/keyboard-shortcuts ⬇
  79. Trabalho para casa • Implementar a vista de detalhes para

    um cão ◦ Nome ◦ Imagem ◦ Detalhes
  80. Trabalho para casa • Carregar num dos itens da RecyclerView

    ◦ Abre a vista de detalhes para aquele cão