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

Android Training Program - Portugal, Aula 6

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

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 • Jetpack • Room • CameraX • Compose • Kotlin para principiantes • Sexta-Feira negra
  6. • É 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
  7. • 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
  8. 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 )
  9. • Biblioteca de conversão de JSON para Kotlin e Java

    • Optimizada para Android • Compatível Retrofit Moshi
  10. • 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
  11. 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?
  12. 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.
  13. 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.
  14. “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
  15. “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
  16. • 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
  17. 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
  18. 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
  19. @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
  20. @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
  21. @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
  22. @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
  23. @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
  24. @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
  25. @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
  26. @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
  27. @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
  28. @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
  29. @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
  30. @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
  31. @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
  32. @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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. class BobiApplication: Application() { val database by lazy { KennelDatabase.getDatabase(this)

    } val repository by lazy { DogRepository(database.dogDao()) } } Como utilizar? O Application BobiApplication.kt
  41. class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by

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

    viewModels { MainViewModelFactory((application as BobiApplication).repository) } ... } Como utilizar? Na Activity MainActivity.kt
  43. 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
  44. 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) }
  45. 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...
  46. 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) } }) } }
  47. 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) } }) } } ‍♀
  48. 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
  49. 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
  50. class CameraActivity : AppCompatActivity() { ... } <activity android:name=".presentation.ui.CameraActivity" />

    Como utilizar? Criar uma nova Activity AndroidManifest.xml CameraActivity.kt
  51. 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
  52. 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
  53. 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
  54. 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
  55. <?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
  56. <?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
  57. 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
  58. 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
  59. 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
  60. 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
  61. 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
  62. 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
  63. 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
  64. 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
  65. 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
  66. 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
  67. 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 α
  68. 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
  69. @Composable fun Example() { // O cĂłdigo vem para aqui

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

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

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

    style = style = MaterialTheme.typography.h5, modifier = Modifier.padding(start = 16.dp) ) } Como utilizar? Example.kt Compose
  73. @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
  74. <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
  75. @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
  76. @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
  77. @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
  78. @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
  79. @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
  80. class ExampleActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState) setContentView(R.layout.activity_example) } } Como utilizar? ExampleActivity.kt Compose
  81. 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
  82. interface Printer { fun print() } interface Writer { fun

    write() } class PrintWriter( printer: Printer, writer: Writer ) : Printer by printer, Writer by writer {} Interface delegation
  83. 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); } }
  84. 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); } }
  85. 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
  86. 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
  87. 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
  88. Exportar como zip 1. Carregar em File na barra de

    açþes 2. Export to Zip File... Nenhum dos ficheiros gerados Ê adicionado ao zip.
  89. E se quisermos alterar o comportamento de sempre que abrimos

    um layout o modo design ser a opção predefinida? ‍
  90. 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
  91. 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
  92. 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
  93. 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)
  94. • 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