Share
Embed
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
Transcript
136 slides
-
• 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 -
Andres-Leonardo Martinez-Ortiz Google Carlos Mota Formador Renato Almeida Formador @davilagrau
@cafonsomota @tallnato Equipa Daniela Ferreira Gestora de comunidades -
• 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 -
#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 ✅ ✅ -
Sumário Photo by Mika Baumeister on Unsplash • Resumo da
aula anterior • Jetpack • Room • CameraX • Compose • Kotlin para principiantes • Sexta-Feira negra -
• É 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 -
RecyclerView Activity RecyclerView LayoutManager Adapter ViewHolder data RecyclerView LayoutManager Adapter
ViewHolder data Adapter ViewHolder data -
• 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 -
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 ) -
• Biblioteca de conversão de JSON para Kotlin e Java
• Optimizada para Android • Compatível Retrofit Moshi -
• 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 -
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? -
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. -
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. -
“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 -
“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 -
• 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 -
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 -
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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
@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 -
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 -
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 -
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 -
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 -
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 -
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 -
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 -
class BobiApplication: Application() { val database by lazy { KennelDatabase.getDatabase(this)
} val repository by lazy { DogRepository(database.dogDao()) } } Como utilizar? O Application BobiApplication.kt -
<manifest ...> ... <application android:name=".BobiApplication" ...> ... </application> </manifest> Como
utilizar? O Application no AndroidManifest AndroidManifest.xml -
class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by
viewModels() ... } Como utilizar? Na Activity MainActivity.kt -
class MainActivity : AppCompatActivity() { private val viewModel: MainViewModel by
viewModels { MainViewModelFactory((application as BobiApplication).repository) } ... } Como utilizar? Na Activity MainActivity.kt -
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 -
CameraXβ Sistema de add-ons Consistência em todos os diapositivos Funciona
com 94% dos Android 5.0+ -
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) } -
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... -
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) } }) } } -
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) } }) } } ♀ -
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 -
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 -
class CameraActivity : AppCompatActivity() { ... } <activity android:name=".presentation.ui.CameraActivity" />
Como utilizar? Criar uma nova Activity AndroidManifest.xml CameraActivity.kt -
<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 -
<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 -
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 -
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 -
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 -
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 -
<?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 -
<?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 -
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 -
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 -
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 -
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 -
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 -
override fun onCreate(savedInstanceState: Bundle?) { ... findViewById<Button>(R.id.camera_capture_button).setOnClickListener { takePhoto() }
} Como utilizar? Tirar uma foto CameraActivity.kt -
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 -
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 -
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 -
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 -
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 -
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 α -
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 -
@Composable fun Example() { // O código vem para aqui
} Como utilizar? Compose Example.kt -
@Composable fun Example() { // O código vem para aqui
} @Composable @Preview fun DefaultExample() { Example() } Como utilizar? Compose Example.kt -
@Composable fun Example() { // O código vem para aqui
} @Composable @Preview fun DefaultExample() { Example() } Como utilizar? Example.kt Compose -
@Composable fun Example() { Text( text = "Olá mundo! ",
style = style = MaterialTheme.typography.h5, modifier = Modifier.padding(start = 16.dp) ) } Como utilizar? Example.kt Compose -
@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 -
<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 -
@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 -
@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 -
@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 -
@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 -
@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 -
class ExampleActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) setContentView(R.layout.activity_example) } } Como utilizar? ExampleActivity.kt Compose -
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 -
interface Printer { fun print() } interface Writer { fun
write() } class PrintWriter( printer: Printer, writer: Writer ) : Printer by printer, Writer by writer {} Interface delegation -
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); } } -
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); } } -
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 -
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 -
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 -
Exportar como zip 1. Carregar em File na barra de
ações 2. Export to Zip File... Nenhum dos ficheiros gerados é adicionado ao zip. -
E se quisermos alterar o comportamento de sempre que abrimos
um layout o modo design ser a opção predefinida? -
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 -
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 -
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 -
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) -
• 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