Slide 1

Slide 1 text

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

Slide 2

Slide 2 text

● 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

Slide 3

Slide 3 text

Andres-Leonardo Martinez-Ortiz Google Carlos Mota Formador Renato Almeida Formador @davilagrau @cafonsomota @tallnato Equipa Daniela Ferreira Gestora de comunidades

Slide 4

Slide 4 text

● 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

Slide 5

Slide 5 text

#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 ✅ ✅

Slide 6

Slide 6 text

Sumário Photo by Mika Baumeister on Unsplash ● Resumo da aula anterior ● Jetpack ● Room ● CameraX ● Compose ● Kotlin para principiantes ● Sexta-Feira negra

Slide 7

Slide 7 text

http://events.withgoogle.com/atp2020 ✉ [email protected] http://bit.ly/atp2020-youtube http://bit.ly/atp2020-discord Links

Slide 8

Slide 8 text

http://bit.ly/atp2020-live

Slide 9

Slide 9 text

http://bit.ly/atp2020-codelabs

Slide 10

Slide 10 text

http://bit.ly/kahoot-aula6

Slide 11

Slide 11 text

Resumo da Aula #5

Slide 12

Slide 12 text

● É 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

Slide 13

Slide 13 text

RecyclerView Activity RecyclerView LayoutManager Adapter ViewHolder data RecyclerView LayoutManager Adapter ViewHolder data Adapter ViewHolder data

Slide 14

Slide 14 text

No content

Slide 15

Slide 15 text

● 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

Slide 16

Slide 16 text

The Dog API

Slide 17

Slide 17 text

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 )

Slide 18

Slide 18 text

● Biblioteca de conversão de JSON para Kotlin e Java ● Optimizada para Android ● Compatível Retrofit Moshi

Slide 19

Slide 19 text

● 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

Slide 20

Slide 20 text

No content

Slide 21

Slide 21 text

Aula #6

Slide 22

Slide 22 text

Jetpack

Slide 23

Slide 23 text

https://developer.android.com/jetpack

Slide 24

Slide 24 text

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?

Slide 25

Slide 25 text

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.

Slide 26

Slide 26 text

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.

Slide 27

Slide 27 text

Menos código == Menos bugs - Alguém

Slide 28

Slide 28 text

ViewModel Paging 3 LiveData

Slide 29

Slide 29 text

LiveData ViewModel Paging 3 Compose Room Camera X

Slide 30

Slide 30 text

Testemunhos ‍⚖

Slide 31

Slide 31 text

“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

Slide 32

Slide 32 text

“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

Slide 33

Slide 33 text

Room

Slide 34

Slide 34 text

● 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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

@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

Slide 38

Slide 38 text

@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

Slide 39

Slide 39 text

@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

Slide 40

Slide 40 text

@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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

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

Slide 45

Slide 45 text

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

Slide 46

Slide 46 text

@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

Slide 47

Slide 47 text

@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

Slide 48

Slide 48 text

@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

Slide 49

Slide 49 text

@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

Slide 50

Slide 50 text

@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

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

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

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

class MainViewModelFactory( private val repository: DogRepository ) : ViewModelProvider.Factory { override fun create(modelClass: Class): 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

Slide 58

Slide 58 text

class BobiApplication: Application() { val database by lazy { KennelDatabase.getDatabase(this) } val repository by lazy { DogRepository(database.dogDao()) } } Como utilizar? O Application BobiApplication.kt

Slide 59

Slide 59 text

... ... Como utilizar? O Application no AndroidManifest AndroidManifest.xml

Slide 60

Slide 60 text

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

Slide 61

Slide 61 text

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

Slide 62

Slide 62 text

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

Slide 63

Slide 63 text

CameraX

Slide 64

Slide 64 text

CameraXβ Sistema de add-ons Consistência em todos os diapositivos Funciona com 94% dos Android 5.0+

Slide 65

Slide 65 text

CameraXβ Captura Análise de imagem Pré-visualização Casos de uso https://developer.android.com/training/camerax

Slide 66

Slide 66 text

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

Slide 67

Slide 67 text

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

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

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

Slide 70

Slide 70 text

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

Slide 71

Slide 71 text

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

Slide 72

Slide 72 text

class CameraActivity : AppCompatActivity() { ... } Como utilizar? Criar uma nova Activity AndroidManifest.xml CameraActivity.kt

Slide 73

Slide 73 text

Como utilizar? Declarar a permissão de câmera AndroidManifest.xml

Slide 74

Slide 74 text

Como utilizar? Declarar a permissão de câmera AndroidManifest.xml

Slide 75

Slide 75 text

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

Slide 76

Slide 76 text

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

Slide 77

Slide 77 text

override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, 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

Slide 78

Slide 78 text

override fun onRequestPermissionsResult( requestCode: Int, permissions: Array, 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

Slide 79

Slide 79 text

Como utilizar? Definir o layout activity_camera.kt

Slide 80

Slide 80 text

Como utilizar? Definir o layout activity_camera.kt

Slide 81

Slide 81 text

private fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this) cameraProvider.addListener( { val preview = Preview.Builder().build().also { it.setSurfaceProvider(findViewById(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

Slide 82

Slide 82 text

private fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this) cameraProvider.addListener( { val preview = Preview.Builder().build().also { it.setSurfaceProvider(findViewById(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

Slide 83

Slide 83 text

private fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this) cameraProvider.addListener( { val preview = Preview.Builder().build().also { it.setSurfaceProvider(findViewById(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

Slide 84

Slide 84 text

private fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this) cameraProvider.addListener( { val preview = Preview.Builder().build().also { it.setSurfaceProvider(findViewById(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

Slide 85

Slide 85 text

private fun startCamera() { val cameraProvider = ProcessCameraProvider.getInstance(this) cameraProvider.addListener( { val preview = Preview.Builder().build().also { it.setSurfaceProvider(findViewById(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

Slide 86

Slide 86 text

override fun onCreate(savedInstanceState: Bundle?) { ... findViewById(R.id.camera_capture_button).setOnClickListener { takePhoto() } } Como utilizar? Tirar uma foto CameraActivity.kt

Slide 87

Slide 87 text

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

Slide 88

Slide 88 text

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

Slide 89

Slide 89 text

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

Slide 90

Slide 90 text

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

Slide 91

Slide 91 text

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

Slide 92

Slide 92 text

Compose

Slide 93

Slide 93 text

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 α

Slide 94

Slide 94 text

⚠ Compose ainda se encontra em alpha

Slide 95

Slide 95 text

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

Slide 96

Slide 96 text

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

Slide 97

Slide 97 text

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

Slide 98

Slide 98 text

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

Slide 99

Slide 99 text

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

Slide 100

Slide 100 text

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

Slide 101

Slide 101 text

@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

Slide 102

Slide 102 text

Como utilizar? example.xml !Compose

Slide 103

Slide 103 text

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

Slide 104

Slide 104 text

@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

Slide 105

Slide 105 text

@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

Slide 106

Slide 106 text

@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

Slide 107

Slide 107 text

@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

Slide 108

Slide 108 text

@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

Slide 109

Slide 109 text

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

Slide 110

Slide 110 text

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

Slide 111

Slide 111 text

IADE Bruno Nobre Universidade Europeia Princípios de UX para Mobile

Slide 112

Slide 112 text

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

Slide 113

Slide 113 text

VS Ronda 5

Slide 114

Slide 114 text

interface Printer { fun print() } interface Writer { fun write() } class PrintWriter( printer: Printer, writer: Writer ) : Printer by printer, Writer by writer {} Interface delegation

Slide 115

Slide 115 text

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

Slide 116

Slide 116 text

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

Slide 117

Slide 117 text

Sexta-Feira negra Photo by Xiaolong Wong on Unsplash

Slide 118

Slide 118 text

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

Slide 119

Slide 119 text

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

Slide 120

Slide 120 text

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

Slide 121

Slide 121 text

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

Slide 122

Slide 122 text

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

Slide 123

Slide 123 text

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

Slide 124

Slide 124 text

Exportar como zip 1. Carregar em File na barra de ações 2. Export to Zip File... Nenhum dos ficheiros gerados é adicionado ao zip.

Slide 125

Slide 125 text

No content

Slide 126

Slide 126 text

E se quisermos alterar o comportamento de sempre que abrimos um layout o modo design ser a opção predefinida? ‍

Slide 127

Slide 127 text

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

Slide 128

Slide 128 text

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

Slide 129

Slide 129 text

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

Slide 130

Slide 130 text

Trabalho Para Casa ‍‍

Slide 131

Slide 131 text

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)

Slide 132

Slide 132 text

● 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

Slide 133

Slide 133 text

Dúvidas?

Slide 134

Slide 134 text

Continuamos a responder no discord

Slide 135

Slide 135 text

Obrigado ‍♀

Slide 136

Slide 136 text

Android Training Program PORTUGAL Aula #7 Firebase Próxima aula: 02 de Dezembro