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

Android Training Program - Portugal, Aula 6

9e9eb825c69d719f2d3c32bdd3bc971e?s=47 ATP Portugal
November 18, 2020

Android Training Program - Portugal, Aula 6

Aula #6: Jetpack, Jetpack, Jetpack! 🚀

Ao longo das aulas temos vindo a utilizar algumas das bibliotecas do Android Jetpack, nesta aula vamos focar-nos essencialmente em três:
- Room
- CameraX
- Introdução a Compose

9e9eb825c69d719f2d3c32bdd3bc971e?s=128

ATP Portugal

November 18, 2020
Tweet

Transcript

  1. Android Training Program PORTUGAL Aula #6 Jetpack, jetpack, jetpack

  2. • 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
  3. Andres-Leonardo Martinez-Ortiz Google Carlos Mota Formador Renato Almeida Formador @davilagrau

    @cafonsomota @tallnato Equipa Daniela Ferreira Gestora de comunidades
  4. • 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
  5. #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 ✅ ✅
  6. Sumário Photo by Mika Baumeister on Unsplash • Resumo da

    aula anterior • Jetpack • Room • CameraX • Compose • Kotlin para principiantes • Sexta-Feira negra
  7. http://events.withgoogle.com/atp2020 ✉ atp-suporte@googlegroups.com http://bit.ly/atp2020-youtube http://bit.ly/atp2020-discord Links

  8. http://bit.ly/atp2020-live

  9. http://bit.ly/atp2020-codelabs

  10. http://bit.ly/kahoot-aula6

  11. Resumo da Aula #5

  12. • É 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
  13. RecyclerView Activity RecyclerView LayoutManager Adapter ViewHolder data RecyclerView LayoutManager Adapter

    ViewHolder data Adapter ViewHolder data
  14. None
  15. • 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
  16. The Dog API

  17. 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 )
  18. • Biblioteca de conversão de JSON para Kotlin e Java

    • Optimizada para Android • Compatível Retrofit Moshi
  19. • Glide é uma biblioteca de carregamento de imagens para

    Android • 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 https://github.com/bumptech/glide
  20. None
  21. Aula #6

  22. Jetpack

  23. https://developer.android.com/jetpack

  24. Seguir boas práticas Construídas com base em boas práticas de

    design modernas, as bibliotecas do Android Jetpack permitem menos crashes e memory leaks, com compatibilidade com versões anteriores já incluídas Porquê usar Android Jetpack?
  25. Seguir boas práticas Construídas com base em boas práticas de

    design modernas, as bibliotecas do Android Jetpack permitem menos crashes e memory leaks, com compatibilidade com versões anteriores já incluídas Porquê usar Android Jetpack? Eliminar código boilerplate O Android Jetpack gere aquelas atividades chatas, como processamento em background, navegação e gestão do ciclo de vida da aplicação, para que os programadores se possam concentrar no que torna a aplicação espetacular.
  26. Seguir boas práticas Construídas com base em boas práticas de

    design modernas, as bibliotecas do Android Jetpack permitem menos crashes e memory leaks, com compatibilidade com versões anteriores já incluídas Porquê usar Android Jetpack? Eliminar código boilerplate O Android Jetpack gere aquelas atividades chatas, como processamento em background, navegação e gestão do ciclo de vida da aplicação, para que os programadores se possam concentrar no que torna a aplicação espetacular. Reduzir a fragmentação Reduzir a complexidade através de bibliotecas que funcionam de forma consistente nas várias versões e dispositivos de Android.
  27. Menos código == Menos bugs - Alguém

  28. ViewModel Paging 3 LiveData

  29. LiveData ViewModel Paging 3 Compose Room Camera X

  30. Testemunhos ‍⚖

  31. “Com os Componentes de Arquitetura do Android, estamos a re-arquitetar

    toda a nossa aplicação. É ótimo existir uma forma recomenda, opinativa, e clara de construir uma aplicação Android que facilita suportar alteração de configurações” - Drew Hannay, Engenheiro de Software, LinkedIn https://developer.android.com/jetpack/testimonials LinkedIn Testemunhos
  32. “Nós utilizamos dezenas de diferentes tecnologias, e o ‘Room’ foi

    uma grande melhoria. A ênfase na testabilidade é enorme. - Andy Lawton, responsável pela plataforma Android no Tinder https://developer.android.com/jetpack/testimonials Tinder Testemunhos
  33. Room

  34. • Camada de abstração sobre SQLite (base de dados) •

    Reduz a quantidade de código repetitivo • Acesso à base de dados de forma mais robusta • Valida as consultas em tempo de compilação • Utilizado como cache da aplicação • Compatível com LiveData Room https://developer.android.com/topic/libraries/architecture/room
  35. Como utilizar? Importar a biblioteca Room Resto da aplicação Data

    Access Object Entidades Obter os DAOs Obter as entidades da BD Persistir os dados para a BD get / set valores
  36. plugins { … id 'kotlin-kapt' } dependencies { ... def

    room_version = "2.2.5" implementation "androidx.room:room-runtime:$room_version" kapt "androidx.room:room-compiler:$room_version" // opcional - Kotlin Extensions e suporte para Coroutines implementation "androidx.room:room-ktx:$room_version" } Como utilizar? Importar a biblioteca app/build.gradle
  37. @Entity(tableName = "Dog") class DogModel( @PrimaryKey(autoGenerate = false) var id:

    Int, @ColumnInfo(name = "bredFor") val bredFor: String, @ColumnInfo(name = "breedGroup") val breedGroup: String, @ColumnInfo(name = "lifeSpan") val lifeSpan: String, @ColumnInfo(name = "name") val name: String, @ColumnInfo(name = "origin") val origin: String, @ColumnInfo(name = "temperament") val temperament: String ) Como utilizar? Definir a entidade DogModel.kt
  38. @Entity(tableName = "Dog") class DogModel( @PrimaryKey(autoGenerate = false) var id:

    Int, @ColumnInfo(name = "bredFor") val bredFor: String, @ColumnInfo(name = "breedGroup") val breedGroup: String, @ColumnInfo(name = "lifeSpan") val lifeSpan: String, @ColumnInfo(name = "name") val name: String, @ColumnInfo(name = "origin") val origin: String, @ColumnInfo(name = "temperament") val temperament: String ) Como utilizar? Definir a entidade DogModel.kt
  39. @Entity(tableName = "Dog") class DogModel( @PrimaryKey(autoGenerate = false) var id:

    Int, @ColumnInfo(name = "bredFor") val bredFor: String, @ColumnInfo(name = "breedGroup") val breedGroup: String, @ColumnInfo(name = "lifeSpan") val lifeSpan: String, @ColumnInfo(name = "name") val name: String, @ColumnInfo(name = "origin") val origin: String, @ColumnInfo(name = "temperament") val temperament: String ) Como utilizar? Definir a entidade DogModel.kt
  40. @Entity(tableName = "Dog") class DogModel( @PrimaryKey(autoGenerate = false) var id:

    Int, @ColumnInfo(name = "bredFor") val bredFor: String, @ColumnInfo(name = "breedGroup") val breedGroup: String, @ColumnInfo(name = "lifeSpan") val lifeSpan: String, @ColumnInfo(name = "name") val name: String, @ColumnInfo(name = "origin") val origin: String, @ColumnInfo(name = "temperament") val temperament: String ) Como utilizar? Definir a entidade DogModel.kt
  41. @Dao interface DogDAO { @Query("SELECT * FROM dog ORDER BY

    name ASC") fun getAlphabetizedDogs(): LiveData<List<DogModel>> @Insert(onConflict = OnConflictStrategy.IGNORE) fun insert(dog: Dog) @Query("DELETE FROM dog") fun deleteAll() } Como utilizar? Definir o DAO (Data access object) DogDAO.kt
  42. @Dao interface DogDAO { @Query("SELECT * FROM dog ORDER BY

    name ASC") fun getAlphabetizedDogs(): LiveData<List<DogModel>> @Insert(onConflict = OnConflictStrategy.IGNORE) fun insert(dog: DogModel) @Query("DELETE FROM dog") fun deleteAll() } Como utilizar? Definir o DAO (Data access object) DogDAO.kt
  43. @Dao interface DogDAO { @Query("SELECT * FROM dog ORDER BY

    name ASC") fun getAlphabetizedDogs(): LiveData<List<DogModel>> @Insert(onConflict = OnConflictStrategy.IGNORE) fun insert(dog: DogModel) @Query("DELETE FROM dog") fun deleteAll() } Como utilizar? Definir o DAO (Data access object) DogDAO.kt
  44. @Dao interface DogDAO { @Query("SELECT * FROM dog ORDER BY

    name ASC") fun getAlphabetizedDogs(): LiveData<List<DogModel>> @Insert(onConflict = OnConflictStrategy.IGNORE) fun insert(dog: DogModel) @Query("DELETE FROM dog") fun deleteAll() } Como utilizar? Definir o DAO (Data access object) DogDAO.kt
  45. @Dao interface DogDAO { @Query("SELECT * FROM dog ORDER BY

    name ASC") fun getAlphabetizedDogs(): LiveData<List<DogModel>> @Insert(onConflict = OnConflictStrategy.IGNORE) fun insert(dog: Dog) @Query("DELETE FROM dog") fun deleteAll() } Como utilizar? Definir o DAO (Data access object) DogDAO.kt
  46. @Database(entities = [DogModel::class], version = 1, exportSchema = false) abstract

    class KennelDatabase : RoomDatabase() { abstract fun dogDao(): DogDAO companion object { @Volatile private var INSTANCE: KennelDatabase? = null fun getDatabase(context: Context): KennelDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext,KennelDatabase::class.java, "dog_database" ).build() INSTANCE = instance instance } } val databaseWriteExecutor = Executors.newFixedThreadPool(2) } } Como utilizar? A base de dados KennelDatabase.kt
  47. @Database(entities = [DogModel::class], version = 1, exportSchema = false) abstract

    class KennelDatabase : RoomDatabase() { abstract fun dogDao(): DogDAO companion object { @Volatile private var INSTANCE: KennelDatabase? = null fun getDatabase(context: Context): KennelDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext,KennelDatabase::class.java, "dog_database" ).build() INSTANCE = instance instance } } val databaseWriteExecutor = Executors.newFixedThreadPool(2) } } Como utilizar? A base de dados KennelDatabase.kt
  48. @Database(entities = [DogModel::class], version = 1, exportSchema = false) abstract

    class KennelDatabase : RoomDatabase() { abstract fun dogDao(): DogDAO companion object { @Volatile private var INSTANCE: KennelDatabase? = null fun getDatabase(context: Context): KennelDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext,KennelDatabase::class.java, "dog_database" ).build() INSTANCE = instance instance } } val databaseWriteExecutor = Executors.newFixedThreadPool(2) } } Como utilizar? A base de dados KennelDatabase.kt
  49. @Database(entities = [DogModel::class], version = 1, exportSchema = false) abstract

    class KennelDatabase : RoomDatabase() { abstract fun dogDao(): DogDAO companion object { @Volatile private var INSTANCE: KennelDatabase? = null fun getDatabase(context: Context): KennelDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext,KennelDatabase::class.java, "dog_database" ).build() INSTANCE = instance instance } } val databaseWriteExecutor = Executors.newFixedThreadPool(2) } } Como utilizar? A base de dados KennelDatabase.kt
  50. @Database(entities = [DogModel::class], version = 1, exportSchema = false) abstract

    class KennelDatabase : RoomDatabase() { abstract fun dogDao(): DogDAO companion object { @Volatile private var INSTANCE: KennelDatabase? = null fun getDatabase(context: Context): KennelDatabase { return INSTANCE ?: synchronized(this) { val instance = Room.databaseBuilder( context.applicationContext,KennelDatabase::class.java, "dog_database" ).build() INSTANCE = instance instance } } val databaseWriteExecutor = Executors.newFixedThreadPool(2) } } Como utilizar? A base de dados KennelDatabase.kt
  51. class DogRepository(private val dogDao: DogDAO) { val allDogs: LiveData<List<DogModel>> =

    dogDao.getAlphabetizedDogs() fun insert(dog: DogModel) { KennelDatabase.databaseWriteExecutor .execute { dogDao.insert(dog) } } } Como utilizar? Repositório DogRepository.kt
  52. class DogRepository(private val dogDao: DogDAO) { val allDogs: LiveData<List<DogModel>> =

    dogDao.getAlphabetizedDogs() fun insert(dog: DogModel) { KennelDatabase.databaseWriteExecutor .execute { dogDao.insert(dog) } } } Como utilizar? Repositório DogRepository.kt
  53. class DogRepository(private val dogDao: DogDAO) { val allDogs: LiveData<List<DogModel>> =

    dogDao.getAlphabetizedDogs() fun insert(dog: DogModel) { KennelDatabase.databaseWriteExecutor .execute { dogDao.insert(dog) } } } Como utilizar? Repositório DogRepository.kt
  54. class MainViewModel( private val repository: DogRepository ) : ViewModel() {

    ... fun getDogsFromDatabase(): LiveData<List<DogModel>> { return repository.allDogs } fun addDog(dog : Dog) { repository.insert(toDogModel(dog)) } } Como utilizar? No ViewModel MainViewModel.kt
  55. class MainViewModel( private val repository: DogRepository ) : ViewModel() {

    ... fun getDogsFromDatabase(): LiveData<List<DogModel>> { return repository.allDogs } fun addDog(dog : Dog) { repository.insert(toDogModel(dog)) } } Como utilizar? No ViewModel MainViewModel.kt
  56. class MainViewModel( private val repository: DogRepository ) : ViewModel() {

    ... fun getDogsFromDatabase(): LiveData<List<DogModel>> { return repository.allDogs } fun addDog(dog : Dog) { repository.insert(toDogModel(dog)) } } Como utilizar? No ViewModel MainViewModel.kt
  57. class MainViewModelFactory( private val repository: DogRepository ) : ViewModelProvider.Factory {

    override fun <T : ViewModel> create(modelClass: Class<T>): T { if (modelClass.isAssignableFrom(MainViewModel::class.java)) { @Suppress("UNCHECKED_CAST") return MainViewModel(repository) as T } throw IllegalArgumentException("Unknown ViewModel class") } } Como utilizar? No ViewModelProvider MainViewModel.kt
  58. class BobiApplication: Application() { val database by lazy { KennelDatabase.getDatabase(this)

    } val repository by lazy { DogRepository(database.dogDao()) } } Como utilizar? O Application BobiApplication.kt
  59. <manifest ...> ... <application android:name=".BobiApplication" ...> ... </application> </manifest> Como

    utilizar? O Application no AndroidManifest AndroidManifest.xml
  60. class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by

    viewModels() ... } Como utilizar? Na Activity MainActivity.kt
  61. class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by

    viewModels { MainViewModelFactory((application as BobiApplication).repository) } ... } Como utilizar? Na Activity MainActivity.kt
  62. class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by

    viewModels { MainViewModelFactory((application as BobiApplication).repository) } fun showDogs() { viewModel.getDogsDatabase().observe(this){ Log.d(TAG, "$it") } } ... } Como utilizar? Na Activity MainActivity.kt
  63. CameraX

  64. CameraXβ Sistema de add-ons Consistência em todos os diapositivos Funciona

    com 94% dos Android 5.0+
  65. CameraXβ Captura Análise de imagem Pré-visualização Casos de uso https://developer.android.com/training/camerax

  66. Em vez de chamarmos a câmera nativa Câmera private fun

    openNativeCamera() { val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) startActivityForResult(intent, REQUEST_IMAGE_CAPTURE) }
  67. Em vez de chamarmos a câmera nativa Câmera private fun

    openNativeCamera() { val intent = Intent(MediaStore.ACTION_IMAGE_CAPTURE) startActivityForResult(intent, REQUEST_IMAGE_CAPTURE) } Passamos a criar tudo de raiz...
  68. Câmera package pt.atp.bobi.presentation.ui private const val TAG = "CameraActivity" private

    const val REQUEST_CAMERA = 0 private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA) class CameraActivity : AppCompatActivity() { private lateinit var imageCapture: ImageCapture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) if (allPermissionsGranted()) { startCamera() } else { ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CAMERA) } findViewById<Button>(R.id.btn_camera).setOnClickListener { takePhoto() } } private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { ContextCompat.checkSelfPermission( baseContext, it) == PackageManager.PERMISSION_GRANTED } private fun getOutputDirectory(): File { val mediaDir = externalMediaDirs.firstOrNull()?.let { File(it, resources.getString(R.string.app_name)).apply { mkdirs() } } return if (mediaDir != null && mediaDir.exists()) mediaDir else filesDir } private fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this) cameraProvider.addListener( { val viewFinder = findViewById<PreviewView>(R.id.viewFinder) val preview = Preview.Builder() .build() .also { it.setSurfaceProvider(viewFinder.surfaceProvider) } val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA imageCapture = ImageCapture.Builder() .setTargetRotation(viewFinder.display.rotation) .build() try { cameraProvider.get().unbindAll() cameraProvider.get().bindToLifecycle(this, cameraSelector, preview, imageCapture) } catch (e: Exception) { Log.e(TAG, "Use case binding failed. Error:$e") } }, ContextCompat.getMainExecutor(this)) } private fun takePhoto() { val photoFile = File(getOutputDirectory(), "${System.currentTimeMillis()}.jpg") val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { val savedUri = Uri.fromFile(photoFile) val msg = "Photo capture succeeded: $savedUri" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } override fun onError(exception: ImageCaptureException) { Log.e(TAG, "Photo capture failed: ${exception.message}", exception) } }) } }
  69. Câmera package pt.atp.bobi.presentation.ui private const val TAG = "CameraActivity" private

    const val REQUEST_CAMERA = 0 private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA) class CameraActivity : AppCompatActivity() { private lateinit var imageCapture: ImageCapture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) if (allPermissionsGranted()) { startCamera() } else { ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CAMERA) } findViewById<Button>(R.id.btn_camera).setOnClickListener { takePhoto() } } private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { ContextCompat.checkSelfPermission( baseContext, it) == PackageManager.PERMISSION_GRANTED } private fun getOutputDirectory(): File { val mediaDir = externalMediaDirs.firstOrNull()?.let { File(it, resources.getString(R.string.app_name)).apply { mkdirs() } } return if (mediaDir != null && mediaDir.exists()) mediaDir else filesDir } private fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this) cameraProvider.addListener( { val viewFinder = findViewById<PreviewView>(R.id.viewFinder) val preview = Preview.Builder() .build() .also { it.setSurfaceProvider(viewFinder.surfaceProvider) } val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA imageCapture = ImageCapture.Builder() .setTargetRotation(viewFinder.display.rotation) .build() try { cameraProvider.get().unbindAll() cameraProvider.get().bindToLifecycle(this, cameraSelector, preview, imageCapture) } catch (e: Exception) { Log.e(TAG, "Use case binding failed. Error:$e") } }, ContextCompat.getMainExecutor(this)) } private fun takePhoto() { val photoFile = File(getOutputDirectory(), "${System.currentTimeMillis()}.jpg") val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { val savedUri = Uri.fromFile(photoFile) val msg = "Photo capture succeeded: $savedUri" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } override fun onError(exception: ImageCaptureException) { Log.e(TAG, "Photo capture failed: ${exception.message}", exception) } }) } } ‍♀
  70. Câmera package pt.atp.bobi.presentation.ui private const val TAG = "CameraActivity" private

    const val REQUEST_CAMERA = 0 private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA) class CameraActivity : AppCompatActivity() { private lateinit var imageCapture: ImageCapture override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) if (allPermissionsGranted()) { startCamera() } else { ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CAMERA) } findViewById<Button>(R.id.btn_camera).setOnClickListener { takePhoto() } } private fun allPermissionsGranted() = REQUIRED_PERMISSIONS.all { ContextCompat.checkSelfPermission( baseContext, it) == PackageManager.PERMISSION_GRANTED } private fun getOutputDirectory(): File { val mediaDir = externalMediaDirs.firstOrNull()?.let { File(it, resources.getString(R.string.app_name)).apply { mkdirs() } } return if (mediaDir != null && mediaDir.exists()) mediaDir else filesDir } private fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this) cameraProvider.addListener( { val viewFinder = findViewById<PreviewView>(R.id.viewFinder) val preview = Preview.Builder() .build() .also { it.setSurfaceProvider(viewFinder.surfaceProvider) } val cameraSelector = CameraSelector.DEFAULT_BACK_CAMERA imageCapture = ImageCapture.Builder() .setTargetRotation(viewFinder.display.rotation) .build() try { cameraProvider.get().unbindAll() cameraProvider.get().bindToLifecycle(this, cameraSelector, preview, imageCapture) } catch (e: Exception) { Log.e(TAG, "Use case binding failed. Error:$e") } }, ContextCompat.getMainExecutor(this)) } private fun takePhoto() { val photoFile = File(getOutputDirectory(), "${System.currentTimeMillis()}.jpg") val outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { val savedUri = Uri.fromFile(photoFile) val msg = "Photo capture succeeded: $savedUri" Toast.makeText(baseContext, msg, Toast.LENGTH_SHORT).show() Log.d(TAG, msg) } override fun onError(exception: ImageCaptureException) { Log.e(TAG, "Photo capture failed: ${exception.message}", exception) } }) } } • Fonte: 2pts • 118 linhas de código • Mostra o que a câmera está a gravar • Permite tirar fotos
  71. plugins { id 'com.android.application' id 'kotlin-android' id 'kotlin-android-extensions' } ...

    dependencies { ... implementation "androidx.camera:camera-camera2:1.0.0-beta12" implementation "androidx.camera:camera-lifecycle:1.0.0-beta12" implementation "androidx.camera:camera-view:1.0.0-alpha19" } Como utilizar? Importar as bibliotecas app/build.gradle
  72. class CameraActivity : AppCompatActivity() { ... } <activity android:name=".presentation.ui.CameraActivity" />

    Como utilizar? Criar uma nova Activity AndroidManifest.xml CameraActivity.kt
  73. <uses-feature android:name="android.hardware.camera.any" /> <uses-permission android:name="android.permission.CAMERA" /> Como utilizar? Declarar a

    permissão de câmera AndroidManifest.xml
  74. <uses-feature android:name="android.hardware.camera.any" /> <uses-permission android:name="android.permission.CAMERA" /> Como utilizar? Declarar a

    permissão de câmera AndroidManifest.xml
  75. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) if (ContextCompat.checkSelfPermission( baseContext,

    Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { startCamera() } else { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA) } } Como utilizar? Pedir a permissão de câmera CameraActivity.kt
  76. override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_camera) if (ContextCompat.checkSelfPermission( baseContext,

    Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { startCamera() } else { ActivityCompat.requestPermissions( this, arrayOf(Manifest.permission.CAMERA), REQUEST_CAMERA) } } Como utilizar? Pedir a permissão de câmera CameraActivity.kt
  77. override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray)

    { if (requestCode == REQUEST_CAMERA) { if (ContextCompat.checkSelfPermission( baseContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { startCamera() } else { Toast.makeText(this, "Permission not granted.", Toast.LENGTH_SHORT).show() } } Como utilizar? Pedir a permissão de câmera CameraActivity.kt
  78. override fun onRequestPermissionsResult( requestCode: Int, permissions: Array<out String>, grantResults: IntArray)

    { if (requestCode == REQUEST_CAMERA) { if (ContextCompat.checkSelfPermission( baseContext, Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED) { startCamera() } else { Toast.makeText(this, "Permission not granted.", Toast.LENGTH_SHORT).show() } } Como utilizar? Pedir a permissão de câmera CameraActivity.kt
  79. <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout ... <ImageButton android:id="@+id/camera_capture_button" ... <androidx.camera.view.PreviewView android:id="@+id/viewFinder"

    android:layout_width="match_parent" android:layout_height="match_parent" /> </androidx.constraintlayout.widget.ConstraintLayout> Como utilizar? Definir o layout activity_camera.kt
  80. <?xml version="1.0" encoding="utf-8"?> <androidx.constraintlayout.widget.ConstraintLayout ... <ImageButton android:id="@+id/camera_capture_button" ... <androidx.camera.view.PreviewView android:id="@+id/viewFinder"

    android:layout_width="match_parent" android:layout_height="match_parent" /> </androidx.constraintlayout.widget.ConstraintLayout> Como utilizar? Definir o layout activity_camera.kt
  81. private fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this) cameraProvider.addListener( {

    val preview = Preview.Builder().build().also { it.setSurfaceProvider(findViewById<PreviewView>(R.id.viewFinder).surfaceProvider) } imageCapture = ImageCapture.Builder().build() cameraProvider.get().unbindAll() cameraProvider.get().bindToLifecycle(this, DEFAULT_BACK_CAMERA, preview, imageCapture) }, ContextCompat.getMainExecutor(this)) } Como utilizar? Inicializar a câmera CameraActivity.kt
  82. private fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this) cameraProvider.addListener( {

    val preview = Preview.Builder().build().also { it.setSurfaceProvider(findViewById<PreviewView>(R.id.viewFinder).surfaceProvider) } imageCapture = ImageCapture.Builder().build() cameraProvider.get().unbindAll() cameraProvider.get().bindToLifecycle(this, DEFAULT_BACK_CAMERA, preview, imageCapture) }, ContextCompat.getMainExecutor(this)) } Como utilizar? Inicializar a câmera CameraActivity.kt ‍ Executado quando a câmera fica disponível
  83. private fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this) cameraProvider.addListener( {

    val preview = Preview.Builder().build().also { it.setSurfaceProvider(findViewById<PreviewView>(R.id.viewFinder).surfaceProvider) } imageCapture = ImageCapture.Builder().build() cameraProvider.get().unbindAll() cameraProvider.get().bindToLifecycle(this, DEFAULT_BACK_CAMERA, preview, imageCapture) }, ContextCompat.getMainExecutor(this)) } Como utilizar? Inicializar a câmera CameraActivity.kt
  84. private fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this) cameraProvider.addListener( {

    val preview = Preview.Builder().build().also { it.setSurfaceProvider(findViewById<PreviewView>(R.id.viewFinder).surfaceProvider) } imageCapture = ImageCapture.Builder().build() cameraProvider.get().unbindAll() cameraProvider.get().bindToLifecycle(this, DEFAULT_BACK_CAMERA, preview, imageCapture) }, ContextCompat.getMainExecutor(this)) } Como utilizar? Inicializar a câmera CameraActivity.kt
  85. private fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this) cameraProvider.addListener( {

    val preview = Preview.Builder().build().also { it.setSurfaceProvider(findViewById<PreviewView>(R.id.viewFinder).surfaceProvider) } imageCapture = ImageCapture.Builder().build() cameraProvider.get().unbindAll() cameraProvider.get().bindToLifecycle(this, DEFAULT_BACK_CAMERA, preview, imageCapture) }, ContextCompat.getMainExecutor(this)) } Como utilizar? Inicializar a câmera CameraActivity.kt
  86. override fun onCreate(savedInstanceState: Bundle?) { ... findViewById<Button>(R.id.camera_capture_button).setOnClickListener { takePhoto() }

    } Como utilizar? Tirar uma foto CameraActivity.kt
  87. private fun takePhoto() { val photoFile = File(getOutputDirectory(), "${System.currentTimeMillis()}.jpg") val

    outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { Toast.makeText(baseContext, "Photo saved", Toast.LENGTH_SHORT).show() } override fun onError(exception: ImageCaptureException) {} }) } Como utilizar? Tirar uma foto CameraActivity.kt
  88. private fun takePhoto() { val photoFile = File(getOutputDirectory(), "${System.currentTimeMillis()}.jpg") val

    outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { Toast.makeText(baseContext, "Photo saved", Toast.LENGTH_SHORT).show() } override fun onError(exception: ImageCaptureException) {} }) } Como utilizar? Tirar uma foto CameraActivity.kt
  89. private fun takePhoto() { val photoFile = File(getOutputDirectory(), "${System.currentTimeMillis()}.jpg") val

    outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { Toast.makeText(baseContext, "Photo saved", Toast.LENGTH_SHORT).show() } override fun onError(exception: ImageCaptureException) {} }) } Como utilizar? Tirar uma foto CameraActivity.kt
  90. private fun takePhoto() { val photoFile = File(getOutputDirectory(), "${System.currentTimeMillis()}.jpg") val

    outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { Toast.makeText(baseContext, "Photo saved", Toast.LENGTH_SHORT).show() } override fun onError(exception: ImageCaptureException) {} }) } Como utilizar? Tirar uma foto CameraActivity.kt
  91. private fun takePhoto() { val photoFile = File(getOutputDirectory(), "${System.currentTimeMillis()}.jpg") val

    outputOptions = ImageCapture.OutputFileOptions.Builder(photoFile).build() imageCapture.takePicture(outputOptions, ContextCompat.getMainExecutor(this), object : ImageCapture.OnImageSavedCallback { override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) { Toast.makeText(baseContext, "Photo saved", Toast.LENGTH_SHORT).show() } override fun onError(exception: ImageCaptureException) {} }) } Como utilizar? Tirar uma foto CameraActivity.kt
  92. Compose

  93. Compose • Forma inovadora de criar layouts nativos (em Android)

    • Permite construir componentes gráficos de forma declarativa • Interoperável com os componentes nativos • Reduz a quantidade de código necessário para criar interfaces gráficas • Tira partido das vantagens de Kotlin para facilitar a escrita de código α
  94. ⚠ Compose ainda se encontra em alpha

  95. Android Studio 4.2 Canary 16 https://developer.android.com/studio/preview

  96. android { ... buildFeatures { compose true } kotlinOptions {

    useIR = true } } dependencies { ... implementation "androidx.compose.ui:ui:1.0.0-alpha07" implementation "androidx.compose.foundation:foundation:1.0.0-alpha07" implementation "androidx.ui:ui-tooling:1.0.0-alpha07" implementation "androidx.compose.material:material:1.0.0-alpha07" } Como utilizar? Importar a biblioteca app/build.gradle
  97. @Composable fun Example() { // O código vem para aqui

    } Como utilizar? Compose Example.kt
  98. @Composable fun Example() { // O código vem para aqui

    } @Composable @Preview fun DefaultExample() { Example() } Como utilizar? Compose Example.kt
  99. @Composable fun Example() { // O código vem para aqui

    } @Composable @Preview fun DefaultExample() { Example() } Como utilizar? Example.kt Compose
  100. @Composable fun Example() { Text( text = "Olá mundo! ",

    style = style = MaterialTheme.typography.h5, modifier = Modifier.padding(start = 16.dp) ) } Como utilizar? Example.kt Compose
  101. @Composable fun Example() { Text( text = "Olá mundo! ",

    style = TextStyle( fontSize = 19.sp, fontWeight = FontWeight.Normal, color = colorResource(id = R.color.white) ), modifier = Modifier.padding(start = 16.dp) ) } Como utilizar? Example.kt Compose
  102. <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding_start="16dp" android:text="Olá mundo! " android:textColor="@android:color/white" android:textSize="19sp"/> Como

    utilizar? example.xml !Compose
  103. <TextView android:layout_width="wrap_content" android:layout_height="wrap_content" android:padding_start="16dp" android:text="Olá mundo! " android:textColor="@android:color/white" android:textSize="19sp"/> Como

    utilizar? example.xml !Compose Text( text = "Olá mundo! ", style = TextStyle( fontSize = 19.sp, fontWeight = FontWeight.Normal, color = colorResource(id = R.color.white) ), modifier = Modifier.padding(start = 16.dp) ) Example.kt
  104. @Composable fun Example() { Text( text = "Olá mundo! ",

    style = TextStyle( fontSize = 19.sp, fontWeight = FontWeight.Normal, color = colorResource(id = R.color.white) ), modifier = Modifier.padding(start = 16.dp) ) Text( text = "Bem-vindos ao ATP!", style = TextStyle( fontSize = 19.sp, fontWeight = FontWeight.Normal, color = colorResource(id = R.color.grey) ), modifier = Modifier.padding(start = 16.dp) ) Como utilizar? Example.kt Compose
  105. @Composable fun Example() { Column { Text( text = "Olá

    mundo! ", style = TextStyle( fontSize = 19.sp, fontWeight = FontWeight.Normal, color = colorResource(id = R.color.white) ), modifier = Modifier.padding(start = 16.dp) ) Text( text = "Bem-vindos ao ATP!", style = TextStyle( fontSize = 19.sp, fontWeight = FontWeight.Normal, color = colorResource(id = R.color.grey) ), modifier = Modifier.padding(start = 16.dp) ) } } Como utilizar? Example.kt Compose
  106. @Composable fun Example() { Column { Image( asset = vectorResource(id

    = R.drawable.ic_paw) ) Text( text = "Olá mundo! ", style = TextStyle( fontSize = 19.sp, fontWeight = FontWeight.Normal, color = colorResource(id = R.color.white) ), modifier = Modifier.padding(start = 16.dp) ) Text( text = "Bem-vindos ao ATP!", style = TextStyle( fontSize = 19.sp, fontWeight = FontWeight.Normal, color = colorResource(id = R.color.grey) ), modifier = Modifier.padding(start = 16.dp) ) } } Como utilizar? Example.kt Compose
  107. @Composable fun Example() { Column { Image( asset = vectorResource(id

    = R.drawable.ic_paw), modifier = Modifier.width(32.dp).height(32.dp) ) Text( text = "Olá mundo! ", style = TextStyle( fontSize = 19.sp, fontWeight = FontWeight.Normal, color = colorResource(id = R.color.white) ), modifier = Modifier.padding(start = 16.dp) ) Text( text = "Bem-vindos ao ATP!", style = TextStyle( fontSize = 19.sp, fontWeight = FontWeight.Normal, color = colorResource(id = R.color.grey) ), modifier = Modifier.padding(start = 16.dp) ) } } Como utilizar? Example.kt Compose
  108. @Composable fun Example() { Row { Image( asset = vectorResource(id

    = R.drawable.ic_paw), modifier = Modifier.width(32.dp).height(32.dp) ) Column { Text( text = "Olá mundo! ", style = TextStyle( fontSize = 19.sp, fontWeight = FontWeight.Normal, color = colorResource(id = R.color.white) ), modifier = Modifier.padding(start = 16.dp) ) Text( text = "Bem-vindos ao ATP!", style = TextStyle( fontSize = 19.sp, fontWeight = FontWeight.Normal, color = colorRsource(id = R.color.grey) ), modifier = Modifier.padding(start = 16.dp) ) } } } Como utilizar? Example.kt Compose
  109. class ExampleActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_example) } } Como utilizar? ExampleActivity.kt Compose
  110. class ExampleActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContent { MaterialTheme { Column(modifier = Modifier.fillMaxWidth()) { TopAppBar(title = { Text(stringResource(id = R.string.example)) }) Example() } } } } } Como utilizar? ExampleActivity.kt Compose
  111. IADE Bruno Nobre Universidade Europeia Princípios de UX para Mobile

  112. Abre o Android Studio e vamos começar a programar ‍‍

  113. VS Ronda 5

  114. interface Printer { fun print() } interface Writer { fun

    write() } class PrintWriter( printer: Printer, writer: Writer ) : Printer by printer, Writer by writer {} Interface delegation
  115. class Dog(private val name: String) { init { println("Dog $name")

    } } Bloco init public class Dog { private String name; public Dog(String name) { this.name = name; System.out.println("Dog " + name); } }
  116. class Dog(private val name: String) { init { println("Dog $name")

    } init { println("Dog $name") } } Bloco init public class Dog { private String name; public Dog(String name) { this.name = name; System.out.println("Dog " + name); } }
  117. Sexta-Feira negra Photo by Xiaolong Wong on Unsplash

  118. ctrl shift + + v Histórico de conteúdo copiado ⌨

  119. cmd shift + + v Histórico de conteúdo copiado ⌨

  120. Histórico de conteúdo copiado ⌨ cmd shift + + v

  121. Mudar a fonte Para mudar a fonte do código: 1.

    Carregar em Android Studio na barra de ações 2. Preferences 3. Pesquisar por Font 4. Editor → General → Font 5. Alterar para a fonte pretendida
  122. Mudar a fonte Para mudar a fonte do código: 1.

    Carregar em Android Studio na barra de ações 2. Preferences 3. Pesquisar por Font 4. Appe... & Behavior → Appearance 5. Alterar para a fonte pretendida
  123. Mudar a fonte Para mudar a fonte do código: 1.

    Carregar em Android Studio na barra de ações 2. Preferences 3. Pesquisar por Font 4. Appe... & Behavior → Appearance 5. Alterar para a fonte pretendida
  124. Exportar como zip 1. Carregar em File na barra de

    ações 2. Export to Zip File... Nenhum dos ficheiros gerados é adicionado ao zip.
  125. None
  126. E se quisermos alterar o comportamento de sempre que abrimos

    um layout o modo design ser a opção predefinida? ‍
  127. Mudar o modo de visualização Para mudar a fonte do

    código: 1. Carregar em Android Studio na barra de ações 2. Preferences 3. Editor → Layout Editor 4. Other Resources
  128. scrcpy O teu telemóvel, no teu computador. • O ecrã

    do teu telemóvel no teu computador • Permite controlares o teu telemóvel remotamente • Funciona em GNU/Linux, Windows e macOS
  129. scrcpy O teu telemóvel, no teu computador. • O ecrã

    do teu telemóvel no teu computador • Permite controlares o teu telemóvel remotamente • Funciona em GNU/Linux, Windows e macOS apt install scrcpy //Linux choco install scrcpy //Windows brew install scrcpy //macOS https://github.com/genymobile/scrcpy
  130. Trabalho Para Casa ‍‍

  131. Trabalho para casa • Implementar um modo de navegação diferente

    ◦ BottomNavigationView ▪ Main (MainFragment.kt) ▪ Lista de raças (ListFragment.kt) ▪ Raças favoritas (FavoritesFragment.kt) ▪ About (AboutFragment.kt)
  132. • Implementar um novo ecrã - FavoritesFragment.kt • Contém todos

    as raças favoritas do utilizador • Permite adicionar/remover uma raça aos/dos favoritos Trabalho para casa
  133. Dúvidas?

  134. Continuamos a responder no discord

  135. Obrigado ‍♀

  136. Android Training Program PORTUGAL Aula #7 Firebase Próxima aula: 02

    de Dezembro