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. Android
    Training
    Program
    PORTUGAL
    Aula #6
    Jetpack, jetpack, jetpack

    View Slide

  2. ● Sejam excelentes uns para os outros
    ● Fale mais alto se vir ou ouvir alguma coisa
    ● O assédio não é tolerado
    ● Pratique "Sim e" um ao outro
    Código de conduta
    Mais informações: http://bit.ly/2IhF0l3

    View Slide

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

    View Slide

  4. ● 12 aulas
    ● 1h30 cada aula
    ● ~1 aula por semana
    ● 14 Outubro a 16 Dezembro
    ● YouTube live
    ● Suporte assíncrono contínuo via Discord/email
    ● Todo o código disponível no GitHub
    Photo by Arif Riyanto on Unspla
    O programa

    View Slide

  5. #0
    14 de Outubro
    Pronto para
    começar
    #1
    21 de Outubro
    Bem-vindos ao
    Android
    #2
    28 de Outubro
    Fundações I
    #3
    04 de Novembro
    Fundações II
    #4
    11 de Novembro
    Fundações III
    #5
    18 de Novembro
    Listas, listas e
    mais listas
    #6
    25 de Novembro
    Jetpack,
    Jetpack,
    Jetpack!
    #7 - #8
    02 - 03 de Dezembro
    Firebase
    #9 - #10
    09 - 10 de Dezembro
    MLKit &
    TensorFlow
    #11
    16 de Dezembro
    Resumo
    Semana Semana
    Calendário
    ✅ ✅ ✅ ✅
    Direto


    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  11. Resumo da Aula #5

    View Slide

  12. ● É a evolução da ListView
    ○ Com uma maior performance e flexibilidade
    ● Permite criar uma lista de objetos facilmente
    ● Esta lista tanto pode ser horizontal como vertical
    ○ Dependendo do LayoutManager definido
    ● É possibilidade adicionar animações (incríveis) por cada item modificado
    RecyclerView

    View Slide

  13. RecyclerView
    Activity
    RecyclerView
    LayoutManager
    Adapter
    ViewHolder
    data
    RecyclerView
    LayoutManager
    Adapter
    ViewHolder
    data
    Adapter
    ViewHolder
    data

    View Slide

  14. View Slide

  15. ● Cliente REST fortemente tipado
    ● Facilita a transferência de JSON através de um webservice
    ○ JSON ou outro tipos de dados
    Retrofit
    https://github.com/square/retrofit

    View Slide

  16. The Dog API

    View Slide

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

    View Slide

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

    View Slide

  19. ● Glide é uma biblioteca de carregamento de imagens para Android
    ● Descarrega, descodifica e mostra
    ○ Imagens
    ○ GIF’s
    ● Permite também redimensionar imagens
    ● Cache automática e simplificada das imagens
    ● Expõe uma API flexível e simples de utilizar
    Vantagens
    https://github.com/bumptech/glide

    View Slide

  20. View Slide

  21. Aula #6

    View Slide

  22. Jetpack

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  28. ViewModel
    Paging 3
    LiveData

    View Slide

  29. LiveData
    ViewModel
    Paging 3
    Compose
    Room
    Camera X

    View Slide

  30. Testemunhos
    ‍⚖

    View Slide

  31. “Com os Componentes de Arquitetura do Android, estamos a re-arquitetar toda a
    nossa aplicação. É ótimo existir uma forma recomenda, opinativa, e clara de
    construir uma aplicação Android que facilita suportar alteração de
    configurações”
    - Drew Hannay, Engenheiro de Software, LinkedIn
    https://developer.android.com/jetpack/testimonials
    LinkedIn
    Testemunhos

    View Slide

  32. “Nós utilizamos dezenas de diferentes tecnologias, e o ‘Room’ foi uma grande
    melhoria. A ênfase na testabilidade é enorme.
    - Andy Lawton, responsável pela plataforma Android no Tinder
    https://developer.android.com/jetpack/testimonials
    Tinder
    Testemunhos

    View Slide

  33. Room

    View Slide

  34. ● Camada de abstração sobre SQLite (base de dados)
    ● Reduz a quantidade de código repetitivo
    ● Acesso à base de dados de forma mais robusta
    ● Valida as consultas em tempo de compilação
    ● Utilizado como cache da aplicação
    ● Compatível com LiveData
    Room
    https://developer.android.com/topic/libraries/architecture/room

    View Slide

  35. Como utilizar?
    Importar a biblioteca
    Room
    Resto da aplicação
    Data Access Object
    Entidades
    Obter os DAOs
    Obter as entidades da BD
    Persistir os dados para a BD
    get / set valores

    View Slide

  36. plugins {

    id 'kotlin-kapt'
    }
    dependencies {
    ...
    def room_version = "2.2.5"
    implementation "androidx.room:room-runtime:$room_version"
    kapt "androidx.room:room-compiler:$room_version"
    // opcional - Kotlin Extensions e suporte para Coroutines
    implementation "androidx.room:room-ktx:$room_version"
    }
    Como utilizar?
    Importar a biblioteca
    app/build.gradle

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide


  59. ...
    android:name=".BobiApplication"
    ...>
    ...


    Como utilizar?
    O Application no AndroidManifest
    AndroidManifest.xml

    View Slide

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

    View Slide

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

    View Slide

  62. class MainActivity : AppCompatActivity() {
    private val viewModel: MainViewModel by viewModels {
    MainViewModelFactory((application as BobiApplication).repository)
    }
    fun showDogs() {
    viewModel.getDogsDatabase().observe(this){
    Log.d(TAG, "$it")
    }
    }
    ...
    }
    Como utilizar?
    Na Activity
    MainActivity.kt

    View Slide

  63. CameraX

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  68. Câmera
    package pt.atp.bobi.presentation.ui
    private const val TAG = "CameraActivity"
    private const val REQUEST_CAMERA = 0
    private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
    class CameraActivity : AppCompatActivity() {
    private lateinit var imageCapture: ImageCapture
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_camera)
    if (allPermissionsGranted()) {
    startCamera()
    } else {
    ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CAMERA)
    }
    findViewById(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)
    }
    })
    }
    }

    View Slide

  69. Câmera
    package pt.atp.bobi.presentation.ui
    private const val TAG = "CameraActivity"
    private const val REQUEST_CAMERA = 0
    private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
    class CameraActivity : AppCompatActivity() {
    private lateinit var imageCapture: ImageCapture
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_camera)
    if (allPermissionsGranted()) {
    startCamera()
    } else {
    ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CAMERA)
    }
    findViewById(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)
    }
    })
    }
    }
    ‍♀

    View Slide

  70. Câmera
    package pt.atp.bobi.presentation.ui
    private const val TAG = "CameraActivity"
    private const val REQUEST_CAMERA = 0
    private val REQUIRED_PERMISSIONS = arrayOf(Manifest.permission.CAMERA)
    class CameraActivity : AppCompatActivity() {
    private lateinit var imageCapture: ImageCapture
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_camera)
    if (allPermissionsGranted()) {
    startCamera()
    } else {
    ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CAMERA)
    }
    findViewById(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

    View Slide

  71. plugins {
    id 'com.android.application'
    id 'kotlin-android'
    id 'kotlin-android-extensions'
    }
    ...
    dependencies {
    ...
    implementation "androidx.camera:camera-camera2:1.0.0-beta12"
    implementation "androidx.camera:camera-lifecycle:1.0.0-beta12"
    implementation "androidx.camera:camera-view:1.0.0-alpha19"
    }
    Como utilizar?
    Importar as bibliotecas
    app/build.gradle

    View Slide

  72. class CameraActivity : AppCompatActivity() {
    ...
    }

    Como utilizar?
    Criar uma nova Activity
    AndroidManifest.xml
    CameraActivity.kt

    View Slide



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

    View Slide



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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide


  79. ...
    android:id="@+id/camera_capture_button"
    ...
    android:id="@+id/viewFinder"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

    Como utilizar?
    Definir o layout
    activity_camera.kt

    View Slide


  80. ...
    android:id="@+id/camera_capture_button"
    ...
    android:id="@+id/viewFinder"
    android:layout_width="match_parent"
    android:layout_height="match_parent" />

    Como utilizar?
    Definir o layout
    activity_camera.kt

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  92. Compose

    View Slide

  93. Compose
    ● Forma inovadora de criar layouts nativos (em Android)
    ● Permite construir componentes gráficos de forma declarativa
    ● Interoperável com os componentes nativos
    ● Reduz a quantidade de código necessário para criar interfaces gráficas
    ● Tira partido das vantagens de Kotlin para facilitar a escrita de código
    α

    View Slide


  94. Compose ainda se encontra em alpha

    View Slide

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

    View Slide

  96. android {
    ...
    buildFeatures { compose true }
    kotlinOptions {
    useIR = true
    }
    }
    dependencies {
    ...
    implementation "androidx.compose.ui:ui:1.0.0-alpha07"
    implementation "androidx.compose.foundation:foundation:1.0.0-alpha07"
    implementation "androidx.ui:ui-tooling:1.0.0-alpha07"
    implementation "androidx.compose.material:material:1.0.0-alpha07"
    }
    Como utilizar?
    Importar a biblioteca
    app/build.gradle

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  110. class ExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContent {
    MaterialTheme {
    Column(modifier = Modifier.fillMaxWidth()) {
    TopAppBar(title = { Text(stringResource(id = R.string.example)) })
    Example()
    }
    }
    }
    }
    }
    Como utilizar?
    ExampleActivity.kt
    Compose

    View Slide

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

    View Slide

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

    View Slide

  113. VS
    Ronda 5

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  117. Sexta-Feira
    negra
    Photo by Xiaolong Wong on Unsplash

    View Slide

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

    View Slide

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

    View Slide

  120. Histórico de conteúdo copiado

    cmd shift
    + + v

    View Slide

  121. Mudar a fonte

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

    View Slide

  122. Mudar a fonte

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

    View Slide

  123. Mudar a fonte

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

    View Slide

  124. Exportar como zip

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

    View Slide

  125. View Slide

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

    View Slide

  127. Mudar o modo de visualização

    Para mudar a fonte do código:
    1. Carregar em Android Studio na barra de ações
    2. Preferences
    3. Editor → Layout Editor
    4. Other Resources

    View Slide

  128. scrcpy

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

    View Slide

  129. scrcpy

    O teu telemóvel, no teu computador.
    ● O ecrã do teu telemóvel no teu computador
    ● Permite controlares o teu telemóvel remotamente
    ● Funciona em GNU/Linux, Windows e macOS
    apt install scrcpy //Linux
    choco install scrcpy //Windows
    brew install scrcpy //macOS
    https://github.com/genymobile/scrcpy

    View Slide

  130. Trabalho Para Casa
    ‍‍

    View Slide

  131. Trabalho para casa
    ● Implementar um modo de navegação diferente
    ○ BottomNavigationView
    ■ Main (MainFragment.kt)
    ■ Lista de raças (ListFragment.kt)
    ■ Raças favoritas (FavoritesFragment.kt)
    ■ About (AboutFragment.kt)

    View Slide

  132. ● Implementar um novo ecrã - FavoritesFragment.kt
    ● Contém todos as raças favoritas do utilizador
    ● Permite adicionar/remover uma raça aos/dos favoritos
    Trabalho para casa

    View Slide

  133. Dúvidas?

    View Slide

  134. Continuamos a
    responder no
    discord

    View Slide

  135. Obrigado
    ‍♀

    View Slide

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

    View Slide